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