xref: /aosp_15_r20/build/bazel/utils/config_setting_boolean_algebra.bzl (revision 7594170e27e0732bc44b93d1440d87a54b6ffe7c)
1*7594170eSAndroid Build Coastguard Worker# Copyright (C) 2023 The Android Open Source Project
2*7594170eSAndroid Build Coastguard Worker#
3*7594170eSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
4*7594170eSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
5*7594170eSAndroid Build Coastguard Worker# You may obtain a copy of the License at
6*7594170eSAndroid Build Coastguard Worker#
7*7594170eSAndroid Build Coastguard Worker#     http://www.apache.org/licenses/LICENSE-2.0
8*7594170eSAndroid Build Coastguard Worker#
9*7594170eSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
10*7594170eSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
11*7594170eSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*7594170eSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
13*7594170eSAndroid Build Coastguard Worker# limitations under the License.
14*7594170eSAndroid Build Coastguard Worker
15*7594170eSAndroid Build Coastguard Workerload("@bazel_skylib//lib:selects.bzl", "selects")
16*7594170eSAndroid Build Coastguard Worker
17*7594170eSAndroid Build Coastguard Workerdef _not(name, config_setting):
18*7594170eSAndroid Build Coastguard Worker    native.alias(
19*7594170eSAndroid Build Coastguard Worker        name = name,
20*7594170eSAndroid Build Coastguard Worker        actual = select({
21*7594170eSAndroid Build Coastguard Worker            config_setting: "//build/bazel/utils:always_off_config_setting",
22*7594170eSAndroid Build Coastguard Worker            "//conditions:default": "//build/bazel/utils:always_on_config_setting",
23*7594170eSAndroid Build Coastguard Worker        }),
24*7594170eSAndroid Build Coastguard Worker    )
25*7594170eSAndroid Build Coastguard Worker
26*7594170eSAndroid Build Coastguard Workerdef _or(name, match_any):
27*7594170eSAndroid Build Coastguard Worker    if not match_any:
28*7594170eSAndroid Build Coastguard Worker        native.alias(
29*7594170eSAndroid Build Coastguard Worker            name = name,
30*7594170eSAndroid Build Coastguard Worker            actual = "//build/bazel/utils:always_off_config_setting",
31*7594170eSAndroid Build Coastguard Worker        )
32*7594170eSAndroid Build Coastguard Worker    else:
33*7594170eSAndroid Build Coastguard Worker        selects.config_setting_group(
34*7594170eSAndroid Build Coastguard Worker            name = name,
35*7594170eSAndroid Build Coastguard Worker            match_any = match_any,
36*7594170eSAndroid Build Coastguard Worker        )
37*7594170eSAndroid Build Coastguard Worker
38*7594170eSAndroid Build Coastguard Workerdef _and(name, match_all):
39*7594170eSAndroid Build Coastguard Worker    if not match_all:
40*7594170eSAndroid Build Coastguard Worker        native.alias(
41*7594170eSAndroid Build Coastguard Worker            name = name,
42*7594170eSAndroid Build Coastguard Worker            actual = "//build/bazel/utils:always_on_config_setting",
43*7594170eSAndroid Build Coastguard Worker        )
44*7594170eSAndroid Build Coastguard Worker    else:
45*7594170eSAndroid Build Coastguard Worker        selects.config_setting_group(
46*7594170eSAndroid Build Coastguard Worker            name = name,
47*7594170eSAndroid Build Coastguard Worker            match_all = match_all,
48*7594170eSAndroid Build Coastguard Worker        )
49*7594170eSAndroid Build Coastguard Worker
50*7594170eSAndroid Build Coastguard Workerdef config_setting_boolean_algebra(*, name, expr):
51*7594170eSAndroid Build Coastguard Worker    """
52*7594170eSAndroid Build Coastguard Worker    Computes the given boolean expression of config settings.
53*7594170eSAndroid Build Coastguard Worker
54*7594170eSAndroid Build Coastguard Worker    The format of the expr argument is a dictionary with a single key/value pair.
55*7594170eSAndroid Build Coastguard Worker    The key can be AND, OR, or NOT. The value or AND/OR keys must be a list
56*7594170eSAndroid Build Coastguard Worker    of strings or more expression dictionaries, where the strings are labels of config settings.
57*7594170eSAndroid Build Coastguard Worker    The value of NOT keys must be a string or an expression dictionary.
58*7594170eSAndroid Build Coastguard Worker
59*7594170eSAndroid Build Coastguard Worker    The result will be a new config setting which is the evaluation of the expression.
60*7594170eSAndroid Build Coastguard Worker
61*7594170eSAndroid Build Coastguard Worker    A bunch of internal config settings will also be created, but they should be treated
62*7594170eSAndroid Build Coastguard Worker    as an implementation detail and not relied on. They could change in future updates to
63*7594170eSAndroid Build Coastguard Worker    this method.
64*7594170eSAndroid Build Coastguard Worker
65*7594170eSAndroid Build Coastguard Worker    Example:
66*7594170eSAndroid Build Coastguard Worker    config_setting_boolean_algebra(
67*7594170eSAndroid Build Coastguard Worker        name = "my_config_setting",
68*7594170eSAndroid Build Coastguard Worker        expr = {"AND": [
69*7594170eSAndroid Build Coastguard Worker            ":config_setting_1",
70*7594170eSAndroid Build Coastguard Worker            {"NOT": ":config_setting_2"},
71*7594170eSAndroid Build Coastguard Worker            {"OR": [
72*7594170eSAndroid Build Coastguard Worker                ":config_setting_3",
73*7594170eSAndroid Build Coastguard Worker                {"NOT": "config_setting_4"},
74*7594170eSAndroid Build Coastguard Worker            ]}
75*7594170eSAndroid Build Coastguard Worker        ]}
76*7594170eSAndroid Build Coastguard Worker    )
77*7594170eSAndroid Build Coastguard Worker    """
78*7594170eSAndroid Build Coastguard Worker
79*7594170eSAndroid Build Coastguard Worker    # The implementation of this function is modeled after a recursive function,
80*7594170eSAndroid Build Coastguard Worker    # but due to the special nature of the problem it's simplified quite a bit from
81*7594170eSAndroid Build Coastguard Worker    # a full recursion-to-iteration algorithm. (no need for return values, no need to return
82*7594170eSAndroid Build Coastguard Worker    # to prior stack frames once we start executing a new one)
83*7594170eSAndroid Build Coastguard Worker    stack = [struct(
84*7594170eSAndroid Build Coastguard Worker        expr = expr,
85*7594170eSAndroid Build Coastguard Worker        name = name,
86*7594170eSAndroid Build Coastguard Worker    )]
87*7594170eSAndroid Build Coastguard Worker
88*7594170eSAndroid Build Coastguard Worker    # Starlark doesn't support infinite loops, so just make a large loop
89*7594170eSAndroid Build Coastguard Worker    for _ in range(1000):
90*7594170eSAndroid Build Coastguard Worker        if not stack:
91*7594170eSAndroid Build Coastguard Worker            break
92*7594170eSAndroid Build Coastguard Worker
93*7594170eSAndroid Build Coastguard Worker        frame = stack.pop()
94*7594170eSAndroid Build Coastguard Worker        name = frame.name
95*7594170eSAndroid Build Coastguard Worker        expr = frame.expr
96*7594170eSAndroid Build Coastguard Worker        expr_type = type(expr)
97*7594170eSAndroid Build Coastguard Worker        if expr_type == "string":
98*7594170eSAndroid Build Coastguard Worker            native.alias(
99*7594170eSAndroid Build Coastguard Worker                name = name,
100*7594170eSAndroid Build Coastguard Worker                actual = expr,
101*7594170eSAndroid Build Coastguard Worker            )
102*7594170eSAndroid Build Coastguard Worker            continue
103*7594170eSAndroid Build Coastguard Worker        elif expr_type == "dict":
104*7594170eSAndroid Build Coastguard Worker            if len(expr) != 1:
105*7594170eSAndroid Build Coastguard Worker                fail("Dictionaries should have exactly 1 key/value")
106*7594170eSAndroid Build Coastguard Worker            op, operands = expr.items()[0]
107*7594170eSAndroid Build Coastguard Worker            if op == "NOT":
108*7594170eSAndroid Build Coastguard Worker                # traditionally this would come after the recursive call, but because it's a rule
109*7594170eSAndroid Build Coastguard Worker                # definition it doesn't matter if name + ".0" exists yet or not. Having the
110*7594170eSAndroid Build Coastguard Worker                # recursive call come last makes the non-recursive version easier to implement
111*7594170eSAndroid Build Coastguard Worker                _not(name, name + ".0")
112*7594170eSAndroid Build Coastguard Worker                stack.append(struct(
113*7594170eSAndroid Build Coastguard Worker                    name = name + ".0",
114*7594170eSAndroid Build Coastguard Worker                    expr = operands,
115*7594170eSAndroid Build Coastguard Worker                ))
116*7594170eSAndroid Build Coastguard Worker                continue
117*7594170eSAndroid Build Coastguard Worker
118*7594170eSAndroid Build Coastguard Worker            if type(operands) != "list":
119*7594170eSAndroid Build Coastguard Worker                fail("Operand to AND/OR must be a list, got %s" % type(operands))
120*7594170eSAndroid Build Coastguard Worker
121*7594170eSAndroid Build Coastguard Worker            operand_names = [name + "." + str(i) for i, elem in enumerate(operands)]
122*7594170eSAndroid Build Coastguard Worker
123*7594170eSAndroid Build Coastguard Worker            if op == "AND":
124*7594170eSAndroid Build Coastguard Worker                _and(name, operand_names)
125*7594170eSAndroid Build Coastguard Worker            elif op == "OR":
126*7594170eSAndroid Build Coastguard Worker                _or(name, operand_names)
127*7594170eSAndroid Build Coastguard Worker            else:
128*7594170eSAndroid Build Coastguard Worker                fail("Operator must be AND, OR, or NOT, got %s" % op)
129*7594170eSAndroid Build Coastguard Worker
130*7594170eSAndroid Build Coastguard Worker            for elem_name, elem in zip(operand_names, operands):
131*7594170eSAndroid Build Coastguard Worker                # because we don't need a return value from these recursive calls,
132*7594170eSAndroid Build Coastguard Worker                # we can queue them all up at once without returning to the current stack frame.
133*7594170eSAndroid Build Coastguard Worker                stack.append(struct(
134*7594170eSAndroid Build Coastguard Worker                    name = elem_name,
135*7594170eSAndroid Build Coastguard Worker                    expr = elem,
136*7594170eSAndroid Build Coastguard Worker                ))
137*7594170eSAndroid Build Coastguard Worker        else:
138*7594170eSAndroid Build Coastguard Worker            fail("Expression must be string or dict, got %s" % expr_type)
139*7594170eSAndroid Build Coastguard Worker    if stack:
140*7594170eSAndroid Build Coastguard Worker        fail("Recursion took too many iterations!")
141