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_append(g, nsname, var, value): 386 """Appends to the value of the variable in the namespace.""" 387 _soong_config_namespace(g, nsname) 388 ns = g[_soong_config_namespaces_key][nsname] 389 oldv = ns.get(var) 390 if oldv == None: 391 ns[var] = _mkstrip(value) 392 else: 393 ns[var] += " " + _mkstrip(value) 394 395 396def _soong_config_get(g, nsname, var): 397 """Gets to the value of the variable in the namespace.""" 398 return g.get(_soong_config_namespaces_key, {}).get(nsname, {}).get(var, None) 399 400def _abspath(paths): 401 """Provided for compatibility, to be removed later.""" 402 cwd = rblf_shell('pwd') 403 results = [] 404 for path in __words(paths): 405 if path[0] != "/": 406 path = cwd + "/" + path 407 408 resultparts = [] 409 for part in path.split('/'): 410 if part == "." or part == "": 411 continue 412 elif part == "..": 413 if resultparts: 414 resultparts.pop() 415 else: 416 resultparts.append(part) 417 results.append("/" + "/".join(resultparts)) 418 419 return " ".join(results) 420 421 422def _addprefix(prefix, string_or_list): 423 """Adds prefix and returns a list. 424 425 If string_or_list is a list, prepends prefix to each element. 426 Otherwise, string_or_list is considered to be a string which 427 is split into words and then prefix is prepended to each one. 428 429 Args: 430 prefix 431 string_or_list 432 433 """ 434 return [prefix + x for x in __words(string_or_list)] 435 436def _addsuffix(suffix, string_or_list): 437 """Adds suffix and returns a list. 438 439 If string_or_list is a list, appends suffix to each element. 440 Otherwise, string_or_list is considered to be a string which 441 is split into words and then suffix is appended to each one. 442 443 Args: 444 suffix 445 string_or_list 446 """ 447 return [x + suffix for x in __words(string_or_list)] 448 449def __words(string_or_list): 450 if type(string_or_list) == "list": 451 for x in string_or_list: 452 if type(x) != "string": 453 return string_or_list 454 string_or_list = " ".join(string_or_list) 455 return _mkstrip(string_or_list).split() 456 457# Handle manipulation functions. 458# A handle passed to a PCM consists of: 459# product attributes dict ("cfg") 460# inherited modules dict (maps module name to PCM) 461# default value list (initially empty, modified by inheriting) 462def __h_new(): 463 """Constructs a handle which is passed to PCM.""" 464 return struct( 465 cfg = dict(), 466 inherited_modules = dict(), 467 default_list_value = list(), 468 artifact_path_requirements = list(), 469 artifact_path_allowed_list = list(), 470 artifact_path_requirement_is_relaxed = [False], # as a list so that we can reassign it 471 product_enforce_packages_exist = [False], 472 product_enforce_packages_exist_allow_list = [], 473 ) 474 475def __h_cfg(handle): 476 """Returns PCM's product configuration attributes dict. 477 478 This function is also exported as rblf.cfg, and every PCM 479 calls it at the beginning. 480 """ 481 return handle.cfg 482 483def _setdefault(handle, attr): 484 """If attribute has not been set, assigns default value to it. 485 486 This function is exported as rblf.setdefault(). 487 Only list attributes are initialized this way. The default 488 value is kept in the PCM's handle. Calling inherit() updates it. 489 """ 490 cfg = handle.cfg 491 if cfg.get(attr) == None: 492 cfg[attr] = list(handle.default_list_value) 493 return cfg[attr] 494 495def _inherit(handle, pcm_name, pcm): 496 """Records inheritance. 497 498 This function is exported as rblf.inherit, PCM calls it when 499 a module is inherited. 500 """ 501 handle.inherited_modules[pcm_name] = pcm 502 handle.default_list_value.append(_indirect(pcm_name)) 503 504 # Add inherited module reference to all configuration values 505 for attr, val in handle.cfg.items(): 506 if type(val) == "list": 507 val.append(_indirect(pcm_name)) 508 509def __base(path): 510 """Returns basename.""" 511 return path.rsplit("/",1)[-1] 512 513def _board_platform_in(g, string_or_list): 514 """Returns true if board is in the list.""" 515 board = g.get("TARGET_BOARD_PLATFORM","") 516 if not board: 517 return False 518 return board in __words(string_or_list) 519 520 521def _board_platform_is(g, s): 522 """True if board is the same as argument.""" 523 return g.get("TARGET_BOARD_PLATFORM","") == s 524 525 526def _copy_files(l, outdir): 527 """Generate <item>:<outdir>/item for each item.""" 528 return ["%s:%s/%s" % (path, outdir, __base(path)) for path in __words(l)] 529 530def _copy_if_exists(path_pair): 531 """If from file exists, returns [from:to] pair.""" 532 value = path_pair.split(":", 2) 533 534 if value[0].find('*') != -1: 535 fail("copy_if_exists: input file cannot contain *") 536 537 # Check that l[0] exists 538 return [":".join(value)] if rblf_wildcard(value[0]) else [] 539 540def _enforce_product_packages_exist(handle, pkg_string_or_list=[]): 541 """Makes including non-existent modules in PRODUCT_PACKAGES an error.""" 542 handle.product_enforce_packages_exist[0] = True 543 handle.product_enforce_packages_exist_allow_list.clear() 544 handle.product_enforce_packages_exist_allow_list.extend(__words(pkg_string_or_list)) 545 546def _add_product_dex_preopt_module_config(handle, modules, config): 547 """Equivalent to add-product-dex-preopt-module-config from build/make/core/product.mk.""" 548 modules = __words(modules) 549 config = _mkstrip(config).replace(" ", "|@SP@|") 550 _setdefault(handle, "PRODUCT_DEX_PREOPT_MODULE_CONFIGS") 551 handle.cfg["PRODUCT_DEX_PREOPT_MODULE_CONFIGS"] += [m + "=" + config for m in modules] 552 553def _find_and_copy(pattern, from_dir, to_dir): 554 """Return a copy list for the files matching the pattern.""" 555 return sorted([("%s/%s:%s/%s" % (from_dir, f, to_dir, f)) 556 .replace("//", "/") for f in rblf_find_files(from_dir, pattern, only_files=1)]) 557 558def _findstring(needle, haystack): 559 """Equivalent to GNU make's $(findstring).""" 560 if haystack.find(needle) < 0: 561 return "" 562 return needle 563 564def _filter_out(pattern, text): 565 """Return all the words from `text' that do not match any word in `pattern'. 566 567 Args: 568 pattern: string or list of words. '%' stands for wildcard (in regex terms, '.*') 569 text: string or list of words 570 Return: 571 list of words 572 """ 573 patterns = [__mkparse_pattern(x) for x in __words(pattern)] 574 res = [] 575 for w in __words(text): 576 match = False 577 for p in patterns: 578 if __mkpattern_matches(p, w): 579 match = True 580 break 581 if not match: 582 res.append(w) 583 return res 584 585def _filter(pattern, text): 586 """Return all the words in `text` that match `pattern`. 587 588 Args: 589 pattern: strings of words or a list. A word can contain '%', 590 which stands for any sequence of characters. 591 text: string or list of words. 592 """ 593 patterns = [__mkparse_pattern(x) for x in __words(pattern)] 594 res = [] 595 for w in __words(text): 596 for p in patterns: 597 if __mkpattern_matches(p, w): 598 res.append(w) 599 break 600 return res 601 602def _first_word(input): 603 """Equivalent to the GNU make function $(firstword).""" 604 input = __words(input) 605 if len(input) == 0: 606 return "" 607 return input[0] 608 609def _last_word(input): 610 """Equivalent to the GNU make function $(lastword).""" 611 input = __words(input) 612 l = len(input) 613 if l == 0: 614 return "" 615 return input[l-1] 616 617def _flatten_2d_list(list): 618 result = [] 619 for x in list: 620 result += x 621 return result 622 623def _dir(paths): 624 """Equivalent to the GNU make function $(dir). 625 626 Returns the folder of the file for each path in paths. 627 """ 628 return " ".join([w.rsplit("/",1)[0] for w in __words(paths)]) 629 630def _notdir(paths): 631 """Equivalent to the GNU make function $(notdir). 632 633 Returns the name of the file at the end of each path in paths. 634 """ 635 return " ".join([__base(w) for w in __words(paths)]) 636 637def _require_artifacts_in_path(handle, paths, allowed_paths): 638 """Equivalent to require-artifacts-in-path in Make.""" 639 handle.artifact_path_requirements.clear() 640 handle.artifact_path_requirements.extend(__words(paths)) 641 handle.artifact_path_allowed_list.clear() 642 handle.artifact_path_allowed_list.extend(__words(allowed_paths)) 643 644def _require_artifacts_in_path_relaxed(handle, paths, allowed_paths): 645 """Equivalent to require-artifacts-in-path-relaxed in Make.""" 646 _require_artifacts_in_path(handle, paths, allowed_paths) 647 handle.artifact_path_requirement_is_relaxed[0] = True 648 649def _expand_wildcard(pattern): 650 """Expands shell wildcard pattern.""" 651 result = [] 652 for word in __words(pattern): 653 result.extend(rblf_wildcard(word)) 654 return result 655 656def _mkdist_for_goals(g, goal, src_dst_list): 657 """Implements dist-for-goals macro.""" 658 goals_map = g.get(_dist_for_goals_key, {}) 659 pairs = goals_map.get(goal) 660 if pairs == None: 661 pairs = [] 662 g[_dist_for_goals_key] = dict([(k,v) for k,v in goals_map.items()] + [(goal, pairs)]) 663 for src_dst in __words(src_dst_list): 664 pair=src_dst.split(":") 665 if len(pair) > 2: 666 fail(src_dst + " should be a :-separated pair") 667 pairs.append((pair[0],pair[1] if len(pair) == 2 and pair[1] else __base(pair[0]))) 668 g[_dist_for_goals_key][goal] = pairs 669 670 671def _mkerror(file, message = ""): 672 """Prints error and stops.""" 673 fail("%s: %s. Stop" % (file, message)) 674 675def _mkwarning(file, message = ""): 676 """Prints warning.""" 677 rblf_log(file, "warning", message, sep = ':') 678 679def _mk2rbc_error(loc, message): 680 """Prints a message about conversion error and stops.""" 681 _mkerror(loc, message) 682 683def _mkinfo(file, message = ""): 684 """Prints info.""" 685 rblf_log(message) 686 687 688def __mkparse_pattern(pattern): 689 """Parses Make's patsubst pattern. 690 691 This is equivalent to pattern.split('%', 1), except it 692 also takes into account escaping the % symbols. 693 """ 694 in_escape = False 695 res = [] 696 acc = "" 697 for c in pattern.elems(): 698 if in_escape: 699 in_escape = False 700 acc += c 701 elif c == '\\': 702 in_escape = True 703 elif c == '%' and not res: 704 res.append(acc) 705 acc = '' 706 else: 707 acc += c 708 if in_escape: 709 acc += '\\' 710 res.append(acc) 711 return res 712 713def __mkpattern_matches(pattern, word): 714 """Returns if a pattern matches a given word. 715 716 The pattern must be a list of strings of length at most 2. 717 This checks if word is either equal to the pattern or 718 starts/ends with the two parts of the pattern. 719 """ 720 if len(pattern) > 2: 721 fail("Pattern can have at most 2 components") 722 elif len(pattern) == 1: 723 return pattern[0]==word 724 else: 725 return ((len(word) >= len(pattern[0])+len(pattern[1])) 726 and word.startswith(pattern[0]) 727 and word.endswith(pattern[1])) 728 729def __mkpatsubst_word(parsed_pattern,parsed_subst, word): 730 (before, after) = parsed_pattern 731 if not word.startswith(before): 732 return word 733 if not word.endswith(after): 734 return word 735 if len(parsed_subst) < 2: 736 return parsed_subst[0] 737 return parsed_subst[0] + word[len(before):len(word) - len(after)] + parsed_subst[1] 738 739 740def _mkpatsubst(pattern, replacement, s): 741 """Emulates Make's patsubst. 742 743 Tokenizes `s` (unless it is already a list), and then performs a simple 744 wildcard substitution (in other words, `foo%bar` pattern is equivalent to 745 the regular expression `^foo(.*)bar$, and the first `%` in replacement is 746 $1 in regex terms). 747 """ 748 parsed_pattern = __mkparse_pattern(pattern) 749 if len(parsed_pattern) == 1: 750 out_words = [ replacement if x == pattern else x for x in __words(s)] 751 else: 752 parsed_replacement = __mkparse_pattern(replacement) 753 out_words = [__mkpatsubst_word(parsed_pattern, parsed_replacement, x) for x in __words(s)] 754 return out_words if type(s) == "list" else " ".join(out_words) 755 756 757def _mksort(input): 758 """Emulate Make's sort. 759 760 This is unique from a regular sort in that it also strips 761 the input, and removes duplicate words from the input. 762 """ 763 input = sorted(__words(input)) 764 result = [] 765 for w in input: 766 if len(result) == 0 or result[-1] != w: 767 result.append(w) 768 return result 769 770 771def _mkstrip(s): 772 """Emulates Make's strip. 773 774 That is, removes string's leading and trailing whitespace characters and 775 replaces any sequence of whitespace characters with with a single space. 776 """ 777 t = type(s) 778 if t == "list": 779 s = " ".join(s) 780 elif t != "string": 781 fail("Argument to mkstrip must be a string or list, got: "+t) 782 result = "" 783 was_space = False 784 for ch in s.strip().elems(): 785 is_space = ch.isspace() 786 if not is_space: 787 if was_space: 788 result += " " 789 result += ch 790 was_space = is_space 791 return result 792 793def _mksubst(old, new, s): 794 """Emulates Make's subst. 795 796 Replaces each occurence of 'old' with 'new'. 797 If 's' is a list, applies substitution to each item. 798 """ 799 if type(s) == "list": 800 return [e.replace(old, new) for e in s] 801 return s.replace(old, new) 802 803 804def _product_copy_files_by_pattern(src, dest, s): 805 """Creates a copy list. 806 807 For each item in a given list, create <from>:<to> pair, where <from> and 808 <to> are the results of applying Make-style patsubst of <src> and <dest> 809 respectively. E.g. the result of calling this function with 810 ("foo/%", "bar/%", ["a", "b"]) will be 811 ["foo/a:bar/a", "foo/b:bar/b"]. 812 """ 813 parsed_src = __mkparse_pattern(src) 814 parsed_dest = __mkparse_pattern(dest) 815 parsed_percent = ["", ""] 816 words = s if type(s) == "list" else _mkstrip(s).split(" ") 817 return [ __mkpatsubst_word(parsed_percent, parsed_src, x) + ":" + __mkpatsubst_word(parsed_percent, parsed_dest, x) for x in words] 818 819 820__zero_values = { 821 "string": "", 822 "list": [], 823 "int": 0, 824 "float": 0, 825 "bool": False, 826 "dict": {}, 827 "NoneType": None, 828 "tuple": (), 829} 830def __zero_value(x): 831 t = type(x) 832 if t in __zero_values: 833 return __zero_values[t] 834 else: 835 fail("Unknown type: "+t) 836 837 838def _clear_var_list(g, h, var_list): 839 cfg = __h_cfg(h) 840 for v in __words(var_list): 841 # Set these variables to their zero values rather than None 842 # or removing them from the dictionary because if they were 843 # removed entirely, ?= would set their value, when it would not 844 # after a make-based clear_var_list call. 845 if v in g: 846 g[v] = __zero_value(g[v]) 847 if v in cfg: 848 cfg[v] = __zero_value(cfg[v]) 849 850 if v not in cfg and v not in g: 851 # Cause the variable to appear set like the make version does 852 g[v] = "" 853 854# Settings used during debugging. 855_options = struct( 856 trace_modules = False, 857 trace_variables = [], 858) 859 860rblf = struct( 861 soong_config_namespace = _soong_config_namespace, 862 soong_config_append = _soong_config_append, 863 soong_config_set = _soong_config_set, 864 soong_config_get = _soong_config_get, 865 abspath = _abspath, 866 add_product_dex_preopt_module_config = _add_product_dex_preopt_module_config, 867 addprefix = _addprefix, 868 addsuffix = _addsuffix, 869 board_platform_in = _board_platform_in, 870 board_platform_is = _board_platform_is, 871 clear_var_list = _clear_var_list, 872 copy_files = _copy_files, 873 copy_if_exists = _copy_if_exists, 874 cfg = __h_cfg, 875 dir = _dir, 876 enforce_product_packages_exist = _enforce_product_packages_exist, 877 expand_wildcard = _expand_wildcard, 878 filter = _filter, 879 filter_out = _filter_out, 880 find_and_copy = _find_and_copy, 881 findstring = _findstring, 882 first_word = _first_word, 883 last_word = _last_word, 884 flatten_2d_list = _flatten_2d_list, 885 inherit = _inherit, 886 indirect = _indirect, 887 mk2rbc_error = _mk2rbc_error, 888 mkdist_for_goals = _mkdist_for_goals, 889 mkinfo = _mkinfo, 890 mkerror = _mkerror, 891 mkpatsubst = _mkpatsubst, 892 mkwarning = _mkwarning, 893 mksort = _mksort, 894 mkstrip = _mkstrip, 895 mksubst = _mksubst, 896 notdir = _notdir, 897 printvars = _printvars, 898 product_configuration = _product_configuration, 899 board_configuration = _board_configuration, 900 product_copy_files_by_pattern = _product_copy_files_by_pattern, 901 require_artifacts_in_path = _require_artifacts_in_path, 902 require_artifacts_in_path_relaxed = _require_artifacts_in_path_relaxed, 903 setdefault = _setdefault, 904 shell = rblf_shell, 905 warning = _mkwarning, 906 words = __words, 907) 908