blob: 246d54c55375a3a442f84a0a5d832d3cfdfebd29 [file]
# Copyright 2023 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.
"""Implementation of cc_tool"""
load("@bazel_skylib//rules/directory:providers.bzl", "DirectoryInfo")
load("//cc/toolchains/impl:collect.bzl", "collect_data", "collect_provider")
load("//cc/toolchains/impl:label_utils.bzl", "deduplicate_label_list")
load("//cc/toolchains/impl:nested_args.bzl", "format_dict_values")
load(
":cc_toolchain_info.bzl",
"ExecutionRequirementsInfo",
"ToolCapabilityInfo",
"ToolInfo",
)
def _cc_tool_impl(ctx):
exe_info = ctx.attr.src[DefaultInfo]
if exe_info.files_to_run != None and exe_info.files_to_run.executable != None:
exe = exe_info.files_to_run.executable
elif len(exe_info.files.to_list()) == 1:
exe = exe_info.files.to_list()[0]
else:
fail("Expected cc_tool's src attribute to be either an executable or a single file")
format_targets = {}
for i in range(len(ctx.attr.format_keys)):
format_targets[ctx.attr.format_keys[i]] = ctx.attr.format_values[ctx.attr.format_value_indexes[i]]
env, _ = format_dict_values(
env = ctx.attr.env,
format = format_targets,
must_use = format_targets.keys(),
)
execution_requirements = []
for info in collect_provider(ctx.attr.execution_requirements, ExecutionRequirementsInfo):
execution_requirements.extend(info.requirements)
runfiles = collect_data(ctx, ctx.attr.data + [ctx.attr.src])
tool = ToolInfo(
label = ctx.label,
exe = exe,
runfiles = runfiles,
execution_requirements = tuple(ctx.attr.tags + execution_requirements),
env = env,
allowlist_include_directories = depset(
direct = [d[DirectoryInfo] for d in ctx.attr.allowlist_include_directories],
),
capabilities = tuple(collect_provider(ctx.attr.capabilities, ToolCapabilityInfo)),
)
link = ctx.actions.declare_file(ctx.label.name)
ctx.actions.symlink(
output = link,
target_file = exe,
is_executable = True,
)
return [
tool,
# This isn't required, but now we can do "bazel run <tool>", which can
# be very helpful when debugging toolchains.
DefaultInfo(
files = depset([link]),
runfiles = runfiles,
executable = link,
),
]
_cc_tool = rule(
implementation = _cc_tool_impl,
# @unsorted-dict-items
attrs = {
"src": attr.label(
allow_files = True,
doc = """The underlying binary that this tool represents.
Usually just a single prebuilt (eg. @toolchain//:bin/clang), but may be any
executable label.
""",
),
"data": attr.label_list(
allow_files = True,
doc = """Additional files that are required for this tool to run.
Frequently, clang and gcc require additional files to execute as they often shell out to
other binaries (e.g. `cc1`).
""",
),
"allowlist_include_directories": attr.label_list(
providers = [DirectoryInfo],
doc = """Include paths implied by using this tool.
Compilers may include a set of built-in headers that are implicitly available
unless flags like `-nostdinc` are provided. 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.
These files are not automatically passed to each action. If they need to be,
add them to 'data' as well.
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)`.
""",
),
"env": attr.string_dict(
doc = """Environment variables to apply when running this tool.
Format expansion is performed on values using the format attribute.
""",
),
"execution_requirements": attr.label_list(
providers = [ExecutionRequirementsInfo],
doc = """Additional execution requirements for actions that run this tool.
Each label must provide `ExecutionRequirementsInfo`. For fixed execution requirements, use `tags`.
""",
),
"format_keys": attr.string_list(
doc = """Format strings used in substitutions for env values.
Use the cc_tool macro's format attribute instead.
""",
),
"format_values": attr.label_list(
allow_files = True,
doc = """Targets used in substitutions for env values.
Use the cc_tool macro's format attribute instead.
""",
),
"format_value_indexes": attr.int_list(
doc = """Index of the target in format_values for each format key.
Use the cc_tool macro's format attribute instead.
""",
),
"capabilities": attr.label_list(
providers = [ToolCapabilityInfo],
doc = """Declares that a tool is capable of doing something.
For example, `@rules_cc//cc/toolchains/capabilities:supports_pic`.
""",
),
},
provides = [ToolInfo],
doc = """Declares a tool for use by toolchain actions.
`cc_tool` rules are used in a `cc_tool_map` rule to ensure all files and
metadata required to run a tool are available when constructing a `cc_toolchain`.
In general, include all files that are always required to run a tool (e.g. libexec/** and
cross-referenced tools in bin/*) in the [data](#cc_tool-data) attribute. If some files are only
required when certain flags are passed to the tool, consider using a `cc_args` rule to
bind the files to the flags that require them. This reduces the overhead required to properly
enumerate a sandbox with all the files required to run a tool, and ensures that there isn't
unintentional leakage across configurations and actions.
Example:
```
load("//cc/toolchains:tool.bzl", "cc_tool")
cc_tool(
name = "clang",
src = "@llvm_toolchain//:bin/clang",
# Suppose clang needs libc to run.
data = ["@llvm_toolchain//:lib/x86_64-linux-gnu/libc.so.6"]
capabilities = ["//cc/toolchains/capabilities:supports_pic"],
)
```
""",
executable = True,
)
def cc_tool(
*,
name,
src = None,
data = None,
allowlist_include_directories = None,
env = None,
execution_requirements = None,
format = {},
capabilities = None,
**kwargs):
"""Declares a tool for use by toolchain actions.
`cc_tool` rules are used in a `cc_tool_map` rule to ensure all files and
metadata required to run a tool are available when constructing a `cc_toolchain`.
In general, include all files that are always required to run a tool (e.g. libexec/** and
cross-referenced tools in bin/*) in the [data](#cc_tool-data) attribute. If some files are only
required when certain flags are passed to the tool, consider using a `cc_args` rule to
bind the files to the flags that require them. This reduces the overhead required to properly
enumerate a sandbox with all the files required to run a tool, and ensures that there isn't
unintentional leakage across configurations and actions.
Example:
```
load("//cc/toolchains:tool.bzl", "cc_tool")
cc_tool(
name = "clang",
src = "@llvm_toolchain//:bin/clang",
# Suppose clang needs libc to run.
data = ["@llvm_toolchain//:lib/x86_64-linux-gnu/libc.so.6"]
capabilities = ["//cc/toolchains/capabilities:supports_pic"],
)
```
Args:
name: (str) The name of the target.
src: (Label) The underlying binary that this tool represents.
Usually just a single prebuilt (e.g. @toolchain//:bin/clang), but may be any
executable label.
data: (List[Label]) Additional files that are required for this tool to run.
Frequently, clang and gcc require additional files to execute as they often shell out to
other binaries (e.g. `cc1`).
allowlist_include_directories: (List[Label]) Include paths implied by using this tool.
Compilers may include a set of built-in headers that are implicitly available
unless flags like `-nostdinc` are provided. 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.
These files are not automatically passed to each action. If they need to be,
add them to 'data' as well.
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)`.
env: (Dict[str, str]) Environment variables to apply when running this tool.
Format expansion is performed on values using `format`.
execution_requirements: (List[Label]) Additional execution requirements for actions that
run this tool. Each label must provide `ExecutionRequirementsInfo`. For fixed
execution requirements, use `tags`.
format: (Dict[str, Label]) A mapping of format strings to the label of a corresponding
target. This target can be a `directory`, `subdirectory`, or a single file that the
value should be pulled from. All instances of `{variable_name}` in the `env` dictionary
values will be replaced with the expanded value in this dictionary.
capabilities: (List[Label]) Declares that a tool is capable of doing something.
For example, `@rules_cc//cc/toolchains/capabilities:supports_pic`.
**kwargs: [common attributes](https://bazel.build/reference/be/common-definitions#common-attributes)
that should be applied to this rule.
"""
format_keys = format.keys()
format_values = deduplicate_label_list(
name = name,
labels = [format[key] for key in format_keys],
)
return _cc_tool(
name = name,
src = src,
data = data,
allowlist_include_directories = allowlist_include_directories,
env = env,
execution_requirements = execution_requirements,
format_keys = format_keys,
format_values = format_values.labels,
format_value_indexes = format_values.indexes,
capabilities = capabilities,
**kwargs
)