1# Copyright 2024 The Bazel Authors. All rights reserved. 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"""Rules for accessing cc build variables in bazel toolchains safely.""" 15 16load("//cc/toolchains:cc_toolchain_info.bzl", "ActionTypeSetInfo", "BuiltinVariablesInfo", "VariableInfo") 17load(":collect.bzl", "collect_action_types", "collect_provider") 18 19visibility([ 20 "//cc/toolchains/variables", 21 "//tests/rule_based_toolchain/...", 22]) 23 24types = struct( 25 unknown = dict(name = "unknown", repr = "unknown"), 26 void = dict(name = "void", repr = "void"), 27 string = dict(name = "string", repr = "string"), 28 bool = dict(name = "bool", repr = "bool"), 29 # File and directory are basically the same thing as string for now. 30 file = dict(name = "file", repr = "File"), 31 directory = dict(name = "directory", repr = "directory"), 32 option = lambda element: dict( 33 name = "option", 34 elements = element, 35 repr = "Option[%s]" % element["repr"], 36 ), 37 list = lambda elements: dict( 38 name = "list", 39 elements = elements, 40 repr = "List[%s]" % elements["repr"], 41 ), 42 struct = lambda **kv: dict( 43 name = "struct", 44 kv = kv, 45 repr = "struct(%s)" % ", ".join([ 46 "{k}={v}".format(k = k, v = v["repr"]) 47 for k, v in sorted(kv.items()) 48 ]), 49 ), 50) 51 52def _cc_variable_impl(ctx): 53 return [VariableInfo( 54 name = ctx.label.name, 55 label = ctx.label, 56 type = json.decode(ctx.attr.type), 57 actions = collect_action_types(ctx.attr.actions) if ctx.attr.actions else None, 58 )] 59 60_cc_variable = rule( 61 implementation = _cc_variable_impl, 62 attrs = { 63 "actions": attr.label_list(providers = [ActionTypeSetInfo]), 64 "type": attr.string(mandatory = True), 65 }, 66 provides = [VariableInfo], 67) 68 69def cc_variable(name, type, **kwargs): 70 """Defines a variable for both the specified variable, and all nested ones. 71 72 Eg. cc_variable( 73 name = "foo", 74 type = types.list(types.struct(bar = types.string)) 75 ) 76 77 would define two targets, ":foo" and ":foo.bar" 78 79 Args: 80 name: (str) The name of the outer variable, and the rule. 81 type: The type of the variable, constructed using types above. 82 **kwargs: kwargs to pass to _cc_variable. 83 """ 84 _cc_variable(name = name, type = json.encode(type), **kwargs) 85 86def _cc_builtin_variables_impl(ctx): 87 return [BuiltinVariablesInfo(variables = { 88 variable.name: variable 89 for variable in collect_provider(ctx.attr.srcs, VariableInfo) 90 })] 91 92cc_builtin_variables = rule( 93 implementation = _cc_builtin_variables_impl, 94 attrs = { 95 "srcs": attr.label_list(providers = [VariableInfo]), 96 }, 97) 98 99def get_type(*, name, variables, overrides, actions, args_label, nested_label, fail): 100 """Gets the type of a variable. 101 102 Args: 103 name: (str) The variable to look up. 104 variables: (dict[str, VariableInfo]) Mapping from variable name to 105 metadata. Top-level variables only 106 overrides: (dict[str, type]) Mapping from variable names to type. 107 Can be used for nested variables. 108 actions: (depset[ActionTypeInfo]) The set of actions for which the 109 variable is requested. 110 args_label: (Label) The label for the args that included the rule that 111 references this variable. Only used for error messages. 112 nested_label: (Label) The label for the rule that references this 113 variable. Only used for error messages. 114 fail: A function to be called upon failure. Use for testing only. 115 Returns: 116 The type of the variable "name". 117 """ 118 outer = name.split(".")[0] 119 if outer not in variables: 120 # With a fail function, we actually need to return since the fail 121 # function doesn't propagate. 122 fail("The variable %s does not exist. Did you mean one of the following?\n%s" % (outer, "\n".join(sorted(variables)))) 123 124 # buildifier: disable=unreachable 125 return types.void 126 127 if variables[outer].actions != None: 128 valid_actions = variables[outer].actions.to_list() 129 for action in actions: 130 if action not in valid_actions: 131 fail("The variable {var} is inaccessible from the action {action}. This is required because it is referenced in {nested_label}, which is included by {args_label}, which references that action".format( 132 var = variables[outer].label, 133 nested_label = nested_label, 134 args_label = args_label, 135 action = action.label, 136 )) 137 138 # buildifier: disable=unreachable 139 return types.void 140 141 type = overrides.get(outer, variables[outer].type) 142 143 parent = outer 144 for part in name.split(".")[1:]: 145 full = parent + "." + part 146 147 if type["name"] != "struct": 148 extra_error = "" 149 if type["name"] == "list" and type["elements"]["name"] == "struct": 150 extra_error = " Maybe you meant to use iterate_over." 151 152 fail("Attempted to access %r, but %r was not a struct - it had type %s.%s" % (full, parent, type["repr"], extra_error)) 153 154 # buildifier: disable=unreachable 155 return types.void 156 157 if part not in type["kv"] and full not in overrides: 158 attrs = [] 159 for attr, value in sorted(type["kv"].items()): 160 attrs.append("%s: %s" % (attr, value["repr"])) 161 fail("Unable to find %r in %r, which had the following attributes:\n%s" % (part, parent, "\n".join(attrs))) 162 163 # buildifier: disable=unreachable 164 return types.void 165 166 type = overrides.get(full, type["kv"][part]) 167 parent = full 168 169 return type 170