Jiyong Park | ae55638 | 2020-05-20 18:33:43 +0900 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
Joe Onorato | 9197a48 | 2011-06-08 16:04:14 -0700 | [diff] [blame] | 2 | # |
| 3 | # Copyright (C) 2009 The Android Open Source Project |
| 4 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | # you may not use this file except in compliance with the License. |
| 7 | # You may obtain a copy of the License at |
| 8 | # |
| 9 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | # |
| 11 | # Unless required by applicable law or agreed to in writing, software |
| 12 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. |
| 16 | |
Jiyong Park | 0b4fccb | 2020-06-26 17:38:00 +0900 | [diff] [blame] | 17 | import argparse |
Joe Onorato | 9197a48 | 2011-06-08 16:04:14 -0700 | [diff] [blame] | 18 | import sys |
| 19 | |
Jiyong Park | d721e87 | 2020-06-22 17:30:57 +0900 | [diff] [blame] | 20 | # Usage: post_process_props.py file.prop [disallowed_key, ...] |
| 21 | # Disallowed keys are removed from the property file, if present |
Jeff Sharkey | 26d22f7 | 2014-03-18 17:20:10 -0700 | [diff] [blame] | 22 | |
Elliott Hughes | 05c1a2a | 2017-02-28 10:04:23 -0800 | [diff] [blame] | 23 | # See PROP_VALUE_MAX in system_properties.h. |
| 24 | # The constant in system_properties.h includes the terminating NUL, |
| 25 | # so we decrease the value by 1 here. |
Ying Wang | 3512321 | 2014-02-11 20:44:09 -0800 | [diff] [blame] | 26 | PROP_VALUE_MAX = 91 |
| 27 | |
Jiyong Park | ae55638 | 2020-05-20 18:33:43 +0900 | [diff] [blame] | 28 | # Put the modifications that you need to make into the */build.prop into this |
| 29 | # function. |
| 30 | def mangle_build_prop(prop_list): |
Jerry Zhang | 1695653 | 2016-10-18 00:01:27 +0000 | [diff] [blame] | 31 | # If ro.debuggable is 1, then enable adb on USB by default |
| 32 | # (this is for userdebug builds) |
Jiyong Park | d721e87 | 2020-06-22 17:30:57 +0900 | [diff] [blame] | 33 | if prop_list.get_value("ro.debuggable") == "1": |
| 34 | val = prop_list.get_value("persist.sys.usb.config") |
Jerry Zhang | 1695653 | 2016-10-18 00:01:27 +0000 | [diff] [blame] | 35 | if "adb" not in val: |
| 36 | if val == "": |
| 37 | val = "adb" |
| 38 | else: |
| 39 | val = val + ",adb" |
Jiyong Park | ae55638 | 2020-05-20 18:33:43 +0900 | [diff] [blame] | 40 | prop_list.put("persist.sys.usb.config", val) |
Joe Onorato | 8ad4bb1 | 2012-05-02 14:36:57 -0700 | [diff] [blame] | 41 | # UsbDeviceManager expects a value here. If it doesn't get it, it will |
| 42 | # default to "adb". That might not the right policy there, but it's better |
| 43 | # to be explicit. |
Jiyong Park | d721e87 | 2020-06-22 17:30:57 +0900 | [diff] [blame] | 44 | if not prop_list.get_value("persist.sys.usb.config"): |
Justin Yun | 07ceaa7 | 2021-04-02 16:29:06 +0900 | [diff] [blame] | 45 | prop_list.put("persist.sys.usb.config", "none") |
| 46 | |
Justin Yun | 48aa78e | 2021-04-13 17:58:59 +0900 | [diff] [blame] | 47 | def validate_grf_props(prop_list, sdk_version): |
Justin Yun | 07ceaa7 | 2021-04-02 16:29:06 +0900 | [diff] [blame] | 48 | """Validate GRF properties if exist. |
| 49 | |
| 50 | If ro.board.first_api_level is defined, check if its value is valid for the |
| 51 | sdk version. |
Justin Yun | 48aa78e | 2021-04-13 17:58:59 +0900 | [diff] [blame] | 52 | Also, validate the value of ro.board.api_level if defined. |
Justin Yun | 07ceaa7 | 2021-04-02 16:29:06 +0900 | [diff] [blame] | 53 | |
| 54 | Returns: |
| 55 | True if the GRF properties are valid. |
| 56 | """ |
| 57 | grf_api_level = prop_list.get_value("ro.board.first_api_level") |
| 58 | board_api_level = prop_list.get_value("ro.board.api_level") |
| 59 | |
| 60 | if not grf_api_level: |
| 61 | if board_api_level: |
| 62 | sys.stderr.write("error: non-GRF device must not define " |
| 63 | "ro.board.api_level\n") |
| 64 | return False |
| 65 | # non-GRF device skips the GRF validation test |
| 66 | return True |
| 67 | |
| 68 | grf_api_level = int(grf_api_level) |
| 69 | if grf_api_level > sdk_version: |
| 70 | sys.stderr.write("error: ro.board.first_api_level(%d) must be less than " |
| 71 | "or equal to ro.build.version.sdk(%d)\n" |
| 72 | % (grf_api_level, sdk_version)) |
| 73 | return False |
| 74 | |
Justin Yun | 07ceaa7 | 2021-04-02 16:29:06 +0900 | [diff] [blame] | 75 | if board_api_level: |
| 76 | board_api_level = int(board_api_level) |
| 77 | if board_api_level < grf_api_level or board_api_level > sdk_version: |
| 78 | sys.stderr.write("error: ro.board.api_level(%d) must be neither less " |
| 79 | "than ro.board.first_api_level(%d) nor greater than " |
| 80 | "ro.build.version.sdk(%d)\n" |
| 81 | % (board_api_level, grf_api_level, sdk_version)) |
| 82 | return False |
Justin Yun | 07ceaa7 | 2021-04-02 16:29:06 +0900 | [diff] [blame] | 83 | |
| 84 | return True |
Joe Onorato | 9197a48 | 2011-06-08 16:04:14 -0700 | [diff] [blame] | 85 | |
Jiyong Park | ae55638 | 2020-05-20 18:33:43 +0900 | [diff] [blame] | 86 | def validate(prop_list): |
Ying Wang | 3512321 | 2014-02-11 20:44:09 -0800 | [diff] [blame] | 87 | """Validate the properties. |
| 88 | |
Jiyong Park | d721e87 | 2020-06-22 17:30:57 +0900 | [diff] [blame] | 89 | If the value of a sysprop exceeds the max limit (91), it's an error, unless |
| 90 | the sysprop is a read-only one. |
| 91 | |
| 92 | Checks if there is no optional prop assignments. |
| 93 | |
Ying Wang | 3512321 | 2014-02-11 20:44:09 -0800 | [diff] [blame] | 94 | Returns: |
| 95 | True if nothing is wrong. |
| 96 | """ |
| 97 | check_pass = True |
Jiyong Park | d721e87 | 2020-06-22 17:30:57 +0900 | [diff] [blame] | 98 | for p in prop_list.get_all_props(): |
Jiyong Park | ae55638 | 2020-05-20 18:33:43 +0900 | [diff] [blame] | 99 | if len(p.value) > PROP_VALUE_MAX and not p.name.startswith("ro."): |
Ying Wang | 38df101 | 2015-02-04 15:10:59 -0800 | [diff] [blame] | 100 | check_pass = False |
| 101 | sys.stderr.write("error: %s cannot exceed %d bytes: " % |
Jiyong Park | ae55638 | 2020-05-20 18:33:43 +0900 | [diff] [blame] | 102 | (p.name, PROP_VALUE_MAX)) |
| 103 | sys.stderr.write("%s (%d)\n" % (p.value, len(p.value))) |
Jiyong Park | d721e87 | 2020-06-22 17:30:57 +0900 | [diff] [blame] | 104 | |
| 105 | if p.is_optional(): |
| 106 | check_pass = False |
| 107 | sys.stderr.write("error: found unresolved optional prop assignment:\n") |
| 108 | sys.stderr.write(str(p) + "\n") |
| 109 | |
Ying Wang | 3512321 | 2014-02-11 20:44:09 -0800 | [diff] [blame] | 110 | return check_pass |
| 111 | |
Jiyong Park | 0b4fccb | 2020-06-26 17:38:00 +0900 | [diff] [blame] | 112 | def override_optional_props(prop_list, allow_dup=False): |
Jiyong Park | d721e87 | 2020-06-22 17:30:57 +0900 | [diff] [blame] | 113 | """Override a?=b with a=c, if the latter exists |
| 114 | |
| 115 | Overriding is done by deleting a?=b |
| 116 | When there are a?=b and a?=c, then only the last one survives |
| 117 | When there are a=b and a=c, then it's an error. |
| 118 | |
| 119 | Returns: |
| 120 | True if the override was successful |
| 121 | """ |
| 122 | success = True |
| 123 | for name in prop_list.get_all_names(): |
| 124 | props = prop_list.get_props(name) |
| 125 | optional_props = [p for p in props if p.is_optional()] |
| 126 | overriding_props = [p for p in props if not p.is_optional()] |
| 127 | if len(overriding_props) > 1: |
| 128 | # duplicated props are allowed when the all have the same value |
| 129 | if all(overriding_props[0].value == p.value for p in overriding_props): |
Jiyong Park | 24d9cad | 2020-06-30 11:41:23 +0900 | [diff] [blame] | 130 | for p in optional_props: |
| 131 | p.delete("overridden by %s" % str(overriding_props[0])) |
Jiyong Park | d721e87 | 2020-06-22 17:30:57 +0900 | [diff] [blame] | 132 | continue |
Jiyong Park | 0b4fccb | 2020-06-26 17:38:00 +0900 | [diff] [blame] | 133 | # or if dup is explicitly allowed for compat reason |
| 134 | if allow_dup: |
| 135 | # this could left one or more optional props unresolved. |
| 136 | # Convert them into non-optional because init doesn't understand ?= |
| 137 | # syntax |
| 138 | for p in optional_props: |
| 139 | p.optional = False |
| 140 | continue |
| 141 | |
Jiyong Park | d721e87 | 2020-06-22 17:30:57 +0900 | [diff] [blame] | 142 | success = False |
| 143 | sys.stderr.write("error: found duplicate sysprop assignments:\n") |
| 144 | for p in overriding_props: |
| 145 | sys.stderr.write("%s\n" % str(p)) |
| 146 | elif len(overriding_props) == 1: |
| 147 | for p in optional_props: |
| 148 | p.delete("overridden by %s" % str(overriding_props[0])) |
| 149 | else: |
| 150 | if len(optional_props) > 1: |
| 151 | for p in optional_props[:-1]: |
| 152 | p.delete("overridden by %s" % str(optional_props[-1])) |
| 153 | # Make the last optional one as non-optional |
| 154 | optional_props[-1].optional = False |
| 155 | |
| 156 | return success |
| 157 | |
Jiyong Park | ae55638 | 2020-05-20 18:33:43 +0900 | [diff] [blame] | 158 | class Prop: |
Yu Liu | 115c66b | 2014-02-10 19:20:36 -0800 | [diff] [blame] | 159 | |
Jiyong Park | d721e87 | 2020-06-22 17:30:57 +0900 | [diff] [blame] | 160 | def __init__(self, name, value, optional=False, comment=None): |
Jiyong Park | ae55638 | 2020-05-20 18:33:43 +0900 | [diff] [blame] | 161 | self.name = name.strip() |
| 162 | self.value = value.strip() |
Jiyong Park | d721e87 | 2020-06-22 17:30:57 +0900 | [diff] [blame] | 163 | if comment != None: |
| 164 | self.comments = [comment] |
| 165 | else: |
| 166 | self.comments = [] |
| 167 | self.optional = optional |
Ying Wang | 3512321 | 2014-02-11 20:44:09 -0800 | [diff] [blame] | 168 | |
Jiyong Park | ae55638 | 2020-05-20 18:33:43 +0900 | [diff] [blame] | 169 | @staticmethod |
| 170 | def from_line(line): |
| 171 | line = line.rstrip('\n') |
| 172 | if line.startswith("#"): |
Jiyong Park | d721e87 | 2020-06-22 17:30:57 +0900 | [diff] [blame] | 173 | return Prop("", "", comment=line) |
| 174 | elif "?=" in line: |
| 175 | name, value = line.split("?=", 1) |
| 176 | return Prop(name, value, optional=True) |
Jiyong Park | ae55638 | 2020-05-20 18:33:43 +0900 | [diff] [blame] | 177 | elif "=" in line: |
| 178 | name, value = line.split("=", 1) |
Jiyong Park | d721e87 | 2020-06-22 17:30:57 +0900 | [diff] [blame] | 179 | return Prop(name, value, optional=False) |
Jiyong Park | ae55638 | 2020-05-20 18:33:43 +0900 | [diff] [blame] | 180 | else: |
| 181 | # don't fail on invalid line |
| 182 | # TODO(jiyong) make this a hard error |
Jiyong Park | d721e87 | 2020-06-22 17:30:57 +0900 | [diff] [blame] | 183 | return Prop("", "", comment=line) |
Jiyong Park | ae55638 | 2020-05-20 18:33:43 +0900 | [diff] [blame] | 184 | |
| 185 | def is_comment(self): |
Jiyong Park | d721e87 | 2020-06-22 17:30:57 +0900 | [diff] [blame] | 186 | return bool(self.comments and not self.name) |
| 187 | |
| 188 | def is_optional(self): |
| 189 | return (not self.is_comment()) and self.optional |
| 190 | |
| 191 | def make_as_comment(self): |
| 192 | # Prepend "#" to the last line which is the prop assignment |
| 193 | if not self.is_comment(): |
| 194 | assignment = str(self).rsplit("\n", 1)[-1] |
| 195 | self.comments.append("#" + assignment) |
| 196 | self.name = "" |
| 197 | self.value = "" |
| 198 | |
| 199 | def delete(self, reason): |
| 200 | self.comments.append("# Removed by post_process_props.py because " + reason) |
| 201 | self.make_as_comment() |
Jiyong Park | ae55638 | 2020-05-20 18:33:43 +0900 | [diff] [blame] | 202 | |
| 203 | def __str__(self): |
Jiyong Park | d721e87 | 2020-06-22 17:30:57 +0900 | [diff] [blame] | 204 | assignment = [] |
| 205 | if not self.is_comment(): |
| 206 | operator = "?=" if self.is_optional() else "=" |
| 207 | assignment.append(self.name + operator + self.value) |
| 208 | return "\n".join(self.comments + assignment) |
Jiyong Park | ae55638 | 2020-05-20 18:33:43 +0900 | [diff] [blame] | 209 | |
| 210 | class PropList: |
| 211 | |
| 212 | def __init__(self, filename): |
| 213 | with open(filename) as f: |
| 214 | self.props = [Prop.from_line(l) |
| 215 | for l in f.readlines() if l.strip() != ""] |
| 216 | |
Jiyong Park | d721e87 | 2020-06-22 17:30:57 +0900 | [diff] [blame] | 217 | def get_all_props(self): |
Jiyong Park | ae55638 | 2020-05-20 18:33:43 +0900 | [diff] [blame] | 218 | return [p for p in self.props if not p.is_comment()] |
Joe Onorato | 9197a48 | 2011-06-08 16:04:14 -0700 | [diff] [blame] | 219 | |
Jiyong Park | d721e87 | 2020-06-22 17:30:57 +0900 | [diff] [blame] | 220 | def get_all_names(self): |
| 221 | return set([p.name for p in self.get_all_props()]) |
| 222 | |
| 223 | def get_props(self, name): |
| 224 | return [p for p in self.get_all_props() if p.name == name] |
| 225 | |
| 226 | def get_value(self, name): |
| 227 | # Caution: only the value of the first sysprop having the name is returned. |
Jiyong Park | ae55638 | 2020-05-20 18:33:43 +0900 | [diff] [blame] | 228 | return next((p.value for p in self.props if p.name == name), "") |
Joe Onorato | 9197a48 | 2011-06-08 16:04:14 -0700 | [diff] [blame] | 229 | |
| 230 | def put(self, name, value): |
Jiyong Park | d721e87 | 2020-06-22 17:30:57 +0900 | [diff] [blame] | 231 | # Note: when there is an optional prop for the name, its value isn't changed. |
| 232 | # Instead a new non-optional prop is appended, which will override the |
| 233 | # optional prop. Otherwise, the new value might be overridden by an existing |
| 234 | # non-optional prop of the same name. |
| 235 | index = next((i for i,p in enumerate(self.props) |
| 236 | if p.name == name and not p.is_optional()), -1) |
Jiyong Park | ae55638 | 2020-05-20 18:33:43 +0900 | [diff] [blame] | 237 | if index == -1: |
Jiyong Park | d721e87 | 2020-06-22 17:30:57 +0900 | [diff] [blame] | 238 | self.props.append(Prop(name, value, |
| 239 | comment="# Auto-added by post_process_props.py")) |
Jiyong Park | ae55638 | 2020-05-20 18:33:43 +0900 | [diff] [blame] | 240 | else: |
Jiyong Park | d721e87 | 2020-06-22 17:30:57 +0900 | [diff] [blame] | 241 | self.props[index].comments.append( |
| 242 | "# Value overridden by post_process_props.py. Original value: %s" % |
| 243 | self.props[index].value) |
Jiyong Park | ae55638 | 2020-05-20 18:33:43 +0900 | [diff] [blame] | 244 | self.props[index].value = value |
Joe Onorato | 9197a48 | 2011-06-08 16:04:14 -0700 | [diff] [blame] | 245 | |
Jiyong Park | ae55638 | 2020-05-20 18:33:43 +0900 | [diff] [blame] | 246 | def write(self, filename): |
| 247 | with open(filename, 'w+') as f: |
| 248 | for p in self.props: |
| 249 | f.write(str(p) + "\n") |
Joe Onorato | 9197a48 | 2011-06-08 16:04:14 -0700 | [diff] [blame] | 250 | |
| 251 | def main(argv): |
Jiyong Park | 0b4fccb | 2020-06-26 17:38:00 +0900 | [diff] [blame] | 252 | parser = argparse.ArgumentParser(description="Post-process build.prop file") |
| 253 | parser.add_argument("--allow-dup", dest="allow_dup", action="store_true", |
| 254 | default=False) |
| 255 | parser.add_argument("filename") |
| 256 | parser.add_argument("disallowed_keys", metavar="KEY", type=str, nargs="*") |
Justin Yun | 07ceaa7 | 2021-04-02 16:29:06 +0900 | [diff] [blame] | 257 | parser.add_argument("--sdk-version", type=int, required=True) |
Jiyong Park | 0b4fccb | 2020-06-26 17:38:00 +0900 | [diff] [blame] | 258 | args = parser.parse_args() |
Joe Onorato | 9197a48 | 2011-06-08 16:04:14 -0700 | [diff] [blame] | 259 | |
Jiyong Park | 0b4fccb | 2020-06-26 17:38:00 +0900 | [diff] [blame] | 260 | if not args.filename.endswith("/build.prop"): |
Joe Onorato | 9197a48 | 2011-06-08 16:04:14 -0700 | [diff] [blame] | 261 | sys.stderr.write("bad command line: " + str(argv) + "\n") |
| 262 | sys.exit(1) |
| 263 | |
Jiyong Park | 0b4fccb | 2020-06-26 17:38:00 +0900 | [diff] [blame] | 264 | props = PropList(args.filename) |
Jiyong Park | ae55638 | 2020-05-20 18:33:43 +0900 | [diff] [blame] | 265 | mangle_build_prop(props) |
Jiyong Park | 0b4fccb | 2020-06-26 17:38:00 +0900 | [diff] [blame] | 266 | if not override_optional_props(props, args.allow_dup): |
Jiyong Park | d721e87 | 2020-06-22 17:30:57 +0900 | [diff] [blame] | 267 | sys.exit(1) |
Justin Yun | 48aa78e | 2021-04-13 17:58:59 +0900 | [diff] [blame] | 268 | if not validate_grf_props(props, args.sdk_version): |
Justin Yun | 07ceaa7 | 2021-04-02 16:29:06 +0900 | [diff] [blame] | 269 | sys.exit(1) |
Jiyong Park | ae55638 | 2020-05-20 18:33:43 +0900 | [diff] [blame] | 270 | if not validate(props): |
Ying Wang | 3512321 | 2014-02-11 20:44:09 -0800 | [diff] [blame] | 271 | sys.exit(1) |
| 272 | |
Jiyong Park | d721e87 | 2020-06-22 17:30:57 +0900 | [diff] [blame] | 273 | # Drop any disallowed keys |
Jiyong Park | 0b4fccb | 2020-06-26 17:38:00 +0900 | [diff] [blame] | 274 | for key in args.disallowed_keys: |
Jiyong Park | d721e87 | 2020-06-22 17:30:57 +0900 | [diff] [blame] | 275 | for p in props.get_props(key): |
| 276 | p.delete("%s is a disallowed key" % key) |
Jeff Sharkey | 26d22f7 | 2014-03-18 17:20:10 -0700 | [diff] [blame] | 277 | |
Jiyong Park | 0b4fccb | 2020-06-26 17:38:00 +0900 | [diff] [blame] | 278 | props.write(args.filename) |
Joe Onorato | 9197a48 | 2011-06-08 16:04:14 -0700 | [diff] [blame] | 279 | |
| 280 | if __name__ == "__main__": |
| 281 | main(sys.argv) |