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 # 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. 44 if not prop_list.get_value("persist.sys.usb.config"): 45 prop_list.put("persist.sys.usb.config", "none") 46 47def validate_grf_props(prop_list, sdk_version): 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. 52 Also, validate the value of ro.board.api_level if defined. 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 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 83 84 return True 85 86def validate(prop_list): 87 """Validate the properties. 88 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 94 Returns: 95 True if nothing is wrong. 96 """ 97 check_pass = True 98 for p in prop_list.get_all_props(): 99 if len(p.value) > PROP_VALUE_MAX and not p.name.startswith("ro."): 100 check_pass = False 101 sys.stderr.write("error: %s cannot exceed %d bytes: " % 102 (p.name, PROP_VALUE_MAX)) 103 sys.stderr.write("%s (%d)\n" % (p.value, len(p.value))) 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 110 return check_pass 111 112def override_optional_props(prop_list, allow_dup=False): 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): 130 for p in optional_props: 131 p.delete("overridden by %s" % str(overriding_props[0])) 132 continue 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 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 158class Prop: 159 160 def __init__(self, name, value, optional=False, comment=None): 161 self.name = name.strip() 162 self.value = value.strip() 163 if comment != None: 164 self.comments = [comment] 165 else: 166 self.comments = [] 167 self.optional = optional 168 169 @staticmethod 170 def from_line(line): 171 line = line.rstrip('\n') 172 if line.startswith("#"): 173 return Prop("", "", comment=line) 174 elif "?=" in line: 175 name, value = line.split("?=", 1) 176 return Prop(name, value, optional=True) 177 elif "=" in line: 178 name, value = line.split("=", 1) 179 return Prop(name, value, optional=False) 180 else: 181 # don't fail on invalid line 182 # TODO(jiyong) make this a hard error 183 return Prop("", "", comment=line) 184 185 def is_comment(self): 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() 202 203 def __str__(self): 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) 209 210class 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 217 def get_all_props(self): 218 return [p for p in self.props if not p.is_comment()] 219 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. 228 return next((p.value for p in self.props if p.name == name), "") 229 230 def put(self, name, value): 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) 237 if index == -1: 238 self.props.append(Prop(name, value, 239 comment="# Auto-added by post_process_props.py")) 240 else: 241 self.props[index].comments.append( 242 "# Value overridden by post_process_props.py. Original value: %s" % 243 self.props[index].value) 244 self.props[index].value = value 245 246 def write(self, filename): 247 with open(filename, 'w+') as f: 248 for p in self.props: 249 f.write(str(p) + "\n") 250 251def main(argv): 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="*") 257 parser.add_argument("--sdk-version", type=int, required=True) 258 args = parser.parse_args() 259 260 if not args.filename.endswith("/build.prop"): 261 sys.stderr.write("bad command line: " + str(argv) + "\n") 262 sys.exit(1) 263 264 props = PropList(args.filename) 265 mangle_build_prop(props) 266 if not override_optional_props(props, args.allow_dup): 267 sys.exit(1) 268 if not validate_grf_props(props, args.sdk_version): 269 sys.exit(1) 270 if not validate(props): 271 sys.exit(1) 272 273 # Drop any disallowed keys 274 for key in args.disallowed_keys: 275 for p in props.get_props(key): 276 p.delete("%s is a disallowed key" % key) 277 278 props.write(args.filename) 279 280if __name__ == "__main__": 281 main(sys.argv) 282