| # Copyright (C) 2023 The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| """ |
| Export build flags (with values) to make. |
| """ |
| |
| load("//build/bazel/utils:schema_validation.scl", "validate") |
| |
| # Partitions that get build system flag summaries |
| _flag_partitions = [ |
| "product", |
| "system", |
| "system_ext", |
| "vendor", |
| ] |
| |
| ALL = ["all"] |
| PRODUCT = ["product"] |
| SYSTEM = ["system"] |
| SYSTEM_EXT = ["system_ext"] |
| VENDOR = ["vendor"] |
| |
| _valid_types = ["NoneType", "bool", "list", "string", "int"] |
| |
| _all_flags_schema = { |
| "type": "list", |
| "of": { |
| "type": "dict", |
| "required_keys": { |
| "name": {"type": "string"}, |
| "partitions": { |
| "type": "list", |
| "of": { |
| "type": "string", |
| "choices": _flag_partitions + ["all"], |
| }, |
| "unique": True, |
| }, |
| "default": { |
| "or": [ |
| {"type": t} |
| for t in _valid_types |
| ], |
| }, |
| "origin": {"type": "string"}, |
| "declared_in": {"type": "string"}, |
| }, |
| "optional_keys": { |
| "appends": { |
| "type": "bool", |
| }, |
| }, |
| }, |
| } |
| |
| _all_values_schema = { |
| "type": "list", |
| "of": { |
| "type": "dict", |
| "required_keys": { |
| "name": {"type": "string"}, |
| "value": { |
| "or": [ |
| {"type": t} |
| for t in _valid_types |
| ], |
| }, |
| "set_in": {"type": "string"}, |
| }, |
| }, |
| } |
| |
| def flag(name, partitions, default, *, origin = "Unknown", appends = False): |
| """Declare a flag. |
| |
| Args: |
| name: name of the flag |
| partitions: the partitions where this should be recorded. |
| default: the default value of the flag. |
| origin: The origin of this flag. |
| appends: Whether new values should be append (not replace) the old. |
| |
| Returns: |
| A dictionary containing the flag declaration. |
| """ |
| if not partitions: |
| fail("At least 1 partition is required") |
| if not name.startswith("RELEASE_"): |
| fail("Release flag names must start with RELEASE_") |
| if " " in name or "\t" in name or "\n" in name: |
| fail("Flag names must not contain whitespace: \"" + name + "\"") |
| for partition in partitions: |
| if partition == "all": |
| if len(partitions) > 1: |
| fail("\"all\" can't be combined with other partitions: " + str(partitions)) |
| elif partition not in _flag_partitions: |
| fail("Invalid partition: " + partition + ", allowed partitions: " + |
| str(_flag_partitions)) |
| if type(default) not in _valid_types: |
| fail("Invalid type of default for flag \"" + name + "\" (" + type(default) + ")") |
| return { |
| "name": name, |
| "partitions": partitions, |
| "default": default, |
| "appends": appends, |
| "origin": origin, |
| } |
| |
| def value(name, value): |
| """Define the flag value for a particular configuration. |
| |
| Args: |
| name: The name of the flag. |
| value: The value for the flag. |
| |
| Returns: |
| A dictionary containing the name and value to be used. |
| """ |
| return { |
| "name": name, |
| "value": value, |
| } |
| |
| def _format_value(val): |
| """Format the starlark type correctly for make. |
| |
| Args: |
| val: The value to format |
| |
| Returns: |
| The value, formatted correctly for make. |
| """ |
| if type(val) == "NoneType": |
| return "" |
| elif type(val) == "bool": |
| return "true" if val else "" |
| else: |
| return val |
| |
| def equal_flag_declaration(flag, other): |
| """Return true if the flag declarations are equal. |
| |
| Args: |
| flag: This flag declaration. |
| other: Another flag declaration. |
| |
| Returns: |
| Whether the declarations are the same. |
| """ |
| for key in "name", "partitions", "default", "appends": |
| if flag[key] != other[key]: |
| return False |
| # For now, allow Unknown to match any other origin. |
| if flag["origin"] == "Unknown" or other["origin"] == "Unknown": |
| return True |
| return flag["origin"] == other["origin"] |
| |
| def release_config(all_flags, all_values): |
| """Return the make variables that should be set for this release config. |
| |
| Args: |
| all_flags: A list of flag objects (from flag() calls). |
| all_values: A list of value objects (from value() calls). |
| |
| Returns: |
| A dictionary of {name: value} variables for make. |
| """ |
| validate(all_flags, _all_flags_schema) |
| validate(all_values, _all_values_schema) |
| |
| # Final values. |
| values = {} |
| # Validate flags |
| flag_names = [] |
| flags_dict = {} |
| for flag in all_flags: |
| name = flag["name"] |
| if name in flag_names: |
| if equal_flag_declaration(flag, flags_dict[name]): |
| continue |
| else: |
| fail(flag["declared_in"] + ": Duplicate declaration of flag " + name + |
| " (declared first in " + flags_dict[name]["declared_in"] + ")") |
| flag_names.append(name) |
| flags_dict[name] = flag |
| # Set the flag value to the default value. |
| values[name] = {"name": name, "value": _format_value(flag["default"]), "set_in": flag["declared_in"]} |
| |
| # Record which flags go on which partition |
| partitions = {} |
| for flag in all_flags: |
| for partition in flag["partitions"]: |
| if partition == "all": |
| if len(flag["partitions"]) > 1: |
| fail("\"all\" can't be combined with other partitions: " + str(flag["partitions"])) |
| for partition in _flag_partitions: |
| partitions.setdefault(partition, []).append(flag["name"]) |
| else: |
| partitions.setdefault(partition, []).append(flag["name"]) |
| |
| # Generate final values. |
| # Only declared flags may have a value. |
| for value in all_values: |
| name = value["name"] |
| if name not in flag_names: |
| fail(value["set_in"] + ": Value set for undeclared build flag: " + name) |
| if flags_dict[name]["appends"]: |
| if name in values: |
| values[name]["value"] += " " + value["value"] |
| values[name]["set_in"] += " " + value["set_in"] |
| else: |
| values[name] = value |
| else: |
| values[name] = value |
| |
| # Collect values |
| result = { |
| "_ALL_RELEASE_FLAGS": sorted(flag_names), |
| } |
| for partition, names in partitions.items(): |
| result["_ALL_RELEASE_FLAGS.PARTITIONS." + partition] = names |
| for flag in all_flags: |
| val = _format_value(values[flag["name"]]["value"]) |
| result[flag["name"]] = val |
| result["_ALL_RELEASE_FLAGS." + flag["name"] + ".PARTITIONS"] = flag["partitions"] |
| result["_ALL_RELEASE_FLAGS." + flag["name"] + ".DEFAULT"] = _format_value(flag["default"]) |
| result["_ALL_RELEASE_FLAGS." + flag["name"] + ".VALUE"] = val |
| result["_ALL_RELEASE_FLAGS." + flag["name"] + ".DECLARED_IN"] = flag["declared_in"] |
| result["_ALL_RELEASE_FLAGS." + flag["name"] + ".SET_IN"] = values[flag["name"]]["set_in"] |
| result["_ALL_RELEASE_FLAGS." + flag["name"] + ".ORIGIN"] = flag["origin"] |
| |
| return result |