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