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