# Copyright 2024 The Bazel Authors. All rights reserved. # # 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. """Rules for accessing cc build variables in bazel toolchains safely.""" load("//cc/toolchains:cc_toolchain_info.bzl", "ActionTypeSetInfo", "BuiltinVariablesInfo", "VariableInfo") load(":collect.bzl", "collect_action_types", "collect_provider") visibility([ "//cc/toolchains/variables", "//tests/rule_based_toolchain/...", ]) types = struct( unknown = dict(name = "unknown", repr = "unknown"), void = dict(name = "void", repr = "void"), string = dict(name = "string", repr = "string"), bool = dict(name = "bool", repr = "bool"), # File and directory are basically the same thing as string for now. file = dict(name = "file", repr = "File"), directory = dict(name = "directory", repr = "directory"), option = lambda element: dict( name = "option", elements = element, repr = "Option[%s]" % element["repr"], ), list = lambda elements: dict( name = "list", elements = elements, repr = "List[%s]" % elements["repr"], ), struct = lambda **kv: dict( name = "struct", kv = kv, repr = "struct(%s)" % ", ".join([ "{k}={v}".format(k = k, v = v["repr"]) for k, v in sorted(kv.items()) ]), ), ) def _cc_variable_impl(ctx): return [VariableInfo( name = ctx.label.name, label = ctx.label, type = json.decode(ctx.attr.type), actions = collect_action_types(ctx.attr.actions) if ctx.attr.actions else None, )] _cc_variable = rule( implementation = _cc_variable_impl, attrs = { "actions": attr.label_list(providers = [ActionTypeSetInfo]), "type": attr.string(mandatory = True), }, provides = [VariableInfo], ) def cc_variable(name, type, **kwargs): """Defines a variable for both the specified variable, and all nested ones. Eg. cc_variable( name = "foo", type = types.list(types.struct(bar = types.string)) ) would define two targets, ":foo" and ":foo.bar" Args: name: (str) The name of the outer variable, and the rule. type: The type of the variable, constructed using types above. **kwargs: kwargs to pass to _cc_variable. """ _cc_variable(name = name, type = json.encode(type), **kwargs) def _cc_builtin_variables_impl(ctx): return [BuiltinVariablesInfo(variables = { variable.name: variable for variable in collect_provider(ctx.attr.srcs, VariableInfo) })] cc_builtin_variables = rule( implementation = _cc_builtin_variables_impl, attrs = { "srcs": attr.label_list(providers = [VariableInfo]), }, ) def get_type(*, name, variables, overrides, actions, args_label, nested_label, fail): """Gets the type of a variable. Args: name: (str) The variable to look up. variables: (dict[str, VariableInfo]) Mapping from variable name to metadata. Top-level variables only overrides: (dict[str, type]) Mapping from variable names to type. Can be used for nested variables. actions: (depset[ActionTypeInfo]) The set of actions for which the variable is requested. args_label: (Label) The label for the args that included the rule that references this variable. Only used for error messages. nested_label: (Label) The label for the rule that references this variable. Only used for error messages. fail: A function to be called upon failure. Use for testing only. Returns: The type of the variable "name". """ outer = name.split(".")[0] if outer not in variables: # With a fail function, we actually need to return since the fail # function doesn't propagate. fail("The variable %s does not exist. Did you mean one of the following?\n%s" % (outer, "\n".join(sorted(variables)))) # buildifier: disable=unreachable return types.void if variables[outer].actions != None: valid_actions = variables[outer].actions.to_list() for action in actions: if action not in valid_actions: 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( var = variables[outer].label, nested_label = nested_label, args_label = args_label, action = action.label, )) # buildifier: disable=unreachable return types.void type = overrides.get(outer, variables[outer].type) parent = outer for part in name.split(".")[1:]: full = parent + "." + part if type["name"] != "struct": extra_error = "" if type["name"] == "list" and type["elements"]["name"] == "struct": extra_error = " Maybe you meant to use iterate_over." fail("Attempted to access %r, but %r was not a struct - it had type %s.%s" % (full, parent, type["repr"], extra_error)) # buildifier: disable=unreachable return types.void if part not in type["kv"] and full not in overrides: attrs = [] for attr, value in sorted(type["kv"].items()): attrs.append("%s: %s" % (attr, value["repr"])) fail("Unable to find %r in %r, which had the following attributes:\n%s" % (part, parent, "\n".join(attrs))) # buildifier: disable=unreachable return types.void type = overrides.get(full, type["kv"][part]) parent = full return type