mirror of https://github.com/bazelbuild/rules_cc
183 lines
7.0 KiB
Python
183 lines
7.0 KiB
Python
# 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):
|
|
"""Exposes a toolchain variable to use in toolchain argument expansions.
|
|
|
|
This internal rule exposes [toolchain variables](https://bazel.build/docs/cc-toolchain-config-reference#cctoolchainconfiginfo-build-variables)
|
|
that may be expanded in `cc_args` or `cc_nested_args`
|
|
rules. Because these varaibles merely expose variables inherrent to Bazel,
|
|
it's not possible to declare custom variables.
|
|
|
|
For a full list of available variables, see
|
|
[@rules_cc//cc/toolchains/varaibles:BUILD](https://github.com/bazelbuild/rules_cc/tree/main/cc/toolchains/variables/BUILD).
|
|
|
|
Example:
|
|
```
|
|
load("//cc/toolchains/impl:variables.bzl", "cc_variable")
|
|
|
|
# Defines two targets, ":foo" and ":foo.bar"
|
|
cc_variable(
|
|
name = "foo",
|
|
type = types.list(types.struct(bar = types.string)),
|
|
)
|
|
```
|
|
|
|
Args:
|
|
name: (str) The name of the outer variable, and the rule.
|
|
type: The type of the variable, constructed using `types` factory in
|
|
[@rules_cc//cc/toolchains/impl:variables.bzl](https://github.com/bazelbuild/rules_cc/tree/main/cc/toolchains/impl/variables.bzl).
|
|
**kwargs: [common attributes](https://bazel.build/reference/be/common-definitions#common-attributes) that should be applied to this rule.
|
|
"""
|
|
_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
|