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 15load("//build/make/core:envsetup.rbc", _envsetup_init = "init") 16 17"""Runtime functions.""" 18 19def _global_init(): 20 """Returns dict created from the runtime environment.""" 21 globals = dict() 22 23 # Environment variables 24 for k in dir(rblf_env): 25 globals[k] = getattr(rblf_env, k) 26 27 # Variables set as var=value command line arguments 28 for k in dir(rblf_cli): 29 globals[k] = getattr(rblf_cli, k) 30 31 globals.setdefault("PRODUCT_SOONG_NAMESPACES", []) 32 _envsetup_init(globals) 33 34 # Variables that should be defined. 35 mandatory_vars = [ 36 "PLATFORM_VERSION_CODENAME", 37 "PLATFORM_VERSION", 38 "PRODUCT_SOONG_NAMESPACES", 39 # TODO(asmundak): do we need TARGET_ARCH? AOSP does not reference it 40 "TARGET_BUILD_TYPE", 41 "TARGET_BUILD_VARIANT", 42 "TARGET_PRODUCT", 43 ] 44 for bv in mandatory_vars: 45 if not bv in globals: 46 fail(bv, " is not defined") 47 48 return globals 49 50_globals_base = _global_init() 51 52def __print_attr(attr, value): 53 if not value: 54 return 55 if type(value) == "list": 56 if _options.rearrange: 57 value = __printvars_rearrange_list(value) 58 if _options.format == "pretty": 59 print(attr, "=", repr(value)) 60 elif _options.format == "make": 61 print(attr, ":=", " ".join(value)) 62 elif _options.format == "pretty": 63 print(attr, "=", repr(value)) 64 elif _options.format == "make": 65 print(attr, ":=", value) 66 else: 67 fail("bad output format", _options.format) 68 69def _printvars(globals, cfg): 70 """Prints known configuration variables.""" 71 for attr, val in sorted(cfg.items()): 72 __print_attr(attr, val) 73 if _options.print_globals: 74 print() 75 for attr, val in sorted(globals.items()): 76 if attr not in _globals_base: 77 __print_attr(attr, val) 78 79def __printvars_rearrange_list(value_list): 80 """Rearrange value list: return only distinct elements, maybe sorted.""" 81 seen = {item: 0 for item in value_list} 82 return sorted(seen.keys()) if _options.rearrange == "sort" else seen.keys() 83 84def _product_configuration(top_pcm_name, top_pcm): 85 """Creates configuration.""" 86 87 # Product configuration is created by traversing product's inheritance 88 # tree. It is traversed twice. 89 # First, beginning with top-level module we execute a module and find 90 # its ancestors, repeating this recursively. At the end of this phase 91 # we get the full inheritance tree. 92 # Second, we traverse the tree in the postfix order (i.e., visiting a 93 # node after its ancestors) to calculate the product configuration. 94 # 95 # PCM means "Product Configuration Module", i.e., a Starlark file 96 # whose body consists of a single init function. 97 98 globals = dict(**_globals_base) 99 100 config_postfix = [] # Configs in postfix order 101 102 # Each PCM is represented by a quadruple of function, config, children names 103 # and readyness (that is, the configurations from inherited PCMs have been 104 # substituted). 105 configs = {top_pcm_name: (top_pcm, None, [], False)} # All known PCMs 106 107 stash = [] # Configs to push once their descendants are done 108 109 # Stack containing PCMs to be processed. An item in the stack 110 # is a pair of PCMs name and its height in the product inheritance tree. 111 pcm_stack = [(top_pcm_name, 0)] 112 pcm_count = 0 113 114 # Run it until pcm_stack is exhausted, but no more than N times 115 for n in range(1000): 116 if not pcm_stack: 117 break 118 (name, height) = pcm_stack.pop() 119 pcm, cfg, c, _ = configs[name] 120 121 # cfg is set only after PCM has been called, leverage this 122 # to prevent calling the same PCM twice 123 if cfg != None: 124 continue 125 126 # Push ancestors until we reach this node's height 127 config_postfix.extend([stash.pop() for i in range(len(stash) - height)]) 128 129 # Run this one, obtaining its configuration and child PCMs. 130 if _options.trace_modules: 131 print("%d:" % n) 132 133 # Run PCM. 134 handle = __h_new() 135 pcm(globals, handle) 136 137 # Now we know everything about this PCM, record it in 'configs'. 138 children = __h_inherited_modules(handle) 139 if _options.trace_modules: 140 print(" ", " ".join(children.keys())) 141 configs[name] = (pcm, __h_cfg(handle), children.keys(), False) 142 pcm_count = pcm_count + 1 143 144 if len(children) == 0: 145 # Leaf PCM goes straight to the config_postfix 146 config_postfix.append(name) 147 continue 148 149 # Stash this PCM, process children in the sorted order 150 stash.append(name) 151 for child_name in sorted(children, reverse = True): 152 if child_name not in configs: 153 configs[child_name] = (children[child_name], None, [], False) 154 pcm_stack.append((child_name, len(stash))) 155 if pcm_stack: 156 fail("Inheritance processing took too many iterations") 157 158 # Flush the stash 159 config_postfix.extend([stash.pop() for i in range(len(stash))]) 160 if len(config_postfix) != pcm_count: 161 fail("Ran %d modules but postfix tree has only %d entries" % (pcm_count, len(config_postfix))) 162 163 if _options.trace_modules: 164 print("\n---Postfix---") 165 for x in config_postfix: 166 print(" ", x) 167 168 # Traverse the tree from the bottom, evaluating inherited values 169 for pcm_name in config_postfix: 170 pcm, cfg, children_names, ready = configs[pcm_name] 171 172 # Should run 173 if cfg == None: 174 fail("%s: has not been run" % pcm_name) 175 176 # Ready once 177 if ready: 178 continue 179 180 # Children should be ready 181 for child_name in children_names: 182 if not configs[child_name][3]: 183 fail("%s: child is not ready" % child_name) 184 185 _substitute_inherited(configs, pcm_name, cfg) 186 _percolate_inherited(configs, pcm_name, cfg, children_names) 187 configs[pcm_name] = pcm, cfg, children_names, True 188 189 return globals, configs[top_pcm_name][1] 190 191def _substitute_inherited(configs, pcm_name, cfg): 192 """Substitutes inherited values in all the attributes. 193 194 When a value of an attribute is a list, some of its items may be 195 references to a value of a same attribute in an inherited product, 196 e.g., for a given module PRODUCT_PACKAGES can be 197 ["foo", (submodule), "bar"] 198 and for 'submodule' PRODUCT_PACKAGES may be ["baz"] 199 (we use a tuple to distinguish submodule references). 200 After the substitution the value of PRODUCT_PACKAGES for the module 201 will become ["foo", "baz", "bar"] 202 """ 203 for attr, val in cfg.items(): 204 # TODO(asmundak): should we handle single vars? 205 if type(val) != "list": 206 continue 207 208 if attr not in _options.trace_variables: 209 cfg[attr] = _value_expand(configs, attr, val) 210 else: 211 old_val = val 212 new_val = _value_expand(configs, attr, val) 213 if new_val != old_val: 214 print("%s(i): %s=%s (was %s)" % (pcm_name, attr, new_val, old_val)) 215 cfg[attr] = new_val 216 217def _value_expand(configs, attr, values_list): 218 """Expands references to inherited values in a given list.""" 219 result = [] 220 expanded = {} 221 for item in values_list: 222 # Inherited values are 1-tuples 223 if type(item) != "tuple": 224 result.append(item) 225 continue 226 child_name = item[0] 227 if child_name in expanded: 228 continue 229 expanded[child_name] = True 230 child = configs[child_name] 231 if not child[3]: 232 fail("%s should be ready" % child_name) 233 __move_items(result, child[1], attr) 234 235 return result 236 237def _percolate_inherited(configs, cfg_name, cfg, children_names): 238 """Percolates the settings that are present only in children.""" 239 percolated_attrs = {} 240 for child_name in children_names: 241 child_cfg = configs[child_name][1] 242 for attr, value in child_cfg.items(): 243 if type(value) != "list": 244 if attr in percolated_attrs or not attr in cfg: 245 cfg[attr] = value 246 percolated_attrs[attr] = True 247 continue 248 if attr in percolated_attrs: 249 # We already are percolating this one, just add this list 250 __move_items(cfg[attr], child_cfg, attr) 251 elif not attr in cfg: 252 percolated_attrs[attr] = True 253 cfg[attr] = [] 254 __move_items(cfg[attr], child_cfg, attr) 255 256 for attr in _options.trace_variables: 257 if attr in percolated_attrs: 258 print("%s: %s^=%s" % (cfg_name, attr, cfg[attr])) 259 260def __move_items(to_list, from_cfg, attr): 261 value = from_cfg.get(attr, []) 262 if value: 263 to_list.extend(value) 264 from_cfg[attr] = [] 265 266def _indirect(pcm_name): 267 """Returns configuration item for the inherited module.""" 268 return (pcm_name,) 269 270def _addprefix(prefix, string_or_list): 271 """Adds prefix and returns a list. 272 273 If string_or_list is a list, prepends prefix to each element. 274 Otherwise, string_or_list is considered to be a string which 275 is split into words and then prefix is prepended to each one. 276 277 Args: 278 prefix 279 string_or_list 280 281 """ 282 return [prefix + x for x in __words(string_or_list)] 283 284def _addsuffix(suffix, string_or_list): 285 """Adds suffix and returns a list. 286 287 If string_or_list is a list, appends suffix to each element. 288 Otherwise, string_or_list is considered to be a string which 289 is split into words and then suffix is appended to each one. 290 291 Args: 292 suffix 293 string_or_list 294 """ 295 return [x + suffix for x in __words(string_or_list)] 296 297def __words(string_or_list): 298 if type(string_or_list) == "list": 299 return string_or_list 300 return string_or_list.split() 301 302# Handle manipulation functions. 303# A handle passed to a PCM consists of: 304# product attributes dict ("cfg") 305# inherited modules dict (maps module name to PCM) 306# default value list (initially empty, modified by inheriting) 307def __h_new(): 308 """Constructs a handle which is passed to PCM.""" 309 return (dict(), dict(), list()) 310 311def __h_inherited_modules(handle): 312 """Returns PCM's inherited modules dict.""" 313 return handle[1] 314 315def __h_cfg(handle): 316 """Returns PCM's product configuration attributes dict. 317 318 This function is also exported as rblf.cfg, and every PCM 319 calls it at the beginning. 320 """ 321 return handle[0] 322 323def _setdefault(handle, attr): 324 """If attribute has not been set, assigns default value to it. 325 326 This function is exported as rblf.setdefault(). 327 Only list attributes are initialized this way. The default 328 value is kept in the PCM's handle. Calling inherit() updates it. 329 """ 330 cfg = handle[0] 331 if cfg.get(attr) == None: 332 cfg[attr] = list(handle[2]) 333 return cfg[attr] 334 335def _inherit(handle, pcm_name, pcm): 336 """Records inheritance. 337 338 This function is exported as rblf.inherit, PCM calls it when 339 a module is inherited. 340 """ 341 cfg, inherited, default_lv = handle 342 inherited[pcm_name] = pcm 343 default_lv.append(_indirect(pcm_name)) 344 345 # Add inherited module reference to all configuration values 346 for attr, val in cfg.items(): 347 if type(val) == "list": 348 val.append(_indirect(pcm_name)) 349 350def _copy_if_exists(path_pair): 351 """If from file exists, returns [from:to] pair.""" 352 value = path_pair.split(":", 2) 353 354 # Check that l[0] exists 355 return [":".join(value)] if rblf_file_exists(value[0]) else [] 356 357def _enforce_product_packages_exist(pkg_string_or_list): 358 """Makes including non-existent modules in PRODUCT_PACKAGES an error.""" 359 360 #TODO(asmundak) 361 pass 362 363def _file_wildcard_exists(file_pattern): 364 """Return True if there are files matching given bash pattern.""" 365 return len(rblf_wildcard(file_pattern)) > 0 366 367def _find_and_copy(pattern, from_dir, to_dir): 368 """Return a copy list for the files matching the pattern.""" 369 return ["%s/%s:%s/%s" % (from_dir, f, to_dir, f) for f in rblf_wildcard(pattern, from_dir)] 370 371def _filter_out(pattern, text): 372 """Return all the words from `text' that do not match any word in `pattern'. 373 374 Args: 375 pattern: string or list of words. '%' stands for wildcard (in regex terms, '.*') 376 text: string or list of words 377 Return: 378 list of words 379 """ 380 rex = __mk2regex(__words(pattern)) 381 res = [] 382 for w in __words(text): 383 if not _regex_match(rex, w): 384 res.append(w) 385 return res 386 387def _filter(pattern, text): 388 """Return all the words in `text` that match `pattern`. 389 390 Args: 391 pattern: strings of words or a list. A word can contain '%', 392 which stands for any sequence of characters. 393 text: string or list of words. 394 """ 395 rex = __mk2regex(__words(pattern)) 396 res = [] 397 for w in __words(text): 398 if _regex_match(rex, w): 399 res.append(w) 400 return res 401 402def __mk2regex(words): 403 """Returns regular expression equivalent to Make pattern.""" 404 405 # TODO(asmundak): this will mishandle '\%' 406 return "^(" + "|".join([w.replace("%", ".*", 1) for w in words]) + ")" 407 408def _regex_match(regex, w): 409 return rblf_regex(regex, w) 410 411def _require_artifacts_in_path(paths, allowed_paths): 412 """TODO.""" 413 pass 414 415def _require_artifacts_in_path_relaxed(paths, allowed_paths): 416 """TODO.""" 417 pass 418 419def _expand_wildcard(pattern): 420 """Expands shell wildcard pattern.""" 421 return rblf_wildcard(pattern) 422 423def _mkerror(file, message = ""): 424 """Prints error and stops.""" 425 fail("%s: %s. Stop" % (file, message)) 426 427def _mkwarning(file, message = ""): 428 """Prints warning.""" 429 print("%s: warning: %s" % (file, message)) 430 431def _mkinfo(file, message = ""): 432 """Prints info.""" 433 print(message) 434 435def __get_options(): 436 """Returns struct containing runtime global settings.""" 437 settings = dict( 438 format = "pretty", 439 print_globals = False, 440 rearrange = "", 441 trace_modules = False, 442 trace_variables = [], 443 ) 444 for x in getattr(rblf_cli, "RBC_OUT", "").split(","): 445 if x == "sort" or x == "unique": 446 if settings["rearrange"]: 447 fail("RBC_OUT: either sort or unique is allowed (and sort implies unique)") 448 settings["rearrange"] = x 449 elif x == "pretty" or x == "make": 450 settings["format"] = x 451 elif x == "global": 452 settings["print_globals"] = True 453 elif x != "": 454 fail("RBC_OUT: got %s, should be one of: [pretty|make] [sort|unique]" % x) 455 for x in getattr(rblf_cli, "RBC_DEBUG", "").split(","): 456 if x == "!trace": 457 settings["trace_modules"] = True 458 elif x != "": 459 settings["trace_variables"].append(x) 460 return struct(**settings) 461 462# Settings used during debugging. 463_options = __get_options() 464rblf = struct( 465 addprefix = _addprefix, 466 addsuffix = _addsuffix, 467 copy_if_exists = _copy_if_exists, 468 cfg = __h_cfg, 469 enforce_product_packages_exist = _enforce_product_packages_exist, 470 expand_wildcard = _expand_wildcard, 471 file_exists = rblf_file_exists, 472 file_wildcard_exists = _file_wildcard_exists, 473 filter = _filter, 474 filter_out = _filter_out, 475 find_and_copy = _find_and_copy, 476 global_init = _global_init, 477 inherit = _inherit, 478 indirect = _indirect, 479 mkinfo = _mkinfo, 480 mkerror = _mkerror, 481 mkwarning = _mkwarning, 482 printvars = _printvars, 483 product_configuration = _product_configuration, 484 require_artifacts_in_path = _require_artifacts_in_path, 485 require_artifacts_in_path_relaxed = _require_artifacts_in_path_relaxed, 486 setdefault = _setdefault, 487 shell = rblf_shell, 488 warning = _mkwarning, 489) 490