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