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