aboutsummaryrefslogtreecommitdiff
path: root/tools/post_process_props.py
blob: efbf614fd553135f5da059d13c4f3d14c3d927f6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
#!/usr/bin/env python3
#
# Copyright (C) 2009 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import argparse
import sys

# Usage: post_process_props.py file.prop [disallowed_key, ...]
# Disallowed keys are removed from the property file, if present

# See PROP_VALUE_MAX in system_properties.h.
# The constant in system_properties.h includes the terminating NUL,
# so we decrease the value by 1 here.
PROP_VALUE_MAX = 91

# Put the modifications that you need to make into the */build.prop into this
# function.
def mangle_build_prop(prop_list):
  # If ro.debuggable is 1, then enable adb on USB by default
  # (this is for userdebug builds)
  if prop_list.get_value("ro.debuggable") == "1":
    val = prop_list.get_value("persist.sys.usb.config")
    if "adb" not in val:
      if val == "":
        val = "adb"
      else:
        val = val + ",adb"
      prop_list.put("persist.sys.usb.config", val)
  # UsbDeviceManager expects a value here.  If it doesn't get it, it will
  # default to "adb". That might not the right policy there, but it's better
  # to be explicit.
  if not prop_list.get_value("persist.sys.usb.config"):
    prop_list.put("persist.sys.usb.config", "none")

def validate_grf_props(prop_list, sdk_version):
  """Validate GRF properties if exist.

  If ro.board.first_api_level is defined, check if its value is valid for the
  sdk version.
  Also, validate the value of ro.board.api_level if defined.

  Returns:
    True if the GRF properties are valid.
  """
  grf_api_level = prop_list.get_value("ro.board.first_api_level")
  board_api_level = prop_list.get_value("ro.board.api_level")

  if not grf_api_level:
    if board_api_level:
      sys.stderr.write("error: non-GRF device must not define "
                       "ro.board.api_level\n")
      return False
    # non-GRF device skips the GRF validation test
    return True

  grf_api_level = int(grf_api_level)
  if grf_api_level > sdk_version:
    sys.stderr.write("error: ro.board.first_api_level(%d) must be less than "
                     "or equal to ro.build.version.sdk(%d)\n"
                     % (grf_api_level, sdk_version))
    return False

  if board_api_level:
    board_api_level = int(board_api_level)
    if board_api_level < grf_api_level or board_api_level > sdk_version:
      sys.stderr.write("error: ro.board.api_level(%d) must be neither less "
                       "than ro.board.first_api_level(%d) nor greater than "
                       "ro.build.version.sdk(%d)\n"
                       % (board_api_level, grf_api_level, sdk_version))
      return False

  return True

def validate(prop_list):
  """Validate the properties.

  If the value of a sysprop exceeds the max limit (91), it's an error, unless
  the sysprop is a read-only one.

  Checks if there is no optional prop assignments.

  Returns:
    True if nothing is wrong.
  """
  check_pass = True
  for p in prop_list.get_all_props():
    if len(p.value) > PROP_VALUE_MAX and not p.name.startswith("ro."):
      check_pass = False
      sys.stderr.write("error: %s cannot exceed %d bytes: " %
                       (p.name, PROP_VALUE_MAX))
      sys.stderr.write("%s (%d)\n" % (p.value, len(p.value)))

    if p.is_optional():
      check_pass = False
      sys.stderr.write("error: found unresolved optional prop assignment:\n")
      sys.stderr.write(str(p) + "\n")

  return check_pass

def override_optional_props(prop_list, allow_dup=False):
  """Override a?=b with a=c, if the latter exists

  Overriding is done by deleting a?=b
  When there are a?=b and a?=c, then only the last one survives
  When there are a=b and a=c, then it's an error.

  Returns:
    True if the override was successful
  """
  success = True
  for name in prop_list.get_all_names():
    props = prop_list.get_props(name)
    optional_props = [p for p in props if p.is_optional()]
    overriding_props = [p for p in props if not p.is_optional()]
    if len(overriding_props) > 1:
      # duplicated props are allowed when the all have the same value
      if all(overriding_props[0].value == p.value for p in overriding_props):
        for p in optional_props:
          p.delete("overridden by %s" % str(overriding_props[0]))
        continue
      # or if dup is explicitly allowed for compat reason
      if allow_dup:
        # this could left one or more optional props unresolved.
        # Convert them into non-optional because init doesn't understand ?=
        # syntax
        for p in optional_props:
          p.optional = False
        continue

      success = False
      sys.stderr.write("error: found duplicate sysprop assignments:\n")
      for p in overriding_props:
        sys.stderr.write("%s\n" % str(p))
    elif len(overriding_props) == 1:
      for p in optional_props:
        p.delete("overridden by %s" % str(overriding_props[0]))
    else:
      if len(optional_props) > 1:
        for p in optional_props[:-1]:
          p.delete("overridden by %s" % str(optional_props[-1]))
      # Make the last optional one as non-optional
      optional_props[-1].optional = False

  return success

class Prop:

  def __init__(self, name, value, optional=False, comment=None):
    self.name = name.strip()
    self.value = value.strip()
    if comment != None:
      self.comments = [comment]
    else:
      self.comments = []
    self.optional = optional

  @staticmethod
  def from_line(line):
    line = line.rstrip('\n')
    if line.startswith("#"):
      return Prop("", "", comment=line)
    elif "?=" in line:
      name, value = line.split("?=", 1)
      return Prop(name, value, optional=True)
    elif "=" in line:
      name, value = line.split("=", 1)
      return Prop(name, value, optional=False)
    else:
      # don't fail on invalid line
      # TODO(jiyong) make this a hard error
      return Prop("", "", comment=line)

  def is_comment(self):
    return bool(self.comments and not self.name)

  def is_optional(self):
    return (not self.is_comment()) and self.optional

  def make_as_comment(self):
    # Prepend "#" to the last line which is the prop assignment
    if not self.is_comment():
      assignment = str(self).rsplit("\n", 1)[-1]
      self.comments.append("#" + assignment)
      self.name = ""
      self.value = ""

  def delete(self, reason):
    self.comments.append("# Removed by post_process_props.py because " + reason)
    self.make_as_comment()

  def __str__(self):
    assignment = []
    if not self.is_comment():
      operator = "?=" if self.is_optional() else "="
      assignment.append(self.name + operator + self.value)
    return "\n".join(self.comments + assignment)

class PropList:

  def __init__(self, filename):
    with open(filename) as f:
      self.props = [Prop.from_line(l)
                    for l in f.readlines() if l.strip() != ""]

  def get_all_props(self):
    return [p for p in self.props if not p.is_comment()]

  def get_all_names(self):
    return set([p.name for p in self.get_all_props()])

  def get_props(self, name):
    return [p for p in self.get_all_props() if p.name == name]

  def get_value(self, name):
    # Caution: only the value of the first sysprop having the name is returned.
    return next((p.value for p in self.props if p.name == name), "")

  def put(self, name, value):
    # Note: when there is an optional prop for the name, its value isn't changed.
    # Instead a new non-optional prop is appended, which will override the
    # optional prop. Otherwise, the new value might be overridden by an existing
    # non-optional prop of the same name.
    index = next((i for i,p in enumerate(self.props)
                  if p.name == name and not p.is_optional()), -1)
    if index == -1:
      self.props.append(Prop(name, value,
                             comment="# Auto-added by post_process_props.py"))
    else:
      self.props[index].comments.append(
          "# Value overridden by post_process_props.py. Original value: %s" %
          self.props[index].value)
      self.props[index].value = value

  def write(self, filename):
    with open(filename, 'w+') as f:
      for p in self.props:
        f.write(str(p) + "\n")

def main(argv):
  parser = argparse.ArgumentParser(description="Post-process build.prop file")
  parser.add_argument("--allow-dup", dest="allow_dup", action="store_true",
                      default=False)
  parser.add_argument("filename")
  parser.add_argument("disallowed_keys", metavar="KEY", type=str, nargs="*")
  parser.add_argument("--sdk-version", type=int, required=True)
  args = parser.parse_args()

  if not args.filename.endswith("/build.prop"):
    sys.stderr.write("bad command line: " + str(argv) + "\n")
    sys.exit(1)

  props = PropList(args.filename)
  mangle_build_prop(props)
  if not override_optional_props(props, args.allow_dup):
    sys.exit(1)
  if not validate_grf_props(props, args.sdk_version):
    sys.exit(1)
  if not validate(props):
    sys.exit(1)

  # Drop any disallowed keys
  for key in args.disallowed_keys:
    for p in props.get_props(key):
      p.delete("%s is a disallowed key" % key)

  props.write(args.filename)

if __name__ == "__main__":
  main(sys.argv)