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