aboutsummaryrefslogtreecommitdiff
path: root/core/release_config.scl
blob: c5815dfe30c21af23fc984c59bb6fa8eb5341f3c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# 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