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