1#!/usr/bin/env python3 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 17import argparse 18import sys 19 20# Usage: post_process_props.py file.prop [disallowed_key, ...] 21# Disallowed keys are removed from the property file, if present 22 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. 26PROP_VALUE_MAX = 91 27 28# Put the modifications that you need to make into the */build.prop into this 29# function. 30def mangle_build_prop(prop_list): 31 # If ro.debuggable is 1, then enable adb on USB by default 32 # (this is for userdebug builds) 33 if prop_list.get_value("ro.debuggable") == "1": 34 val = prop_list.get_value("persist.sys.usb.config") 35 if "adb" not in val: 36 if val == "": 37 val = "adb" 38 else: 39 val = val + ",adb" 40 prop_list.put("persist.sys.usb.config", val) 41 42def validate_grf_props(prop_list, sdk_version): 43 """Validate GRF properties if exist. 44 45 If ro.board.first_api_level is defined, check if its value is valid for the 46 sdk version. This is only for the release version. 47 Also, validate the value of ro.board.api_level if defined. 48 49 Returns: 50 True if the GRF properties are valid. 51 """ 52 grf_api_level = prop_list.get_value("ro.board.first_api_level") 53 board_api_level = prop_list.get_value("ro.board.api_level") 54 platform_version_codename = prop_list.get_value("ro.build.version.codename") 55 56 if not grf_api_level: 57 if board_api_level: 58 sys.stderr.write("error: non-GRF device must not define " 59 "ro.board.api_level\n") 60 return False 61 # non-GRF device skips the GRF validation test 62 return True 63 64 grf_api_level = int(grf_api_level) 65 if board_api_level: 66 board_api_level = int(board_api_level) 67 if board_api_level < grf_api_level: 68 sys.stderr.write("error: ro.board.api_level(%d) must be greater than " 69 "ro.board.first_api_level(%d)\n" 70 % (board_api_level, grf_api_level)) 71 return False 72 73 # skip sdk version validation for dev-stage non-REL devices 74 if platform_version_codename != "REL": 75 return True 76 77 if grf_api_level > sdk_version: 78 sys.stderr.write("error: ro.board.first_api_level(%d) must be less than " 79 "or equal to ro.build.version.sdk(%d)\n" 80 % (grf_api_level, sdk_version)) 81 return False 82 83 if board_api_level: 84 if board_api_level > sdk_version: 85 sys.stderr.write("error: ro.board.api_level(%d) must be less than or " 86 "equal to ro.build.version.sdk(%d)\n" 87 % (board_api_level, sdk_version)) 88 return False 89 90 return True 91 92def validate(prop_list): 93 """Validate the properties. 94 95 If the value of a sysprop exceeds the max limit (91), it's an error, unless 96 the sysprop is a read-only one. 97 98 Checks if there is no optional prop assignments. 99 100 Returns: 101 True if nothing is wrong. 102 """ 103 check_pass = True 104 for p in prop_list.get_all_props(): 105 if len(p.value) > PROP_VALUE_MAX and not p.name.startswith("ro."): 106 check_pass = False 107 sys.stderr.write("error: %s cannot exceed %d bytes: " % 108 (p.name, PROP_VALUE_MAX)) 109 sys.stderr.write("%s (%d)\n" % (p.value, len(p.value))) 110 111 if p.is_optional(): 112 check_pass = False 113 sys.stderr.write("error: found unresolved optional prop assignment:\n") 114 sys.stderr.write(str(p) + "\n") 115 116 return check_pass 117 118def override_optional_props(prop_list, allow_dup=False): 119 """Override a?=b with a=c, if the latter exists 120 121 Overriding is done by deleting a?=b 122 When there are a?=b and a?=c, then only the last one survives 123 When there are a=b and a=c, then it's an error. 124 125 Returns: 126 True if the override was successful 127 """ 128 success = True 129 for name in prop_list.get_all_names(): 130 props = prop_list.get_props(name) 131 optional_props = [p for p in props if p.is_optional()] 132 overriding_props = [p for p in props if not p.is_optional()] 133 if len(overriding_props) > 1: 134 # duplicated props are allowed when the all have the same value 135 if all(overriding_props[0].value == p.value for p in overriding_props): 136 for p in optional_props: 137 p.delete("overridden by %s" % str(overriding_props[0])) 138 continue 139 # or if dup is explicitly allowed for compat reason 140 if allow_dup: 141 # this could left one or more optional props unresolved. 142 # Convert them into non-optional because init doesn't understand ?= 143 # syntax 144 for p in optional_props: 145 p.optional = False 146 continue 147 148 success = False 149 sys.stderr.write("error: found duplicate sysprop assignments:\n") 150 for p in overriding_props: 151 sys.stderr.write("%s\n" % str(p)) 152 elif len(overriding_props) == 1: 153 for p in optional_props: 154 p.delete("overridden by %s" % str(overriding_props[0])) 155 else: 156 if len(optional_props) > 1: 157 for p in optional_props[:-1]: 158 p.delete("overridden by %s" % str(optional_props[-1])) 159 # Make the last optional one as non-optional 160 optional_props[-1].optional = False 161 162 return success 163 164class Prop: 165 166 def __init__(self, name, value, optional=False, comment=None): 167 self.name = name.strip() 168 self.value = value.strip() 169 if comment != None: 170 self.comments = [comment] 171 else: 172 self.comments = [] 173 self.optional = optional 174 175 @staticmethod 176 def from_line(line): 177 line = line.rstrip('\n') 178 if line.startswith("#"): 179 return Prop("", "", comment=line) 180 elif "?=" in line: 181 name, value = line.split("?=", 1) 182 return Prop(name, value, optional=True) 183 elif "=" in line: 184 name, value = line.split("=", 1) 185 return Prop(name, value, optional=False) 186 else: 187 # don't fail on invalid line 188 # TODO(jiyong) make this a hard error 189 return Prop("", "", comment=line) 190 191 def is_comment(self): 192 return bool(self.comments and not self.name) 193 194 def is_optional(self): 195 return (not self.is_comment()) and self.optional 196 197 def make_as_comment(self): 198 # Prepend "#" to the last line which is the prop assignment 199 if not self.is_comment(): 200 assignment = str(self).rsplit("\n", 1)[-1] 201 self.comments.append("#" + assignment) 202 self.name = "" 203 self.value = "" 204 205 def delete(self, reason): 206 self.comments.append("# Removed by post_process_props.py because " + reason) 207 self.make_as_comment() 208 209 def __str__(self): 210 assignment = [] 211 if not self.is_comment(): 212 operator = "?=" if self.is_optional() else "=" 213 assignment.append(self.name + operator + self.value) 214 return "\n".join(self.comments + assignment) 215 216class PropList: 217 218 def __init__(self, filename): 219 with open(filename) as f: 220 self.props = [Prop.from_line(l) 221 for l in f.readlines() if l.strip() != ""] 222 223 def get_all_props(self): 224 return [p for p in self.props if not p.is_comment()] 225 226 def get_all_names(self): 227 return set([p.name for p in self.get_all_props()]) 228 229 def get_props(self, name): 230 return [p for p in self.get_all_props() if p.name == name] 231 232 def get_value(self, name): 233 # Caution: only the value of the first sysprop having the name is returned. 234 return next((p.value for p in self.props if p.name == name), "") 235 236 def put(self, name, value): 237 # Note: when there is an optional prop for the name, its value isn't changed. 238 # Instead a new non-optional prop is appended, which will override the 239 # optional prop. Otherwise, the new value might be overridden by an existing 240 # non-optional prop of the same name. 241 index = next((i for i,p in enumerate(self.props) 242 if p.name == name and not p.is_optional()), -1) 243 if index == -1: 244 self.props.append(Prop(name, value, 245 comment="# Auto-added by post_process_props.py")) 246 else: 247 self.props[index].comments.append( 248 "# Value overridden by post_process_props.py. Original value: %s" % 249 self.props[index].value) 250 self.props[index].value = value 251 252 def write(self, filename): 253 with open(filename, 'w+') as f: 254 for p in self.props: 255 f.write(str(p) + "\n") 256 257def main(argv): 258 parser = argparse.ArgumentParser(description="Post-process build.prop file") 259 parser.add_argument("--allow-dup", dest="allow_dup", action="store_true", 260 default=False) 261 parser.add_argument("filename") 262 parser.add_argument("disallowed_keys", metavar="KEY", type=str, nargs="*") 263 parser.add_argument("--sdk-version", type=int, required=True) 264 args = parser.parse_args() 265 266 if not args.filename.endswith("/build.prop"): 267 sys.stderr.write("bad command line: " + str(argv) + "\n") 268 sys.exit(1) 269 270 props = PropList(args.filename) 271 mangle_build_prop(props) 272 if not override_optional_props(props, args.allow_dup): 273 sys.exit(1) 274 if not validate_grf_props(props, args.sdk_version): 275 sys.exit(1) 276 if not validate(props): 277 sys.exit(1) 278 279 # Drop any disallowed keys 280 for key in args.disallowed_keys: 281 for p in props.get_props(key): 282 p.delete("%s is a disallowed key" % key) 283 284 props.write(args.filename) 285 286if __name__ == "__main__": 287 main(sys.argv) 288