1# Copyright 2021 Google LLC 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""Runtime functions.""" 16 17_soong_config_namespaces_key = "$SOONG_CONFIG_NAMESPACES" 18_dist_for_goals_key = "$dist_for_goals" 19def _init_globals(input_variables_init): 20 """Initializes dictionaries of global variables. 21 22 This function runs the given input_variables_init function, 23 passing it a globals dictionary and a handle as if it 24 were a regular product. It then returns 2 copies of 25 the globals dictionary, so that one can be kept around 26 to diff changes made to the other later. 27 """ 28 globals_base = {"PRODUCT_SOONG_NAMESPACES": []} 29 input_variables_init(globals_base, __h_new()) 30 31 # Rerun input_variables_init to produce a copy 32 # of globals_base, because starlark doesn't support 33 # deep copying objects. 34 globals = {"PRODUCT_SOONG_NAMESPACES": []} 35 input_variables_init(globals, __h_new()) 36 37 # Variables that should be defined. 38 mandatory_vars = [ 39 "PLATFORM_VERSION_CODENAME", 40 "PLATFORM_VERSION", 41 "PRODUCT_SOONG_NAMESPACES", 42 # TODO(asmundak): do we need TARGET_ARCH? AOSP does not reference it 43 "TARGET_BUILD_VARIANT", 44 "TARGET_PRODUCT", 45 ] 46 for bv in mandatory_vars: 47 if not bv in globals: 48 fail(bv, " is not defined") 49 50 return (globals, globals_base) 51 52def __print_attr(attr, value): 53 # Allow using empty strings to clear variables, but not None values 54 if value == None: 55 return 56 if type(value) == "list": 57 if _options.rearrange: 58 value = __printvars_rearrange_list(value) 59 if _options.format == "pretty": 60 print(attr, "=", repr(value)) 61 elif _options.format == "make": 62 value = list(value) 63 for i, x in enumerate(value): 64 if type(x) == "tuple" and len(x) == 1: 65 value[i] = "@inherit:" + x[0] + ".mk" 66 elif type(x) != "string": 67 fail("Wasn't a list of strings:", attr, " value:", value) 68 print(attr, ":=", " ".join(value)) 69 elif _options.format == "pretty": 70 print(attr, "=", repr(value)) 71 elif _options.format == "make": 72 # Trim all spacing to a single space 73 print(attr, ":=", _mkstrip(value)) 74 else: 75 fail("bad output format", _options.format) 76 77def _printvars(state): 78 """Prints configuration and global variables.""" 79 (globals, globals_base) = state 80 for attr, val in sorted(globals.items()): 81 if attr == _soong_config_namespaces_key: 82 __print_attr("SOONG_CONFIG_NAMESPACES", val.keys()) 83 for nsname, nsvars in sorted(val.items()): 84 # Define SOONG_CONFIG_<ns> for Make, othewise 85 # it cannot be added to .KATI_READONLY list 86 if _options.format == "make": 87 print("SOONG_CONFIG_" + nsname, ":=", " ".join(nsvars.keys())) 88 for var, val in sorted(nsvars.items()): 89 if val: 90 __print_attr("SOONG_CONFIG_%s_%s" % (nsname, var), val) 91 else: 92 print("SOONG_CONFIG_%s_%s :=" % (nsname, var)) 93 elif attr == _dist_for_goals_key: 94 goals = [] 95 src_dst_list = [] 96 goal_dst_list = [] 97 for goal_name, goal_src_dst_list in sorted(val.items()): 98 goals.append(goal_name) 99 for sd in sorted(goal_src_dst_list): 100 src_dst_list.append(":".join(sd)) 101 goal_dst_list.append(":".join((goal_name, sd[1]))) 102 print("_all_dist_goal_output_pairs:=", " ".join(goal_dst_list)) 103 print("_all_dist_goals:=", " ".join(goals)) 104 print("_all_dist_src_dst_pairs:=", " ".join(src_dst_list)) 105 elif attr not in globals_base or globals_base[attr] != val: 106 __print_attr(attr, val) 107 108def __printvars_rearrange_list(value_list): 109 """Rearrange value list: return only distinct elements, maybe sorted.""" 110 seen = {item: 0 for item in value_list} 111 return sorted(seen.keys()) if _options.rearrange == "sort" else seen.keys() 112 113def __sort_pcm_names(pcm_names): 114 # We have to add an extension back onto the pcm names when sorting, 115 # or else the sort order could be wrong when one is a prefix of another. 116 return [x[:-3] for x in sorted([y + ".mk" for y in pcm_names], reverse=True)] 117 118def _product_configuration(top_pcm_name, top_pcm, input_variables_init): 119 """Creates configuration.""" 120 121 # Product configuration is created by traversing product's inheritance 122 # tree. It is traversed twice. 123 # First, beginning with top-level module we execute a module and find 124 # its ancestors, repeating this recursively. At the end of this phase 125 # we get the full inheritance tree. 126 # Second, we traverse the tree in the postfix order (i.e., visiting a 127 # node after its ancestors) to calculate the product configuration. 128 # 129 # PCM means "Product Configuration Module", i.e., a Starlark file 130 # whose body consists of a single init function. 131 132 globals, globals_base = _init_globals(input_variables_init) 133 134 # Each PCM is represented by a quadruple of function, config, children names 135 # and readyness (that is, the configurations from inherited PCMs have been 136 # substituted). 137 configs = {top_pcm_name: (top_pcm, None, [], False)} # All known PCMs 138 139 # Stack containing PCMs to be processed 140 pcm_stack = [top_pcm_name] 141 142 # Run it until pcm_stack is exhausted, but no more than N times 143 for n in range(1000): 144 if not pcm_stack: 145 break 146 name = pcm_stack.pop() 147 pcm, cfg, c, _ = configs[name] 148 149 # cfg is set only after PCM has been called, leverage this 150 # to prevent calling the same PCM twice 151 if cfg != None: 152 continue 153 154 # Run this one, obtaining its configuration and child PCMs. 155 if _options.trace_modules: 156 rblf_log("%d: %s" % (n, name)) 157 158 # Run PCM. 159 handle = __h_new() 160 pcm(globals, handle) 161 162 if handle.artifact_path_requirements: 163 globals["PRODUCTS."+name+".mk.ARTIFACT_PATH_REQUIREMENTS"] = handle.artifact_path_requirements 164 globals["PRODUCTS."+name+".mk.ARTIFACT_PATH_ALLOWED_LIST"] = handle.artifact_path_allowed_list 165 globals["PRODUCTS."+name+".mk.ARTIFACT_PATH_REQUIREMENT_IS_RELAXED"] = "true" if handle.artifact_path_requirement_is_relaxed[0] else "" 166 globals.setdefault("ARTIFACT_PATH_REQUIREMENT_PRODUCTS", []) 167 globals["ARTIFACT_PATH_REQUIREMENT_PRODUCTS"] = sorted(globals["ARTIFACT_PATH_REQUIREMENT_PRODUCTS"] + [name+".mk"]) 168 169 if handle.product_enforce_packages_exist[0]: 170 globals["PRODUCTS."+name+".mk.PRODUCT_ENFORCE_PACKAGES_EXIST"] = "true" 171 globals["PRODUCTS."+name+".mk.PRODUCT_ENFORCE_PACKAGES_EXIST_ALLOW_LIST"] = handle.product_enforce_packages_exist_allow_list 172 173 # Now we know everything about this PCM, record it in 'configs'. 174 children = handle.inherited_modules 175 if _options.trace_modules: 176 rblf_log(" ", " ".join(children.keys())) 177 # Starlark dictionaries are guaranteed to iterate through in insertion order, 178 # so children.keys() will be ordered by the inherit() calls 179 configs[name] = (pcm, handle.cfg, children.keys(), False) 180 181 for child_name in __sort_pcm_names(children.keys()): 182 if child_name not in configs: 183 configs[child_name] = (children[child_name], None, [], False) 184 pcm_stack.append(child_name) 185 if pcm_stack: 186 fail("Inheritance processing took too many iterations") 187 188 for pcm_name in globals.get("ARTIFACT_PATH_REQUIREMENT_PRODUCTS", []): 189 for var, val in evaluate_finalized_product_variables(configs, pcm_name[:-3]).items(): 190 globals["PRODUCTS."+pcm_name+"."+var] = val 191 192 # Copy product config variables from the cfg dictionary to the 193 # PRODUCTS.<top_level_makefile_name>.<var_name> global variables. 194 for var, val in evaluate_finalized_product_variables(configs, top_pcm_name, _options.trace_modules).items(): 195 globals["PRODUCTS."+top_pcm_name+".mk."+var] = val 196 197 # Record inheritance hierarchy in PRODUCTS.<file>.INHERITS_FROM variables. 198 # This is required for m product-graph. 199 for config in configs: 200 if len(configs[config][2]) > 0: 201 globals["PRODUCTS."+config+".mk.INHERITS_FROM"] = sorted([x + ".mk" for x in configs[config][2]]) 202 globals["PRODUCTS"] = __words(globals.get("PRODUCTS", [])) + [top_pcm_name + ".mk"] 203 204 return (globals, globals_base) 205 206def evaluate_finalized_product_variables(configs, top_level_pcm_name, trace=False): 207 configs_postfix = [] 208 pcm_stack = [(top_level_pcm_name, True)] 209 for i in range(1000): 210 if not pcm_stack: 211 break 212 213 pcm_name, before = pcm_stack.pop() 214 if before: 215 pcm_stack.append((pcm_name, False)) 216 for child in __sort_pcm_names(configs[pcm_name][2]): 217 pcm_stack.append((child, True)) 218 else: 219 configs_postfix.append(pcm_name) 220 if pcm_stack: 221 fail("Inheritance processing took too many iterations") 222 223 # clone the configs, because in the process of evaluating the 224 # final cfg dictionary we will remove values from the intermediate 225 # cfg dictionaries. We need to be able to call evaluate_finalized_product_variables() 226 # multiple times, so we can't change the origional configs object. 227 cloned_configs = {} 228 for pcm_name in configs: 229 # skip unneeded pcms 230 if pcm_name not in configs_postfix: 231 continue 232 pcm, cfg, children_names, ready = configs[pcm_name] 233 cloned_cfg = {} 234 for var, val in cfg.items(): 235 if type(val) == 'list': 236 cloned_cfg[var] = list(val) 237 else: 238 cloned_cfg[var] = val 239 cloned_configs[pcm_name] = (pcm, cloned_cfg, children_names, ready) 240 configs = cloned_configs 241 242 if trace: 243 rblf_log("\n---Postfix---") 244 for x in configs_postfix: 245 rblf_log(" ", x) 246 247 # Traverse the tree from the bottom, evaluating inherited values 248 for pcm_name in configs_postfix: 249 pcm, cfg, children_names, ready = configs[pcm_name] 250 251 # Should run 252 if cfg == None: 253 fail("%s: has not been run" % pcm_name) 254 255 # Ready once 256 if ready: 257 continue 258 259 # Children should be ready 260 for child_name in children_names: 261 if not configs[child_name][3]: 262 fail("%s: child is not ready" % child_name) 263 264 _substitute_inherited(configs, pcm_name, cfg) 265 _percolate_inherited(configs, pcm_name, cfg, children_names) 266 configs[pcm_name] = pcm, cfg, children_names, True 267 return configs[top_level_pcm_name][1] 268 269def _dictionary_difference(a, b): 270 result = {} 271 for attr, val in a.items(): 272 if attr not in b or b[attr] != val: 273 result[attr] = val 274 return result 275 276def _board_configuration(board_config_init, input_variables_init): 277 globals_base = {} 278 h_base = __h_new() 279 globals = {} 280 h = __h_new() 281 282 input_variables_init(globals_base, h_base) 283 input_variables_init(globals, h) 284 board_config_init(globals, h) 285 286 # Board configuration files aren't really supposed to change 287 # product configuration variables, but some do. You lose the 288 # inheritance features of the product config variables if you do. 289 for var, value in _dictionary_difference(h.cfg, h_base.cfg).items(): 290 globals[var] = value 291 292 return (globals, globals_base) 293 294 295def _substitute_inherited(configs, pcm_name, cfg): 296 """Substitutes inherited values in all the attributes. 297 298 When a value of an attribute is a list, some of its items may be 299 references to a value of a same attribute in an inherited product, 300 e.g., for a given module PRODUCT_PACKAGES can be 301 ["foo", (submodule), "bar"] 302 and for 'submodule' PRODUCT_PACKAGES may be ["baz"] 303 (we use a tuple to distinguish submodule references). 304 After the substitution the value of PRODUCT_PACKAGES for the module 305 will become ["foo", "baz", "bar"] 306 """ 307 for attr, val in cfg.items(): 308 # TODO(asmundak): should we handle single vars? 309 if type(val) != "list": 310 continue 311 312 if attr not in _options.trace_variables: 313 cfg[attr] = _value_expand(configs, attr, val) 314 else: 315 old_val = val 316 new_val = _value_expand(configs, attr, val) 317 if new_val != old_val: 318 rblf_log("%s(i): %s=%s (was %s)" % (pcm_name, attr, new_val, old_val)) 319 cfg[attr] = new_val 320 321def _value_expand(configs, attr, values_list): 322 """Expands references to inherited values in a given list.""" 323 result = [] 324 expanded = {} 325 for item in values_list: 326 # Inherited values are 1-tuples 327 if type(item) != "tuple": 328 result.append(item) 329 continue 330 child_name = item[0] 331 if child_name in expanded: 332 continue 333 expanded[child_name] = True 334 child = configs[child_name] 335 if not child[3]: 336 fail("%s should be ready" % child_name) 337 __move_items(result, child[1], attr) 338 339 return result 340 341def _percolate_inherited(configs, cfg_name, cfg, children_names): 342 """Percolates the settings that are present only in children.""" 343 percolated_attrs = {} 344 for child_name in children_names: 345 child_cfg = configs[child_name][1] 346 for attr, value in child_cfg.items(): 347 if type(value) != "list": 348 continue 349 if attr in percolated_attrs: 350 # We already are percolating this one, just add this list 351 __move_items(cfg[attr], child_cfg, attr) 352 elif not attr in cfg: 353 percolated_attrs[attr] = True 354 cfg[attr] = [] 355 __move_items(cfg[attr], child_cfg, attr) 356 357 # single value variables need to be inherited in alphabetical order, 358 # not in the order of inherit() calls. 359 for child_name in sorted(children_names): 360 child_cfg = configs[child_name][1] 361 for attr, value in child_cfg.items(): 362 if type(value) != "list": 363 # Single value variables take the first value available from the leftmost 364 # branch of the tree. If we also had "or attr in percolated_attrs" in this 365 # if statement, it would take the value from the rightmost branch. 366 if cfg.get(attr, "") == "": 367 cfg[attr] = value 368 percolated_attrs[attr] = True 369 370 for attr in _options.trace_variables: 371 if attr in percolated_attrs: 372 rblf_log("%s: %s^=%s" % (cfg_name, attr, cfg[attr])) 373 374def __move_items(to_list, from_cfg, attr): 375 value = from_cfg.get(attr, []) 376 if value: 377 to_list.extend(value) 378 from_cfg[attr] = [] 379 380def _indirect(pcm_name): 381 """Returns configuration item for the inherited module.""" 382 return (pcm_name,) 383 384def _soong_config_namespace(g, nsname): 385 """Adds given namespace if it does not exist.""" 386 387 old = g.get(_soong_config_namespaces_key, {}) 388 if old.get(nsname): 389 return 390 391 # A value cannot be updated, so we need to create a new dictionary 392 g[_soong_config_namespaces_key] = dict([(k,v) for k,v in old.items()] + [(nsname, {})]) 393 394def _soong_config_set(g, nsname, var, value): 395 """Assigns the value to the variable in the namespace.""" 396 _soong_config_namespace(g, nsname) 397 g[_soong_config_namespaces_key][nsname][var]=value 398 399def _soong_config_append(g, nsname, var, value): 400 """Appends to the value of the variable in the namespace.""" 401 _soong_config_namespace(g, nsname) 402 ns = g[_soong_config_namespaces_key][nsname] 403 oldv = ns.get(var) 404 if oldv == None: 405 ns[var] = value 406 else: 407 ns[var] += " " + value 408 409 410def _soong_config_get(g, nsname, var): 411 """Gets to the value of the variable in the namespace.""" 412 return g.get(_soong_config_namespaces_key, {}).get(nsname, {}).get(var, None) 413 414def _abspath(paths): 415 """Provided for compatibility, to be removed later.""" 416 cwd = rblf_shell('pwd') 417 results = [] 418 for path in __words(paths): 419 if path[0] != "/": 420 path = cwd + "/" + path 421 422 resultparts = [] 423 for part in path.split('/'): 424 if part == "." or part == "": 425 continue 426 elif part == "..": 427 if resultparts: 428 resultparts.pop() 429 else: 430 resultparts.append(part) 431 results.append("/" + "/".join(resultparts)) 432 433 return " ".join(results) 434 435 436def _addprefix(prefix, string_or_list): 437 """Adds prefix and returns a list. 438 439 If string_or_list is a list, prepends prefix to each element. 440 Otherwise, string_or_list is considered to be a string which 441 is split into words and then prefix is prepended to each one. 442 443 Args: 444 prefix 445 string_or_list 446 447 """ 448 return [prefix + x for x in __words(string_or_list)] 449 450def _addsuffix(suffix, string_or_list): 451 """Adds suffix and returns a list. 452 453 If string_or_list is a list, appends suffix to each element. 454 Otherwise, string_or_list is considered to be a string which 455 is split into words and then suffix is appended to each one. 456 457 Args: 458 suffix 459 string_or_list 460 """ 461 return [x + suffix for x in __words(string_or_list)] 462 463def __words(string_or_list): 464 if type(string_or_list) == "list": 465 for x in string_or_list: 466 if type(x) != "string": 467 return string_or_list 468 string_or_list = " ".join(string_or_list) 469 return _mkstrip(string_or_list).split() 470 471# Handle manipulation functions. 472# A handle passed to a PCM consists of: 473# product attributes dict ("cfg") 474# inherited modules dict (maps module name to PCM) 475# default value list (initially empty, modified by inheriting) 476def __h_new(): 477 """Constructs a handle which is passed to PCM.""" 478 return struct( 479 cfg = dict(), 480 inherited_modules = dict(), 481 default_list_value = list(), 482 artifact_path_requirements = list(), 483 artifact_path_allowed_list = list(), 484 artifact_path_requirement_is_relaxed = [False], # as a list so that we can reassign it 485 product_enforce_packages_exist = [False], 486 product_enforce_packages_exist_allow_list = [], 487 ) 488 489def __h_cfg(handle): 490 """Returns PCM's product configuration attributes dict. 491 492 This function is also exported as rblf.cfg, and every PCM 493 calls it at the beginning. 494 """ 495 return handle.cfg 496 497def _setdefault(handle, attr): 498 """If attribute has not been set, assigns default value to it. 499 500 This function is exported as rblf.setdefault(). 501 Only list attributes are initialized this way. The default 502 value is kept in the PCM's handle. Calling inherit() updates it. 503 """ 504 cfg = handle.cfg 505 if cfg.get(attr) == None: 506 cfg[attr] = list(handle.default_list_value) 507 return cfg[attr] 508 509def _inherit(handle, pcm_name, pcm): 510 """Records inheritance. 511 512 This function is exported as rblf.inherit, PCM calls it when 513 a module is inherited. 514 """ 515 handle.inherited_modules[pcm_name] = pcm 516 handle.default_list_value.append(_indirect(pcm_name)) 517 518 # Add inherited module reference to all configuration values 519 for attr, val in handle.cfg.items(): 520 if type(val) == "list": 521 val.append(_indirect(pcm_name)) 522 523def __base(path): 524 """Returns basename.""" 525 return path.rsplit("/",1)[-1] 526 527def _board_platform_in(g, string_or_list): 528 """Returns true if board is in the list.""" 529 board = g.get("TARGET_BOARD_PLATFORM","") 530 if not board: 531 return False 532 return board in __words(string_or_list) 533 534 535def _board_platform_is(g, s): 536 """True if board is the same as argument.""" 537 return g.get("TARGET_BOARD_PLATFORM","") == s 538 539 540def _copy_files(l, outdir): 541 """Generate <item>:<outdir>/item for each item.""" 542 return ["%s:%s/%s" % (path, outdir, __base(path)) for path in __words(l)] 543 544def _copy_if_exists(path_pair): 545 """If from file exists, returns [from:to] pair.""" 546 value = path_pair.split(":", 2) 547 548 if value[0].find('*') != -1: 549 fail("copy_if_exists: input file cannot contain *") 550 551 # Check that l[0] exists 552 return [":".join(value)] if rblf_wildcard(value[0]) else [] 553 554def _enforce_product_packages_exist(handle, pkg_string_or_list=[]): 555 """Makes including non-existent modules in PRODUCT_PACKAGES an error.""" 556 handle.product_enforce_packages_exist[0] = True 557 handle.product_enforce_packages_exist_allow_list.clear() 558 handle.product_enforce_packages_exist_allow_list.extend(__words(pkg_string_or_list)) 559 560def _add_product_dex_preopt_module_config(handle, modules, config): 561 """Equivalent to add-product-dex-preopt-module-config from build/make/core/product.mk.""" 562 modules = __words(modules) 563 config = _mkstrip(config).replace(" ", "|@SP@|") 564 _setdefault(handle, "PRODUCT_DEX_PREOPT_MODULE_CONFIGS") 565 handle.cfg["PRODUCT_DEX_PREOPT_MODULE_CONFIGS"] += [m + "=" + config for m in modules] 566 567def _find_and_copy(pattern, from_dir, to_dir): 568 """Return a copy list for the files matching the pattern.""" 569 return sorted([("%s/%s:%s/%s" % (from_dir, f, to_dir, f)) 570 .replace("//", "/") for f in rblf_find_files(from_dir, pattern, only_files=1)]) 571 572def _findstring(needle, haystack): 573 """Equivalent to GNU make's $(findstring).""" 574 if haystack.find(needle) < 0: 575 return "" 576 return needle 577 578def _filter_out(pattern, text): 579 """Return all the words from `text' that do not match any word in `pattern'. 580 581 Args: 582 pattern: string or list of words. '%' stands for wildcard (in regex terms, '.*') 583 text: string or list of words 584 Return: 585 list of words 586 """ 587 patterns = [__mkparse_pattern(x) for x in __words(pattern)] 588 res = [] 589 for w in __words(text): 590 match = False 591 for p in patterns: 592 if __mkpattern_matches(p, w): 593 match = True 594 break 595 if not match: 596 res.append(w) 597 return res 598 599def _filter(pattern, text): 600 """Return all the words in `text` that match `pattern`. 601 602 Args: 603 pattern: strings of words or a list. A word can contain '%', 604 which stands for any sequence of characters. 605 text: string or list of words. 606 """ 607 patterns = [__mkparse_pattern(x) for x in __words(pattern)] 608 res = [] 609 for w in __words(text): 610 for p in patterns: 611 if __mkpattern_matches(p, w): 612 res.append(w) 613 break 614 return res 615 616def _first_word(input): 617 """Equivalent to the GNU make function $(firstword).""" 618 input = __words(input) 619 if len(input) == 0: 620 return "" 621 return input[0] 622 623def _last_word(input): 624 """Equivalent to the GNU make function $(lastword).""" 625 input = __words(input) 626 l = len(input) 627 if l == 0: 628 return "" 629 return input[l-1] 630 631def _flatten_2d_list(list): 632 result = [] 633 for x in list: 634 result += x 635 return result 636 637def _dir(paths): 638 """Equivalent to the GNU make function $(dir). 639 640 Returns the folder of the file for each path in paths. 641 """ 642 return " ".join([w.rsplit("/",1)[0] for w in __words(paths)]) 643 644def _notdir(paths): 645 """Equivalent to the GNU make function $(notdir). 646 647 Returns the name of the file at the end of each path in paths. 648 """ 649 return " ".join([__base(w) for w in __words(paths)]) 650 651def _require_artifacts_in_path(handle, paths, allowed_paths): 652 """Equivalent to require-artifacts-in-path in Make.""" 653 handle.artifact_path_requirements.clear() 654 handle.artifact_path_requirements.extend(__words(paths)) 655 handle.artifact_path_allowed_list.clear() 656 handle.artifact_path_allowed_list.extend(__words(allowed_paths)) 657 658def _require_artifacts_in_path_relaxed(handle, paths, allowed_paths): 659 """Equivalent to require-artifacts-in-path-relaxed in Make.""" 660 _require_artifacts_in_path(handle, paths, allowed_paths) 661 handle.artifact_path_requirement_is_relaxed[0] = True 662 663def _expand_wildcard(pattern): 664 """Expands shell wildcard pattern.""" 665 result = [] 666 for word in __words(pattern): 667 result.extend(rblf_wildcard(word)) 668 return result 669 670def _mkdist_for_goals(g, goal, src_dst_list): 671 """Implements dist-for-goals macro.""" 672 goals_map = g.get(_dist_for_goals_key, {}) 673 pairs = goals_map.get(goal) 674 if pairs == None: 675 pairs = [] 676 g[_dist_for_goals_key] = dict([(k,v) for k,v in goals_map.items()] + [(goal, pairs)]) 677 for src_dst in __words(src_dst_list): 678 pair=src_dst.split(":") 679 if len(pair) > 2: 680 fail(src_dst + " should be a :-separated pair") 681 pairs.append((pair[0],pair[1] if len(pair) == 2 and pair[1] else __base(pair[0]))) 682 g[_dist_for_goals_key][goal] = pairs 683 684 685def _mkerror(file, message = ""): 686 """Prints error and stops.""" 687 fail("%s: %s. Stop" % (file, message)) 688 689def _mkwarning(file, message = ""): 690 """Prints warning.""" 691 rblf_log(file, "warning", message, sep = ':') 692 693def _mk2rbc_error(loc, message): 694 """Prints a message about conversion error and stops. 695 696 If RBC_MK2RBC_CONTINUE environment variable is set, 697 the execution will continue after the message is printed. 698 """ 699 if _options.mk2rbc_continue: 700 rblf_log(loc, message, sep = ':') 701 else: 702 _mkerror(loc, message) 703 704 705def _mkinfo(file, message = ""): 706 """Prints info.""" 707 rblf_log(message) 708 709 710def __mkparse_pattern(pattern): 711 """Parses Make's patsubst pattern. 712 713 This is equivalent to pattern.split('%', 1), except it 714 also takes into account escaping the % symbols. 715 """ 716 in_escape = False 717 res = [] 718 acc = "" 719 for c in pattern.elems(): 720 if in_escape: 721 in_escape = False 722 acc += c 723 elif c == '\\': 724 in_escape = True 725 elif c == '%' and not res: 726 res.append(acc) 727 acc = '' 728 else: 729 acc += c 730 if in_escape: 731 acc += '\\' 732 res.append(acc) 733 return res 734 735def __mkpattern_matches(pattern, word): 736 """Returns if a pattern matches a given word. 737 738 The pattern must be a list of strings of length at most 2. 739 This checks if word is either equal to the pattern or 740 starts/ends with the two parts of the pattern. 741 """ 742 if len(pattern) > 2: 743 fail("Pattern can have at most 2 components") 744 elif len(pattern) == 1: 745 return pattern[0]==word 746 else: 747 return ((len(word) >= len(pattern[0])+len(pattern[1])) 748 and word.startswith(pattern[0]) 749 and word.endswith(pattern[1])) 750 751def __mkpatsubst_word(parsed_pattern,parsed_subst, word): 752 (before, after) = parsed_pattern 753 if not word.startswith(before): 754 return word 755 if not word.endswith(after): 756 return word 757 if len(parsed_subst) < 2: 758 return parsed_subst[0] 759 return parsed_subst[0] + word[len(before):len(word) - len(after)] + parsed_subst[1] 760 761 762def _mkpatsubst(pattern, replacement, s): 763 """Emulates Make's patsubst. 764 765 Tokenizes `s` (unless it is already a list), and then performs a simple 766 wildcard substitution (in other words, `foo%bar` pattern is equivalent to 767 the regular expression `^foo(.*)bar$, and the first `%` in replacement is 768 $1 in regex terms). 769 """ 770 parsed_pattern = __mkparse_pattern(pattern) 771 if len(parsed_pattern) == 1: 772 out_words = [ replacement if x == pattern else x for x in __words(s)] 773 else: 774 parsed_replacement = __mkparse_pattern(replacement) 775 out_words = [__mkpatsubst_word(parsed_pattern, parsed_replacement, x) for x in __words(s)] 776 return out_words if type(s) == "list" else " ".join(out_words) 777 778 779def _mksort(input): 780 """Emulate Make's sort. 781 782 This is unique from a regular sort in that it also strips 783 the input, and removes duplicate words from the input. 784 """ 785 input = sorted(__words(input)) 786 result = [] 787 for w in input: 788 if len(result) == 0 or result[-1] != w: 789 result.append(w) 790 return result 791 792 793def _mkstrip(s): 794 """Emulates Make's strip. 795 796 That is, removes string's leading and trailing whitespace characters and 797 replaces any sequence of whitespace characters with with a single space. 798 """ 799 t = type(s) 800 if t == "list": 801 s = " ".join(s) 802 elif t != "string": 803 fail("Argument to mkstrip must be a string or list, got: "+t) 804 result = "" 805 was_space = False 806 for ch in s.strip().elems(): 807 is_space = ch.isspace() 808 if not is_space: 809 if was_space: 810 result += " " 811 result += ch 812 was_space = is_space 813 return result 814 815def _mksubst(old, new, s): 816 """Emulates Make's subst. 817 818 Replaces each occurence of 'old' with 'new'. 819 If 's' is a list, applies substitution to each item. 820 """ 821 if type(s) == "list": 822 return [e.replace(old, new) for e in s] 823 return s.replace(old, new) 824 825 826def _product_copy_files_by_pattern(src, dest, s): 827 """Creates a copy list. 828 829 For each item in a given list, create <from>:<to> pair, where <from> and 830 <to> are the results of applying Make-style patsubst of <src> and <dest> 831 respectively. E.g. the result of calling this function with 832 ("foo/%", "bar/%", ["a", "b"]) will be 833 ["foo/a:bar/a", "foo/b:bar/b"]. 834 """ 835 parsed_src = __mkparse_pattern(src) 836 parsed_dest = __mkparse_pattern(dest) 837 parsed_percent = ["", ""] 838 words = s if type(s) == "list" else _mkstrip(s).split(" ") 839 return [ __mkpatsubst_word(parsed_percent, parsed_src, x) + ":" + __mkpatsubst_word(parsed_percent, parsed_dest, x) for x in words] 840 841 842__zero_values = { 843 "string": "", 844 "list": [], 845 "int": 0, 846 "float": 0, 847 "bool": False, 848 "dict": {}, 849 "NoneType": None, 850 "tuple": (), 851} 852def __zero_value(x): 853 t = type(x) 854 if t in __zero_values: 855 return __zero_values[t] 856 else: 857 fail("Unknown type: "+t) 858 859 860def _clear_var_list(g, h, var_list): 861 cfg = __h_cfg(h) 862 for v in __words(var_list): 863 # Set these variables to their zero values rather than None 864 # or removing them from the dictionary because if they were 865 # removed entirely, ?= would set their value, when it would not 866 # after a make-based clear_var_list call. 867 if v in g: 868 g[v] = __zero_value(g[v]) 869 if v in cfg: 870 cfg[v] = __zero_value(cfg[v]) 871 872 if v not in cfg and v not in g: 873 # Cause the variable to appear set like the make version does 874 g[v] = "" 875 876 877def __get_options(): 878 """Returns struct containing runtime global settings.""" 879 settings = dict( 880 format = "pretty", 881 rearrange = "", 882 trace_modules = False, 883 trace_variables = [], 884 mk2rbc_continue = False, 885 ) 886 for x in getattr(rblf_cli, "RBC_OUT", "").split(","): 887 if x == "sort" or x == "unique": 888 if settings["rearrange"]: 889 fail("RBC_OUT: either sort or unique is allowed (and sort implies unique)") 890 settings["rearrange"] = x 891 elif x == "pretty" or x == "make": 892 settings["format"] = x 893 elif x == "global": 894 # TODO: Remove this, kept for backwards compatibility 895 pass 896 elif x != "": 897 fail("RBC_OUT: got %s, should be one of: [pretty|make] [sort|unique]" % x) 898 for x in getattr(rblf_cli, "RBC_DEBUG", "").split(","): 899 if x == "!trace": 900 settings["trace_modules"] = True 901 elif x != "": 902 settings["trace_variables"].append(x) 903 if getattr(rblf_cli, "RBC_MK2RBC_CONTINUE", ""): 904 settings["mk2rbc_continue"] = True 905 return struct(**settings) 906 907# Settings used during debugging. 908_options = __get_options() 909rblf = struct( 910 soong_config_namespace = _soong_config_namespace, 911 soong_config_append = _soong_config_append, 912 soong_config_set = _soong_config_set, 913 soong_config_get = _soong_config_get, 914 abspath = _abspath, 915 add_product_dex_preopt_module_config = _add_product_dex_preopt_module_config, 916 addprefix = _addprefix, 917 addsuffix = _addsuffix, 918 board_platform_in = _board_platform_in, 919 board_platform_is = _board_platform_is, 920 clear_var_list = _clear_var_list, 921 copy_files = _copy_files, 922 copy_if_exists = _copy_if_exists, 923 cfg = __h_cfg, 924 dir = _dir, 925 enforce_product_packages_exist = _enforce_product_packages_exist, 926 expand_wildcard = _expand_wildcard, 927 filter = _filter, 928 filter_out = _filter_out, 929 find_and_copy = _find_and_copy, 930 findstring = _findstring, 931 first_word = _first_word, 932 last_word = _last_word, 933 flatten_2d_list = _flatten_2d_list, 934 inherit = _inherit, 935 indirect = _indirect, 936 mk2rbc_error = _mk2rbc_error, 937 mkdist_for_goals = _mkdist_for_goals, 938 mkinfo = _mkinfo, 939 mkerror = _mkerror, 940 mkpatsubst = _mkpatsubst, 941 mkwarning = _mkwarning, 942 mksort = _mksort, 943 mkstrip = _mkstrip, 944 mksubst = _mksubst, 945 notdir = _notdir, 946 printvars = _printvars, 947 product_configuration = _product_configuration, 948 board_configuration = _board_configuration, 949 product_copy_files_by_pattern = _product_copy_files_by_pattern, 950 require_artifacts_in_path = _require_artifacts_in_path, 951 require_artifacts_in_path_relaxed = _require_artifacts_in_path_relaxed, 952 setdefault = _setdefault, 953 shell = rblf_shell, 954 warning = _mkwarning, 955 words = __words, 956) 957