Address reviewer comments from the previous commits

Fixes: 170637441
Test: rbcrun build/make/tests/run.rbc
Change-Id: Ifc16fe02d96bc3a4c5562b74da8c1e7b393dc000
diff --git a/core/envsetup.rbc b/core/envsetup.rbc
index 2924ee1..451623b 100644
--- a/core/envsetup.rbc
+++ b/core/envsetup.rbc
@@ -1,4 +1,3 @@
-
 # Copyright 2021 Google LLC
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,14 +12,14 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-load(":build_id.rbc|init", _build_id_init="init")
+load(":build_id.rbc|init", _build_id_init = "init")
 
 def _all_versions():
     """Returns all known versions."""
-    versions = ["OPR1", "OPD1", "OPD2","OPM1", "OPM2", "PPR1", "PPD1", "PPD2", "PPM1",  "PPM2", "QPR1" ]
+    versions = ["OPR1", "OPD1", "OPD2", "OPM1", "OPM2", "PPR1", "PPD1", "PPD2", "PPM1", "PPM2", "QPR1"]
     for v in ("Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"):
-        for e in ("P1A", "P1B", "P2A", "P2B", "D1A", "D1B", "D2A", "D2B", "Q1A", "Q1B", "Q2A", "Q2B", "Q3A",  "Q3B"):
-            versions.append(v+e)
+        for e in ("P1A", "P1B", "P2A", "P2B", "D1A", "D1B", "D2A", "D2B", "Q1A", "Q1B", "Q2A", "Q2B", "Q3A", "Q3B"):
+            versions.append(v + e)
     return versions
 
 def _allowed_versions(all_versions, min_version, max_version, default_version):
@@ -36,7 +35,7 @@
         fail("%s should come before %s in the version list" % (min_version, max_version))
     if def_i < min_i or def_i > max_i:
         fail("%s should come between % and %s" % (default_version, min_version, max_version))
-    return all_versions[min_i:max_i+1]
+    return all_versions[min_i:max_i + 1]
 
 # This function is a manual conversion of the version_defaults.mk
 def _versions_default(g, all_versions):
@@ -70,21 +69,22 @@
     g.setdefault("PLATFORM_VERSION_CODENAME", g["TARGET_PLATFORM_VERSION"])
     # TODO(asmundak): set PLATFORM_VERSION_ALL_CODENAMES
 
-    g.setdefault("PLATFORM_VERSION",
-        g["PLATFORM_VERSION_LAST_STABLE"] if g["PLATFORM_VERSION_CODENAME"] == "REL" else g["PLATFORM_VERSION_CODENAME"])
     g.setdefault("PLATFORM_SDK_VERSION", 30)
-    if g["PLATFORM_VERSION_CODENAME"] == "REL":
+    version_codename = g["PLATFORM_VERSION_CODENAME"]
+    if version_codename == "REL":
+        g.setdefault("PLATFORM_VERSION", g["PLATFORM_VERSION_LAST_STABLE"])
         g["PLATFORM_PREVIEW_SDK_VERSION"] = 0
+        g.setdefault("DEFAULT_APP_TARGET_SDK", g["PLATFORM_SDK_VERSION"])
+        g.setdefault("PLATFORM_VNDK_VERSION", g["PLATFORM_SDK_VERSION"])
     else:
+        g.setdefault("PLATFORM_VERSION", version_codename)
         g.setdefault("PLATFORM_PREVIEW_SDK_VERSION", 1)
+        g.setdefault("DEFAULT_APP_TARGET_SDK", version_codename)
+        g.setdefault("PLATFORM_VNDK_VERSION", version_codename)
 
-    g.setdefault("DEFAULT_APP_TARGET_SDK",
-        g["PLATFORM_SDK_VERSION"] if g["PLATFORM_VERSION_CODENAME"] == "REL" else g["PLATFORM_VERSION_CODENAME"])
-    g.setdefault("PLATFORM_VNDK_VERSION",
-        g["PLATFORM_SDK_VERSION"] if g["PLATFORM_VERSION_CODENAME"] == "REL" else g["PLATFORM_VERSION_CODENAME"])
     g.setdefault("PLATFORM_SYSTEMSDK_MIN_VERSION", 28)
     versions = [str(i) for i in range(g["PLATFORM_SYSTEMSDK_MIN_VERSION"], g["PLATFORM_SDK_VERSION"] + 1)]
-    versions.append(g["PLATFORM_VERSION_CODENAME"])
+    versions.append(version_codename)
     g["PLATFORM_SYSTEMSDK_VERSIONS"] = sorted(versions)
 
     #  Used to indicate the security patch that has been applied to the device.
@@ -117,6 +117,13 @@
     g.setdefault("PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION", 23)
 
 def init(g):
+    """Initializes globals.
+
+    The code is the Starlark counterpart of the contents of the
+    envsetup.mk file.
+    Args:
+        g: globals dictionary
+    """
     all_versions = _all_versions()
     _versions_default(g, all_versions)
     for v in all_versions:
@@ -168,7 +175,8 @@
     g["HOST_CROSS_2ND_ARCH_MODULE_SUFFIX"] = "_64"
     g["TARGET_2ND_ARCH_VAR_PREFIX"] = "2ND_"
 
-    # TODO(asmundak): combo-related stuff
+    # TODO(asmundak): envsetup.mk lines 216-226:
+    # convert combo-related stuff from combo/select.mk
 
     # on windows, the tools have .exe at the end, and we depend on the
     # host config stuff being done first
@@ -177,23 +185,23 @@
 
     # the host build defaults to release, and it must be release or debug
     g.setdefault("HOST_BUILD_TYPE", "release")
-    if g["HOST_BUILD_TYPE"] != "release" and g["HOST_BUILD_TYPE"] != "debug":
+    if g["HOST_BUILD_TYPE"] not in ["release", "debug"]:
         fail("HOST_BUILD_TYPE must be either release or debug, not '%s'" % g["HOST_BUILD_TYPE"])
 
-    # TODO(asmundak): a lot more, but not needed for the product configuration
+    # TODO(asmundak): there is more stuff in envsetup.mk lines 249-292, but
+    # it does not seem to affect product configuration. Revisit this.
 
     g["ART_APEX_JARS"] = [
         "com.android.art:core-oj",
         "com.android.art:core-libart",
         "com.android.art:okhttp",
         "com.android.art:bouncycastle",
-        "com.android.art:apache-xml"
+        "com.android.art:apache-xml",
     ]
 
     if g.get("TARGET_BUILD_TYPE", "") != "debug":
         g["TARGET_BUILD_TYPE"] = "release"
 
-
 v_default = "SP1A"
 v_min = "SP1A"
 v_max = "SP1A"
diff --git a/core/product_config.rbc b/core/product_config.rbc
index 6e96822..111e759 100644
--- a/core/product_config.rbc
+++ b/core/product_config.rbc
@@ -1,4 +1,3 @@
-
 # Copyright 2021 Google LLC
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,7 +12,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-load("//build/make/core:envsetup.rbc", _envsetup_init="init")
+load("//build/make/core:envsetup.rbc", _envsetup_init = "init")
+
 """Runtime functions."""
 
 def _global_init():
@@ -33,10 +33,13 @@
 
     # Variables that should be defined.
     mandatory_vars = [
-        "PLATFORM_VERSION_CODENAME", "PLATFORM_VERSION",
+        "PLATFORM_VERSION_CODENAME",
+        "PLATFORM_VERSION",
         "PRODUCT_SOONG_NAMESPACES",
         # TODO(asmundak): do we need TARGET_ARCH? AOSP does not reference it
-        "TARGET_BUILD_TYPE", "TARGET_BUILD_VARIANT", "TARGET_PRODUCT",
+        "TARGET_BUILD_TYPE",
+        "TARGET_BUILD_VARIANT",
+        "TARGET_PRODUCT",
     ]
     for bv in mandatory_vars:
         if not bv in globals:
@@ -56,29 +59,26 @@
             print(attr, "=", repr(value))
         elif _options.format == "make":
             print(attr, ":=", " ".join(value))
+    elif _options.format == "pretty":
+        print(attr, "=", repr(value))
+    elif _options.format == "make":
+        print(attr, ":=", value)
     else:
-        if _options.format == "pretty":
-            print(attr, "=", repr(value))
-        elif _options.format == "make":
-            print(attr, ":=", value)
-        else:
-            fail("bad output format", _options.format)
-
+        fail("bad output format", _options.format)
 
 def _printvars(globals, cfg):
     """Prints known configuration variables."""
     for attr, val in sorted(cfg.items()):
         __print_attr(attr, val)
     if _options.print_globals:
+        print()
         for attr, val in sorted(globals.items()):
-            print()
             if attr not in _globals_base:
                 __print_attr(attr, val)
 
-
-def __printvars_rearrange_list(l):
+def __printvars_rearrange_list(value_list):
     """Rearrange value list: return only distinct elements, maybe sorted."""
-    seen = { item: 0 for item in l}
+    seen = {item: 0 for item in value_list}
     return sorted(seen.keys()) if _options.rearrange == "sort" else seen.keys()
 
 def _product_configuration(top_pcm_name, top_pcm):
@@ -97,44 +97,49 @@
 
     globals = dict(**_globals_base)
 
-    config_postfix = []     # Configs in postfix order
+    config_postfix = []  # Configs in postfix order
+
     # Each PCM is represented by a quadruple of function, config, children names
     # and readyness (that is, the configurations from inherited PCMs have been
     # substituted).
-    configs = { top_pcm_name: (top_pcm, None, [], False)}  # All known PCMs
+    configs = {top_pcm_name: (top_pcm, None, [], False)}  # All known PCMs
 
-    stash = []              # Configs to push once their descendants are done
+    stash = []  # Configs to push once their descendants are done
 
-    # Stack maintaining PCMs to be processed. An item in the stack
+    # Stack containing PCMs to be processed. An item in the stack
     # is a pair of PCMs name and its height in the product inheritance tree.
-    pcm_stack = []
-    pcm_stack.append((top_pcm_name, 0))
+    pcm_stack = [(top_pcm_name, 0)]
     pcm_count = 0
+
     # Run it until pcm_stack is exhausted, but no more than N times
     for n in range(1000):
         if not pcm_stack:
             break
         (name, height) = pcm_stack.pop()
         pcm, cfg, c, _ = configs[name]
-        # each PCM is executed once
+
+        # cfg is set only after PCM has been called, leverage this
+        # to prevent calling the same PCM twice
         if cfg != None:
             continue
+
         # Push ancestors until we reach this node's height
         config_postfix.extend([stash.pop() for i in range(len(stash) - height)])
 
         # Run this one, obtaining its configuration and child PCMs.
         if _options.trace_modules:
-            print("%s:" % n[0])
+            print("%d:" % n)
 
-        # The handle passed to the PCM consists of config and inheritance state.dict of inherited modules
-        # and a list containing the current default value of a list variable.
+        # Run PCM.
         handle = __h_new()
         pcm(globals, handle)
+
+        # Now we know everything about this PCM, record it in 'configs'.
         children = __h_inherited_modules(handle)
         if _options.trace_modules:
             print("   ", "    ".join(children.keys()))
         configs[name] = (pcm, __h_cfg(handle), children.keys(), False)
-        pcm_count = pcm_count+1
+        pcm_count = pcm_count + 1
 
         if len(children) == 0:
             # Leaf PCM goes straight to the config_postfix
@@ -143,17 +148,18 @@
 
         # Stash this PCM, process children in the sorted order
         stash.append(name)
-        for child_name in sorted(children, reverse=True):
+        for child_name in sorted(children, reverse = True):
             if child_name not in configs:
                 configs[child_name] = (children[child_name], None, [], False)
             pcm_stack.append((child_name, len(stash)))
+    if pcm_stack:
+        fail("Inheritance processing took too many iterations")
 
     # Flush the stash
     config_postfix.extend([stash.pop() for i in range(len(stash))])
     if len(config_postfix) != pcm_count:
         fail("Ran %d modules but postfix tree has only %d entries" % (pcm_count, len(config_postfix)))
 
-
     if _options.trace_modules:
         print("\n---Postfix---")
         for x in config_postfix:
@@ -162,55 +168,56 @@
     # Traverse the tree from the bottom, evaluating inherited values
     for pcm_name in config_postfix:
         pcm, cfg, children_names, ready = configs[pcm_name]
+
         # Should run
         if cfg == None:
             fail("%s: has not been run" % pcm_name)
+
         # Ready once
         if ready:
             continue
+
         # Children should be ready
         for child_name in children_names:
             if not configs[child_name][3]:
                 fail("%s: child is not ready" % child_name)
 
-        # if _options.trace_modules:
-        #     print(">%s: %s" % (pcm_name, cfg))
-
         _substitute_inherited(configs, pcm_name, cfg)
         _percolate_inherited(configs, pcm_name, cfg, children_names)
         configs[pcm_name] = pcm, cfg, children_names, True
-        # if _options.trace_modules:
-        #     print("<%s: %s" % (pcm_name, cfg))
 
     return globals, configs[top_pcm_name][1]
 
-
 def _substitute_inherited(configs, pcm_name, cfg):
-    """Substitutes inherited values in all the configuration settings."""
-    for attr, val in cfg.items():
-        trace_it = attr in _options.trace_variables
-        if trace_it:
-            old_val = val
+    """Substitutes inherited values in all the attributes.
 
+    When a value of an attribute is a list, some of its items may be
+    references to a value of a same attribute in an inherited product,
+    e.g., for a given module PRODUCT_PACKAGES can be
+      ["foo", (submodule), "bar"]
+    and for 'submodule' PRODUCT_PACKAGES may be ["baz"]
+    (we use a tuple to distinguish submodule references).
+    After the substitution the value of PRODUCT_PACKAGES for the module
+    will become ["foo", "baz", "bar"]
+    """
+    for attr, val in cfg.items():
         # TODO(asmundak): should we handle single vars?
         if type(val) != "list":
             continue
 
-        if trace_it:
+        if attr not in _options.trace_variables:
+            cfg[attr] = _value_expand(configs, attr, val)
+        else:
+            old_val = val
             new_val = _value_expand(configs, attr, val)
             if new_val != old_val:
                 print("%s(i): %s=%s (was %s)" % (pcm_name, attr, new_val, old_val))
             cfg[attr] = new_val
-            continue
-
-        cfg[attr] = _value_expand(configs, attr, val)
-
-
 
 def _value_expand(configs, attr, values_list):
     """Expands references to inherited values in a given list."""
     result = []
-    expanded={}
+    expanded = {}
     for item in values_list:
         # Inherited values are 1-tuples
         if type(item) != "tuple":
@@ -227,7 +234,6 @@
 
     return result
 
-
 def _percolate_inherited(configs, cfg_name, cfg, children_names):
     """Percolates the settings that are present only in children."""
     percolated_attrs = {}
@@ -251,19 +257,16 @@
         if attr in percolated_attrs:
             print("%s: %s^=%s" % (cfg_name, attr, cfg[attr]))
 
-
 def __move_items(to_list, from_cfg, attr):
-    l = from_cfg.get(attr, [])
-    if l:
-        to_list.extend(l)
+    value = from_cfg.get(attr, [])
+    if value:
+        to_list.extend(value)
         from_cfg[attr] = []
 
-
 def _indirect(pcm_name):
     """Returns configuration item for the inherited module."""
     return (pcm_name,)
 
-
 def _addprefix(prefix, string_or_list):
     """Adds prefix and returns a list.
 
@@ -276,8 +279,7 @@
         string_or_list
 
     """
-    return [ prefix + x for x in __words(string_or_list)]
-
+    return [prefix + x for x in __words(string_or_list)]
 
 def _addsuffix(suffix, string_or_list):
     """Adds suffix and returns a list.
@@ -290,67 +292,82 @@
       suffix
       string_or_list
     """
-    return [ x + suffix for x in __words(string_or_list)]
-
+    return [x + suffix for x in __words(string_or_list)]
 
 def __words(string_or_list):
     if type(string_or_list) == "list":
         return string_or_list
     return string_or_list.split()
 
-
+# Handle manipulation functions.
+# A handle passed to a PCM consists of:
+#   product attributes dict ("cfg")
+#   inherited modules dict (maps module name to PCM)
+#   default value list (initially empty, modified by inheriting)
 def __h_new():
     """Constructs a handle which is passed to PCM."""
     return (dict(), dict(), list())
 
 def __h_inherited_modules(handle):
+    """Returns PCM's inherited modules dict."""
     return handle[1]
 
-
 def __h_cfg(handle):
+    """Returns PCM's product configuration attributes dict.
+
+    This function is also exported as rblf.cfg, and every PCM
+    calls it at the beginning.
+    """
     return handle[0]
 
-
 def _setdefault(handle, attr):
-    """Sets given attribute's value if it has not been set."""
+    """If attribute has not been set, assigns default value to it.
+
+    This function is exported as rblf.setdefault().
+    Only list attributes are initialized this way. The default
+    value is kept in the PCM's handle. Calling inherit() updates it.
+    """
     cfg = handle[0]
     if cfg.get(attr) == None:
         cfg[attr] = list(handle[2])
     return cfg[attr]
 
 def _inherit(handle, pcm_name, pcm):
-    """Records inheritance."""
+    """Records inheritance.
+
+    This function is exported as rblf.inherit, PCM calls it when
+    a module is inherited.
+    """
     cfg, inherited, default_lv = handle
-    inherited[pcm_name]=pcm
+    inherited[pcm_name] = pcm
     default_lv.append(_indirect(pcm_name))
+
     # Add inherited module reference to all configuration values
     for attr, val in cfg.items():
         if type(val) == "list":
             val.append(_indirect(pcm_name))
 
-
 def _copy_if_exists(path_pair):
     """If from file exists, returns [from:to] pair."""
-    l = path_pair.split(":", 2)
+    value = path_pair.split(":", 2)
+
     # Check that l[0] exists
-    return [":".join(l)] if rblf_file_exists(l[0]) else []
+    return [":".join(value)] if rblf_file_exists(value[0]) else []
 
 def _enforce_product_packages_exist(pkg_string_or_list):
     """Makes including non-existent modules in PRODUCT_PACKAGES an error."""
+
     #TODO(asmundak)
     pass
 
-
 def _file_wildcard_exists(file_pattern):
     """Return True if there are files matching given bash pattern."""
     return len(rblf_wildcard(file_pattern)) > 0
 
-
 def _find_and_copy(pattern, from_dir, to_dir):
     """Return a copy list for the files matching the pattern."""
     return ["%s/%s:%s/%s" % (from_dir, f, to_dir, f) for f in rblf_wildcard(pattern, from_dir)]
 
-
 def _filter_out(pattern, text):
     """Return all the words from `text' that do not match any word in `pattern'.
 
@@ -367,7 +384,6 @@
             res.append(w)
     return res
 
-
 def _filter(pattern, text):
     """Return all the words in `text` that match `pattern`.
 
@@ -383,49 +399,39 @@
             res.append(w)
     return res
 
-
 def __mk2regex(words):
     """Returns regular expression equivalent to Make pattern."""
 
     # TODO(asmundak): this will mishandle '\%'
     return "^(" + "|".join([w.replace("%", ".*", 1) for w in words]) + ")"
 
-
 def _regex_match(regex, w):
     return rblf_regex(regex, w)
 
-
 def _require_artifacts_in_path(paths, allowed_paths):
     """TODO."""
-    #print("require_artifacts_in_path(", __words(paths), ",", __words(allowed_paths), ")")
     pass
 
-
 def _require_artifacts_in_path_relaxed(paths, allowed_paths):
     """TODO."""
     pass
 
-
 def _expand_wildcard(pattern):
     """Expands shell wildcard pattern."""
     return rblf_wildcard(pattern)
 
-
-def _mkerror(file, message=""):
+def _mkerror(file, message = ""):
     """Prints error and stops."""
     fail("%s: %s. Stop" % (file, message))
 
-
-def _mkwarning(file, message=""):
+def _mkwarning(file, message = ""):
     """Prints warning."""
     print("%s: warning: %s" % (file, message))
 
-
-def _mkinfo(file, message=""):
+def _mkinfo(file, message = ""):
     """Prints info."""
     print(message)
 
-
 def __get_options():
     """Returns struct containing runtime global settings."""
     settings = dict(
@@ -455,29 +461,29 @@
 
 # Settings used during debugging.
 _options = __get_options()
-rblf = struct(addprefix=_addprefix,
-              addsuffix=_addsuffix,
-              copy_if_exists=_copy_if_exists,
-              cfg=__h_cfg,
-              enforce_product_packages_exist=_enforce_product_packages_exist,
-              expand_wildcard=_expand_wildcard,
-              file_exists=rblf_file_exists,
-              file_wildcard_exists=_file_wildcard_exists,
-              filter=_filter,
-              filter_out=_filter_out,
-              find_and_copy=_find_and_copy,
-              global_init=_global_init,
-              inherit=_inherit,
-              indirect=_indirect,
-              mkinfo=_mkinfo,
-              mkerror=_mkerror,
-              mkwarning=_mkwarning,
-              printvars=_printvars,
-              product_configuration=_product_configuration,
-              require_artifacts_in_path=_require_artifacts_in_path,
-              require_artifacts_in_path_relaxed=_require_artifacts_in_path_relaxed,
-              setdefault=_setdefault,
-              shell=rblf_shell,
-              warning=_mkwarning,
-              )
-
+rblf = struct(
+    addprefix = _addprefix,
+    addsuffix = _addsuffix,
+    copy_if_exists = _copy_if_exists,
+    cfg = __h_cfg,
+    enforce_product_packages_exist = _enforce_product_packages_exist,
+    expand_wildcard = _expand_wildcard,
+    file_exists = rblf_file_exists,
+    file_wildcard_exists = _file_wildcard_exists,
+    filter = _filter,
+    filter_out = _filter_out,
+    find_and_copy = _find_and_copy,
+    global_init = _global_init,
+    inherit = _inherit,
+    indirect = _indirect,
+    mkinfo = _mkinfo,
+    mkerror = _mkerror,
+    mkwarning = _mkwarning,
+    printvars = _printvars,
+    product_configuration = _product_configuration,
+    require_artifacts_in_path = _require_artifacts_in_path,
+    require_artifacts_in_path_relaxed = _require_artifacts_in_path_relaxed,
+    setdefault = _setdefault,
+    shell = rblf_shell,
+    warning = _mkwarning,
+)