blob: 6a0133d744797f4cf072b360d48400fe67e31b8e [file] [log] [blame] [edit]
# 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.
"""All providers for rule-based bazel toolchain config."""
load("@bazel_skylib//rules/directory:providers.bzl", "DirectoryInfo")
load("//cc/toolchains/impl:args_utils.bzl", "validate_env_variables", "validate_nested_args")
load(
"//cc/toolchains/impl:collect.bzl",
"collect_action_types",
"collect_files",
"collect_provider",
)
load(
"//cc/toolchains/impl:nested_args.bzl",
"NESTED_ARGS_ATTRS",
"format_dict_values",
"nested_args_provider_from_ctx",
)
load(
":cc_toolchain_info.bzl",
"ActionTypeSetInfo",
"ArgsInfo",
"ArgsListInfo",
"BuiltinVariablesInfo",
"EnvInfo",
"FeatureConstraintInfo",
"VariableInfo",
)
visibility("public")
def _cc_args_impl(ctx):
actions = collect_action_types(ctx.attr.actions)
formatted_env, used_format_vars = format_dict_values(
env = ctx.attr.env,
must_use = [], # checking for unused variables in done when formatting `args`.
format = {k: v for v, k in ctx.attr.format.items()},
)
for path in ctx.attr.allowlist_absolute_include_directories:
if not path.startswith("/"):
fail("`{}` is not an absolute paths".format(path))
nested = None
if ctx.attr.args or ctx.attr.nested:
# Forward the format variables used by the env formatting so they don't trigger
# errors if they go unused during the argument formatting.
nested = nested_args_provider_from_ctx(ctx, used_format_vars)
validate_nested_args(
variables = ctx.attr._variables[BuiltinVariablesInfo].variables,
nested_args = nested,
actions = actions.to_list(),
label = ctx.label,
)
files = nested.files
else:
files = collect_files(ctx.attr.data + ctx.attr.allowlist_include_directories)
requires = collect_provider(ctx.attr.requires_any_of, FeatureConstraintInfo)
env = EnvInfo(
label = ctx.label,
entries = formatted_env,
requires_not_none = ctx.attr.requires_not_none[VariableInfo].name if ctx.attr.requires_not_none else None,
)
validate_env_variables(
actions = actions.to_list(),
env = env,
variables = ctx.attr._variables[BuiltinVariablesInfo].variables,
used_format_vars = used_format_vars,
)
args = ArgsInfo(
label = ctx.label,
actions = actions,
requires_any_of = tuple(requires),
nested = nested,
env = env,
files = files,
allowlist_include_directories = depset(
direct = [d[DirectoryInfo] for d in ctx.attr.allowlist_include_directories],
),
allowlist_absolute_include_directories = depset(
direct = ctx.attr.allowlist_absolute_include_directories,
),
)
return [
args,
ArgsListInfo(
label = ctx.label,
args = tuple([args]),
files = files,
by_action = tuple([
struct(action = action, args = tuple([args]), files = files)
for action in actions.to_list()
]),
allowlist_include_directories = args.allowlist_include_directories,
allowlist_absolute_include_directories = args.allowlist_absolute_include_directories,
),
]
_cc_args = rule(
implementation = _cc_args_impl,
attrs = {
"actions": attr.label_list(
providers = [ActionTypeSetInfo],
mandatory = True,
doc = """See documentation for cc_args macro wrapper.""",
),
"allowlist_absolute_include_directories": attr.string_list(
doc = """See documentation for cc_args macro wrapper.""",
),
"allowlist_include_directories": attr.label_list(
providers = [DirectoryInfo],
doc = """See documentation for cc_args macro wrapper.""",
),
"env": attr.string_dict(
doc = """See documentation for cc_args macro wrapper.""",
),
"requires_any_of": attr.label_list(
providers = [FeatureConstraintInfo],
doc = """See documentation for cc_args macro wrapper.""",
),
"_variables": attr.label(
default = "//cc/toolchains/variables:variables",
),
} | NESTED_ARGS_ATTRS,
provides = [ArgsInfo],
doc = """Declares a list of arguments bound to a set of actions.
Roughly equivalent to ctx.actions.args()
Examples:
cc_args(
name = "warnings_as_errors",
args = ["-Werror"],
)
""",
)
def cc_args(
*,
name,
actions = None,
allowlist_include_directories = None,
allowlist_absolute_include_directories = None,
args = None,
data = None,
env = None,
format = {},
iterate_over = None,
nested = None,
requires_not_none = None,
requires_none = None,
requires_true = None,
requires_false = None,
requires_equal = None,
requires_equal_value = None,
requires_any_of = None,
**kwargs):
"""Action-specific arguments for use with a `cc_toolchain`.
This rule is the fundamental building building block for every toolchain tool invocation. Each
argument expressed in a toolchain tool invocation (e.g. `gcc`, `llvm-ar`) is declared in a
`cc_args` rule that applies an ordered list of arguments to a set of toolchain
actions. `cc_args` rules can be added unconditionally to a
`cc_toolchain`, conditionally via `select()` statements, or dynamically via an
intermediate `cc_feature`.
Conceptually, this is similar to the old `CFLAGS`, `CPPFLAGS`, etc. environment variables that
many build systems use to determine which flags to use for a given action. The significant
difference is that `cc_args` rules are declared in a structured way that allows for
significantly more powerful and sharable toolchain configurations. Also, due to Bazel's more
granular action types, it's possible to bind flags to very specific actions (e.g. LTO indexing
for an executable vs a dynamic library) multiple different actions (e.g. C++ compile and link
simultaneously).
Example usage:
```
load("//cc/toolchains:args.bzl", "cc_args")
# Basic usage: a trivial flag.
#
# An example of expressing `-Werror` as a `cc_args` rule.
cc_args(
name = "warnings_as_errors",
actions = [
# Applies to all C/C++ compile actions.
"//cc/toolchains/actions:compile_actions",
],
args = ["-Werror"],
)
# Basic usage: ordered flags.
#
# An example of linking against libc++, which uses two flags that must be applied in order.
cc_args(
name = "link_libcxx",
actions = [
# Applies to all link actions.
"//cc/toolchains/actions:link_actions",
],
# On tool invocation, this appears as `-Xlinker -lc++`. Nothing will ever end up between
# the two flags.
args = [
"-Xlinker",
"-lc++",
],
)
# Advanced usage: built-in variable expansions.
#
# Expands to `-L/path/to/search_dir` for each directory in the built-in variable
# `library_search_directories`. This variable is managed internally by Bazel through inherent
# behaviors of Bazel and the interactions between various C/C++ build rules.
cc_args(
name = "library_search_directories",
actions = [
"//cc/toolchains/actions:link_actions",
],
args = ["-L{search_dir}"],
iterate_over = "//cc/toolchains/variables:library_search_directories",
requires_not_none = "//cc/toolchains/variables:library_search_directories",
format = {
"search_dir": "//cc/toolchains/variables:library_search_directories",
},
)
```
For more extensive examples, see the usages here:
https://github.com/bazelbuild/rules_cc/tree/main/cc/toolchains/args
Args:
name: (str) The name of the target.
actions: (List[Label]) A list of labels of `cc_action_type` or
`cc_action_type_set` rules that dictate which actions these
arguments should be applied to.
allowlist_include_directories: (List[Label]) A list of include paths that are implied by
using this rule. These must point to a skylib
[directory](https://github.com/bazelbuild/bazel-skylib/tree/main/docs/directory_doc.md#directory)
or [subdirectory](https://github.com/bazelbuild/bazel-skylib/tree/main/docs/directory_subdirectory_doc.md#subdirectory) rule.
Some flags (e.g. --sysroot) imply certain include paths are available despite
not explicitly specifying a normal include path flag (`-I`, `-isystem`, etc.).
Bazel checks that all included headers are properly provided by a dependency or
allowlisted through this mechanism.
As a rule of thumb, only use this if Bazel is complaining about absolute paths in
your toolchain and you've ensured that the toolchain is compiling with the
`-no-canonical-prefixes` and/or `-fno-canonical-system-headers` arguments.
This can help work around errors like:
`the source file 'main.c' includes the following non-builtin files with absolute paths
(if these are builtin files, make sure these paths are in your toolchain)`.
allowlist_absolute_include_directories: (List[str]) Allowlists absolute include directories,
preventing Bazel from emitting errors when an #include of local system files in the
directory occurs. Be careful when adding directories to this list, as it inherently
causes leaks in hermeticity. Prefer to reserve use of this for cases like Xcode, where
the conventional expectation is to allowlist well-known system-absolute include paths
rather than redistributing the SDK.
args: (List[str]) The command-line arguments that are applied by using this rule. This is
mutually exclusive with [nested](#cc_args-nested).
data: (List[Label]) A list of runtime data dependencies that are required for these
arguments to work as intended.
env: (Dict[str, str]) Environment variables that should be set when the tool is invoked.
format: (Dict[str, Label]) A mapping of format strings to the label of a corresponding
target. This target can be a `directory`, `subdirectory`, `cc_variable`, or a single
file that the value should be pulled from. All instances of `{variable_name}` in the
`args` list will be replaced with the expanded value in this dictionary.
The complete list of possible variables can be found in
https://github.com/bazelbuild/rules_cc/tree/main/cc/toolchains/variables/BUILD.
It is not possible to declare custom variables--these are inherent to Bazel itself.
iterate_over: (Label) The label of a `cc_variable` that should be iterated over. This is
intended for use with built-in variables that are lists.
nested: (List[Label]) A list of `cc_nested_args` rules that should be
expanded to command-line arguments when this rule is used. This is mutually exclusive
with [args](#cc_args-args).
requires_not_none: (Label) The label of a `cc_variable` that should be checked
for existence before expanding this rule. If the variable is None, this rule will be
ignored.
requires_none: (Label) The label of a `cc_variable` that should be checked for
non-existence before expanding this rule. If the variable is not None, this rule will be
ignored.
requires_true: (Label) The label of a `cc_variable` that should be checked for
truthiness before expanding this rule. If the variable is false, this rule will be
ignored.
requires_false: (Label) The label of a `cc_variable` that should be checked
for falsiness before expanding this rule. If the variable is true, this rule will be
ignored.
requires_equal: (Label) The label of a `cc_variable` that should be checked
for equality before expanding this rule. If the variable is not equal to
(requires_equal_value)[#cc_args-requires_equal_value], this rule will be ignored.
requires_equal_value: (str) The value to compare (requires_equal)[#cc_args-requires_equal]
against.
requires_any_of: (List[Label]) These arguments will be used
in a tool invocation when at least one of the [cc_feature_constraint](#cc_feature_constraint)
entries in this list are satisfied. If omitted, this flag set will be enabled
unconditionally.
**kwargs: [common attributes](https://bazel.build/reference/be/common-definitions#common-attributes) that should be applied to this rule.
"""
return _cc_args(
name = name,
actions = actions,
allowlist_include_directories = allowlist_include_directories,
allowlist_absolute_include_directories = allowlist_absolute_include_directories,
args = args,
data = data,
env = env,
# We flip the key/value pairs in the dictionary here because Bazel doesn't have a
# string-keyed label dict attribute type.
format = {k: v for v, k in format.items()},
iterate_over = iterate_over,
nested = nested,
requires_not_none = requires_not_none,
requires_none = requires_none,
requires_true = requires_true,
requires_false = requires_false,
requires_equal = requires_equal,
requires_equal_value = requires_equal_value,
requires_any_of = requires_any_of,
**kwargs
)