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