#!/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(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="*") 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(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)