aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--METADATA23
-rw-r--r--MODULE.bazel7
-rw-r--r--README.md13
-rw-r--r--WORKSPACE21
-rw-r--r--cc/action_names.bzl2
-rw-r--r--cc/cc_toolchain_config_lib.bzl2
-rw-r--r--cc/defs.bzl2
-rw-r--r--cc/proto/BUILD4
-rw-r--r--cc/toolchains/BUILD21
-rw-r--r--cc/toolchains/README.md333
-rw-r--r--cc/toolchains/action_type_config.bzl137
-rw-r--r--cc/toolchains/actions.bzl83
-rw-r--r--cc/toolchains/actions/BUILD284
-rw-r--r--cc/toolchains/args.bzl120
-rw-r--r--cc/toolchains/args_list.bzl35
-rw-r--r--cc/toolchains/cc_toolchain_info.bzl191
-rw-r--r--cc/toolchains/feature.bzl243
-rw-r--r--cc/toolchains/feature_constraint.bzl54
-rw-r--r--cc/toolchains/feature_set.bzl57
-rw-r--r--cc/toolchains/features/BUILD98
-rw-r--r--cc/toolchains/features/legacy/BUILD279
-rw-r--r--cc/toolchains/format.bzl26
-rw-r--r--cc/toolchains/impl/BUILD6
-rw-r--r--cc/toolchains/impl/args_utils.bzl121
-rw-r--r--cc/toolchains/impl/collect.bzl173
-rw-r--r--cc/toolchains/impl/external_feature.bzl72
-rw-r--r--cc/toolchains/impl/legacy_converter.bzl190
-rw-r--r--cc/toolchains/impl/nested_args.bzl387
-rw-r--r--cc/toolchains/impl/toolchain_config.bzl123
-rw-r--r--cc/toolchains/impl/toolchain_config_info.bzl178
-rw-r--r--cc/toolchains/impl/variables.bzl169
-rw-r--r--cc/toolchains/mutually_exclusive_category.bzl29
-rw-r--r--cc/toolchains/nested_args.bzl45
-rw-r--r--cc/toolchains/tool.bzl106
-rw-r--r--cc/toolchains/toolchain.bzl143
-rw-r--r--cc/toolchains/variables/BUILD481
-rw-r--r--examples/BUILD3
-rw-r--r--examples/my_c_compile/BUILD1
-rw-r--r--tests/compiler_settings/BUILD2
-rw-r--r--tests/rule_based_toolchain/BUILD0
-rw-r--r--tests/rule_based_toolchain/action_type_config/BUILD42
-rw-r--r--tests/rule_based_toolchain/action_type_config/action_type_config_test.bzl108
-rw-r--r--tests/rule_based_toolchain/actions/BUILD34
-rw-r--r--tests/rule_based_toolchain/actions/actions_test.bzl43
-rw-r--r--tests/rule_based_toolchain/analysis_test_suite.bzl50
-rw-r--r--tests/rule_based_toolchain/args/BUILD36
-rw-r--r--tests/rule_based_toolchain/args/args_test.bzl113
-rw-r--r--tests/rule_based_toolchain/args_list/BUILD49
-rw-r--r--tests/rule_based_toolchain/args_list/args_list_test.bzl67
-rw-r--r--tests/rule_based_toolchain/features/BUILD116
-rw-r--r--tests/rule_based_toolchain/features/features_test.bzl173
-rw-r--r--tests/rule_based_toolchain/generate_factory.bzl130
-rw-r--r--tests/rule_based_toolchain/generics.bzl139
-rw-r--r--tests/rule_based_toolchain/nested_args/BUILD14
-rw-r--r--tests/rule_based_toolchain/nested_args/nested_args_test.bzl205
-rw-r--r--tests/rule_based_toolchain/subjects.bzl247
-rw-r--r--tests/rule_based_toolchain/testdata/BUILD32
-rwxr-xr-xtests/rule_based_toolchain/testdata/bin3
-rwxr-xr-xtests/rule_based_toolchain/testdata/bin_wrapper.sh3
-rw-r--r--tests/rule_based_toolchain/testdata/file10
-rw-r--r--tests/rule_based_toolchain/testdata/file20
-rw-r--r--tests/rule_based_toolchain/testdata/multiple10
-rw-r--r--tests/rule_based_toolchain/testdata/multiple20
-rw-r--r--tests/rule_based_toolchain/tool/BUILD26
-rw-r--r--tests/rule_based_toolchain/tool/tool_test.bzl122
-rw-r--r--tests/rule_based_toolchain/toolchain_config/BUILD197
-rw-r--r--tests/rule_based_toolchain/toolchain_config/toolchain_config_test.bzl293
-rw-r--r--tests/rule_based_toolchain/variables/BUILD148
-rw-r--r--tests/rule_based_toolchain/variables/variables_test.bzl200
-rw-r--r--tests/system_library/BUILD14
-rw-r--r--tools/migration/BUILD12
-rw-r--r--tools/migration/ctoolchain_comparator_lib_test.py464
72 files changed, 7134 insertions, 210 deletions
diff --git a/METADATA b/METADATA
index 921edac..8d58189 100644
--- a/METADATA
+++ b/METADATA
@@ -1,14 +1,19 @@
-name: "bazelbuild-rules_cc"
-description:
- "A repository of Starlark implementation of C++ rules in Bazel"
+# This project was upgraded with external_updater.
+# Usage: tools/external_updater/updater.sh update external/bazelbuild-rules_cc
+# For more info, check https://cs.android.com/android/platform/superproject/+/main:tools/external_updater/README.md
+name: "bazelbuild-rules_cc"
+description: "A repository of Starlark implementation of C++ rules in Bazel"
third_party {
- url {
- type: GIT
+ license_type: NOTICE
+ last_upgrade_date {
+ year: 2024
+ month: 5
+ day: 9
+ }
+ identifier {
+ type: "Git"
value: "https://github.com/bazelbuild/rules_cc"
+ version: "f88663dc502aacb6a6f377030d0652309412c8a9"
}
- version: "34bcaf6223a39ec002efcf06e110871a6f562f44"
- last_upgrade_date { year: 2023 month: 7 day: 14 }
- license_type: NOTICE
}
-
diff --git a/MODULE.bazel b/MODULE.bazel
index 3848c66..ee4789b 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -4,11 +4,12 @@ module(
compatibility_level = 1,
)
-bazel_dep(name = "platforms", version = "0.0.6")
+bazel_dep(name = "bazel_skylib", version = "1.3.0")
+bazel_dep(name = "platforms", version = "0.0.7")
-cc_configure = use_extension("@rules_cc//cc:extensions.bzl", "cc_configure")
+cc_configure = use_extension("@bazel_tools//tools/cpp:cc_configure.bzl", "cc_configure_extension")
use_repo(cc_configure, "local_config_cc_toolchains")
register_toolchains("@local_config_cc_toolchains//:all")
-bazel_dep(name = "bazel_skylib", version = "1.3.0", dev_dependency = True)
+bazel_dep(name = "rules_testing", version = "0.6.0", dev_dependency = True)
diff --git a/README.md b/README.md
index 2cc1973..16245ef 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
* Postsubmit [![Build status](https://badge.buildkite.com/f03592ae2d7d25a2abc2a2ba776e704823fa17fd3e061f5103.svg?branch=main)](https://buildkite.com/bazel/rules-cc)
* Postsubmit + Current Bazel Incompatible flags [![Build status](https://badge.buildkite.com/5ba709cc33e5855078a1f8570adcf8e0a78ea93591bc0b4e81.svg?branch=master)](https://buildkite.com/bazel/rules-cc-plus-bazelisk-migrate)
-This repository contains Starlark implementation of C++ rules in Bazel.
+This repository contains a Starlark implementation of C++ rules in Bazel.
The rules are being incrementally converted from their native implementations in the [Bazel source tree](https://source.bazel.build/bazel/+/master:src/main/java/com/google/devtools/build/lib/rules/cpp/).
@@ -37,7 +37,16 @@ cc_library(
# Using the rules_cc toolchain
-If you'd like to use the cc toolchain defined in this repo add this to
+This repo contains an auto-detecting toolchain that expects to find tools installed on your host machine.
+This is non-hermetic, and may have varying behaviors depending on the versions of tools found.
+
+There are third-party contributed hermetic toolchains you may want to investigate:
+
+- LLVM: <https://github.com/grailbio/bazel-toolchain>
+- GCC (Linux only): <https://github.com/aspect-build/gcc-toolchain>
+- zig cc: <https://github.com/uber/hermetic_cc_toolchain>
+
+If you'd like to use the cc toolchain defined in this repo, add this to
your WORKSPACE after you include rules_cc:
```bzl
diff --git a/WORKSPACE b/WORKSPACE
index 8550fce..875888e 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -32,19 +32,19 @@ http_archive(
http_archive(
name = "io_bazel_rules_go",
- sha256 = "dd926a88a564a9246713a9c00b35315f54cbd46b31a26d5d8fb264c07045f05d",
+ sha256 = "91585017debb61982f7054c9688857a2ad1fd823fc3f9cb05048b0025c47d023",
urls = [
- "https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.38.1/rules_go-v0.38.1.zip",
- "https://github.com/bazelbuild/rules_go/releases/download/v0.38.1/rules_go-v0.38.1.zip",
+ "https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.42.0/rules_go-v0.42.0.zip",
+ "https://github.com/bazelbuild/rules_go/releases/download/v0.42.0/rules_go-v0.42.0.zip",
],
)
http_archive(
name = "platforms",
- sha256 = "5308fc1d8865406a49427ba24a9ab53087f17f5266a7aabbfc28823f3916e1ca",
+ sha256 = "3a561c99e7bdbe9173aa653fd579fe849f1d8d67395780ab4770b1f381431d51",
urls = [
- "https://mirror.bazel.build/github.com/bazelbuild/platforms/releases/download/0.0.6/platforms-0.0.6.tar.gz",
- "https://github.com/bazelbuild/platforms/releases/download/0.0.6/platforms-0.0.6.tar.gz",
+ "https://mirror.bazel.build/github.com/bazelbuild/platforms/releases/download/0.0.7/platforms-0.0.7.tar.gz",
+ "https://github.com/bazelbuild/platforms/releases/download/0.0.7/platforms-0.0.7.tar.gz",
],
)
@@ -83,10 +83,17 @@ load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_depe
go_rules_dependencies()
-go_register_toolchains(version = "1.19.4")
+go_register_toolchains(version = "1.20.5")
load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies", "rules_proto_toolchains")
rules_proto_dependencies()
rules_proto_toolchains()
+
+http_archive(
+ name = "rules_testing",
+ sha256 = "02c62574631876a4e3b02a1820cb51167bb9cdcdea2381b2fa9d9b8b11c407c4",
+ strip_prefix = "rules_testing-0.6.0",
+ url = "https://github.com/bazelbuild/rules_testing/releases/download/v0.6.0/rules_testing-v0.6.0.tar.gz",
+)
diff --git a/cc/action_names.bzl b/cc/action_names.bzl
index 82325d1..3df7cfa 100644
--- a/cc/action_names.bzl
+++ b/cc/action_names.bzl
@@ -13,6 +13,8 @@
# limitations under the License.
"""Constants for action names used for C++ rules."""
+# Keep in sync with //cc/toolchains/actions:BUILD.
+
# Name for the C compilation action.
C_COMPILE_ACTION_NAME = "c-compile"
diff --git a/cc/cc_toolchain_config_lib.bzl b/cc/cc_toolchain_config_lib.bzl
index 3a259de..876ed4d 100644
--- a/cc/cc_toolchain_config_lib.bzl
+++ b/cc/cc_toolchain_config_lib.bzl
@@ -492,7 +492,7 @@ def tool(path = None, with_features = [], execution_requirements = [], tool = No
execution_requirements: Requirements on the execution environment for
the execution of this tool, to be passed as out-of-band "hints" to
the execution backend.
- Ex. "requires-darwin"
+ Ex. "requires-mem:24g"
Returns:
A ToolInfo provider.
diff --git a/cc/defs.bzl b/cc/defs.bzl
index a3acac7..11be6fd 100644
--- a/cc/defs.bzl
+++ b/cc/defs.bzl
@@ -103,7 +103,7 @@ def cc_proto_library(**attrs):
**attrs: Rule attributes
"""
- # buildifier: disable=native-cc
+ # buildifier: disable=native-cc-proto
native.cc_proto_library(**_add_tags(attrs))
def fdo_prefetch_hints(**attrs):
diff --git a/cc/proto/BUILD b/cc/proto/BUILD
new file mode 100644
index 0000000..3be6187
--- /dev/null
+++ b/cc/proto/BUILD
@@ -0,0 +1,4 @@
+package(default_visibility = ["//visibility:public"])
+
+# Toolchain type provided by proto_lang_toolchain rule and used by cc_proto_library
+toolchain_type(name = "toolchain_type")
diff --git a/cc/toolchains/BUILD b/cc/toolchains/BUILD
new file mode 100644
index 0000000..cde0cb5
--- /dev/null
+++ b/cc/toolchains/BUILD
@@ -0,0 +1,21 @@
+# 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.
+
+load("@bazel_skylib//rules:common_settings.bzl", "bool_flag")
+
+bool_flag(
+ name = "experimental_enable_rule_based_toolchains",
+ build_setting_default = False,
+ visibility = ["//visibility:public"],
+)
diff --git a/cc/toolchains/README.md b/cc/toolchains/README.md
new file mode 100644
index 0000000..42266b7
--- /dev/null
+++ b/cc/toolchains/README.md
@@ -0,0 +1,333 @@
+# Writing a custom rule_based C++ toolchain with rule-based definition.
+
+Work in progress!
+
+This document serves two purposes:
+* Until complete, this serves as an agreement for the final user-facing API.
+* Once complete, this will serve as onboarding documentation.
+
+This section will be removed once complete.
+
+## Step 1: Define tools
+A tool is simply a binary. Just like any other bazel binary, a tool can specify
+additional files required to run.
+
+We can use any bazel binary as an input to anything that requires tools. In the
+example below, you could use both clang and ld as tools.
+
+```
+# @sysroot//:BUILD
+cc_tool(
+ name = "clang",
+ exe = ":bin/clang",
+ execution_requirements = ["requires-mem:24g"],
+ data = [...],
+)
+
+sh_binary(
+ name = "ld",
+ srcs = ["ld_wrapper.sh"],
+ data = [":bin/ld"],
+)
+
+```
+
+## Step 2: Generate action configs from those tools
+An action config is a mapping from action to:
+
+* A list of tools, (the first one matching the execution requirements is used).
+* A list of args and features that are always enabled for the action
+* A set of additional files required for the action
+
+Each action can only be specified once in the toolchain. Specifying multiple
+actions in a single `cc_action_type_config` is just a shorthand for specifying the
+same config for every one of those actions.
+
+If you're already familiar with how to define toolchains, the additional files
+is a replacement for `compile_files`, `link_files`, etc.
+
+Additionally, to replace `all_files`, we add `cc_additional_files_for_actions`.
+This allows you to specify that particular files are required for particular
+actions.
+
+We provide `additional_files` on the `cc_action_type_config` as a shorthand for
+specifying `cc_additional_files_for_actions`
+
+Warning: Implying a feature that is not listed directly in the toolchain will throw
+an error. This is to ensure you don't accidentally add a feature to the
+toolchain.
+
+```
+cc_action_type_config(
+ name = "c_compile",
+ actions = ["@rules_cc//actions:all_c_compile"],
+ tools = ["@sysroot//:clang"],
+ args = [":my_args"],
+ implies = [":my_feature"],
+ additional_files = ["@sysroot//:all_header_files"],
+)
+
+cc_additional_files_for_actions(
+ name = "all_action_files",
+ actions = ["@rules_cc//actions:all_actions"],
+ additional_files = ["@sysroot//:always_needed_files"]
+)
+```
+
+## Step 3: Define some arguments
+Arguments are our replacement for `flag_set` and `env_set`. To add arguments to
+our tools, we take heavy inspiration from bazel's
+[`Args`](https://bazel.build/rules/lib/builtins/Args) type. We provide the same
+API, with the following caveats:
+* `actions` specifies which actions the arguments apply to (same as `flag_set`).
+* `requires_any_of` is equivalent to `with_features` on the `flag_set`.
+* `args` may be used instead of `add` if your command-line is only strings.
+* `env` may be used to add environment variables to the arguments. Environment
+ variables set by later args take priority.
+* By default, all inputs are automatically added to the corresponding actions.
+ `additional_files` specifies files that are required for an action when using
+ that argument.
+
+```
+cc_args(
+ name = "inline",
+ actions = ["@rules_cc//actions:all_cpp_compile_actions"],
+ args = ["--foo"],
+ requires_any_of = [":feature"]
+ env = {"FOO": "bar"},
+ additional_files = [":file"],
+)
+```
+
+For more complex use cases, we use the same API as `Args`. Values are either:
+* A list of files (or a single file for `cc_add_args`).
+* Something returning `CcVariableInfo`, which is equivalent to a list of strings.
+
+```
+cc_variable(
+ name = "bar_baz",
+ values = ["bar", "baz"],
+)
+
+# Expands to CcVariableInfo(values = ["x86_64-unknown-linux-gnu"])
+custom_variable_rule(
+ name = "triple",
+ ...
+)
+
+# Taken from https://bazel.build/rules/lib/builtins/Args#add
+cc_add_args(
+ name = "single",
+ arg_name = "--platform",
+ value = ":triple", # Either a single file or a cc_variable
+ format = "%s",
+)
+
+# Taken from https://bazel.build/rules/lib/builtins/Args#add_all
+cc_add_args_all(
+ name = "multiple",
+ arg_name = "--foo",
+ values = [":file", ":file_set"], # Either files or cc_variable.
+ # map_each not supported. Write a custom rule if you want that.
+ format_each = "%s",
+ before_each = "--foo",
+ omit_if_empty = True,
+ uniquify = False,
+ # Expand_directories not yet supported.
+ terminate_with = "foo",
+)
+
+# Taken from https://bazel.build/rules/lib/builtins/Args#add_joined
+cc_add_args_joined(
+ name = "joined",
+ arg_name = "--foo",
+ values = [":file", ":file_set"], # Either files or cc_variable.
+ join_with = ",",
+ # map_each not supported. Write a custom rule if you want that.
+ format_each = "%s",
+ format_joined = "--foo=%s",
+ omit_if_empty = True,
+ uniquify = False,
+ # Expand_directories not yet supported.
+)
+
+cc_args(
+ name = "complex",
+ actions = ["@rules_cc//actions:c_compile"],
+ add = [":single", ":multiple", ":joined"],
+)
+
+cc_args_list(
+ name = "all_flags",
+ args = [":inline", ":complex"],
+)
+```
+
+## Step 4: Define some features
+A feature is a set of args and configurations that can be enabled or disabled.
+
+Although the existing toolchain recommends using features to avoid duplication
+of definitions, we recommend avoiding using features unless you want the user to
+be able to enable / disable the feature themselves. This is because we provide
+alternatives such as `cc_args_list` to allow combining arguments and
+specifying them on each action in the action config.
+
+```
+cc_feature(
+ name = "my_feature",
+ feature_name = "my_feature",
+ args = [":all_args"],
+ implies = [":other_feature"],
+)
+```
+
+## Step 5: Generate the toolchain
+The `cc_toolchain` macro:
+
+* Performs validation on the inputs (eg. no two action configs for a single
+ action)
+* Converts the type-safe providers to the unsafe ones in
+ `cc_toolchain_config_lib.bzl`
+* Generates a set of providers for each of the filegroups respectively
+* Generates the appropriate `native.cc_toolchain` invocation.
+
+```
+cc_toolchain(
+ name = "toolchain",
+ features = [":my_feature"]
+ unconditional_args = [":all_warnings"],
+ action_type_configs = [":c_compile"],
+ additional_files = [":all_action_files"],
+)
+```
+
+# Ancillary components for type-safe toolchains.
+## Well-known features
+Well-known features will be defined in `@rules_cc//features/well_known:*`.
+Any feature with `feature_name` in the well known features will have to specify
+overrides.
+
+`cc_toolchain` is aware of the builtin / well-known features. In order to
+ensure that a user understands that this overrides the builtin opt feature (I
+originally thought that it added extra flags to opt, but you still got the
+default ones, so that can definitely happen), and to ensure that they don't
+accidentally do so, we will force them to explicitly specify that it overrides
+the builtin one. This is essentially just an acknowledgement of "I know what
+I'm doing".
+
+Warning: Specifying two features with the same name is an error, unless one
+overrides the other.
+
+```
+cc_feature(
+ name = "opt",
+ ...,
+ overrides = "@rules_cc//features/well_known:opt",
+)
+```
+
+In addition to well-known features, we could also consider in future iterations
+to also use known features for partial migrations, where you still imply a
+feature that's still defined by the legacy API:
+
+```
+# Implementation
+def cc_legacy_features(name, features):
+ for feature in features:
+ cc_known_feature(name = name + "_" + feature.name)
+ cc_legacy_features(name = name, features = FEATURES)
+
+
+# Build file
+FOO = feature(name = "foo", args=[arg_group(...)])
+FEATURES = [FOO]
+cc_legacy_features(name = "legacy_features", features = FEATURES)
+
+cc_feature(name = "bar", implies = [":legacy_features_foo"])
+
+cc_toolchain(
+ name = "toolchain",
+ legacy_features = ":legacy_features",
+ features = [":bar"],
+)
+```
+
+## Mutual exclusion
+Features can be mutually exclusive.
+
+We allow two approaches to mutual exclusion - via features or via categories.
+
+The existing toolchain uses `provides` for both of these. We rename it so that
+it makes more sense semantically.
+
+```
+cc_feature(
+ name = "incompatible_with_my_feature",
+ feature_name = "bar",
+ mutually_exclusive = [":my_feature"],
+)
+
+
+# This is an example of how we would define compilation mode.
+# Since it already exists, this wouldn't work.
+cc_mutual_exclusion_category(
+ name = "compilation_mode",
+)
+
+cc_feature(
+ name = "opt",
+ ...
+ mutually_exclusive = [":compilation_mode"],
+)
+cc_feature(
+ name = "dbg",
+ ...
+ mutually_exclusive = [":compilation_mode"],
+)
+```
+
+## Feature requirements
+Feature requirements can come in two formats.
+
+For example:
+
+* Features can require some subset of features to be enabled.
+* Arguments can require some subset of features to be enabled, but others to be
+ disabled.
+
+This is very confusing for toolchain authors, so we will simplify things with
+the use of providers:
+
+* `cc_feature` will provide `feature`, `feature_set`, and `with_feature`
+* `cc_feature_set` will provide `feature_set` and `with_feature`.
+* `cc_feature_constraint` will provide `with_features` only.
+
+We will rename all `with_features` and `requires` to `requires_any_of`, to make
+it very clear that only one of the requirements needs to be met.
+
+```
+cc_feature_set(
+ name = "my_feature_set",
+ all_of = [":my_feature"],
+)
+
+cc_feature_constraint(
+ name = "my_feature_constraint",
+ all_of = [":my_feature"],
+ none_of = [":my_other_feature"],
+)
+
+cc_args(
+ name = "foo",
+ # All of these provide with_feature.
+ requires_any_of = [":my_feature", ":my_feature_set", ":my_feature_constraint"]
+)
+
+# my_feature_constraint would be an error here.
+cc_feature(
+ name = "foo",
+ # Both of these provide feature_set.
+ requires_any_of = [":my_feature", ":my_feature_set"]
+ implies = [":my_other_feature", :my_other_feature_set"],
+)
+```
diff --git a/cc/toolchains/action_type_config.bzl b/cc/toolchains/action_type_config.bzl
new file mode 100644
index 0000000..4d5a8cd
--- /dev/null
+++ b/cc/toolchains/action_type_config.bzl
@@ -0,0 +1,137 @@
+# 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.
+"""Implementation of cc_action_type_config."""
+
+load("//cc/toolchains/impl:args_utils.bzl", "get_action_type")
+load(
+ "//cc/toolchains/impl:collect.bzl",
+ "collect_action_types",
+ "collect_args_lists",
+ "collect_features",
+ "collect_files",
+ "collect_tools",
+)
+load(
+ ":cc_toolchain_info.bzl",
+ "ActionTypeConfigInfo",
+ "ActionTypeConfigSetInfo",
+ "ActionTypeSetInfo",
+ "ArgsListInfo",
+ "FeatureSetInfo",
+)
+
+def _cc_action_type_config_impl(ctx):
+ if not ctx.attr.action_types:
+ fail("At least one action type is required for cc_action_type_config")
+ if not ctx.attr.tools:
+ fail("At least one tool is required for cc_action_type_config")
+
+ tools = tuple(collect_tools(ctx, ctx.attr.tools))
+ implies = collect_features(ctx.attr.implies)
+ args_list = collect_args_lists(ctx.attr.args, ctx.label)
+ files = collect_files(ctx.attr.data)
+
+ configs = {}
+ for action_type in collect_action_types(ctx.attr.action_types).to_list():
+ for_action = get_action_type(args_list, action_type)
+ configs[action_type] = ActionTypeConfigInfo(
+ label = ctx.label,
+ action_type = action_type,
+ tools = tools,
+ args = for_action.args,
+ implies = implies,
+ files = ctx.runfiles(
+ transitive_files = depset(transitive = [files, for_action.files]),
+ ).merge_all([tool.runfiles for tool in tools]),
+ )
+
+ return [ActionTypeConfigSetInfo(label = ctx.label, configs = configs)]
+
+cc_action_type_config = rule(
+ implementation = _cc_action_type_config_impl,
+ # @unsorted-dict-items
+ attrs = {
+ "action_types": attr.label_list(
+ providers = [ActionTypeSetInfo],
+ mandatory = True,
+ doc = """A list of action names to apply this action to.
+
+See @toolchain//actions:all for valid options.
+""",
+ ),
+ "tools": attr.label_list(
+ mandatory = True,
+ cfg = "exec",
+ allow_files = True,
+ doc = """The tool to use for the specified actions.
+
+A tool can be a `cc_tool`, or a binary.
+
+If multiple tools are specified, the first tool that has `with_features` that
+satisfy the currently enabled feature set is used.
+""",
+ ),
+ "args": attr.label_list(
+ providers = [ArgsListInfo],
+ doc = """Labels that point to `cc_arg`s / `cc_arg_list`s that are
+unconditionally bound to the specified actions.
+""",
+ ),
+ "implies": attr.label_list(
+ providers = [FeatureSetInfo],
+ doc = "Features that should be enabled when this action is used.",
+ ),
+ "data": attr.label_list(
+ allow_files = True,
+ doc = """Files required for this action type.
+
+For example, the c-compile action type might add the C standard library header
+files from the sysroot.
+""",
+ ),
+ },
+ provides = [ActionTypeConfigSetInfo],
+ doc = """Declares the configuration and selection of `cc_tool` rules.
+
+Action configs are bound to a toolchain through `action_configs`, and are the
+driving mechanism for controlling toolchain tool invocation/behavior.
+
+Action configs define three key things:
+
+* Which tools to invoke for a given type of action.
+* Tool features and compatibility.
+* `cc_args`s that are unconditionally bound to a tool invocation.
+
+Examples:
+
+ cc_action_config(
+ name = "ar",
+ action_types = ["@toolchain//actions:all_ar_actions"],
+ implies = [
+ "@toolchain//features/legacy:archiver_flags",
+ "@toolchain//features/legacy:linker_param_file",
+ ],
+ tools = [":ar_tool"],
+ )
+
+ cc_action_config(
+ name = "clang",
+ action_types = [
+ "@toolchain//actions:all_asm_actions",
+ "@toolchain//actions:all_c_compiler_actions",
+ ],
+ tools = [":clang_tool"],
+ )
+""",
+)
diff --git a/cc/toolchains/actions.bzl b/cc/toolchains/actions.bzl
new file mode 100644
index 0000000..fc91787
--- /dev/null
+++ b/cc/toolchains/actions.bzl
@@ -0,0 +1,83 @@
+# 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 to turn action types into bazel labels."""
+
+load("//cc/toolchains/impl:collect.bzl", "collect_action_types")
+load(":cc_toolchain_info.bzl", "ActionTypeInfo", "ActionTypeSetInfo")
+
+visibility("public")
+
+def _cc_action_type_impl(ctx):
+ action_type = ActionTypeInfo(label = ctx.label, name = ctx.attr.action_name)
+ return [
+ action_type,
+ ActionTypeSetInfo(
+ label = ctx.label,
+ actions = depset([action_type]),
+ ),
+ ]
+
+cc_action_type = rule(
+ implementation = _cc_action_type_impl,
+ attrs = {
+ "action_name": attr.string(
+ mandatory = True,
+ ),
+ },
+ doc = """A type of action (eg. c_compile, assemble, strip).
+
+Example:
+
+load("@rules_cc//cc:action_names.bzl", "ACTION_NAMES")
+
+cc_action_type(
+ name = "cpp_compile",
+ action_name = = ACTION_NAMES.cpp_compile,
+)
+""",
+ provides = [ActionTypeInfo, ActionTypeSetInfo],
+)
+
+def _cc_action_type_set_impl(ctx):
+ if not ctx.attr.actions and not ctx.attr.allow_empty:
+ fail("Each cc_action_type_set must contain at least one action type.")
+ return [ActionTypeSetInfo(
+ label = ctx.label,
+ actions = collect_action_types(ctx.attr.actions),
+ )]
+
+cc_action_type_set = rule(
+ doc = """Represents a set of actions.
+
+Example:
+
+cc_action_type_set(
+ name = "link_executable_actions",
+ actions = [
+ ":cpp_link_executable",
+ ":lto_index_for_executable",
+ ],
+)
+""",
+ implementation = _cc_action_type_set_impl,
+ attrs = {
+ "actions": attr.label_list(
+ providers = [ActionTypeSetInfo],
+ mandatory = True,
+ doc = "A list of cc_action_type or cc_action_type_set",
+ ),
+ "allow_empty": attr.bool(default = False),
+ },
+ provides = [ActionTypeSetInfo],
+)
diff --git a/cc/toolchains/actions/BUILD b/cc/toolchains/actions/BUILD
new file mode 100644
index 0000000..e122f5c
--- /dev/null
+++ b/cc/toolchains/actions/BUILD
@@ -0,0 +1,284 @@
+# 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.
+load("//cc:action_names.bzl", "ACTION_NAMES")
+load("//cc/toolchains:actions.bzl", "cc_action_type", "cc_action_type_set")
+
+package(default_visibility = ["//visibility:public"])
+
+# Keep in sync with //cc:action_names.bzl.
+
+cc_action_type(
+ name = "c_compile",
+ action_name = ACTION_NAMES.c_compile,
+)
+
+cc_action_type(
+ name = "cpp_compile",
+ action_name = ACTION_NAMES.cpp_compile,
+)
+
+cc_action_type(
+ name = "linkstamp_compile",
+ action_name = ACTION_NAMES.linkstamp_compile,
+)
+
+cc_action_type(
+ name = "cc_flags_make_variable",
+ action_name = ACTION_NAMES.cc_flags_make_variable,
+)
+
+cc_action_type(
+ name = "cpp_module_codegen",
+ action_name = ACTION_NAMES.cpp_module_codegen,
+)
+
+cc_action_type(
+ name = "cpp_header_analysis",
+ action_name = "c++-header-analysis",
+)
+
+cc_action_type(
+ name = "cpp_header_parsing",
+ action_name = ACTION_NAMES.cpp_header_parsing,
+)
+
+cc_action_type(
+ name = "cpp_module_compile",
+ action_name = ACTION_NAMES.cpp_module_compile,
+)
+
+cc_action_type(
+ name = "assemble",
+ action_name = ACTION_NAMES.assemble,
+)
+
+cc_action_type(
+ name = "preprocess_assemble",
+ action_name = ACTION_NAMES.preprocess_assemble,
+)
+
+cc_action_type(
+ name = "lto_indexing",
+ action_name = ACTION_NAMES.lto_indexing,
+)
+
+cc_action_type(
+ name = "lto_backend",
+ action_name = ACTION_NAMES.lto_backend,
+)
+
+cc_action_type(
+ name = "lto_index_for_executable",
+ action_name = ACTION_NAMES.lto_index_for_executable,
+)
+
+cc_action_type(
+ name = "lto_index_for_dynamic_library",
+ action_name = ACTION_NAMES.lto_index_for_dynamic_library,
+)
+
+cc_action_type(
+ name = "lto_index_for_nodeps_dynamic_library",
+ action_name = ACTION_NAMES.lto_index_for_nodeps_dynamic_library,
+)
+
+cc_action_type(
+ name = "cpp_link_executable",
+ action_name = ACTION_NAMES.cpp_link_executable,
+)
+
+cc_action_type(
+ name = "cpp_link_dynamic_library",
+ action_name = ACTION_NAMES.cpp_link_dynamic_library,
+)
+
+cc_action_type(
+ name = "cpp_link_nodeps_dynamic_library",
+ action_name = ACTION_NAMES.cpp_link_nodeps_dynamic_library,
+)
+
+cc_action_type(
+ name = "cpp_link_static_library",
+ action_name = ACTION_NAMES.cpp_link_static_library,
+)
+
+cc_action_type(
+ name = "strip",
+ action_name = ACTION_NAMES.strip,
+)
+
+cc_action_type(
+ name = "objcopy_embed_data",
+ action_name = "objcopy_embed_data",
+)
+
+# ld_embed_data is only available within google.
+cc_action_type(
+ # # copybara-comment-this-out-please
+ name = "ld_embed_data_action", # # copybara-comment-this-out-please
+ action_name = "ld_embed_data", # # copybara-comment-this-out-please
+) # # copybara-comment-this-out-please
+
+# To make things simple, both internal and external rules will refer to
+# ld_embed_data, but externally it will evaluate to the empty set.
+cc_action_type_set(
+ name = "ld_embed_data",
+ actions = [
+ ":ld_embed_data_action", # # copybara-comment-this-out-please
+ ],
+ allow_empty = True,
+ visibility = ["//cc/toolchains:__subpackages__"],
+)
+
+cc_action_type(
+ name = "objc_compile",
+ action_name = ACTION_NAMES.objc_compile,
+)
+
+cc_action_type(
+ name = "objc_executable",
+ action_name = ACTION_NAMES.objc_executable,
+)
+
+cc_action_type(
+ name = "objc_fully_link",
+ action_name = ACTION_NAMES.objc_fully_link,
+)
+
+cc_action_type(
+ name = "objcpp_compile",
+ action_name = ACTION_NAMES.objcpp_compile,
+)
+
+cc_action_type(
+ name = "objcpp_executable",
+ action_name = "objc++-executable",
+)
+
+cc_action_type(
+ name = "clif_match",
+ action_name = ACTION_NAMES.clif_match,
+)
+
+cc_action_type_set(
+ name = "ar_actions",
+ actions = [":cpp_link_static_library"],
+)
+
+cc_action_type_set(
+ name = "assembly_actions",
+ actions = [
+ ":preprocess_assemble",
+ ":assemble",
+ ],
+)
+
+cc_action_type_set(
+ name = "cpp_compile_actions",
+ actions = [
+ ":linkstamp_compile",
+ ":cpp_compile",
+ ":cpp_header_parsing",
+ ":cpp_module_compile",
+ ":cpp_module_codegen",
+ ":lto_backend",
+ ":clif_match",
+ ],
+)
+
+cc_action_type_set(
+ name = "compile_actions",
+ actions = [
+ ":cpp_compile_actions",
+ ":c_compile",
+ ":assembly_actions",
+ ],
+)
+
+cc_action_type_set(
+ name = "link_actions",
+ actions = [
+ ":link_executable_actions",
+ ":dynamic_library_link_actions",
+ ],
+)
+
+cc_action_type_set(
+ name = "link_executable_actions",
+ actions = [
+ ":cpp_link_executable",
+ ":lto_index_for_executable",
+ ],
+)
+
+cc_action_type_set(
+ name = "dynamic_library_link_actions",
+ actions = [
+ ":cpp_link_dynamic_library",
+ ":lto_index_for_dynamic_library",
+ ":nodeps_dynamic_library_link_actions",
+ ],
+)
+
+cc_action_type_set(
+ name = "nodeps_dynamic_library_link_actions",
+ actions = [
+ ":cpp_link_nodeps_dynamic_library",
+ ":lto_index_for_nodeps_dynamic_library",
+ ],
+)
+
+cc_action_type_set(
+ name = "transitive_link_actions",
+ actions = [
+ ":cpp_link_executable",
+ ":cpp_link_dynamic_library",
+ ":lto_index_for_executable",
+ ":lto_index_for_dynamic_library",
+ ],
+)
+
+cc_action_type_set(
+ name = "all_actions",
+ actions = [
+ ":c_compile",
+ ":cpp_compile",
+ ":linkstamp_compile",
+ ":cc_flags_make_variable",
+ ":cpp_module_codegen",
+ ":cpp_header_analysis",
+ ":cpp_header_parsing",
+ ":cpp_module_compile",
+ ":assemble",
+ ":preprocess_assemble",
+ ":lto_indexing",
+ ":lto_backend",
+ ":lto_index_for_executable",
+ ":lto_index_for_dynamic_library",
+ ":lto_index_for_nodeps_dynamic_library",
+ ":cpp_link_executable",
+ ":cpp_link_dynamic_library",
+ ":cpp_link_nodeps_dynamic_library",
+ ":cpp_link_static_library",
+ ":strip",
+ ":objcopy_embed_data",
+ ":ld_embed_data",
+ ":objc_compile",
+ ":objc_executable",
+ ":objc_fully_link",
+ ":objcpp_compile",
+ ":objcpp_executable",
+ ":clif_match",
+ ],
+)
diff --git a/cc/toolchains/args.bzl b/cc/toolchains/args.bzl
new file mode 100644
index 0000000..1df3333
--- /dev/null
+++ b/cc/toolchains/args.bzl
@@ -0,0 +1,120 @@
+# 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("//cc/toolchains/impl:args_utils.bzl", "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",
+ "args_wrapper_macro",
+ "nested_args_provider_from_ctx",
+)
+load(
+ ":cc_toolchain_info.bzl",
+ "ActionTypeSetInfo",
+ "ArgsInfo",
+ "ArgsListInfo",
+ "BuiltinVariablesInfo",
+ "FeatureConstraintInfo",
+)
+
+visibility("public")
+
+def _cc_args_impl(ctx):
+ actions = collect_action_types(ctx.attr.actions)
+
+ if not ctx.attr.args and not ctx.attr.nested and not ctx.attr.env:
+ fail("cc_args requires at least one of args, nested, and env")
+
+ nested = None
+ if ctx.attr.args or ctx.attr.nested:
+ nested = nested_args_provider_from_ctx(ctx)
+ 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)
+
+ requires = collect_provider(ctx.attr.requires_any_of, FeatureConstraintInfo)
+
+ args = ArgsInfo(
+ label = ctx.label,
+ actions = actions,
+ requires_any_of = tuple(requires),
+ nested = nested,
+ env = ctx.attr.env,
+ files = files,
+ )
+ 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()
+ ]),
+ ),
+ ]
+
+_cc_args = rule(
+ implementation = _cc_args_impl,
+ attrs = {
+ "actions": attr.label_list(
+ providers = [ActionTypeSetInfo],
+ mandatory = True,
+ doc = """A list of action types that this flag set applies to.
+
+See @rules_cc//cc/toolchains/actions:all for valid options.
+""",
+ ),
+ "env": attr.string_dict(
+ doc = "Environment variables to be added to the command-line.",
+ ),
+ "requires_any_of": attr.label_list(
+ providers = [FeatureConstraintInfo],
+ doc = """This will be enabled when any of the constraints are met.
+
+If omitted, this flag set will be enabled unconditionally.
+""",
+ ),
+ "_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"],
+ )
+""",
+)
+
+cc_args = lambda **kwargs: args_wrapper_macro(rule = _cc_args, **kwargs)
diff --git a/cc/toolchains/args_list.bzl b/cc/toolchains/args_list.bzl
new file mode 100644
index 0000000..fbbaad5
--- /dev/null
+++ b/cc/toolchains/args_list.bzl
@@ -0,0 +1,35 @@
+# 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(
+ "//cc/toolchains/impl:collect.bzl",
+ "collect_args_lists",
+)
+load(":cc_toolchain_info.bzl", "ArgsListInfo")
+
+def _cc_args_list_impl(ctx):
+ return [collect_args_lists(ctx.attr.args, ctx.label)]
+
+cc_args_list = rule(
+ implementation = _cc_args_list_impl,
+ doc = "A list of cc_args",
+ attrs = {
+ "args": attr.label_list(
+ providers = [ArgsListInfo],
+ doc = "The cc_args to include",
+ ),
+ },
+ provides = [ArgsListInfo],
+)
diff --git a/cc/toolchains/cc_toolchain_info.bzl b/cc/toolchains/cc_toolchain_info.bzl
new file mode 100644
index 0000000..3a499f6
--- /dev/null
+++ b/cc/toolchains/cc_toolchain_info.bzl
@@ -0,0 +1,191 @@
+# 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."""
+
+# Until the providers are stabilized, ensure that rules_cc is the only place
+# that can access the providers directly.
+# Once it's stabilized, we *may* consider opening up parts of the API, or we may
+# decide to just require users to use the public user-facing rules.
+visibility([
+ "//cc/toolchains/...",
+ "//tests/rule_based_toolchain/...",
+])
+
+# Note that throughout this file, we never use a list. This is because mutable
+# types cannot be stored in depsets. Thus, we type them as a sequence in the
+# provider, and convert them to a tuple in the constructor to ensure
+# immutability.
+
+ActionTypeInfo = provider(
+ doc = "A type of action (eg. c-compile, c++-link-executable)",
+ # @unsorted-dict-items
+ fields = {
+ "label": "(Label) The label defining this provider. Place in error messages to simplify debugging",
+ "name": "(str) The action name, as defined by action_names.bzl",
+ },
+)
+
+ActionTypeSetInfo = provider(
+ doc = "A set of types of actions",
+ # @unsorted-dict-items
+ fields = {
+ "label": "(Label) The label defining this provider. Place in error messages to simplify debugging",
+ "actions": "(depset[ActionTypeInfo]) Set of action types",
+ },
+)
+
+VariableInfo = provider(
+ """A variable defined by the toolchain""",
+ # @unsorted-dict-items
+ fields = {
+ "name": "(str) The variable name",
+ "label": "(Label) The label defining this provider. Place in error messages to simplify debugging",
+ "actions": "(Optional[depset[ActionTypeInfo]]) The actions this variable is available for",
+ "type": "A type constructed using variables.types.*",
+ },
+)
+
+BuiltinVariablesInfo = provider(
+ doc = "The builtin variables",
+ fields = {
+ "variables": "(dict[str, VariableInfo]) A mapping from variable name to variable metadata.",
+ },
+)
+
+NestedArgsInfo = provider(
+ doc = "A provider representation of Args.add/add_all/add_joined parameters",
+ # @unsorted-dict-items
+ fields = {
+ "label": "(Label) The label defining this provider. Place in error messages to simplify debugging",
+ "nested": "(Sequence[NestedArgsInfo]) The nested arg expansion. Mutually exclusive with args",
+ "iterate_over": "(Optional[str]) The variable to iterate over",
+ "files": "(depset[File]) The files required to use this variable",
+ "requires_types": "(dict[str, str]) A mapping from variables to their expected type name (not type). This means that we can require the generic type Option, rather than an Option[T]",
+ "legacy_flag_group": "(flag_group) The flag_group this corresponds to",
+ "unwrap_options": "(List[str]) A list of variables for which we should unwrap the option. For example, if a user writes `requires_not_none = \":foo\"`, then we change the type of foo from Option[str] to str",
+ },
+)
+
+ArgsInfo = provider(
+ doc = "A set of arguments to be added to the command line for specific actions",
+ # @unsorted-dict-items
+ fields = {
+ "label": "(Label) The label defining this provider. Place in error messages to simplify debugging",
+ "actions": "(depset[ActionTypeInfo]) The set of actions this is associated with",
+ "requires_any_of": "(Sequence[FeatureConstraintInfo]) This will be enabled if any of the listed predicates are met. Equivalent to with_features",
+ "nested": "(Optional[NestedArgsInfo]) The args expand. Equivalent to a flag group.",
+ "files": "(depset[File]) Files required for the args",
+ "env": "(dict[str, str]) Environment variables to apply",
+ },
+)
+ArgsListInfo = provider(
+ doc = "A ordered list of arguments",
+ # @unsorted-dict-items
+ fields = {
+ "label": "(Label) The label defining this provider. Place in error messages to simplify debugging",
+ "args": "(Sequence[ArgsInfo]) The flag sets contained within",
+ "files": "(depset[File]) The files required for all of the arguments",
+ "by_action": "(Sequence[struct(action=ActionTypeInfo, args=List[ArgsInfo], files=depset[Files])]) Relevant information about the args keyed by the action type.",
+ },
+)
+
+FeatureInfo = provider(
+ doc = "Contains all flag specifications for one feature.",
+ # @unsorted-dict-items
+ fields = {
+ "label": "(Label) The label defining this provider. Place in error messages to simplify debugging",
+ "name": "(str) The name of the feature",
+ "enabled": "(bool) Whether this feature is enabled by default",
+ "args": "(ArgsListInfo) Args enabled by this feature",
+ "implies": "(depset[FeatureInfo]) Set of features implied by this feature",
+ "requires_any_of": "(Sequence[FeatureSetInfo]) A list of feature sets, at least one of which is required to enable this feature. This is semantically equivalent to the requires attribute of rules_cc's FeatureInfo",
+ "mutually_exclusive": "(Sequence[MutuallyExclusiveCategoryInfo]) Indicates that this feature is one of several mutually exclusive alternate features.",
+ "external": "(bool) Whether a feature is defined elsewhere.",
+ "overridable": "(bool) Whether the feature is an overridable feature.",
+ "overrides": "(Optional[FeatureInfo]) The feature that this overrides. Must be a known feature",
+ },
+)
+FeatureSetInfo = provider(
+ doc = "A set of features",
+ # @unsorted-dict-items
+ fields = {
+ "label": "(Label) The label defining this provider. Place in error messages to simplify debugging",
+ "features": "(depset[FeatureInfo]) The set of features this corresponds to",
+ },
+)
+
+FeatureConstraintInfo = provider(
+ doc = "A predicate checking that certain features are enabled and others disabled.",
+ # @unsorted-dict-items
+ fields = {
+ "label": "(Label) The label defining this provider. Place in error messages to simplify debugging",
+ "all_of": "(depset[FeatureInfo]) A set of features which must be enabled",
+ "none_of": "(depset[FeatureInfo]) A set of features, none of which can be enabled",
+ },
+)
+
+MutuallyExclusiveCategoryInfo = provider(
+ doc = "Multiple features with the category will be mutally exclusive",
+ # @unsorted-dict-items
+ fields = {
+ "label": "(Label) The label defining this provider. Place in error messages to simplify debugging",
+ "name": "(str) The name of the category",
+ },
+)
+
+ToolInfo = provider(
+ doc = "A binary, with additional metadata to make it useful for action configs.",
+ # @unsorted-dict-items
+ fields = {
+ "label": "(Label) The label defining this provider. Place in error messages to simplify debugging",
+ "exe": "(File) The file corresponding to the tool",
+ "runfiles": "(depset[File]) The files required to run the tool",
+ "requires_any_of": "(Sequence[FeatureConstraintInfo]) A set of constraints, one of which is required to enable the tool. Equivalent to with_features",
+ "execution_requirements": "(Sequence[str]) A set of execution requirements of the tool",
+ },
+)
+
+ActionTypeConfigInfo = provider(
+ doc = "Configuration of a Bazel action.",
+ # @unsorted-dict-items
+ fields = {
+ "label": "(Label) The label defining this provider. Place in error messages to simplify debugging",
+ "action_type": "(ActionTypeInfo) The type of the action",
+ "tools": "(Sequence[ToolInfo]) The tool applied to the action will be the first tool in the sequence with a feature set that matches the feature configuration",
+ "args": "(Sequence[ArgsInfo]) Set of flag sets the action sets",
+ "implies": "(depset[FeatureInfo]) Set of features implied by this action config",
+ "files": "(runfiles) The files required to run these actions",
+ },
+)
+
+ActionTypeConfigSetInfo = provider(
+ doc = "A set of action configs",
+ # @unsorted-dict-items
+ fields = {
+ "label": "(Label) The label defining this provider. Place in error messages to simplify debugging",
+ "configs": "(dict[ActionTypeInfo, ActionTypeConfigInfo]) A set of action configs",
+ },
+)
+
+ToolchainConfigInfo = provider(
+ doc = "The configuration for a toolchain",
+ # @unsorted-dict-items
+ fields = {
+ "label": "(Label) The label defining this provider. Place in error messages to simplify debugging",
+ "features": "(Sequence[FeatureInfo]) The features available for this toolchain",
+ "action_type_configs": "(dict[ActionTypeInfo, ActionTypeConfigInfo]) The configuration of action configs for the toolchain.",
+ "args": "(Sequence[ArgsInfo]) A list of arguments to be unconditionally applied to the toolchain.",
+ "files": "(dict[ActionTypeInfo, depset[File]]) Files required for the toolchain, keyed by the action type.",
+ },
+)
diff --git a/cc/toolchains/feature.bzl b/cc/toolchains/feature.bzl
new file mode 100644
index 0000000..c81a756
--- /dev/null
+++ b/cc/toolchains/feature.bzl
@@ -0,0 +1,243 @@
+# 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.
+"""Implementation of the cc_feature rule."""
+
+load(
+ "//cc/toolchains/impl:collect.bzl",
+ "collect_args_lists",
+ "collect_features",
+ "collect_provider",
+)
+load(
+ ":cc_toolchain_info.bzl",
+ "ArgsListInfo",
+ "FeatureConstraintInfo",
+ "FeatureInfo",
+ "FeatureSetInfo",
+ "MutuallyExclusiveCategoryInfo",
+)
+
+def _cc_feature_impl(ctx):
+ if bool(ctx.attr.feature_name) == (ctx.attr.overrides != None):
+ fail("Exactly one of 'feature_name' and 'overrides' are required")
+
+ if ctx.attr.overrides == None:
+ overrides = None
+
+ # In the future, we may consider making feature_name optional,
+ # defaulting to ctx.label.name. However, starting that way would make it
+ # very difficult if we did want to later change that.
+ name = ctx.attr.feature_name
+ else:
+ overrides = ctx.attr.overrides[FeatureInfo]
+ if not overrides.overridable:
+ fail("Attempting to override %s, which is not overridable" % overrides.label)
+ name = overrides.name
+
+ # In the following scenario:
+ # cc_args(name = "foo", env = {"FOO": "BAR"}, args = ["--foo"])
+ # cc_action_config(name = "ac", args=[":foo"])
+
+ # We will translate this into providers roughly equivalent to the following:
+ # cc_args(name = "implied_by_ac_env", env = {"FOO": "BAR"}, args = ["--foo"])
+ # cc_feature(name = "implied_by_ac", args = [":implied_by_ac_env"])
+ # cc_action_config(
+ # name = "c_compile",
+ # implies = [":implied_by_c_compile"]
+ # )
+
+ # The reason for this is because although the legacy providers support
+ # flag_sets in action_config, they don't support env sets.
+ if name.startswith("implied_by_"):
+ fail("Feature names starting with 'implied_by' are reserved")
+
+ feature = FeatureInfo(
+ label = ctx.label,
+ name = name,
+ enabled = ctx.attr.enabled,
+ args = collect_args_lists(ctx.attr.args, ctx.label),
+ implies = collect_features(ctx.attr.implies),
+ requires_any_of = tuple(collect_provider(
+ ctx.attr.requires_any_of,
+ FeatureSetInfo,
+ )),
+ mutually_exclusive = tuple(collect_provider(
+ ctx.attr.mutually_exclusive,
+ MutuallyExclusiveCategoryInfo,
+ )),
+ external = False,
+ overridable = False,
+ overrides = overrides,
+ )
+
+ return [
+ feature,
+ FeatureSetInfo(label = ctx.label, features = depset([feature])),
+ FeatureConstraintInfo(
+ label = ctx.label,
+ all_of = depset([feature]),
+ none_of = depset([]),
+ ),
+ MutuallyExclusiveCategoryInfo(label = ctx.label, name = name),
+ ]
+
+cc_feature = rule(
+ implementation = _cc_feature_impl,
+ # @unsorted-dict-items
+ attrs = {
+ "feature_name": attr.string(
+ doc = """The name of the feature that this rule implements.
+
+The feature name is a string that will be used in the `features` attribute of
+rules to enable them (eg. `cc_binary(..., features = ["opt"])`.
+
+While two features with the same `feature_name` may not be bound to the same
+toolchain, they can happily live alongside each other in the same BUILD file.
+
+Example:
+
+ cc_feature(
+ name = "sysroot_macos",
+ feature_name = "sysroot",
+ ...
+ )
+
+ cc_feature(
+ name = "sysroot_linux",
+ feature_name = "sysroot",
+ ...
+ )
+""",
+ ),
+ "enabled": attr.bool(
+ mandatory = True,
+ doc = """Whether or not this feature is enabled by default.""",
+ ),
+ "args": attr.label_list(
+ mandatory = True,
+ doc = """Args that, when expanded, implement this feature.""",
+ providers = [ArgsListInfo],
+ ),
+ "requires_any_of": attr.label_list(
+ doc = """A list of feature sets that define toolchain compatibility.
+
+If *at least one* of the listed `cc_feature_set`s are fully satisfied (all
+features exist in the toolchain AND are currently enabled), this feature is
+deemed compatible and may be enabled.
+
+Note: Even if `cc_feature.requires_any_of` is satisfied, a feature is not
+enabled unless another mechanism (e.g. command-line flags, `cc_feature.implies`,
+`cc_feature.enabled`) signals that the feature should actually be enabled.
+""",
+ providers = [FeatureSetInfo],
+ ),
+ "implies": attr.label_list(
+ providers = [FeatureSetInfo],
+ doc = """List of features enabled along with this feature.
+
+Warning: If any of the features cannot be enabled, this feature is
+silently disabled.
+""",
+ ),
+ "mutually_exclusive": attr.label_list(
+ providers = [MutuallyExclusiveCategoryInfo],
+ doc = """A list of things that this is mutually exclusive with.
+
+It can be either:
+* A feature, in which case the two features are mutually exclusive.
+* A `cc_mutually_exclusive_category`, in which case all features that write
+ `mutually_exclusive = [":category"]` are mutually exclusive with each other.
+
+If this feature has a side-effect of implementing another feature, it can be
+useful to list that feature here to ensure they aren't enabled at the
+same time.
+""",
+ ),
+ "overrides": attr.label(
+ providers = [FeatureInfo],
+ doc = """A declaration that this feature overrides a known feature.
+
+In the example below, if you missed the "overrides" attribute, it would complain
+that the feature "opt" was defined twice.
+
+Example:
+
+ cc_feature(
+ name = "opt",
+ feature_name = "opt",
+ ...
+ overrides = "@toolchain//features/well_known:opt",
+ )
+
+""",
+ ),
+ },
+ provides = [
+ FeatureInfo,
+ FeatureSetInfo,
+ FeatureConstraintInfo,
+ MutuallyExclusiveCategoryInfo,
+ ],
+ doc = """Defines the implemented behavior of a C/C++ toolchain feature.
+
+A feature is basically a toggleable list of args. There are a variety of
+dependencies and compatibility requirements that must be satisfied for the
+listed args to be applied.
+
+A feature may be enabled or disabled through the following mechanisms:
+* Via command-line flags, or a `.bazelrc`.
+* Through inter-feature relationships (enabling one feature may implicitly
+ enable another).
+* Individual rules may elect to manually enable or disable features through the
+ builtin `features` attribute.
+
+Because of the toggleable nature of toolchain features, it's generally best to
+avoid defining features as part of your toolchain with the following exceptions:
+* You want build files to be able to configure compiler flags. For example, a
+ binary might specify `features = ["optimize_for_size"]` to create a small
+ binary instead of optimizing for performance.
+* You need to carry forward Starlark toolchain behaviors. If you're migrating a
+ complex Starlark-based toolchain definition to these rules, many of the
+ workflows and flags were likely based on features. This rule exists to support
+ those existing structures.
+
+If you want to be able to configure flags via the bazel command-line, instead
+consider making a bool_flag, and then making your `cc_args` `select` on those
+flags.
+
+For more details about how Bazel handles features, see the official Bazel
+documentation at
+https://bazel.build/docs/cc-toolchain-config-reference#features.
+
+Examples:
+
+ # A feature that can be easily toggled to optimize for size
+ cc_feature(
+ name = "optimize_for_size",
+ enabled = False,
+ feature_name = "optimize_for_size",
+ args = [":optimize_for_size_args"],
+ )
+
+ # This feature signals a capability, and doesn't have associated flags.
+ #
+ # For a list of well-known features, see:
+ # https://bazel.build/docs/cc-toolchain-config-reference#wellknown-features
+ cc_feature(
+ name = "supports_pic",
+ enabled = True,
+ overrides = "//cc/toolchains/features:supports_pic
+ )
+""",
+)
diff --git a/cc/toolchains/feature_constraint.bzl b/cc/toolchains/feature_constraint.bzl
new file mode 100644
index 0000000..c6ae44a
--- /dev/null
+++ b/cc/toolchains/feature_constraint.bzl
@@ -0,0 +1,54 @@
+# 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.
+"""Implementation of the cc_feature_constraint rule."""
+
+load(
+ "//cc/toolchains/impl:collect.bzl",
+ "collect_features",
+ "collect_provider",
+)
+load(
+ ":cc_toolchain_info.bzl",
+ "FeatureConstraintInfo",
+ "FeatureSetInfo",
+)
+
+def _cc_feature_constraint_impl(ctx):
+ if ctx.attr.features:
+ fail("Features is a reserved attribute in bazel. Use the attributes `all_of` and `none_of` for feature constraints")
+ all_of = collect_provider(ctx.attr.all_of, FeatureConstraintInfo)
+ none_of = [collect_features(ctx.attr.none_of)]
+ none_of.extend([fc.none_of for fc in all_of])
+ return [FeatureConstraintInfo(
+ label = ctx.label,
+ all_of = depset(transitive = [fc.all_of for fc in all_of]),
+ none_of = depset(transitive = none_of),
+ )]
+
+cc_feature_constraint = rule(
+ implementation = _cc_feature_constraint_impl,
+ attrs = {
+ "all_of": attr.label_list(
+ providers = [FeatureConstraintInfo],
+ ),
+ "none_of": attr.label_list(
+ providers = [FeatureSetInfo],
+ ),
+ },
+ provides = [FeatureConstraintInfo],
+ doc = """Defines a constraint on features.
+
+Can be used with require_any_of to specify that something is only enabled when
+a constraint is met.""",
+)
diff --git a/cc/toolchains/feature_set.bzl b/cc/toolchains/feature_set.bzl
new file mode 100644
index 0000000..07af6d1
--- /dev/null
+++ b/cc/toolchains/feature_set.bzl
@@ -0,0 +1,57 @@
+# 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.
+"""Implementation of the cc_feature_set rule."""
+
+load("//cc/toolchains/impl:collect.bzl", "collect_features")
+load(
+ ":cc_toolchain_info.bzl",
+ "FeatureConstraintInfo",
+ "FeatureSetInfo",
+)
+
+def _cc_feature_set_impl(ctx):
+ if ctx.attr.features:
+ fail("Features is a reserved attribute in bazel. cc_feature_set takes `all_of` instead.")
+ features = collect_features(ctx.attr.all_of)
+ return [
+ FeatureSetInfo(label = ctx.label, features = features),
+ FeatureConstraintInfo(
+ label = ctx.label,
+ all_of = features,
+ none_of = depset([]),
+ ),
+ ]
+
+cc_feature_set = rule(
+ implementation = _cc_feature_set_impl,
+ attrs = {
+ "all_of": attr.label_list(
+ providers = [FeatureSetInfo],
+ doc = "A set of features",
+ ),
+ },
+ provides = [FeatureSetInfo],
+ doc = """Defines a set of features.
+
+Example:
+
+ cc_feature_set(
+ name = "thin_lto_requirements",
+ all_of = [
+ ":thin_lto",
+ ":opt",
+ ],
+ )
+""",
+)
diff --git a/cc/toolchains/features/BUILD b/cc/toolchains/features/BUILD
new file mode 100644
index 0000000..6c6088b
--- /dev/null
+++ b/cc/toolchains/features/BUILD
@@ -0,0 +1,98 @@
+load("//cc/toolchains:feature_set.bzl", "cc_feature_set")
+load("//cc/toolchains/impl:external_feature.bzl", "cc_external_feature")
+
+package(default_visibility = ["//visibility:public"])
+
+# See https://bazel.build/docs/cc-toolchain-config-reference#wellknown-features
+
+cc_external_feature(
+ name = "opt",
+ feature_name = "opt",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "dbg",
+ feature_name = "dbg",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "fastbuild",
+ feature_name = "fastbuild",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "static_linking_mode",
+ feature_name = "static_linking_mode",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "dynamic_linking_mode",
+ feature_name = "dynamic_linking_mode",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "per_object_debug_info",
+ feature_name = "per_object_debug_info",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "supports_start_end_lib",
+ feature_name = "supports_start_end_lib",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "supports_interface_shared_libraries",
+ feature_name = "supports_interface_shared_libraries",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "supports_dynamic_linker",
+ feature_name = "supports_dynamic_linker",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "static_link_cpp_runtimes",
+ feature_name = "static_link_cpp_runtimes",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "supports_pic",
+ feature_name = "supports_pic",
+ overridable = True,
+)
+
+cc_feature_set(
+ name = "all_non_legacy_builtin_features",
+ all_of = [
+ ":opt",
+ ":dbg",
+ ":fastbuild",
+ ":static_linking_mode",
+ ":dynamic_linking_mode",
+ ":per_object_debug_info",
+ ":supports_start_end_lib",
+ ":supports_interface_shared_libraries",
+ ":supports_dynamic_linker",
+ ":static_link_cpp_runtimes",
+ ":supports_pic",
+ ],
+ visibility = ["//visibility:private"],
+)
+
+cc_feature_set(
+ name = "all_builtin_features",
+ all_of = [
+ ":all_non_legacy_builtin_features",
+ "//cc/toolchains/features/legacy:all_legacy_builtin_features",
+ ],
+)
diff --git a/cc/toolchains/features/legacy/BUILD b/cc/toolchains/features/legacy/BUILD
new file mode 100644
index 0000000..c7953a0
--- /dev/null
+++ b/cc/toolchains/features/legacy/BUILD
@@ -0,0 +1,279 @@
+load("//cc/toolchains:feature_set.bzl", "cc_feature_set")
+load("//cc/toolchains/impl:external_feature.bzl", "cc_external_feature")
+
+package(default_visibility = ["//visibility:public"])
+
+# See https://bazel.build/docs/cc-toolchain-config-reference#wellknown-features.
+
+cc_external_feature(
+ name = "legacy_compile_flags",
+ feature_name = "legacy_compile_flags",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "default_compile_flags",
+ feature_name = "default_compile_flags",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "dependency_file",
+ feature_name = "dependency_file",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "pic",
+ feature_name = "pic",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "preprocessor_defines",
+ feature_name = "preprocessor_defines",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "includes",
+ feature_name = "includes",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "include_paths",
+ feature_name = "include_paths",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "fdo_instrument",
+ feature_name = "fdo_instrument",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "fdo_optimize",
+ feature_name = "fdo_optimize",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "cs_fdo_instrument",
+ feature_name = "cs_fdo_instrument",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "cs_fdo_optimize",
+ feature_name = "cs_fdo_optimize",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "fdo_prefetch_hints",
+ feature_name = "fdo_prefetch_hints",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "autofdo",
+ feature_name = "autofdo",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "build_interface_libraries",
+ feature_name = "build_interface_libraries",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "dynamic_library_linker_tool",
+ feature_name = "dynamic_library_linker_tool",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "shared_flag",
+ feature_name = "shared_flag",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "linkstamps",
+ feature_name = "linkstamps",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "output_execpath_flags",
+ feature_name = "output_execpath_flags",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "runtime_library_search_directories",
+ feature_name = "runtime_library_search_directories",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "library_search_directories",
+ feature_name = "library_search_directories",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "archiver_flags",
+ feature_name = "archiver_flags",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "libraries_to_link",
+ feature_name = "libraries_to_link",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "force_pic_flags",
+ feature_name = "force_pic_flags",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "user_link_flags",
+ feature_name = "user_link_flags",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "legacy_link_flags",
+ feature_name = "legacy_link_flags",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "static_libgcc",
+ feature_name = "static_libgcc",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "fission_support",
+ feature_name = "fission_support",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "strip_debug_symbols",
+ feature_name = "strip_debug_symbols",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "coverage",
+ feature_name = "coverage",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "llvm_coverage_map_format",
+ feature_name = "llvm_coverage_map_format",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "gcc_coverage_map_format",
+ feature_name = "gcc_coverage_map_format",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "fully_static_link",
+ feature_name = "fully_static_link",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "user_compile_flags",
+ feature_name = "user_compile_flags",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "sysroot",
+ feature_name = "sysroot",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "unfiltered_compile_flags",
+ feature_name = "unfiltered_compile_flags",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "linker_param_file",
+ feature_name = "linker_param_file",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "compiler_input_flags",
+ feature_name = "compiler_input_flags",
+ overridable = True,
+)
+
+cc_external_feature(
+ name = "compiler_output_flags",
+ feature_name = "compiler_output_flags",
+ overridable = True,
+)
+
+cc_feature_set(
+ name = "all_legacy_builtin_features",
+ all_of = [
+ ":legacy_compile_flags",
+ ":default_compile_flags",
+ ":dependency_file",
+ ":pic",
+ ":preprocessor_defines",
+ ":includes",
+ ":include_paths",
+ ":fdo_instrument",
+ ":fdo_optimize",
+ ":cs_fdo_instrument",
+ ":cs_fdo_optimize",
+ ":fdo_prefetch_hints",
+ ":autofdo",
+ ":build_interface_libraries",
+ ":dynamic_library_linker_tool",
+ ":shared_flag",
+ ":linkstamps",
+ ":output_execpath_flags",
+ ":runtime_library_search_directories",
+ ":library_search_directories",
+ ":archiver_flags",
+ ":libraries_to_link",
+ ":force_pic_flags",
+ ":user_link_flags",
+ ":legacy_link_flags",
+ ":static_libgcc",
+ ":fission_support",
+ ":strip_debug_symbols",
+ ":coverage",
+ ":llvm_coverage_map_format",
+ ":gcc_coverage_map_format",
+ ":fully_static_link",
+ ":user_compile_flags",
+ ":sysroot",
+ ":unfiltered_compile_flags",
+ ":linker_param_file",
+ ":compiler_input_flags",
+ ":compiler_output_flags",
+ ],
+ visibility = ["//cc/toolchains/features:__pkg__"],
+)
diff --git a/cc/toolchains/format.bzl b/cc/toolchains/format.bzl
new file mode 100644
index 0000000..bdbb0c8
--- /dev/null
+++ b/cc/toolchains/format.bzl
@@ -0,0 +1,26 @@
+# 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.
+"""Functions to format arguments for the cc toolchain"""
+
+def format_arg(format, value = None):
+ """Generate metadata to format a variable with a given value.
+
+ Args:
+ format: (str) The format string
+ value: (Optional[Label]) The variable to format. Any is used because it can
+ be any representation of a variable.
+ Returns:
+ A struct corresponding to the formatted variable.
+ """
+ return struct(format_type = "format_arg", format = format, value = value)
diff --git a/cc/toolchains/impl/BUILD b/cc/toolchains/impl/BUILD
new file mode 100644
index 0000000..8484e1c
--- /dev/null
+++ b/cc/toolchains/impl/BUILD
@@ -0,0 +1,6 @@
+# This directory contains implementations of starlark functions that contain
+# complex logic. The objective is to keep the rules themselves as simple as
+# possible, so that we can perform very thorough testing on the implementation.
+
+# I wanted to call it private / internal, but then buildifier complains about
+# referencing it from the tests directory.
diff --git a/cc/toolchains/impl/args_utils.bzl b/cc/toolchains/impl/args_utils.bzl
new file mode 100644
index 0000000..55b4841
--- /dev/null
+++ b/cc/toolchains/impl/args_utils.bzl
@@ -0,0 +1,121 @@
+# 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.
+"""Helper functions for working with args."""
+
+load(":variables.bzl", "get_type")
+
+visibility([
+ "//cc/toolchains",
+ "//tests/rule_based_toolchain/...",
+])
+
+def get_action_type(args_list, action_type):
+ """Returns the corresponding entry in ArgsListInfo.by_action.
+
+ Args:
+ args_list: (ArgsListInfo) The args list to look through
+ action_type: (ActionTypeInfo) The action type to look up.
+ Returns:
+ The information corresponding to this action type.
+
+ """
+ for args in args_list.by_action:
+ if args.action == action_type:
+ return args
+
+ return struct(action = action_type, args = tuple(), files = depset([]))
+
+def validate_nested_args(*, nested_args, variables, actions, label, fail = fail):
+ """Validates the typing for an nested_args invocation.
+
+ Args:
+ nested_args: (NestedArgsInfo) The nested_args to validate
+ variables: (Dict[str, VariableInfo]) A mapping from variable name to
+ the metadata (variable type and valid actions).
+ actions: (List[ActionTypeInfo]) The actions we require these variables
+ to be valid for.
+ label: (Label) The label of the rule we're currently validating.
+ Used for error messages.
+ fail: The fail function. Use for testing only.
+ """
+ stack = [(nested_args, {})]
+
+ for _ in range(9999999):
+ if not stack:
+ break
+ nested_args, overrides = stack.pop()
+ if nested_args.iterate_over != None or nested_args.unwrap_options:
+ # Make sure we don't keep using the same object.
+ overrides = dict(**overrides)
+
+ if nested_args.iterate_over != None:
+ type = get_type(
+ name = nested_args.iterate_over,
+ variables = variables,
+ overrides = overrides,
+ actions = actions,
+ args_label = label,
+ nested_label = nested_args.label,
+ fail = fail,
+ )
+ if type["name"] == "list":
+ # Rewrite the type of the thing we iterate over from a List[T]
+ # to a T.
+ overrides[nested_args.iterate_over] = type["elements"]
+ elif type["name"] == "option" and type["elements"]["name"] == "list":
+ # Rewrite Option[List[T]] to T.
+ overrides[nested_args.iterate_over] = type["elements"]["elements"]
+ else:
+ fail("Attempting to iterate over %s, but it was not a list - it was a %s" % (nested_args.iterate_over, type["repr"]))
+
+ # 1) Validate variables marked with after_option_unwrap = False.
+ # 2) Unwrap Option[T] to T as required.
+ # 3) Validate variables marked with after_option_unwrap = True.
+ for after_option_unwrap in [False, True]:
+ for var_name, requirements in nested_args.requires_types.items():
+ for requirement in requirements:
+ if requirement.after_option_unwrap == after_option_unwrap:
+ type = get_type(
+ name = var_name,
+ variables = variables,
+ overrides = overrides,
+ actions = actions,
+ args_label = label,
+ nested_label = nested_args.label,
+ fail = fail,
+ )
+ if type["name"] not in requirement.valid_types:
+ fail("{msg}, but {var_name} has type {type}".format(
+ var_name = var_name,
+ msg = requirement.msg,
+ type = type["repr"],
+ ))
+
+ # Only unwrap the options after the first iteration of this loop.
+ if not after_option_unwrap:
+ for var in nested_args.unwrap_options:
+ type = get_type(
+ name = var,
+ variables = variables,
+ overrides = overrides,
+ actions = actions,
+ args_label = label,
+ nested_label = nested_args.label,
+ fail = fail,
+ )
+ if type["name"] == "option":
+ overrides[var] = type["elements"]
+
+ for child in nested_args.nested:
+ stack.append((child, overrides))
diff --git a/cc/toolchains/impl/collect.bzl b/cc/toolchains/impl/collect.bzl
new file mode 100644
index 0000000..3fab3e6
--- /dev/null
+++ b/cc/toolchains/impl/collect.bzl
@@ -0,0 +1,173 @@
+# 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.
+"""Helper functions to allow us to collect data from attr.label_list."""
+
+load(
+ "//cc/toolchains:cc_toolchain_info.bzl",
+ "ActionTypeConfigSetInfo",
+ "ActionTypeSetInfo",
+ "ArgsListInfo",
+ "FeatureSetInfo",
+ "ToolInfo",
+)
+
+visibility([
+ "//cc/toolchains/...",
+ "//tests/rule_based_toolchain/...",
+])
+
+def collect_provider(targets, provider):
+ """Collects providers from a label list.
+
+ Args:
+ targets: (List[Target]) An attribute from attr.label_list
+ provider: (provider) The provider to look up
+ Returns:
+ A list of the providers
+ """
+ return [target[provider] for target in targets]
+
+def collect_defaultinfo(targets):
+ """Collects DefaultInfo from a label list.
+
+ Args:
+ targets: (List[Target]) An attribute from attr.label_list
+ Returns:
+ A list of the associated defaultinfo
+ """
+ return collect_provider(targets, DefaultInfo)
+
+def _make_collector(provider, field):
+ def collector(targets, direct = [], transitive = []):
+ # Avoid mutating what was passed in.
+ transitive = transitive[:]
+ for value in collect_provider(targets, provider):
+ transitive.append(getattr(value, field))
+ return depset(direct = direct, transitive = transitive)
+
+ return collector
+
+collect_action_types = _make_collector(ActionTypeSetInfo, "actions")
+collect_features = _make_collector(FeatureSetInfo, "features")
+collect_files = _make_collector(DefaultInfo, "files")
+
+def collect_data(ctx, targets):
+ """Collects from a 'data' attribute.
+
+ This is distinguished from collect_files by the fact that data attributes
+ attributes include runfiles.
+
+ Args:
+ ctx: (Context) The ctx for the current rule
+ targets: (List[Target]) A list of files or executables
+
+ Returns:
+ A depset containing all files for each of the targets, and all runfiles
+ required to run them.
+ """
+ return ctx.runfiles(transitive_files = collect_files(targets)).merge_all([
+ info.default_runfiles
+ for info in collect_defaultinfo(targets)
+ if info.default_runfiles != None
+ ])
+
+def collect_tools(ctx, targets, fail = fail):
+ """Collects tools from a label_list.
+
+ Each entry in the label list may either be a cc_tool or a binary.
+
+ Args:
+ ctx: (Context) The ctx for the current rule
+ targets: (List[Target]) A list of targets. Each of these targets may be
+ either a cc_tool or an executable.
+ fail: (function) The fail function. Should only be used in tests.
+
+ Returns:
+ A List[ToolInfo], with regular executables creating custom tool info.
+ """
+ tools = []
+ for target in targets:
+ info = target[DefaultInfo]
+ if ToolInfo in target:
+ tools.append(target[ToolInfo])
+ elif info.files_to_run != None and info.files_to_run.executable != None:
+ tools.append(ToolInfo(
+ label = target.label,
+ exe = info.files_to_run.executable,
+ runfiles = collect_data(ctx, [target]),
+ requires_any_of = tuple(),
+ execution_requirements = tuple(),
+ ))
+ else:
+ fail("Expected %s to be a cc_tool or a binary rule" % target.label)
+
+ return tools
+
+def collect_args_lists(targets, label):
+ """Collects a label_list of ArgsListInfo into a single ArgsListInfo
+
+ Args:
+ targets: (List[Target]) A label_list of targets providing ArgsListInfo
+ label: The label to attach to the resulting ArgsListInfo
+ Returns:
+ An ArgsListInfo that is the result of joining all of the ArgsListInfos
+ together.
+ """
+ args = []
+ by_action = {}
+ transitive_files = []
+ for target in targets:
+ args_list = target[ArgsListInfo]
+ args.extend(args_list.args)
+ transitive_files.extend([args_info.files for args_info in args_list.args])
+ for value in args_list.by_action:
+ out = by_action.setdefault(
+ value.action,
+ struct(args = [], transitive_files = [], action = value.action),
+ )
+ out.args.extend(value.args)
+ out.transitive_files.append(value.files)
+
+ return ArgsListInfo(
+ label = label,
+ args = tuple(args),
+ files = depset(transitive = transitive_files),
+ by_action = tuple([
+ struct(
+ action = k,
+ args = tuple(v.args),
+ files = depset(transitive = v.transitive_files),
+ )
+ for k, v in by_action.items()
+ ]),
+ )
+
+def collect_action_type_config_sets(targets, label, fail = fail):
+ """Collects several `cc_action_type_config` labels together.
+
+ Args:
+ targets: (List[Target]) A list of targets providing ActionTypeConfigSetInfo
+ label: The label to apply to the resulting config.
+ fail: (function) The fail function. Should only be used in tests.
+ Returns:
+ A combined ActionTypeConfigSetInfo representing a variety of action
+ types.
+ """
+ configs = {}
+ for atcs in collect_provider(targets, ActionTypeConfigSetInfo):
+ for action_type, config in atcs.configs.items():
+ if action_type in configs:
+ fail("The action type %s is configured by both %s and %s. Each action type may only be configured once." % (action_type.label, config.label, configs[action_type].label))
+ configs[action_type] = config
+ return ActionTypeConfigSetInfo(label = label, configs = configs)
diff --git a/cc/toolchains/impl/external_feature.bzl b/cc/toolchains/impl/external_feature.bzl
new file mode 100644
index 0000000..0853b32
--- /dev/null
+++ b/cc/toolchains/impl/external_feature.bzl
@@ -0,0 +1,72 @@
+# 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.
+"""Implementation of the cc_external_feature rule."""
+
+load(
+ "//cc/toolchains:cc_toolchain_info.bzl",
+ "ArgsListInfo",
+ "FeatureConstraintInfo",
+ "FeatureInfo",
+ "FeatureSetInfo",
+)
+
+visibility([
+ "//cc/toolchains/...",
+ "//tests/rule_based_toolchain/...",
+])
+
+def _cc_external_feature_impl(ctx):
+ feature = FeatureInfo(
+ label = ctx.label,
+ name = ctx.attr.feature_name,
+ enabled = False,
+ args = ArgsListInfo(
+ label = ctx.label,
+ args = (),
+ files = depset([]),
+ by_action = (),
+ ),
+ implies = depset([]),
+ requires_any_of = (),
+ mutually_exclusive = (),
+ external = True,
+ overridable = ctx.attr.overridable,
+ overrides = None,
+ )
+ providers = [
+ feature,
+ FeatureSetInfo(label = ctx.label, features = depset([feature])),
+ FeatureConstraintInfo(
+ label = ctx.label,
+ all_of = depset([feature]),
+ none_of = depset([]),
+ ),
+ ]
+ return providers
+
+cc_external_feature = rule(
+ implementation = _cc_external_feature_impl,
+ attrs = {
+ "feature_name": attr.string(
+ mandatory = True,
+ doc = "The name of the feature",
+ ),
+ "overridable": attr.bool(
+ doc = "Whether the feature can be overridden",
+ mandatory = True,
+ ),
+ },
+ provides = [FeatureInfo, FeatureSetInfo, FeatureConstraintInfo],
+ doc = "A declaration that a feature with this name is defined elsewhere.",
+)
diff --git a/cc/toolchains/impl/legacy_converter.bzl b/cc/toolchains/impl/legacy_converter.bzl
new file mode 100644
index 0000000..9f9d2a9
--- /dev/null
+++ b/cc/toolchains/impl/legacy_converter.bzl
@@ -0,0 +1,190 @@
+# 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.
+"""Conversion helper functions to legacy cc_toolchain_config_info."""
+
+load(
+ "//cc:cc_toolchain_config_lib.bzl",
+ legacy_action_config = "action_config",
+ legacy_env_entry = "env_entry",
+ legacy_env_set = "env_set",
+ legacy_feature = "feature",
+ legacy_feature_set = "feature_set",
+ legacy_flag_set = "flag_set",
+ legacy_tool = "tool",
+ legacy_with_feature_set = "with_feature_set",
+)
+load(
+ "//cc/toolchains:cc_toolchain_info.bzl",
+ "ArgsListInfo",
+ "FeatureInfo",
+)
+
+visibility([
+ "//cc/toolchains/...",
+ "//tests/rule_based_toolchain/...",
+])
+
+# Note that throughout this file, we sort anything for which the order is
+# nondeterministic (eg. depset's .to_list(), dictionary iteration).
+# This allows our tests to call equals() on the output,
+# and *may* provide better caching properties.
+
+def _convert_actions(actions):
+ return sorted([action.name for action in actions.to_list()])
+
+def convert_feature_constraint(constraint):
+ return legacy_with_feature_set(
+ features = sorted([ft.name for ft in constraint.all_of.to_list()]),
+ not_features = sorted([ft.name for ft in constraint.none_of.to_list()]),
+ )
+
+def convert_args(args):
+ """Converts an ArgsInfo to flag_sets and env_sets.
+
+ Args:
+ args: (ArgsInfo) The args to convert
+ Returns:
+ struct(flag_sets = List[flag_set], env_sets = List[env_sets])
+ """
+ actions = _convert_actions(args.actions)
+ with_features = [
+ convert_feature_constraint(fc)
+ for fc in args.requires_any_of
+ ]
+
+ flag_sets = []
+ if args.nested != None:
+ flag_sets.append(legacy_flag_set(
+ actions = actions,
+ with_features = with_features,
+ flag_groups = [args.nested.legacy_flag_group],
+ ))
+
+ env_sets = []
+ if args.env:
+ env_sets.append(legacy_env_set(
+ actions = actions,
+ with_features = with_features,
+ env_entries = [
+ legacy_env_entry(
+ key = key,
+ value = value,
+ )
+ for key, value in args.env.items()
+ ],
+ ))
+ return struct(
+ flag_sets = flag_sets,
+ env_sets = env_sets,
+ )
+
+def _convert_args_sequence(args_sequence):
+ flag_sets = []
+ env_sets = []
+ for args in args_sequence:
+ legacy_args = convert_args(args)
+ flag_sets.extend(legacy_args.flag_sets)
+ env_sets.extend(legacy_args.env_sets)
+
+ return struct(flag_sets = flag_sets, env_sets = env_sets)
+
+def convert_feature(feature):
+ if feature.external:
+ return None
+
+ args = _convert_args_sequence(feature.args.args)
+
+ return legacy_feature(
+ name = feature.name,
+ enabled = feature.enabled,
+ flag_sets = args.flag_sets,
+ env_sets = args.env_sets,
+ implies = sorted([ft.name for ft in feature.implies.to_list()]),
+ requires = [
+ legacy_feature_set(sorted([
+ feature.name
+ for feature in requirement.features.to_list()
+ ]))
+ for requirement in feature.requires_any_of
+ ],
+ provides = [
+ mutex.name
+ for mutex in feature.mutually_exclusive
+ ],
+ )
+
+def convert_tool(tool):
+ return legacy_tool(
+ tool = tool.exe,
+ execution_requirements = list(tool.execution_requirements),
+ with_features = [
+ convert_feature_constraint(fc)
+ for fc in tool.requires_any_of
+ ],
+ )
+
+def _convert_action_type_config(atc):
+ implies = sorted([ft.name for ft in atc.implies.to_list()])
+ if atc.args:
+ implies.append("implied_by_%s" % atc.action_type.name)
+
+ return legacy_action_config(
+ action_name = atc.action_type.name,
+ enabled = True,
+ tools = [convert_tool(tool) for tool in atc.tools],
+ implies = implies,
+ )
+
+def convert_toolchain(toolchain):
+ """Converts a rule-based toolchain into the legacy providers.
+
+ Args:
+ toolchain: CcToolchainConfigInfo: The toolchain config to convert.
+ Returns:
+ A struct containing parameters suitable to pass to
+ cc_common.create_cc_toolchain_config_info.
+ """
+ features = [convert_feature(feature) for feature in toolchain.features]
+ features.append(convert_feature(FeatureInfo(
+ # We reserve names starting with implied_by. This ensures we don't
+ # conflict with the name of a feature the user creates.
+ name = "implied_by_always_enabled",
+ enabled = True,
+ args = ArgsListInfo(args = toolchain.args),
+ implies = depset([]),
+ requires_any_of = [],
+ mutually_exclusive = [],
+ external = False,
+ )))
+ action_configs = []
+ for atc in toolchain.action_type_configs.values():
+ # Action configs don't take in an env like they do a flag set.
+ # In order to support them, we create a feature with the env that the action
+ # config will enable, and imply it in the action config.
+ if atc.args:
+ features.append(convert_feature(FeatureInfo(
+ name = "implied_by_%s" % atc.action_type.name,
+ enabled = False,
+ args = ArgsListInfo(args = atc.args),
+ implies = depset([]),
+ requires_any_of = [],
+ mutually_exclusive = [],
+ external = False,
+ )))
+ action_configs.append(_convert_action_type_config(atc))
+
+ return struct(
+ features = sorted([ft for ft in features if ft != None], key = lambda ft: ft.name),
+ action_configs = sorted(action_configs, key = lambda ac: ac.action_name),
+ )
diff --git a/cc/toolchains/impl/nested_args.bzl b/cc/toolchains/impl/nested_args.bzl
new file mode 100644
index 0000000..ed83cf1
--- /dev/null
+++ b/cc/toolchains/impl/nested_args.bzl
@@ -0,0 +1,387 @@
+# 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.
+"""Helper functions for working with args."""
+
+load("@bazel_skylib//lib:structs.bzl", "structs")
+load("//cc:cc_toolchain_config_lib.bzl", "flag_group", "variable_with_value")
+load("//cc/toolchains:cc_toolchain_info.bzl", "NestedArgsInfo", "VariableInfo")
+load(":collect.bzl", "collect_files", "collect_provider")
+
+visibility([
+ "//cc/toolchains",
+ "//tests/rule_based_toolchain/...",
+])
+
+REQUIRES_MUTUALLY_EXCLUSIVE_ERR = "requires_none, requires_not_none, requires_true, requires_false, and requires_equal are mutually exclusive"
+REQUIRES_NOT_NONE_ERR = "requires_not_none only works on options"
+REQUIRES_NONE_ERR = "requires_none only works on options"
+REQUIRES_TRUE_ERR = "requires_true only works on bools"
+REQUIRES_FALSE_ERR = "requires_false only works on bools"
+REQUIRES_EQUAL_ERR = "requires_equal only works on strings"
+REQUIRES_EQUAL_VALUE_ERR = "When requires_equal is provided, you must also provide requires_equal_value to specify what it should be equal to"
+FORMAT_ARGS_ERR = "format_args can only format strings, files, or directories"
+
+_NOT_ESCAPED_FMT = "%% should always either of the form %%s, or escaped with %%%%. Instead, got %r"
+
+_EXAMPLE = """
+
+cc_args(
+ ...,
+ args = [format_arg("--foo=%s", "//cc/toolchains/variables:foo")]
+)
+
+or
+
+cc_args(
+ ...,
+ # If foo_list contains ["a", "b"], then this expands to ["--foo", "+a", "--foo", "+b"].
+ args = ["--foo", format_arg("+%s")],
+ iterate_over = "//toolchains/variables:foo_list",
+"""
+
+# @unsorted-dict-items.
+NESTED_ARGS_ATTRS = {
+ "args": attr.string_list(
+ doc = """json-encoded arguments to be added to the command-line.
+
+Usage:
+cc_args(
+ ...,
+ args = ["--foo", format_arg("%s", "//cc/toolchains/variables:foo")]
+)
+
+This is equivalent to flag_group(flags = ["--foo", "%{foo}"])
+
+Mutually exclusive with nested.
+""",
+ ),
+ "nested": attr.label_list(
+ providers = [NestedArgsInfo],
+ doc = """nested_args that should be added on the command-line.
+
+Mutually exclusive with args.""",
+ ),
+ "data": attr.label_list(
+ allow_files = True,
+ doc = """Files required to add this argument to the command-line.
+
+For example, a flag that sets the header directory might add the headers in that
+directory as additional files.
+""",
+ ),
+ "variables": attr.label_list(
+ providers = [VariableInfo],
+ doc = "Variables to be used in substitutions",
+ ),
+ "iterate_over": attr.label(providers = [VariableInfo], doc = "Replacement for flag_group.iterate_over"),
+ "requires_not_none": attr.label(providers = [VariableInfo], doc = "Replacement for flag_group.expand_if_available"),
+ "requires_none": attr.label(providers = [VariableInfo], doc = "Replacement for flag_group.expand_if_not_available"),
+ "requires_true": attr.label(providers = [VariableInfo], doc = "Replacement for flag_group.expand_if_true"),
+ "requires_false": attr.label(providers = [VariableInfo], doc = "Replacement for flag_group.expand_if_false"),
+ "requires_equal": attr.label(providers = [VariableInfo], doc = "Replacement for flag_group.expand_if_equal"),
+ "requires_equal_value": attr.string(),
+}
+
+def args_wrapper_macro(*, name, rule, args = [], **kwargs):
+ """Invokes a rule by converting args to attributes.
+
+ Args:
+ name: (str) The name of the target.
+ rule: (rule) The rule to invoke. Either cc_args or cc_nested_args.
+ args: (List[str|Formatted]) A list of either strings, or function calls
+ from format.bzl. For example:
+ ["--foo", format_arg("--sysroot=%s", "//cc/toolchains/variables:sysroot")]
+ **kwargs: kwargs to pass through into the rule invocation.
+ """
+ out_args = []
+ vars = []
+ if type(args) != "list":
+ fail("Args must be a list in %s" % native.package_relative_label(name))
+ for arg in args:
+ if type(arg) == "string":
+ out_args.append(raw_string(arg))
+ elif getattr(arg, "format_type") == "format_arg":
+ arg = structs.to_dict(arg)
+ if arg["value"] == None:
+ out_args.append(arg)
+ else:
+ var = arg.pop("value")
+
+ # Swap the variable from a label to an index. This allows us to
+ # actually get the providers in a rule.
+ out_args.append(struct(value = len(vars), **arg))
+ vars.append(var)
+ else:
+ fail("Invalid type of args in %s. Expected either a string or format_args(format_string, variable_label), got value %r" % (native.package_relative_label(name), arg))
+
+ rule(
+ name = name,
+ args = [json.encode(arg) for arg in out_args],
+ variables = vars,
+ **kwargs
+ )
+
+def _var(target):
+ if target == None:
+ return None
+ return target[VariableInfo].name
+
+# TODO: Consider replacing this with a subrule in the future. However, maybe not
+# for a long time, since it'll break compatibility with all bazel versions < 7.
+def nested_args_provider_from_ctx(ctx):
+ """Gets the nested args provider from a rule that has NESTED_ARGS_ATTRS.
+
+ Args:
+ ctx: The rule context
+ Returns:
+ NestedArgsInfo
+ """
+ variables = collect_provider(ctx.attr.variables, VariableInfo)
+ args = []
+ for arg in ctx.attr.args:
+ arg = json.decode(arg)
+ if "value" in arg:
+ if arg["value"] != None:
+ arg["value"] = variables[arg["value"]]
+ args.append(struct(**arg))
+
+ return nested_args_provider(
+ label = ctx.label,
+ args = args,
+ nested = collect_provider(ctx.attr.nested, NestedArgsInfo),
+ files = collect_files(ctx.attr.data),
+ iterate_over = _var(ctx.attr.iterate_over),
+ requires_not_none = _var(ctx.attr.requires_not_none),
+ requires_none = _var(ctx.attr.requires_none),
+ requires_true = _var(ctx.attr.requires_true),
+ requires_false = _var(ctx.attr.requires_false),
+ requires_equal = _var(ctx.attr.requires_equal),
+ requires_equal_value = ctx.attr.requires_equal_value,
+ )
+
+def raw_string(s):
+ """Constructs metadata for creating a raw string.
+
+ Args:
+ s: (str) The string to input.
+ Returns:
+ Metadata suitable for format_variable.
+ """
+ return struct(format_type = "raw", format = s)
+
+def format_string_indexes(s, fail = fail):
+ """Gets the index of a '%s' in a string.
+
+ Args:
+ s: (str) The string
+ fail: The fail function. Used for tests
+
+ Returns:
+ List[int] The indexes of the '%s' in the string
+ """
+ indexes = []
+ escaped = False
+ for i in range(len(s)):
+ if not escaped and s[i] == "%":
+ escaped = True
+ elif escaped:
+ if s[i] == "{":
+ fail('Using the old mechanism for variables, %%{variable}, but we instead use format_arg("--foo=%%s", "//cc/toolchains/variables:<variable>"). Got %r' % s)
+ elif s[i] == "s":
+ indexes.append(i - 1)
+ elif s[i] != "%":
+ fail(_NOT_ESCAPED_FMT % s)
+ escaped = False
+ if escaped:
+ return fail(_NOT_ESCAPED_FMT % s)
+ return indexes
+
+def format_variable(arg, iterate_over, fail = fail):
+ """Lists all of the variables referenced by an argument.
+
+ Eg: referenced_variables([
+ format_arg("--foo", None),
+ format_arg("--bar=%s", ":bar")
+ ]) => ["--foo", "--bar=%{bar}"]
+
+ Args:
+ arg: [Formatted] The command-line arguments, as created by the format_arg function.
+ iterate_over: (Optional[str]) The name of the variable we're iterating over.
+ fail: The fail function. Used for tests
+
+ Returns:
+ A string defined to be compatible with flag groups.
+ """
+ indexes = format_string_indexes(arg.format, fail = fail)
+ if arg.format_type == "raw":
+ if indexes:
+ return fail("Can't use %s with a raw string. Either escape it with %%s or use format_arg, like the following examples:" + _EXAMPLE)
+ return arg.format
+ else:
+ if len(indexes) == 0:
+ return fail('format_arg requires a "%%s" in the format string, but got %r' % arg.format)
+ elif len(indexes) > 1:
+ return fail("Only one %%s can be used in a format string, but got %r" % arg.format)
+
+ if arg.value == None:
+ if iterate_over == None:
+ return fail("format_arg requires either a variable to format, or iterate_over must be provided. For example:" + _EXAMPLE)
+ var = iterate_over
+ else:
+ var = arg.value.name
+
+ index = indexes[0]
+ return arg.format[:index] + "%{" + var + "}" + arg.format[index + 2:]
+
+def nested_args_provider(
+ *,
+ label,
+ args = [],
+ nested = [],
+ files = depset([]),
+ iterate_over = None,
+ requires_not_none = None,
+ requires_none = None,
+ requires_true = None,
+ requires_false = None,
+ requires_equal = None,
+ requires_equal_value = "",
+ fail = fail):
+ """Creates a validated NestedArgsInfo.
+
+ Does not validate types, as you can't know the type of a variable until
+ you have a cc_args wrapping it, because the outer layers can change that
+ type using iterate_over.
+
+ Args:
+ label: (Label) The context we are currently evaluating in. Used for
+ error messages.
+ args: (List[str]) The command-line arguments to add.
+ nested: (List[NestedArgsInfo]) command-line arguments to expand.
+ files: (depset[File]) Files required for this set of command-line args.
+ iterate_over: (Optional[str]) Variable to iterate over
+ requires_not_none: (Optional[str]) If provided, this NestedArgsInfo will
+ be ignored if the variable is None
+ requires_none: (Optional[str]) If provided, this NestedArgsInfo will
+ be ignored if the variable is not None
+ requires_true: (Optional[str]) If provided, this NestedArgsInfo will
+ be ignored if the variable is false
+ requires_false: (Optional[str]) If provided, this NestedArgsInfo will
+ be ignored if the variable is true
+ requires_equal: (Optional[str]) If provided, this NestedArgsInfo will
+ be ignored if the variable is not equal to requires_equal_value.
+ requires_equal_value: (str) The value to compare the requires_equal
+ variable with
+ fail: A fail function. Use only for testing.
+ Returns:
+ NestedArgsInfo
+ """
+ if bool(args) == bool(nested):
+ fail("Exactly one of args and nested must be provided")
+
+ transitive_files = [ea.files for ea in nested]
+ transitive_files.append(files)
+
+ has_value = [attr for attr in [
+ requires_not_none,
+ requires_none,
+ requires_true,
+ requires_false,
+ requires_equal,
+ ] if attr != None]
+
+ # We may want to reconsider this down the line, but it's easier to open up
+ # an API than to lock down an API.
+ if len(has_value) > 1:
+ fail(REQUIRES_MUTUALLY_EXCLUSIVE_ERR)
+
+ kwargs = {}
+ requires_types = {}
+ if nested:
+ kwargs["flag_groups"] = [ea.legacy_flag_group for ea in nested]
+
+ unwrap_options = []
+
+ if iterate_over:
+ kwargs["iterate_over"] = iterate_over
+
+ if requires_not_none:
+ kwargs["expand_if_available"] = requires_not_none
+ requires_types.setdefault(requires_not_none, []).append(struct(
+ msg = REQUIRES_NOT_NONE_ERR,
+ valid_types = ["option"],
+ after_option_unwrap = False,
+ ))
+ unwrap_options.append(requires_not_none)
+ elif requires_none:
+ kwargs["expand_if_not_available"] = requires_none
+ requires_types.setdefault(requires_none, []).append(struct(
+ msg = REQUIRES_NONE_ERR,
+ valid_types = ["option"],
+ after_option_unwrap = False,
+ ))
+ elif requires_true:
+ kwargs["expand_if_true"] = requires_true
+ requires_types.setdefault(requires_true, []).append(struct(
+ msg = REQUIRES_TRUE_ERR,
+ valid_types = ["bool"],
+ after_option_unwrap = True,
+ ))
+ unwrap_options.append(requires_true)
+ elif requires_false:
+ kwargs["expand_if_false"] = requires_false
+ requires_types.setdefault(requires_false, []).append(struct(
+ msg = REQUIRES_FALSE_ERR,
+ valid_types = ["bool"],
+ after_option_unwrap = True,
+ ))
+ unwrap_options.append(requires_false)
+ elif requires_equal:
+ if not requires_equal_value:
+ fail(REQUIRES_EQUAL_VALUE_ERR)
+ kwargs["expand_if_equal"] = variable_with_value(
+ name = requires_equal,
+ value = requires_equal_value,
+ )
+ unwrap_options.append(requires_equal)
+ requires_types.setdefault(requires_equal, []).append(struct(
+ msg = REQUIRES_EQUAL_ERR,
+ valid_types = ["string"],
+ after_option_unwrap = True,
+ ))
+
+ for arg in args:
+ if arg.format_type != "raw":
+ var_name = arg.value.name if arg.value != None else iterate_over
+ requires_types.setdefault(var_name, []).append(struct(
+ msg = FORMAT_ARGS_ERR,
+ valid_types = ["string", "file", "directory"],
+ after_option_unwrap = True,
+ ))
+
+ if args:
+ kwargs["flags"] = [
+ format_variable(arg, iterate_over = iterate_over, fail = fail)
+ for arg in args
+ ]
+
+ return NestedArgsInfo(
+ label = label,
+ nested = nested,
+ files = depset(transitive = transitive_files),
+ iterate_over = iterate_over,
+ unwrap_options = unwrap_options,
+ requires_types = requires_types,
+ legacy_flag_group = flag_group(**kwargs),
+ )
diff --git a/cc/toolchains/impl/toolchain_config.bzl b/cc/toolchains/impl/toolchain_config.bzl
new file mode 100644
index 0000000..dde94f2
--- /dev/null
+++ b/cc/toolchains/impl/toolchain_config.bzl
@@ -0,0 +1,123 @@
+# 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.
+"""Implementation of the cc_toolchain rule."""
+
+load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
+load(
+ "//cc/toolchains:cc_toolchain_info.bzl",
+ "ActionTypeConfigSetInfo",
+ "ActionTypeSetInfo",
+ "ArgsListInfo",
+ "FeatureSetInfo",
+ "ToolchainConfigInfo",
+)
+load(":collect.bzl", "collect_action_types")
+load(":legacy_converter.bzl", "convert_toolchain")
+load(":toolchain_config_info.bzl", "toolchain_config_info")
+
+visibility([
+ "//cc/toolchains/...",
+ "//tests/rule_based_toolchain/...",
+])
+
+def _cc_legacy_file_group_impl(ctx):
+ files = ctx.attr.config[ToolchainConfigInfo].files
+
+ return [DefaultInfo(files = depset(transitive = [
+ files[action]
+ for action in collect_action_types(ctx.attr.actions).to_list()
+ if action in files
+ ]))]
+
+cc_legacy_file_group = rule(
+ implementation = _cc_legacy_file_group_impl,
+ attrs = {
+ "actions": attr.label_list(providers = [ActionTypeSetInfo], mandatory = True),
+ "config": attr.label(providers = [ToolchainConfigInfo], mandatory = True),
+ },
+)
+
+def _cc_toolchain_config_impl(ctx):
+ if ctx.attr.features:
+ fail("Features is a reserved attribute in bazel. Did you mean 'toolchain_features'")
+
+ if not ctx.attr._enabled[BuildSettingInfo].value and not ctx.attr.skip_experimental_flag_validation_for_test:
+ fail("Rule based toolchains are experimental. To use it, please add --@rules_cc//cc/toolchains:experimental_enable_rule_based_toolchains to your bazelrc")
+
+ toolchain_config = toolchain_config_info(
+ label = ctx.label,
+ features = ctx.attr.toolchain_features + [ctx.attr._builtin_features],
+ action_type_configs = ctx.attr.action_type_configs,
+ args = ctx.attr.args,
+ )
+
+ legacy = convert_toolchain(toolchain_config)
+
+ return [
+ toolchain_config,
+ cc_common.create_cc_toolchain_config_info(
+ ctx = ctx,
+ action_configs = legacy.action_configs,
+ features = legacy.features,
+ cxx_builtin_include_directories = ctx.attr.cxx_builtin_include_directories,
+ # toolchain_identifier is deprecated, but setting it to None results
+ # in an error that it expected a string, and for safety's sake, I'd
+ # prefer to provide something unique.
+ toolchain_identifier = str(ctx.label),
+ target_system_name = ctx.attr.target_system_name,
+ target_cpu = ctx.attr.target_cpu,
+ target_libc = ctx.attr.target_libc,
+ compiler = ctx.attr.compiler,
+ abi_version = ctx.attr.abi_version,
+ abi_libc_version = ctx.attr.abi_libc_version,
+ builtin_sysroot = ctx.attr.sysroot or None,
+ ),
+ # This allows us to support all_files.
+ # If all_files was simply an alias to
+ # ///cc/toolchains/actions:all_actions,
+ # then if a toolchain introduced a new type of action, it wouldn't get
+ # put in all_files.
+ DefaultInfo(files = depset(transitive = toolchain_config.files.values())),
+ ]
+
+cc_toolchain_config = rule(
+ implementation = _cc_toolchain_config_impl,
+ # @unsorted-dict-items
+ attrs = {
+ # Attributes new to this rule.
+ "action_type_configs": attr.label_list(providers = [ActionTypeConfigSetInfo]),
+ "args": attr.label_list(providers = [ArgsListInfo]),
+ "toolchain_features": attr.label_list(providers = [FeatureSetInfo]),
+ "skip_experimental_flag_validation_for_test": attr.bool(default = False),
+ "_builtin_features": attr.label(default = "//cc/toolchains/features:all_builtin_features"),
+ "_enabled": attr.label(default = "//cc/toolchains:experimental_enable_rule_based_toolchains"),
+
+ # Attributes from create_cc_toolchain_config_info.
+ # artifact_name_patterns is currently unused. Consider adding it later.
+ # TODO: Consider making this into a label_list that takes a
+ # cc_directory_marker rule as input.
+ "cxx_builtin_include_directories": attr.string_list(),
+ "target_system_name": attr.string(mandatory = True),
+ "target_cpu": attr.string(mandatory = True),
+ "target_libc": attr.string(mandatory = True),
+ "compiler": attr.string(mandatory = True),
+ "abi_version": attr.string(),
+ "abi_libc_version": attr.string(),
+ # tool_paths currently unused.
+ # TODO: Consider making this into a label that takes a
+ # cc_directory_marker rule as an input.
+ "sysroot": attr.string(),
+ },
+ provides = [ToolchainConfigInfo],
+)
diff --git a/cc/toolchains/impl/toolchain_config_info.bzl b/cc/toolchains/impl/toolchain_config_info.bzl
new file mode 100644
index 0000000..e2a8b37
--- /dev/null
+++ b/cc/toolchains/impl/toolchain_config_info.bzl
@@ -0,0 +1,178 @@
+# 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.
+"""Helper functions to create and validate a ToolchainConfigInfo."""
+
+load("//cc/toolchains:cc_toolchain_info.bzl", "ToolchainConfigInfo")
+load(":args_utils.bzl", "get_action_type")
+load(":collect.bzl", "collect_action_type_config_sets", "collect_args_lists", "collect_features")
+
+visibility([
+ "//cc/toolchains/...",
+ "//tests/rule_based_toolchain/...",
+])
+
+_FEATURE_NAME_ERR = """The feature name {name} was defined by both {lhs} and {rhs}.
+
+Possible causes:
+* If you're overriding a feature in //cc/toolchains/features/..., then try adding the "overrides" parameter instead of specifying a feature name.
+* If you intentionally have multiple features with the same name (eg. one for ARM and one for x86), then maybe you need add select() calls so that they're not defined at the same time.
+* Otherwise, this is probably a real problem, and you need to give them different names.
+"""
+
+_INVALID_CONSTRAINT_ERR = """It is impossible to enable {provider}.
+
+None of the entries in requires_any_of could be matched. This is required features are not implicitly added to the toolchain. It's likely that the feature that you require needs to be added to the toolchain explicitly.
+"""
+
+_UNKNOWN_FEATURE_ERR = """{self} implies the feature {ft}, which was unable to be found.
+
+Implied features are not implicitly added to your toolchain. You likely need to add features = ["{ft}"] to your cc_toolchain rule.
+"""
+
+# Equality comparisons with bazel do not evaluate depsets.
+# s = struct()
+# d = depset([s])
+# depset([s]) != depset([s])
+# d == d
+# This means that complex structs such as FeatureInfo will only compare as equal
+# iff they are the *same* object or if there are no depsets inside them.
+# Unfortunately, it seems that the FeatureInfo is copied during the
+# cc_action_type_config rule. Ideally we'd like to fix that, but I don't really
+# know what power we even have over such a thing.
+def _feature_key(feature):
+ # This should be sufficiently unique.
+ return (feature.label, feature.name)
+
+def _get_known_features(features, fail):
+ feature_names = {}
+ for ft in features:
+ if ft.name in feature_names:
+ other = feature_names[ft.name]
+ if other.overrides != ft and ft.overrides != other:
+ fail(_FEATURE_NAME_ERR.format(
+ name = ft.name,
+ lhs = ft.label,
+ rhs = other.label,
+ ))
+ feature_names[ft.name] = ft
+
+ return {_feature_key(feature): None for feature in features}
+
+def _can_theoretically_be_enabled(requirement, known_features):
+ return all([
+ _feature_key(ft) in known_features
+ for ft in requirement
+ ])
+
+def _validate_requires_any_of(fn, self, known_features, fail):
+ valid = any([
+ _can_theoretically_be_enabled(fn(requirement), known_features)
+ for requirement in self.requires_any_of
+ ])
+
+ # No constraints is always valid.
+ if self.requires_any_of and not valid:
+ fail(_INVALID_CONSTRAINT_ERR.format(provider = self.label))
+
+def _validate_requires_any_of_constraint(self, known_features, fail):
+ return _validate_requires_any_of(
+ lambda constraint: constraint.all_of.to_list(),
+ self,
+ known_features,
+ fail,
+ )
+
+def _validate_requires_any_of_feature_set(self, known_features, fail):
+ return _validate_requires_any_of(
+ lambda feature_set: feature_set.features.to_list(),
+ self,
+ known_features,
+ fail,
+ )
+
+def _validate_implies(self, known_features, fail = fail):
+ for ft in self.implies.to_list():
+ if _feature_key(ft) not in known_features:
+ fail(_UNKNOWN_FEATURE_ERR.format(self = self.label, ft = ft.label))
+
+def _validate_args(self, known_features, fail):
+ _validate_requires_any_of_constraint(self, known_features, fail = fail)
+
+def _validate_tool(self, known_features, fail):
+ _validate_requires_any_of_constraint(self, known_features, fail = fail)
+
+def _validate_action_config(self, known_features, fail):
+ _validate_implies(self, known_features, fail = fail)
+ for tool in self.tools:
+ _validate_tool(tool, known_features, fail = fail)
+ for args in self.args:
+ _validate_args(args, known_features, fail = fail)
+
+def _validate_feature(self, known_features, fail):
+ _validate_requires_any_of_feature_set(self, known_features, fail = fail)
+ for arg in self.args.args:
+ _validate_args(arg, known_features, fail = fail)
+ _validate_implies(self, known_features, fail = fail)
+
+def _validate_toolchain(self, fail = fail):
+ known_features = _get_known_features(self.features, fail = fail)
+
+ for feature in self.features:
+ _validate_feature(feature, known_features, fail = fail)
+ for atc in self.action_type_configs.values():
+ _validate_action_config(atc, known_features, fail = fail)
+ for args in self.args:
+ _validate_args(args, known_features, fail = fail)
+
+def _collect_files_for_action_type(atc, features, args):
+ transitive_files = [atc.files.files, get_action_type(args, atc.action_type).files]
+ for ft in features:
+ transitive_files.append(get_action_type(ft.args, atc.action_type).files)
+
+ return depset(transitive = transitive_files)
+
+def toolchain_config_info(label, features = [], args = [], action_type_configs = [], fail = fail):
+ """Generates and validates a ToolchainConfigInfo from lists of labels.
+
+ Args:
+ label: (Label) The label to apply to the ToolchainConfigInfo
+ features: (List[Target]) A list of targets providing FeatureSetInfo
+ args: (List[Target]) A list of targets providing ArgsListInfo
+ action_type_configs: (List[Target]) A list of targets providing
+ ActionTypeConfigSetInfo
+ fail: A fail function. Use only during tests.
+ Returns:
+ A validated ToolchainConfigInfo
+ """
+ features = collect_features(features).to_list()
+ args = collect_args_lists(args, label = label)
+ action_type_configs = collect_action_type_config_sets(
+ action_type_configs,
+ label = label,
+ fail = fail,
+ ).configs
+ files = {
+ atc.action_type: _collect_files_for_action_type(atc, features, args)
+ for atc in action_type_configs.values()
+ }
+
+ toolchain_config = ToolchainConfigInfo(
+ label = label,
+ features = features,
+ action_type_configs = action_type_configs,
+ args = args.args,
+ files = files,
+ )
+ _validate_toolchain(toolchain_config, fail = fail)
+ return toolchain_config
diff --git a/cc/toolchains/impl/variables.bzl b/cc/toolchains/impl/variables.bzl
new file mode 100644
index 0000000..c2820f3
--- /dev/null
+++ b/cc/toolchains/impl/variables.bzl
@@ -0,0 +1,169 @@
+# 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
diff --git a/cc/toolchains/mutually_exclusive_category.bzl b/cc/toolchains/mutually_exclusive_category.bzl
new file mode 100644
index 0000000..9920290
--- /dev/null
+++ b/cc/toolchains/mutually_exclusive_category.bzl
@@ -0,0 +1,29 @@
+# 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.
+"""Rule for mutually exclusive categories in the rule based toolchain."""
+
+load(":cc_toolchain_info.bzl", "MutuallyExclusiveCategoryInfo")
+
+def _cc_mutually_exclusive_category_impl(ctx):
+ return [MutuallyExclusiveCategoryInfo(
+ label = ctx.label,
+ name = str(ctx.label),
+ )]
+
+cc_mutually_exclusive_category = rule(
+ implementation = _cc_mutually_exclusive_category_impl,
+ doc = "A category of features, for which only one can be enabled",
+ attrs = {},
+ provides = [MutuallyExclusiveCategoryInfo],
+)
diff --git a/cc/toolchains/nested_args.bzl b/cc/toolchains/nested_args.bzl
new file mode 100644
index 0000000..e4e3d53
--- /dev/null
+++ b/cc/toolchains/nested_args.bzl
@@ -0,0 +1,45 @@
+# 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(
+ "//cc/toolchains/impl:nested_args.bzl",
+ "NESTED_ARGS_ATTRS",
+ "args_wrapper_macro",
+ "nested_args_provider_from_ctx",
+)
+load(
+ ":cc_toolchain_info.bzl",
+ "NestedArgsInfo",
+)
+
+visibility("public")
+
+_cc_nested_args = rule(
+ implementation = lambda ctx: [nested_args_provider_from_ctx(ctx)],
+ attrs = NESTED_ARGS_ATTRS,
+ provides = [NestedArgsInfo],
+ doc = """Declares a list of arguments bound to a set of actions.
+
+Roughly equivalent to ctx.actions.args()
+
+Examples:
+ cc_nested_args(
+ name = "warnings_as_errors",
+ args = ["-Werror"],
+ )
+""",
+)
+
+cc_nested_args = lambda **kwargs: args_wrapper_macro(rule = _cc_nested_args, **kwargs)
diff --git a/cc/toolchains/tool.bzl b/cc/toolchains/tool.bzl
new file mode 100644
index 0000000..fb552ca
--- /dev/null
+++ b/cc/toolchains/tool.bzl
@@ -0,0 +1,106 @@
+# 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("//cc/toolchains/impl:collect.bzl", "collect_data", "collect_provider")
+load(
+ ":cc_toolchain_info.bzl",
+ "FeatureConstraintInfo",
+ "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")
+
+ runfiles = collect_data(ctx, ctx.attr.data + [ctx.attr.src])
+ tool = ToolInfo(
+ label = ctx.label,
+ exe = exe,
+ runfiles = runfiles,
+ requires_any_of = tuple(collect_provider(
+ ctx.attr.requires_any_of,
+ FeatureConstraintInfo,
+ )),
+ execution_requirements = tuple(ctx.attr.execution_requirements),
+ )
+
+ 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,
+ cfg = "exec",
+ doc = """The underlying binary that this tool represents.
+
+Usually just a single prebuilt (eg. @sysroot//: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.",
+ ),
+ "execution_requirements": attr.string_list(
+ doc = "A list of strings that provide hints for execution environment compatibility (e.g. `requires-network`).",
+ ),
+ "requires_any_of": attr.label_list(
+ providers = [FeatureConstraintInfo],
+ doc = """This will be enabled when any of the constraints are met.
+
+If omitted, this tool will be enabled unconditionally.
+""",
+ ),
+ },
+ provides = [ToolInfo],
+ doc = """Declares a tool that can be bound to action configs.
+
+A tool is a binary with extra metadata for the action config rule to consume
+(eg. execution_requirements).
+
+Example:
+```
+cc_tool(
+ name = "clang_tool",
+ executable = "@llvm_toolchain//:bin/clang",
+ # Suppose clang needs libc to run.
+ data = ["@llvm_toolchain//:lib/x86_64-linux-gnu/libc.so.6"]
+)
+```
+""",
+ executable = True,
+)
diff --git a/cc/toolchains/toolchain.bzl b/cc/toolchains/toolchain.bzl
new file mode 100644
index 0000000..0ce1abf
--- /dev/null
+++ b/cc/toolchains/toolchain.bzl
@@ -0,0 +1,143 @@
+# 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.
+"""Implementation of the cc_toolchain rule."""
+
+load("//cc:defs.bzl", _cc_toolchain = "cc_toolchain")
+load(
+ "//cc/toolchains/impl:toolchain_config.bzl",
+ "cc_legacy_file_group",
+ "cc_toolchain_config",
+)
+
+visibility("public")
+
+# Taken from https://bazel.build/docs/cc-toolchain-config-reference#actions
+# TODO: This is best-effort. Update this with the correct file groups once we
+# work out what actions correspond to what file groups.
+_LEGACY_FILE_GROUPS = {
+ "ar_files": [
+ "@rules_cc//cc/toolchains/actions:ar_actions", # copybara-use-repo-external-label
+ ],
+ "as_files": [
+ "@rules_cc//cc/toolchains/actions:assembly_actions", # copybara-use-repo-external-label
+ ],
+ "compiler_files": [
+ "@rules_cc//cc/toolchains/actions:cc_flags_make_variable", # copybara-use-repo-external-label
+ "@rules_cc//cc/toolchains/actions:c_compile", # copybara-use-repo-external-label
+ "@rules_cc//cc/toolchains/actions:cpp_compile", # copybara-use-repo-external-label
+ "@rules_cc//cc/toolchains/actions:cpp_header_parsing", # copybara-use-repo-external-label
+ ],
+ # There are no actions listed for coverage, dwp, and objcopy in action_names.bzl.
+ "coverage_files": [],
+ "dwp_files": [],
+ "linker_files": [
+ "@rules_cc//cc/toolchains/actions:cpp_link_dynamic_library", # copybara-use-repo-external-label
+ "@rules_cc//cc/toolchains/actions:cpp_link_nodeps_dynamic_library", # copybara-use-repo-external-label
+ "@rules_cc//cc/toolchains/actions:cpp_link_executable", # copybara-use-repo-external-label
+ ],
+ "objcopy_files": [],
+ "strip_files": [
+ "@rules_cc//cc/toolchains/actions:strip", # copybara-use-repo-external-label
+ ],
+}
+
+def cc_toolchain(
+ name,
+ dynamic_runtime_lib = None,
+ libc_top = None,
+ module_map = None,
+ output_licenses = [],
+ static_runtime_lib = None,
+ supports_header_parsing = False,
+ supports_param_files = True,
+ target_compatible_with = None,
+ exec_compatible_with = None,
+ compatible_with = None,
+ tags = [],
+ visibility = None,
+ **kwargs):
+ """A macro that invokes native.cc_toolchain under the hood.
+
+ Generated rules:
+ {name}: A `cc_toolchain` for this toolchain.
+ _{name}_config: A `cc_toolchain_config` for this toolchain.
+ _{name}_*_files: Generated rules that group together files for
+ "ar_files", "as_files", "compiler_files", "coverage_files",
+ "dwp_files", "linker_files", "objcopy_files", and "strip_files"
+ normally enumerated as part of the `cc_toolchain` rule.
+
+ Args:
+ name: str: The name of the label for the toolchain.
+ dynamic_runtime_lib: See cc_toolchain.dynamic_runtime_lib
+ libc_top: See cc_toolchain.libc_top
+ module_map: See cc_toolchain.module_map
+ output_licenses: See cc_toolchain.output_licenses
+ static_runtime_lib: See cc_toolchain.static_runtime_lib
+ supports_header_parsing: See cc_toolchain.supports_header_parsing
+ supports_param_files: See cc_toolchain.supports_param_files
+ target_compatible_with: target_compatible_with to apply to all generated
+ rules
+ exec_compatible_with: exec_compatible_with to apply to all generated
+ rules
+ compatible_with: compatible_with to apply to all generated rules
+ tags: Tags to apply to all generated rules
+ visibility: Visibility of toolchain rule
+ **kwargs: Args to be passed through to cc_toolchain_config.
+ """
+ all_kwargs = {
+ "compatible_with": compatible_with,
+ "exec_compatible_with": exec_compatible_with,
+ "tags": tags,
+ "target_compatible_with": target_compatible_with,
+ }
+ for group in _LEGACY_FILE_GROUPS:
+ if group in kwargs:
+ fail("Don't use legacy file groups such as %s. Instead, associate files with tools, actions, and args." % group)
+
+ config_name = "_{}_config".format(name)
+ cc_toolchain_config(
+ name = config_name,
+ visibility = ["//visibility:private"],
+ **(all_kwargs | kwargs)
+ )
+
+ # Provides ar_files, compiler_files, linker_files, ...
+ legacy_file_groups = {}
+ for group, actions in _LEGACY_FILE_GROUPS.items():
+ group_name = "_{}_{}".format(name, group)
+ cc_legacy_file_group(
+ name = group_name,
+ config = config_name,
+ actions = actions,
+ visibility = ["//visibility:private"],
+ **all_kwargs
+ )
+ legacy_file_groups[group] = group_name
+
+ if visibility != None:
+ all_kwargs["visibility"] = visibility
+
+ _cc_toolchain(
+ name = name,
+ toolchain_config = config_name,
+ all_files = config_name,
+ dynamic_runtime_lib = dynamic_runtime_lib,
+ libc_top = libc_top,
+ module_map = module_map,
+ output_licenses = output_licenses,
+ static_runtime_lib = static_runtime_lib,
+ supports_header_parsing = supports_header_parsing,
+ supports_param_files = supports_param_files,
+ **(all_kwargs | legacy_file_groups)
+ )
diff --git a/cc/toolchains/variables/BUILD b/cc/toolchains/variables/BUILD
new file mode 100644
index 0000000..ec07287
--- /dev/null
+++ b/cc/toolchains/variables/BUILD
@@ -0,0 +1,481 @@
+load("//cc/toolchains/impl:variables.bzl", "cc_builtin_variables", "cc_variable", "types")
+
+package(default_visibility = ["//visibility:public"])
+
+cc_variable(
+ name = "cs_fdo_instrument_path",
+ actions = [
+ "//cc/toolchains/actions:link_actions",
+ "//cc/toolchains/actions:compile_actions",
+ ],
+ type = types.directory,
+)
+
+cc_variable(
+ name = "def_file_path",
+ actions = ["//cc/toolchains/actions:link_actions"],
+ type = types.option(types.file),
+)
+
+cc_variable(
+ name = "dependency_file",
+ actions = ["//cc/toolchains/actions:compile_actions"],
+ type = types.file,
+)
+
+cc_variable(
+ name = "dependent_module_map_files",
+ actions = ["//cc/toolchains/actions:compile_actions"],
+ type = types.option(types.list(types.file)),
+)
+
+cc_variable(
+ name = "external_include_paths",
+ actions = ["//cc/toolchains/actions:compile_actions"],
+ type = types.option(types.list(types.directory)),
+)
+
+cc_variable(
+ name = "fdo_instrument_path",
+ actions = [
+ "//cc/toolchains/actions:link_actions",
+ "//cc/toolchains/actions:compile_actions",
+ ],
+ type = types.directory,
+)
+
+cc_variable(
+ name = "fdo_prefetch_hints_path",
+ actions = ["//cc/toolchains/actions:compile_actions"],
+ type = types.file,
+)
+
+cc_variable(
+ name = "fdo_profile_path",
+ actions = ["//cc/toolchains/actions:compile_actions"],
+ type = types.file,
+)
+
+cc_variable(
+ name = "force_pic",
+ actions = ["//cc/toolchains/actions:link_actions"],
+ # Provided when --force-pic is passed
+ type = types.option(types.void),
+)
+
+cc_variable(
+ name = "framework_include_paths",
+ actions = ["//cc/toolchains/actions:compile_actions"],
+ type = types.list(types.directory),
+)
+
+cc_variable(
+ name = "gcov_gcno_file",
+ actions = ["//cc/toolchains/actions:compile_actions"],
+ type = types.file,
+)
+
+cc_variable(
+ name = "generate_interface_library",
+ actions = ["//cc/toolchains/actions:link_actions"],
+ # "yes" or "no"
+ type = types.option(types.string),
+)
+
+cc_variable(
+ name = "include",
+ actions = ["//cc/toolchains/actions:compile_actions"],
+ type = types.list(types.file),
+)
+
+cc_variable(
+ name = "include_paths",
+ actions = ["//cc/toolchains/actions:compile_actions"],
+ type = types.list(types.directory),
+)
+
+cc_variable(
+ name = "includes",
+ actions = ["//cc/toolchains/actions:compile_actions"],
+ type = types.option(types.list(types.file)),
+)
+
+cc_variable(
+ name = "input_file",
+ actions = ["//cc/toolchains/actions:strip"],
+ type = types.file,
+)
+
+cc_variable(
+ name = "interface_library_builder_path",
+ actions = ["//cc/toolchains/actions:link_actions"],
+ # Should be a file, but contains the string "ignored" when there's no value.
+ type = types.option(types.string),
+)
+
+cc_variable(
+ name = "interface_library_input_path",
+ actions = ["//cc/toolchains/actions:link_actions"],
+ # Should be a file, but contains the string "ignored" when there's no value.
+ type = types.option(types.string),
+)
+
+cc_variable(
+ name = "interface_library_output_path",
+ actions = ["//cc/toolchains/actions:link_actions"],
+ # Should be a file, but contains the string "ignored" when there's no value.
+ type = types.option(types.string),
+)
+
+cc_variable(
+ name = "is_cc_test",
+ actions = ["//cc/toolchains/actions:link_actions"],
+ type = types.option(types.bool),
+)
+
+cc_variable(
+ name = "is_using_fission",
+ actions = [
+ "//cc/toolchains/actions:link_actions",
+ "//cc/toolchains/actions:compile_actions",
+ ],
+ type = types.option(types.void),
+)
+
+cc_variable(
+ name = "legacy_compile_flags",
+ actions = ["//cc/toolchains/actions:compile_actions"],
+ type = types.list(types.string),
+)
+
+cc_variable(
+ name = "legacy_link_flags",
+ actions = ["//cc/toolchains/actions:link_actions"],
+ type = types.list(types.string),
+)
+
+cc_variable(
+ name = "libraries_to_link",
+ actions = ["//cc/toolchains/actions:link_actions"],
+ type = types.option(types.list(types.struct(
+ shared_libraries = types.list(types.struct(
+ name = types.string,
+ is_whole_archive = types.bool,
+ object_files = types.list(types.file),
+ path = types.file,
+ type = types.string,
+ )),
+ ))),
+)
+
+cc_variable(
+ name = "libraries_to_link.shared_libraries",
+ actions = ["//cc/toolchains/actions:link_actions"],
+ # See :libraries_to_link.
+ type = types.unknown,
+)
+
+cc_variable(
+ name = "libraries_to_link.shared_libraries.is_whole_archive",
+ actions = ["//cc/toolchains/actions:link_actions"],
+ type = types.string,
+)
+
+cc_variable(
+ name = "libraries_to_link.shared_libraries.name",
+ actions = ["//cc/toolchains/actions:link_actions"],
+ type = types.string,
+)
+
+cc_variable(
+ name = "libraries_to_link.shared_libraries.object_files",
+ actions = ["//cc/toolchains/actions:link_actions"],
+ type = types.list(types.file),
+)
+
+cc_variable(
+ name = "libraries_to_link.shared_libraries.path",
+ actions = ["//cc/toolchains/actions:link_actions"],
+ type = types.file,
+)
+
+cc_variable(
+ name = "libraries_to_link.shared_libraries.type",
+ actions = ["//cc/toolchains/actions:link_actions"],
+ type = types.string,
+)
+
+cc_variable(
+ name = "library_search_directories",
+ actions = ["//cc/toolchains/actions:link_actions"],
+ type = types.list(types.directory),
+)
+
+cc_variable(
+ name = "linker_param_file",
+ actions = ["//cc/toolchains/actions:link_actions"],
+ type = types.file,
+)
+
+cc_variable(
+ name = "linkstamp_paths",
+ actions = ["//cc/toolchains/actions:link_actions"],
+ type = types.list(types.directory),
+)
+
+cc_variable(
+ name = "lto_indexing_bitcode_file",
+ actions = ["//cc/toolchains/actions:compile_actions"],
+ type = types.option(types.file),
+)
+
+cc_variable(
+ name = "module_files",
+ actions = ["//cc/toolchains/actions:compile_actions"],
+ type = types.option(types.list(types.file)),
+)
+
+cc_variable(
+ name = "module_map_file",
+ actions = ["//cc/toolchains/actions:compile_actions"],
+ type = types.option(types.file),
+)
+
+cc_variable(
+ name = "module_name",
+ actions = ["//cc/toolchains/actions:compile_actions"],
+ type = types.option(types.string),
+)
+
+cc_variable(
+ name = "output_assembly_file",
+ actions = ["//cc/toolchains/actions:compile_actions"],
+ type = types.file,
+)
+
+cc_variable(
+ name = "output_execpath",
+ actions = ["//cc/toolchains/actions:link_actions"],
+ type = types.option(types.directory),
+)
+
+cc_variable(
+ name = "output_file",
+ actions = ["//cc/toolchains/actions:compile_actions"],
+ type = types.file,
+)
+
+cc_variable(
+ name = "output_preprocess_file",
+ actions = ["//cc/toolchains/actions:compile_actions"],
+ type = types.file,
+)
+
+cc_variable(
+ name = "per_object_debug_info_file",
+ actions = ["//cc/toolchains/actions:compile_actions"],
+ type = types.file,
+)
+
+cc_variable(
+ name = "pic",
+ actions = ["//cc/toolchains/actions:compile_actions"],
+ type = types.option(types.void),
+)
+
+cc_variable(
+ name = "preprocessor_defines",
+ actions = ["//cc/toolchains/actions:compile_actions"],
+ type = types.list(types.string),
+)
+
+cc_variable(
+ name = "propellor_optimize_ld_path",
+ actions = ["//cc/toolchains/actions:link_actions"],
+ type = types.option(types.file),
+)
+
+cc_variable(
+ name = "quote_include_paths",
+ actions = ["//cc/toolchains/actions:compile_actions"],
+ type = types.list(types.directory),
+)
+
+cc_variable(
+ name = "runtime_library_search_directories",
+ actions = ["//cc/toolchains/actions:link_actions"],
+ type = types.option(types.list(types.directory)),
+)
+
+cc_variable(
+ name = "runtime_solib_name",
+ actions = ["//cc/toolchains/actions:link_actions"],
+ type = types.option(types.string),
+)
+
+cc_variable(
+ name = "serialized_diagnostics_file",
+ actions = ["//cc/toolchains/actions:compile_actions"],
+ type = types.option(types.file),
+)
+
+cc_variable(
+ name = "source_file",
+ actions = ["//cc/toolchains/actions:compile_actions"],
+ type = types.file,
+)
+
+cc_variable(
+ name = "strip_debug_symbols",
+ actions = ["//cc/toolchains/actions:link_actions"],
+ type = types.option(types.void),
+)
+
+cc_variable(
+ name = "stripopts",
+ actions = ["//cc/toolchains/actions:strip"],
+ type = types.list(types.string),
+)
+
+cc_variable(
+ name = "sysroot",
+ type = types.directory,
+)
+
+cc_variable(
+ name = "system_include_paths",
+ actions = ["//cc/toolchains/actions:compile_actions"],
+ type = types.list(types.directory),
+)
+
+cc_variable(
+ name = "thinlto_index",
+ actions = ["//cc/toolchains/actions:compile_actions"],
+ type = types.option(types.string),
+)
+
+cc_variable(
+ name = "thinlto_indexing_param_file",
+ actions = ["//cc/toolchains/actions:link_actions"],
+ type = types.option(types.string),
+)
+
+cc_variable(
+ name = "thinlto_input_bitcode_file",
+ actions = ["//cc/toolchains/actions:compile_actions"],
+ type = types.option(types.file),
+)
+
+cc_variable(
+ name = "thinlto_merged_object_file",
+ actions = ["//cc/toolchains/actions:link_actions"],
+ type = types.option(types.file),
+)
+
+cc_variable(
+ name = "thinlto_object_suffix_replace",
+ actions = ["//cc/toolchains/actions:link_actions"],
+ type = types.option(types.string),
+)
+
+cc_variable(
+ name = "thinlto_output_object_file",
+ actions = ["//cc/toolchains/actions:compile_actions"],
+ type = types.option(types.file),
+)
+
+cc_variable(
+ name = "thinlto_param_file",
+ actions = ["//cc/toolchains/actions:link_actions"],
+ type = types.option(types.file),
+)
+
+cc_variable(
+ name = "thinlto_prefix_replace",
+ actions = ["//cc/toolchains/actions:link_actions"],
+ type = types.option(types.string),
+)
+
+cc_variable(
+ name = "unfiltered_compile_flags",
+ actions = ["//cc/toolchains/actions:compile_actions"],
+ type = types.list(types.string),
+)
+
+cc_variable(
+ name = "user_compile_flags",
+ actions = ["//cc/toolchains/actions:compile_actions"],
+ type = types.list(types.string),
+)
+
+cc_variable(
+ name = "user_link_flags",
+ actions = ["//cc/toolchains/actions:link_actions"],
+ type = types.list(types.string),
+)
+
+cc_builtin_variables(
+ name = "variables",
+ srcs = [
+ ":cs_fdo_instrument_path",
+ ":def_file_path",
+ ":dependency_file",
+ ":dependent_module_map_files",
+ ":external_include_paths",
+ ":fdo_instrument_path",
+ ":fdo_prefetch_hints_path",
+ ":fdo_profile_path",
+ ":force_pic",
+ ":framework_include_paths",
+ ":gcov_gcno_file",
+ ":generate_interface_library",
+ ":include",
+ ":include_paths",
+ ":includes",
+ ":input_file",
+ ":interface_library_builder_path",
+ ":interface_library_input_path",
+ ":interface_library_output_path",
+ ":is_cc_test",
+ ":is_using_fission",
+ ":legacy_compile_flags",
+ ":legacy_link_flags",
+ ":libraries_to_link",
+ ":library_search_directories",
+ ":linker_param_file",
+ ":linkstamp_paths",
+ ":lto_indexing_bitcode_file",
+ ":module_files",
+ ":module_map_file",
+ ":module_name",
+ ":output_assembly_file",
+ ":output_execpath",
+ ":output_file",
+ ":output_preprocess_file",
+ ":per_object_debug_info_file",
+ ":pic",
+ ":preprocessor_defines",
+ ":propellor_optimize_ld_path",
+ ":quote_include_paths",
+ ":runtime_library_search_directories",
+ ":runtime_solib_name",
+ ":serialized_diagnostics_file",
+ ":source_file",
+ ":strip_debug_symbols",
+ ":stripopts",
+ ":sysroot",
+ ":system_include_paths",
+ ":thinlto_index",
+ ":thinlto_indexing_param_file",
+ ":thinlto_input_bitcode_file",
+ ":thinlto_merged_object_file",
+ ":thinlto_object_suffix_replace",
+ ":thinlto_output_object_file",
+ ":thinlto_param_file",
+ ":thinlto_prefix_replace",
+ ":unfiltered_compile_flags",
+ ":user_compile_flags",
+ ":user_link_flags",
+ ],
+ visibility = ["//visibility:public"],
+)
diff --git a/examples/BUILD b/examples/BUILD
index c7da75d..3123ba8 100644
--- a/examples/BUILD
+++ b/examples/BUILD
@@ -12,10 +12,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+# A collection of examples showing the usage of rules_cc
+
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
load("@bazel_skylib//rules:common_settings.bzl", "bool_flag")
-# A collection of examples showing the usage of rules_cc
licenses(["notice"])
bool_flag(
diff --git a/examples/my_c_compile/BUILD b/examples/my_c_compile/BUILD
index b045509..0248b99 100644
--- a/examples/my_c_compile/BUILD
+++ b/examples/my_c_compile/BUILD
@@ -16,6 +16,7 @@ load("//examples/my_c_compile:my_c_compile.bzl", "my_c_compile")
# limitations under the License.
# Example showing how to create a custom Starlark rule that just compiles C sources
+
licenses(["notice"])
my_c_compile(
diff --git a/tests/compiler_settings/BUILD b/tests/compiler_settings/BUILD
index 33c8206..a377a51 100644
--- a/tests/compiler_settings/BUILD
+++ b/tests/compiler_settings/BUILD
@@ -21,8 +21,8 @@ cc_binary(
srcs = ["main.cc"],
local_defines = select(
{
- "//cc/compiler:clang-cl": ["COMPILER=clang-cl"],
"//cc/compiler:clang": ["COMPILER=clang"],
+ "//cc/compiler:clang-cl": ["COMPILER=clang-cl"],
"//cc/compiler:gcc": ["COMPILER=gcc"],
"//cc/compiler:mingw-gcc": ["COMPILER=mingw-gcc"],
"//cc/compiler:msvc-cl": ["COMPILER=msvc-cl"],
diff --git a/tests/rule_based_toolchain/BUILD b/tests/rule_based_toolchain/BUILD
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/rule_based_toolchain/BUILD
diff --git a/tests/rule_based_toolchain/action_type_config/BUILD b/tests/rule_based_toolchain/action_type_config/BUILD
new file mode 100644
index 0000000..e7b9194
--- /dev/null
+++ b/tests/rule_based_toolchain/action_type_config/BUILD
@@ -0,0 +1,42 @@
+load("@rules_testing//lib:util.bzl", "util")
+load("//cc/toolchains:action_type_config.bzl", "cc_action_type_config")
+load("//tests/rule_based_toolchain:analysis_test_suite.bzl", "analysis_test_suite")
+load(":action_type_config_test.bzl", "TARGETS", "TESTS")
+
+util.helper_target(
+ cc_action_type_config,
+ name = "file_map",
+ action_types = ["//tests/rule_based_toolchain/actions:all_compile"],
+ args = ["//tests/rule_based_toolchain/args_list"],
+ data = [
+ "//tests/rule_based_toolchain/testdata:multiple2",
+ ],
+ tools = [
+ "//tests/rule_based_toolchain/testdata:bin_wrapper.sh",
+ "//tests/rule_based_toolchain/tool:wrapped_tool",
+ ],
+)
+
+util.helper_target(
+ cc_action_type_config,
+ name = "c_compile_config",
+ action_types = ["//tests/rule_based_toolchain/actions:c_compile"],
+ tools = [
+ "//tests/rule_based_toolchain/testdata:bin_wrapper.sh",
+ ],
+)
+
+util.helper_target(
+ cc_action_type_config,
+ name = "cpp_compile_config",
+ action_types = ["//tests/rule_based_toolchain/actions:cpp_compile"],
+ tools = [
+ "//tests/rule_based_toolchain/testdata:bin_wrapper.sh",
+ ],
+)
+
+analysis_test_suite(
+ name = "test_suite",
+ targets = TARGETS,
+ tests = TESTS,
+)
diff --git a/tests/rule_based_toolchain/action_type_config/action_type_config_test.bzl b/tests/rule_based_toolchain/action_type_config/action_type_config_test.bzl
new file mode 100644
index 0000000..7ee85e6
--- /dev/null
+++ b/tests/rule_based_toolchain/action_type_config/action_type_config_test.bzl
@@ -0,0 +1,108 @@
+# 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.
+"""Tests for the action_type_config rule."""
+
+load(
+ "//cc/toolchains:cc_toolchain_info.bzl",
+ "ActionTypeConfigSetInfo",
+ "ActionTypeInfo",
+)
+load("//cc/toolchains/impl:collect.bzl", _collect_action_type_configs = "collect_action_type_config_sets")
+load("//tests/rule_based_toolchain:subjects.bzl", "result_fn_wrapper", "subjects")
+
+visibility("private")
+
+_TOOL_FILES = [
+ "tests/rule_based_toolchain/testdata/bin",
+ "tests/rule_based_toolchain/testdata/bin_wrapper",
+ "tests/rule_based_toolchain/testdata/bin_wrapper.sh",
+]
+_ADDITIONAL_FILES = [
+ "tests/rule_based_toolchain/testdata/multiple2",
+]
+_C_COMPILE_FILES = [
+ "tests/rule_based_toolchain/testdata/file1",
+ "tests/rule_based_toolchain/testdata/multiple1",
+]
+_CPP_COMPILE_FILES = [
+ "tests/rule_based_toolchain/testdata/file2",
+ "tests/rule_based_toolchain/testdata/multiple1",
+]
+
+collect_action_type_configs = result_fn_wrapper(_collect_action_type_configs)
+
+def _files_taken_test(env, targets):
+ configs = env.expect.that_target(targets.file_map).provider(ActionTypeConfigSetInfo).configs()
+ c_compile = configs.get(targets.c_compile[ActionTypeInfo])
+ c_compile.files().contains_exactly(
+ _C_COMPILE_FILES + _TOOL_FILES + _ADDITIONAL_FILES,
+ )
+ c_compile.args().contains_exactly([
+ targets.c_compile_args.label,
+ targets.all_compile_args.label,
+ ])
+
+ cpp_compile = configs.get(targets.cpp_compile[ActionTypeInfo])
+ cpp_compile.files().contains_exactly(
+ _CPP_COMPILE_FILES + _TOOL_FILES + _ADDITIONAL_FILES,
+ )
+ cpp_compile.args().contains_exactly([
+ targets.cpp_compile_args.label,
+ targets.all_compile_args.label,
+ ])
+
+def _merge_distinct_configs_succeeds_test(env, targets):
+ configs = env.expect.that_value(
+ collect_action_type_configs(
+ targets = [targets.c_compile_config, targets.cpp_compile_config],
+ label = env.ctx.label,
+ ),
+ factory = subjects.result(subjects.ActionTypeConfigSetInfo),
+ ).ok().configs()
+ configs.get(targets.c_compile[ActionTypeInfo]).label().equals(
+ targets.c_compile_config.label,
+ )
+ configs.get(targets.cpp_compile[ActionTypeInfo]).label().equals(
+ targets.cpp_compile_config.label,
+ )
+
+def _merge_overlapping_configs_fails_test(env, targets):
+ err = env.expect.that_value(
+ collect_action_type_configs(
+ targets = [targets.file_map, targets.c_compile_config],
+ label = env.ctx.label,
+ ),
+ factory = subjects.result(subjects.ActionTypeConfigSetInfo),
+ ).err()
+ err.contains("//tests/rule_based_toolchain/actions:c_compile is configured by both")
+ err.contains("//tests/rule_based_toolchain/action_type_config:c_compile_config")
+ err.contains("//tests/rule_based_toolchain/action_type_config:file_map")
+
+TARGETS = [
+ ":file_map",
+ ":c_compile_config",
+ ":cpp_compile_config",
+ "//tests/rule_based_toolchain/actions:c_compile",
+ "//tests/rule_based_toolchain/actions:cpp_compile",
+ "//tests/rule_based_toolchain/args_list:c_compile_args",
+ "//tests/rule_based_toolchain/args_list:cpp_compile_args",
+ "//tests/rule_based_toolchain/args_list:all_compile_args",
+ "//tests/rule_based_toolchain/args_list:args_list",
+]
+
+TESTS = {
+ "files_taken_test": _files_taken_test,
+ "merge_distinct_configs_succeeds_test": _merge_distinct_configs_succeeds_test,
+ "merge_overlapping_configs_fails_test": _merge_overlapping_configs_fails_test,
+}
diff --git a/tests/rule_based_toolchain/actions/BUILD b/tests/rule_based_toolchain/actions/BUILD
new file mode 100644
index 0000000..4e45f7e
--- /dev/null
+++ b/tests/rule_based_toolchain/actions/BUILD
@@ -0,0 +1,34 @@
+load("@rules_testing//lib:util.bzl", "util")
+load("//cc/toolchains:actions.bzl", "cc_action_type", "cc_action_type_set")
+load("//tests/rule_based_toolchain:analysis_test_suite.bzl", "analysis_test_suite")
+load(":actions_test.bzl", "TARGETS", "TESTS")
+
+util.helper_target(
+ cc_action_type,
+ name = "c_compile",
+ action_name = "c_compile",
+ visibility = ["//tests/rule_based_toolchain:__subpackages__"],
+)
+
+util.helper_target(
+ cc_action_type,
+ name = "cpp_compile",
+ action_name = "cpp_compile",
+ visibility = ["//tests/rule_based_toolchain:__subpackages__"],
+)
+
+util.helper_target(
+ cc_action_type_set,
+ name = "all_compile",
+ actions = [
+ ":c_compile",
+ ":cpp_compile",
+ ],
+ visibility = ["//tests/rule_based_toolchain:__subpackages__"],
+)
+
+analysis_test_suite(
+ name = "test_suite",
+ targets = TARGETS,
+ tests = TESTS,
+)
diff --git a/tests/rule_based_toolchain/actions/actions_test.bzl b/tests/rule_based_toolchain/actions/actions_test.bzl
new file mode 100644
index 0000000..284ed9a
--- /dev/null
+++ b/tests/rule_based_toolchain/actions/actions_test.bzl
@@ -0,0 +1,43 @@
+# 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.
+"""Tests for actions for the rule based toolchain."""
+
+load(
+ "//cc/toolchains:cc_toolchain_info.bzl",
+ "ActionTypeInfo",
+ "ActionTypeSetInfo",
+)
+
+visibility("private")
+
+def _test_action_types_impl(env, targets):
+ env.expect.that_target(targets.c_compile).provider(ActionTypeInfo) \
+ .name().equals("c_compile")
+ env.expect.that_target(targets.cpp_compile).provider(ActionTypeSetInfo) \
+ .actions().contains_exactly([targets.cpp_compile.label])
+ env.expect.that_target(targets.all_compile).provider(ActionTypeSetInfo) \
+ .actions().contains_exactly([
+ targets.c_compile.label,
+ targets.cpp_compile.label,
+ ])
+
+TARGETS = [
+ ":c_compile",
+ ":cpp_compile",
+ ":all_compile",
+]
+
+TESTS = {
+ "actions_test": _test_action_types_impl,
+}
diff --git a/tests/rule_based_toolchain/analysis_test_suite.bzl b/tests/rule_based_toolchain/analysis_test_suite.bzl
new file mode 100644
index 0000000..01ba4e2
--- /dev/null
+++ b/tests/rule_based_toolchain/analysis_test_suite.bzl
@@ -0,0 +1,50 @@
+# 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.
+"""Test suites for the rule based toolchain."""
+
+load("@rules_testing//lib:analysis_test.bzl", "analysis_test")
+load(":subjects.bzl", "FACTORIES")
+
+visibility("//tests/rule_based_toolchain/...")
+
+_DEFAULT_TARGET = "//tests/rule_based_toolchain/actions:c_compile"
+
+# Tests of internal starlark functions will often not require any targets,
+# but analysis_test requires at least one, so we pick an arbitrary one.
+def analysis_test_suite(name, tests, targets = [_DEFAULT_TARGET]):
+ """A test suite for the internals of the toolchain.
+
+ Args:
+ name: (str) The name of the test suite.
+ tests: (dict[str, fn]) A mapping from test name to implementations.
+ targets: (List[Label|str]) List of targets accessible to the test.
+ """
+ targets = [native.package_relative_label(target) for target in targets]
+
+ test_case_names = []
+ for test_name, impl in tests.items():
+ if not test_name.endswith("_test"):
+ fail("Expected test keys to end with '_test', got test case %r" % test_name)
+ test_case_names.append(":" + test_name)
+ analysis_test(
+ name = test_name,
+ impl = impl,
+ provider_subject_factories = FACTORIES,
+ targets = {label.name: label for label in targets},
+ )
+
+ native.test_suite(
+ name = name,
+ tests = test_case_names,
+ )
diff --git a/tests/rule_based_toolchain/args/BUILD b/tests/rule_based_toolchain/args/BUILD
new file mode 100644
index 0000000..585ec91
--- /dev/null
+++ b/tests/rule_based_toolchain/args/BUILD
@@ -0,0 +1,36 @@
+load("@rules_testing//lib:util.bzl", "util")
+load("//cc/toolchains:args.bzl", "cc_args")
+load("//tests/rule_based_toolchain:analysis_test_suite.bzl", "analysis_test_suite")
+load(":args_test.bzl", "TARGETS", "TESTS")
+
+util.helper_target(
+ cc_args,
+ name = "simple",
+ actions = ["//tests/rule_based_toolchain/actions:all_compile"],
+ args = [
+ "--foo",
+ "foo",
+ ],
+ data = [
+ "//tests/rule_based_toolchain/testdata:file1",
+ "//tests/rule_based_toolchain/testdata:multiple",
+ ],
+ env = {"BAR": "bar"},
+)
+
+util.helper_target(
+ cc_args,
+ name = "env_only",
+ actions = ["//tests/rule_based_toolchain/actions:all_compile"],
+ data = [
+ "//tests/rule_based_toolchain/testdata:file1",
+ "//tests/rule_based_toolchain/testdata:multiple",
+ ],
+ env = {"BAR": "bar"},
+)
+
+analysis_test_suite(
+ name = "test_suite",
+ targets = TARGETS,
+ tests = TESTS,
+)
diff --git a/tests/rule_based_toolchain/args/args_test.bzl b/tests/rule_based_toolchain/args/args_test.bzl
new file mode 100644
index 0000000..fbd4ce9
--- /dev/null
+++ b/tests/rule_based_toolchain/args/args_test.bzl
@@ -0,0 +1,113 @@
+# 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.
+"""Tests for the cc_args rule."""
+
+load(
+ "//cc:cc_toolchain_config_lib.bzl",
+ "env_entry",
+ "env_set",
+ "flag_group",
+ "flag_set",
+)
+load(
+ "//cc/toolchains:cc_toolchain_info.bzl",
+ "ActionTypeInfo",
+ "ArgsInfo",
+ "ArgsListInfo",
+)
+load(
+ "//cc/toolchains/impl:legacy_converter.bzl",
+ "convert_args",
+)
+load("//tests/rule_based_toolchain:subjects.bzl", "subjects")
+
+visibility("private")
+
+_SIMPLE_FILES = [
+ "tests/rule_based_toolchain/testdata/file1",
+ "tests/rule_based_toolchain/testdata/multiple1",
+ "tests/rule_based_toolchain/testdata/multiple2",
+]
+
+_CONVERTED_ARGS = subjects.struct(
+ flag_sets = subjects.collection,
+ env_sets = subjects.collection,
+)
+
+def _simple_test(env, targets):
+ simple = env.expect.that_target(targets.simple).provider(ArgsInfo)
+ simple.actions().contains_exactly([
+ targets.c_compile.label,
+ targets.cpp_compile.label,
+ ])
+ simple.env().contains_exactly({"BAR": "bar"})
+ simple.files().contains_exactly(_SIMPLE_FILES)
+
+ c_compile = env.expect.that_target(targets.simple).provider(ArgsListInfo).by_action().get(
+ targets.c_compile[ActionTypeInfo],
+ )
+ c_compile.args().contains_exactly([targets.simple[ArgsInfo]])
+ c_compile.files().contains_exactly(_SIMPLE_FILES)
+
+ converted = env.expect.that_value(
+ convert_args(targets.simple[ArgsInfo]),
+ factory = _CONVERTED_ARGS,
+ )
+ converted.env_sets().contains_exactly([env_set(
+ actions = ["c_compile", "cpp_compile"],
+ env_entries = [env_entry(key = "BAR", value = "bar")],
+ )])
+
+ converted.flag_sets().contains_exactly([flag_set(
+ actions = ["c_compile", "cpp_compile"],
+ flag_groups = [flag_group(flags = ["--foo", "foo"])],
+ )])
+
+def _env_only_test(env, targets):
+ env_only = env.expect.that_target(targets.env_only).provider(ArgsInfo)
+ env_only.actions().contains_exactly([
+ targets.c_compile.label,
+ targets.cpp_compile.label,
+ ])
+ env_only.env().contains_exactly({"BAR": "bar"})
+ env_only.files().contains_exactly(_SIMPLE_FILES)
+
+ c_compile = env.expect.that_target(targets.simple).provider(ArgsListInfo).by_action().get(
+ targets.c_compile[ActionTypeInfo],
+ )
+ c_compile.files().contains_exactly(_SIMPLE_FILES)
+
+ converted = env.expect.that_value(
+ convert_args(targets.env_only[ArgsInfo]),
+ factory = _CONVERTED_ARGS,
+ )
+ converted.env_sets().contains_exactly([env_set(
+ actions = ["c_compile", "cpp_compile"],
+ env_entries = [env_entry(key = "BAR", value = "bar")],
+ )])
+
+ converted.flag_sets().contains_exactly([])
+
+TARGETS = [
+ ":simple",
+ ":env_only",
+ "//tests/rule_based_toolchain/actions:c_compile",
+ "//tests/rule_based_toolchain/actions:cpp_compile",
+]
+
+# @unsorted-dict-items
+TESTS = {
+ "simple_test": _simple_test,
+ "env_only_test_test": _env_only_test,
+}
diff --git a/tests/rule_based_toolchain/args_list/BUILD b/tests/rule_based_toolchain/args_list/BUILD
new file mode 100644
index 0000000..9fc9f88
--- /dev/null
+++ b/tests/rule_based_toolchain/args_list/BUILD
@@ -0,0 +1,49 @@
+load("@rules_testing//lib:util.bzl", "util")
+load("//cc/toolchains:args.bzl", "cc_args")
+load("//cc/toolchains:args_list.bzl", "cc_args_list")
+load("//tests/rule_based_toolchain:analysis_test_suite.bzl", "analysis_test_suite")
+load(":args_list_test.bzl", "TARGETS", "TESTS")
+
+util.helper_target(
+ cc_args,
+ name = "c_compile_args",
+ actions = ["//tests/rule_based_toolchain/actions:c_compile"],
+ args = ["c"],
+ data = ["//tests/rule_based_toolchain/testdata:file1"],
+ visibility = ["//tests/rule_based_toolchain:__subpackages__"],
+)
+
+util.helper_target(
+ cc_args,
+ name = "cpp_compile_args",
+ actions = ["//tests/rule_based_toolchain/actions:cpp_compile"],
+ args = ["cpp"],
+ data = ["//tests/rule_based_toolchain/testdata:file2"],
+ visibility = ["//tests/rule_based_toolchain:__subpackages__"],
+)
+
+util.helper_target(
+ cc_args,
+ name = "all_compile_args",
+ actions = ["//tests/rule_based_toolchain/actions:all_compile"],
+ args = ["all"],
+ data = ["//tests/rule_based_toolchain/testdata:multiple1"],
+ visibility = ["//tests/rule_based_toolchain:__subpackages__"],
+)
+
+util.helper_target(
+ cc_args_list,
+ name = "args_list",
+ args = [
+ ":c_compile_args",
+ ":cpp_compile_args",
+ ":all_compile_args",
+ ],
+ visibility = ["//tests/rule_based_toolchain:__subpackages__"],
+)
+
+analysis_test_suite(
+ name = "test_suite",
+ targets = TARGETS,
+ tests = TESTS,
+)
diff --git a/tests/rule_based_toolchain/args_list/args_list_test.bzl b/tests/rule_based_toolchain/args_list/args_list_test.bzl
new file mode 100644
index 0000000..1d37145
--- /dev/null
+++ b/tests/rule_based_toolchain/args_list/args_list_test.bzl
@@ -0,0 +1,67 @@
+# 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.
+"""Tests for the cc_args rule."""
+
+load(
+ "//cc/toolchains:cc_toolchain_info.bzl",
+ "ActionTypeInfo",
+ "ArgsInfo",
+ "ArgsListInfo",
+)
+
+visibility("private")
+
+_C_COMPILE_FILE = "tests/rule_based_toolchain/testdata/file1"
+_CPP_COMPILE_FILE = "tests/rule_based_toolchain/testdata/file2"
+_BOTH_FILE = "tests/rule_based_toolchain/testdata/multiple1"
+
+def _collect_args_lists_test(env, targets):
+ args = env.expect.that_target(targets.args_list).provider(ArgsListInfo)
+ args.args().contains_exactly([
+ targets.c_compile_args.label,
+ targets.cpp_compile_args.label,
+ targets.all_compile_args.label,
+ ])
+ args.files().contains_exactly([
+ _C_COMPILE_FILE,
+ _CPP_COMPILE_FILE,
+ _BOTH_FILE,
+ ])
+
+ c_compile_action = args.by_action().get(targets.c_compile[ActionTypeInfo])
+ cpp_compile_action = args.by_action().get(targets.cpp_compile[ActionTypeInfo])
+
+ c_compile_action.files().contains_exactly([_C_COMPILE_FILE, _BOTH_FILE])
+ c_compile_action.args().contains_exactly([
+ targets.c_compile_args[ArgsInfo],
+ targets.all_compile_args[ArgsInfo],
+ ])
+ cpp_compile_action.files().contains_exactly([_CPP_COMPILE_FILE, _BOTH_FILE])
+ cpp_compile_action.args().contains_exactly([
+ targets.cpp_compile_args[ArgsInfo],
+ targets.all_compile_args[ArgsInfo],
+ ])
+
+TARGETS = [
+ ":c_compile_args",
+ ":cpp_compile_args",
+ ":all_compile_args",
+ ":args_list",
+ "//tests/rule_based_toolchain/actions:c_compile",
+ "//tests/rule_based_toolchain/actions:cpp_compile",
+]
+
+TESTS = {
+ "collect_args_lists_test": _collect_args_lists_test,
+}
diff --git a/tests/rule_based_toolchain/features/BUILD b/tests/rule_based_toolchain/features/BUILD
new file mode 100644
index 0000000..cc3c0c7
--- /dev/null
+++ b/tests/rule_based_toolchain/features/BUILD
@@ -0,0 +1,116 @@
+load("@rules_testing//lib:util.bzl", "util")
+load("//cc/toolchains:args.bzl", "cc_args")
+load("//cc/toolchains:feature.bzl", "cc_feature")
+load("//cc/toolchains:feature_constraint.bzl", "cc_feature_constraint")
+load("//cc/toolchains:feature_set.bzl", "cc_feature_set")
+load("//cc/toolchains:mutually_exclusive_category.bzl", "cc_mutually_exclusive_category")
+load("//cc/toolchains/impl:external_feature.bzl", "cc_external_feature")
+load("//tests/rule_based_toolchain:analysis_test_suite.bzl", "analysis_test_suite")
+load(":features_test.bzl", "TARGETS", "TESTS")
+
+util.helper_target(
+ cc_args,
+ name = "c_compile",
+ actions = ["//tests/rule_based_toolchain/actions:c_compile"],
+ args = ["c"],
+ data = ["//tests/rule_based_toolchain/testdata:file1"],
+)
+
+util.helper_target(
+ cc_feature,
+ name = "simple",
+ args = [":c_compile"],
+ enabled = False,
+ feature_name = "feature_name",
+ visibility = ["//tests/rule_based_toolchain:__subpackages__"],
+)
+
+util.helper_target(
+ cc_feature,
+ name = "simple2",
+ args = [":c_compile"],
+ enabled = False,
+ feature_name = "simple2",
+)
+
+util.helper_target(
+ cc_feature_set,
+ name = "feature_set",
+ all_of = [
+ ":simple",
+ ":simple2",
+ ],
+)
+
+util.helper_target(
+ cc_feature,
+ name = "requires",
+ args = [":c_compile"],
+ enabled = True,
+ feature_name = "requires",
+ requires_any_of = [":feature_set"],
+)
+
+util.helper_target(
+ cc_feature,
+ name = "implies",
+ args = [":c_compile"],
+ enabled = True,
+ feature_name = "implies",
+ implies = [":simple"],
+)
+
+cc_mutually_exclusive_category(
+ name = "category",
+)
+
+util.helper_target(
+ cc_feature,
+ name = "mutual_exclusion_feature",
+ args = [":c_compile"],
+ enabled = True,
+ feature_name = "mutual_exclusion",
+ mutually_exclusive = [
+ ":simple",
+ ":category",
+ ],
+)
+
+util.helper_target(
+ cc_feature_constraint,
+ name = "direct_constraint",
+ all_of = [":simple"],
+ none_of = [":simple2"],
+ visibility = ["//tests/rule_based_toolchain:__subpackages__"],
+)
+
+util.helper_target(
+ cc_feature_constraint,
+ name = "transitive_constraint",
+ all_of = [
+ ":direct_constraint",
+ ":requires",
+ ],
+ none_of = [":implies"],
+)
+
+util.helper_target(
+ cc_external_feature,
+ name = "builtin_feature",
+ feature_name = "builtin_feature",
+ overridable = True,
+)
+
+util.helper_target(
+ cc_feature,
+ name = "overrides",
+ args = [":c_compile"],
+ enabled = True,
+ overrides = ":builtin_feature",
+)
+
+analysis_test_suite(
+ name = "test_suite",
+ targets = TARGETS,
+ tests = TESTS,
+)
diff --git a/tests/rule_based_toolchain/features/features_test.bzl b/tests/rule_based_toolchain/features/features_test.bzl
new file mode 100644
index 0000000..2345cd7
--- /dev/null
+++ b/tests/rule_based_toolchain/features/features_test.bzl
@@ -0,0 +1,173 @@
+# 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.
+"""Tests for features for the rule based toolchain."""
+
+load(
+ "//cc:cc_toolchain_config_lib.bzl",
+ legacy_feature_set = "feature_set",
+ legacy_flag_group = "flag_group",
+ legacy_flag_set = "flag_set",
+)
+load(
+ "//cc/toolchains:cc_toolchain_info.bzl",
+ "ArgsInfo",
+ "FeatureConstraintInfo",
+ "FeatureInfo",
+ "FeatureSetInfo",
+ "MutuallyExclusiveCategoryInfo",
+)
+load(
+ "//cc/toolchains/impl:legacy_converter.bzl",
+ "convert_feature",
+ "convert_feature_constraint",
+)
+
+visibility("private")
+
+_C_COMPILE_FILE = "tests/rule_based_toolchain/testdata/file1"
+
+def _simple_feature_test(env, targets):
+ simple = env.expect.that_target(targets.simple).provider(FeatureInfo)
+ simple.name().equals("feature_name")
+ simple.args().args().contains_exactly([targets.c_compile.label])
+ simple.enabled().equals(False)
+ simple.overrides().is_none()
+ simple.overridable().equals(False)
+
+ simple.args().files().contains_exactly([_C_COMPILE_FILE])
+ c_compile_action = simple.args().by_action().get(
+ targets.c_compile[ArgsInfo].actions.to_list()[0],
+ )
+ c_compile_action.files().contains_exactly([_C_COMPILE_FILE])
+ c_compile_action.args().contains_exactly([targets.c_compile[ArgsInfo]])
+
+ legacy = convert_feature(simple.actual)
+ env.expect.that_str(legacy.name).equals("feature_name")
+ env.expect.that_bool(legacy.enabled).equals(False)
+ env.expect.that_collection(legacy.flag_sets).contains_exactly([
+ legacy_flag_set(
+ actions = ["c_compile"],
+ with_features = [],
+ flag_groups = [legacy_flag_group(flags = ["c"])],
+ ),
+ ])
+
+def _feature_collects_requirements_test(env, targets):
+ ft = env.expect.that_target(targets.requires).provider(FeatureInfo)
+ ft.requires_any_of().contains_exactly([
+ targets.feature_set.label,
+ ])
+
+ legacy = convert_feature(ft.actual)
+ env.expect.that_collection(legacy.requires).contains_exactly([
+ legacy_feature_set(features = ["feature_name", "simple2"]),
+ ])
+
+def _feature_collects_implies_test(env, targets):
+ env.expect.that_target(targets.implies).provider(
+ FeatureInfo,
+ ).implies().contains_exactly([
+ targets.simple.label,
+ ])
+
+def _feature_collects_mutual_exclusion_test(env, targets):
+ env.expect.that_target(targets.simple).provider(
+ MutuallyExclusiveCategoryInfo,
+ ).name().equals("feature_name")
+ env.expect.that_target(targets.mutual_exclusion_feature).provider(
+ FeatureInfo,
+ ).mutually_exclusive().contains_exactly([
+ targets.simple.label,
+ targets.category.label,
+ ])
+
+def _feature_set_collects_features_test(env, targets):
+ env.expect.that_target(targets.feature_set).provider(
+ FeatureSetInfo,
+ ).features().contains_exactly([
+ targets.simple.label,
+ targets.simple2.label,
+ ])
+
+def _feature_constraint_collects_direct_features_test(env, targets):
+ constraint = env.expect.that_target(targets.direct_constraint).provider(
+ FeatureConstraintInfo,
+ )
+ constraint.all_of().contains_exactly([targets.simple.label])
+ constraint.none_of().contains_exactly([targets.simple2.label])
+
+def _feature_constraint_collects_transitive_features_test(env, targets):
+ constraint = env.expect.that_target(targets.transitive_constraint).provider(
+ FeatureConstraintInfo,
+ )
+ constraint.all_of().contains_exactly([
+ targets.simple.label,
+ targets.requires.label,
+ ])
+ constraint.none_of().contains_exactly([
+ targets.simple2.label,
+ targets.implies.label,
+ ])
+
+ legacy = convert_feature_constraint(constraint.actual)
+ env.expect.that_collection(legacy.features).contains_exactly([
+ "feature_name",
+ "requires",
+ ])
+ env.expect.that_collection(legacy.not_features).contains_exactly([
+ "simple2",
+ "implies",
+ ])
+
+def _external_feature_is_a_feature_test(env, targets):
+ external_feature = env.expect.that_target(targets.builtin_feature).provider(
+ FeatureInfo,
+ )
+ external_feature.name().equals("builtin_feature")
+
+ # It's not a string, but we don't have a factory for the type.
+ env.expect.that_str(convert_feature(external_feature.actual)).equals(None)
+
+def _feature_can_be_overridden_test(env, targets):
+ overrides = env.expect.that_target(targets.overrides).provider(FeatureInfo)
+ overrides.name().equals("builtin_feature")
+ overrides.overrides().some().label().equals(targets.builtin_feature.label)
+
+TARGETS = [
+ ":builtin_feature",
+ ":c_compile",
+ ":category",
+ ":direct_constraint",
+ ":feature_set",
+ ":implies",
+ ":mutual_exclusion_feature",
+ ":overrides",
+ ":requires",
+ ":simple",
+ ":simple2",
+ ":transitive_constraint",
+]
+
+# @unsorted-dict-items
+TESTS = {
+ "simple_feature_test": _simple_feature_test,
+ "feature_collects_requirements_test": _feature_collects_requirements_test,
+ "feature_collects_implies_test": _feature_collects_implies_test,
+ "feature_collects_mutual_exclusion_test": _feature_collects_mutual_exclusion_test,
+ "feature_set_collects_features_test": _feature_set_collects_features_test,
+ "feature_constraint_collects_direct_features_test": _feature_constraint_collects_direct_features_test,
+ "feature_constraint_collects_transitive_features_test": _feature_constraint_collects_transitive_features_test,
+ "external_feature_is_a_feature_test": _external_feature_is_a_feature_test,
+ "feature_can_be_overridden_test": _feature_can_be_overridden_test,
+}
diff --git a/tests/rule_based_toolchain/generate_factory.bzl b/tests/rule_based_toolchain/generate_factory.bzl
new file mode 100644
index 0000000..c58bb51
--- /dev/null
+++ b/tests/rule_based_toolchain/generate_factory.bzl
@@ -0,0 +1,130 @@
+# 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.
+"""Generates provider factories."""
+
+load("@bazel_skylib//lib:structs.bzl", "structs")
+load("@rules_testing//lib:truth.bzl", "subjects")
+
+visibility("private")
+
+def generate_factory(type, name, attrs):
+ """Generates a factory for a custom struct.
+
+ There are three reasons we need to do so:
+ 1. It's very difficult to read providers printed by these types.
+ eg. If you have a 10 layer deep diamond dependency graph, and try to
+ print the top value, the bottom value will be printed 2^10 times.
+ 2. Collections of subjects are not well supported by rules_testing
+ eg. `FeatureInfo(flag_sets = [FlagSetInfo(...)])`
+ (You can do it, but the inner values are just regular bazel structs and
+ you can't do fluent assertions on them).
+ 3. Recursive types are not supported at all
+ eg. `FeatureInfo(implies = depset([FeatureInfo(...)]))`
+
+ To solve this, we create a factory that:
+ * Validates that the types of the children are correct.
+ * Inlines providers to their labels when unambiguous.
+
+ For example, given:
+
+ ```
+ foo = FeatureInfo(name = "foo", label = Label("//:foo"))
+ bar = FeatureInfo(..., implies = depset([foo]))
+ ```
+
+ It would convert itself a subject for the following struct:
+ `FeatureInfo(..., implies = depset([Label("//:foo")]))`
+
+ Args:
+ type: (type) The type to create a factory for (eg. FooInfo)
+ name: (str) The name of the type (eg. "FooInfo")
+ attrs: (dict[str, Factory]) The attributes associated with this type.
+
+ Returns:
+ A struct `FooFactory` suitable for use with
+ * `analysis_test(provider_subject_factories=[FooFactory])`
+ * `generate_factory(..., attrs=dict(foo = FooFactory))`
+ * `ProviderSequence(FooFactory)`
+ * `DepsetSequence(FooFactory)`
+ """
+ attrs["label"] = subjects.label
+
+ want_keys = sorted(attrs.keys())
+
+ def validate(*, value, meta):
+ if value == None:
+ meta.add_failure("Wanted a %s but got" % name, value)
+ got_keys = sorted(structs.to_dict(value).keys())
+ subjects.collection(got_keys, meta = meta.derive(details = [
+ "Value was not a %s - it has a different set of fields" % name,
+ ])).contains_exactly(want_keys).in_order()
+
+ def type_factory(value, *, meta):
+ validate(value = value, meta = meta)
+
+ transformed_value = {}
+ transformed_factories = {}
+ for field, factory in attrs.items():
+ field_value = getattr(value, field)
+
+ # If it's a type generated by generate_factory, inline it.
+ if hasattr(factory, "factory"):
+ factory.validate(value = field_value, meta = meta.derive(field))
+ transformed_value[field] = field_value.label
+ transformed_factories[field] = subjects.label
+ else:
+ transformed_value[field] = field_value
+ transformed_factories[field] = factory
+
+ return subjects.struct(
+ struct(**transformed_value),
+ meta = meta,
+ attrs = transformed_factories,
+ )
+
+ return struct(
+ type = type,
+ name = name,
+ factory = type_factory,
+ validate = validate,
+ )
+
+def _provider_collection(element_factory, fn):
+ def factory(value, *, meta):
+ value = fn(value)
+
+ # Validate that it really is the correct type
+ for i in range(len(value)):
+ element_factory.validate(
+ value = value[i],
+ meta = meta.derive("offset({})".format(i)),
+ )
+
+ # Inline the providers to just labels.
+ return subjects.collection([v.label for v in value], meta = meta)
+
+ return factory
+
+# This acts like a class, so we name it like one.
+# buildifier: disable=name-conventions
+ProviderSequence = lambda element_factory: _provider_collection(
+ element_factory,
+ fn = lambda x: list(x),
+)
+
+# buildifier: disable=name-conventions
+ProviderDepset = lambda element_factory: _provider_collection(
+ element_factory,
+ fn = lambda x: x.to_list(),
+)
diff --git a/tests/rule_based_toolchain/generics.bzl b/tests/rule_based_toolchain/generics.bzl
new file mode 100644
index 0000000..17bd3a6
--- /dev/null
+++ b/tests/rule_based_toolchain/generics.bzl
@@ -0,0 +1,139 @@
+# 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.
+"""Implementation of a result type for use with rules_testing."""
+
+load("@bazel_skylib//lib:structs.bzl", "structs")
+load("@rules_testing//lib:truth.bzl", "subjects")
+
+visibility("//tests/rule_based_toolchain/...")
+
+def result_fn_wrapper(fn):
+ """Wraps a function that may fail in a type similar to rust's Result type.
+
+ An example usage is the following:
+ # Implementation file
+ def get_only(value, fail=fail):
+ if len(value) == 1:
+ return value[0]
+ elif not value:
+ fail("Unexpectedly empty")
+ else:
+ fail("%r had length %d, expected 1" % (value, len(value))
+
+ # Test file
+ load("...", _fn=fn)
+
+ fn = result_fn_wrapper(_fn)
+ int_result = result_subject(subjects.int)
+
+ def my_test(env, _):
+ env.expect.that_value(fn([]), factory=int_result)
+ .err().equals("Unexpectedly empty")
+ env.expect.that_value(fn([1]), factory=int_result)
+ .ok().equals(1)
+ env.expect.that_value(fn([1, 2]), factory=int_result)
+ .err().contains("had length 2, expected 1")
+
+ Args:
+ fn: A function that takes in a parameter fail and calls it on failure.
+
+ Returns:
+ On success: struct(ok = <result>, err = None)
+ On failure: struct(ok = None, err = <first error message>
+ """
+
+ def new_fn(*args, **kwargs):
+ # Use a mutable type so that the fail_wrapper can modify this.
+ failures = []
+
+ def fail_wrapper(msg):
+ failures.append(msg)
+
+ result = fn(fail = fail_wrapper, *args, **kwargs)
+ if failures:
+ return struct(ok = None, err = failures[0])
+ else:
+ return struct(ok = result, err = None)
+
+ return new_fn
+
+def result_subject(factory):
+ """A subject factory for Result<T>.
+
+ Args:
+ factory: A subject factory for T
+ Returns:
+ A subject factory for Result<T>
+ """
+
+ def new_factory(value, *, meta):
+ def ok():
+ if value.err != None:
+ meta.add_failure("Wanted a value, but got an error", value.err)
+ return factory(value.ok, meta = meta.derive("ok()"))
+
+ def err():
+ if value.err == None:
+ meta.add_failure("Wanted an error, but got a value", value.ok)
+ subject = subjects.str(value.err, meta = meta.derive("err()"))
+
+ def contains_all_of(values):
+ for value in values:
+ subject.contains(str(value))
+
+ return struct(contains_all_of = contains_all_of, **structs.to_dict(subject))
+
+ return struct(ok = ok, err = err)
+
+ return new_factory
+
+def optional_subject(factory):
+ """A subject factory for Optional<T>.
+
+ Args:
+ factory: A subject factory for T
+ Returns:
+ A subject factory for Optional<T>
+ """
+
+ def new_factory(value, *, meta):
+ def some():
+ if value == None:
+ meta.add_failure("Wanted a value, but got None", None)
+ return factory(value, meta = meta)
+
+ def is_none():
+ if value != None:
+ meta.add_failure("Wanted None, but got a value", value)
+
+ return struct(some = some, is_none = is_none)
+
+ return new_factory
+
+# Curry subjects.struct so the type is actually generic.
+struct_subject = lambda **attrs: lambda value, *, meta: subjects.struct(
+ value,
+ meta = meta,
+ attrs = attrs,
+)
+
+# We can't do complex assertions on containers. This allows you to write
+# assert.that_value({"foo": 1), factory=dict_key_subject(subjects.int))
+# .get("foo").equals(1)
+dict_key_subject = lambda factory: lambda value, *, meta: struct(
+ get = lambda key: factory(
+ value[key],
+ meta = meta.derive("get({})".format(key)),
+ ),
+)
diff --git a/tests/rule_based_toolchain/nested_args/BUILD b/tests/rule_based_toolchain/nested_args/BUILD
new file mode 100644
index 0000000..30e75ed
--- /dev/null
+++ b/tests/rule_based_toolchain/nested_args/BUILD
@@ -0,0 +1,14 @@
+load("//cc/toolchains/impl:variables.bzl", "cc_variable", "types")
+load("//tests/rule_based_toolchain:analysis_test_suite.bzl", "analysis_test_suite")
+load(":nested_args_test.bzl", "TARGETS", "TESTS")
+
+cc_variable(
+ name = "foo",
+ type = types.string,
+)
+
+analysis_test_suite(
+ name = "test_suite",
+ targets = TARGETS,
+ tests = TESTS,
+)
diff --git a/tests/rule_based_toolchain/nested_args/nested_args_test.bzl b/tests/rule_based_toolchain/nested_args/nested_args_test.bzl
new file mode 100644
index 0000000..96a361c
--- /dev/null
+++ b/tests/rule_based_toolchain/nested_args/nested_args_test.bzl
@@ -0,0 +1,205 @@
+# 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.
+"""Tests for the cc_args rule."""
+
+load("//cc:cc_toolchain_config_lib.bzl", "flag_group", "variable_with_value")
+load("//cc/toolchains:cc_toolchain_info.bzl", "VariableInfo")
+load("//cc/toolchains:format.bzl", "format_arg")
+load(
+ "//cc/toolchains/impl:nested_args.bzl",
+ "FORMAT_ARGS_ERR",
+ "REQUIRES_EQUAL_ERR",
+ "REQUIRES_MUTUALLY_EXCLUSIVE_ERR",
+ "REQUIRES_NONE_ERR",
+ "format_string_indexes",
+ "format_variable",
+ "nested_args_provider",
+ "raw_string",
+)
+load("//tests/rule_based_toolchain:subjects.bzl", "result_fn_wrapper", "subjects")
+
+visibility("private")
+
+def _expect_that_nested(env, expr = None, **kwargs):
+ return env.expect.that_value(
+ expr = expr,
+ value = result_fn_wrapper(nested_args_provider)(
+ label = Label("//:args"),
+ **kwargs
+ ),
+ factory = subjects.result(subjects.NestedArgsInfo),
+ )
+
+def _expect_that_formatted(env, var, iterate_over = None, expr = None):
+ return env.expect.that_value(
+ result_fn_wrapper(format_variable)(var, iterate_over),
+ factory = subjects.result(subjects.str),
+ expr = expr or "format_variable(var=%r, iterate_over=%r" % (var, iterate_over),
+ )
+
+def _expect_that_format_string_indexes(env, var, expr = None):
+ return env.expect.that_value(
+ result_fn_wrapper(format_string_indexes)(var),
+ factory = subjects.result(subjects.collection),
+ expr = expr or "format_string_indexes(%r)" % var,
+ )
+
+def _format_string_indexes_test(env, _):
+ _expect_that_format_string_indexes(env, "foo").ok().contains_exactly([])
+ _expect_that_format_string_indexes(env, "%%").ok().contains_exactly([])
+ _expect_that_format_string_indexes(env, "%").err().equals(
+ '% should always either of the form %s, or escaped with %%. Instead, got "%"',
+ )
+ _expect_that_format_string_indexes(env, "%a").err().equals(
+ '% should always either of the form %s, or escaped with %%. Instead, got "%a"',
+ )
+ _expect_that_format_string_indexes(env, "%s").ok().contains_exactly([0])
+ _expect_that_format_string_indexes(env, "%%%s%s").ok().contains_exactly([2, 4])
+ _expect_that_format_string_indexes(env, "%%{").ok().contains_exactly([])
+ _expect_that_format_string_indexes(env, "%%s").ok().contains_exactly([])
+ _expect_that_format_string_indexes(env, "%{foo}").err().equals(
+ 'Using the old mechanism for variables, %{variable}, but we instead use format_arg("--foo=%s", "//cc/toolchains/variables:<variable>"). Got "%{foo}"',
+ )
+
+def _formats_raw_strings_test(env, _):
+ _expect_that_formatted(
+ env,
+ raw_string("foo"),
+ ).ok().equals("foo")
+ _expect_that_formatted(
+ env,
+ raw_string("%s"),
+ ).err().contains("Can't use %s with a raw string. Either escape it with %%s or use format_arg")
+
+def _formats_variables_test(env, targets):
+ _expect_that_formatted(
+ env,
+ format_arg("ab %s cd", targets.foo[VariableInfo]),
+ ).ok().equals("ab %{foo} cd")
+
+ _expect_that_formatted(
+ env,
+ format_arg("foo", targets.foo[VariableInfo]),
+ ).err().equals('format_arg requires a "%s" in the format string, but got "foo"')
+ _expect_that_formatted(
+ env,
+ format_arg("%s%s", targets.foo[VariableInfo]),
+ ).err().equals('Only one %s can be used in a format string, but got "%s%s"')
+
+ _expect_that_formatted(
+ env,
+ format_arg("%s"),
+ iterate_over = "foo",
+ ).ok().equals("%{foo}")
+ _expect_that_formatted(
+ env,
+ format_arg("%s"),
+ ).err().contains("format_arg requires either a variable to format, or iterate_over must be provided")
+
+def _iterate_over_test(env, _):
+ inner = _expect_that_nested(
+ env,
+ args = [raw_string("--foo")],
+ ).ok().actual
+ env.expect.that_str(inner.legacy_flag_group).equals(flag_group(flags = ["--foo"]))
+
+ nested = _expect_that_nested(
+ env,
+ nested = [inner],
+ iterate_over = "my_list",
+ ).ok()
+ nested.iterate_over().some().equals("my_list")
+ nested.legacy_flag_group().equals(flag_group(
+ iterate_over = "my_list",
+ flag_groups = [inner.legacy_flag_group],
+ ))
+ nested.requires_types().contains_exactly({})
+
+def _requires_types_test(env, targets):
+ _expect_that_nested(
+ env,
+ requires_not_none = "abc",
+ requires_none = "def",
+ args = [raw_string("--foo")],
+ expr = "mutually_exclusive",
+ ).err().equals(REQUIRES_MUTUALLY_EXCLUSIVE_ERR)
+
+ _expect_that_nested(
+ env,
+ requires_none = "var",
+ args = [raw_string("--foo")],
+ expr = "requires_none",
+ ).ok().requires_types().contains_exactly(
+ {"var": [struct(
+ msg = REQUIRES_NONE_ERR,
+ valid_types = ["option"],
+ after_option_unwrap = False,
+ )]},
+ )
+
+ _expect_that_nested(
+ env,
+ args = [raw_string("foo %s baz")],
+ expr = "no_variable",
+ ).err().contains("Can't use %s with a raw string")
+
+ _expect_that_nested(
+ env,
+ args = [format_arg("foo %s baz", targets.foo[VariableInfo])],
+ expr = "type_validation",
+ ).ok().requires_types().contains_exactly(
+ {"foo": [struct(
+ msg = FORMAT_ARGS_ERR,
+ valid_types = ["string", "file", "directory"],
+ after_option_unwrap = True,
+ )]},
+ )
+
+ nested = _expect_that_nested(
+ env,
+ requires_equal = "foo",
+ requires_equal_value = "value",
+ args = [format_arg("--foo=%s", targets.foo[VariableInfo])],
+ expr = "type_and_requires_equal_validation",
+ ).ok()
+ nested.requires_types().contains_exactly(
+ {"foo": [
+ struct(
+ msg = REQUIRES_EQUAL_ERR,
+ valid_types = ["string"],
+ after_option_unwrap = True,
+ ),
+ struct(
+ msg = FORMAT_ARGS_ERR,
+ valid_types = ["string", "file", "directory"],
+ after_option_unwrap = True,
+ ),
+ ]},
+ )
+ nested.legacy_flag_group().equals(flag_group(
+ expand_if_equal = variable_with_value(name = "foo", value = "value"),
+ flags = ["--foo=%{foo}"],
+ ))
+
+TARGETS = [
+ ":foo",
+]
+
+TESTS = {
+ "format_string_indexes_test": _format_string_indexes_test,
+ "formats_raw_strings_test": _formats_raw_strings_test,
+ "formats_variables_test": _formats_variables_test,
+ "iterate_over_test": _iterate_over_test,
+ "requires_types_test": _requires_types_test,
+}
diff --git a/tests/rule_based_toolchain/subjects.bzl b/tests/rule_based_toolchain/subjects.bzl
new file mode 100644
index 0000000..f42d5d7
--- /dev/null
+++ b/tests/rule_based_toolchain/subjects.bzl
@@ -0,0 +1,247 @@
+# 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.
+"""Test subjects for cc_toolchain_info providers."""
+
+load("@bazel_skylib//lib:structs.bzl", "structs")
+load("@rules_testing//lib:truth.bzl", _subjects = "subjects")
+load(
+ "//cc/toolchains:cc_toolchain_info.bzl",
+ "ActionTypeConfigInfo",
+ "ActionTypeConfigSetInfo",
+ "ActionTypeInfo",
+ "ActionTypeSetInfo",
+ "ArgsInfo",
+ "ArgsListInfo",
+ "FeatureConstraintInfo",
+ "FeatureInfo",
+ "FeatureSetInfo",
+ "MutuallyExclusiveCategoryInfo",
+ "NestedArgsInfo",
+ "ToolInfo",
+ "ToolchainConfigInfo",
+)
+load(":generate_factory.bzl", "ProviderDepset", "ProviderSequence", "generate_factory")
+load(":generics.bzl", "dict_key_subject", "optional_subject", "result_subject", "struct_subject", _result_fn_wrapper = "result_fn_wrapper")
+
+visibility("//tests/rule_based_toolchain/...")
+
+# The default runfiles subject uses path instead of short_path.
+# This makes it rather awkward for copybara.
+runfiles_subject = lambda value, meta: _subjects.depset_file(value.files, meta = meta)
+
+# The string type has .equals(), which is all we can really do for an unknown
+# type.
+unknown_subject = _subjects.str
+
+# buildifier: disable=name-conventions
+_ActionTypeFactory = generate_factory(
+ ActionTypeInfo,
+ "ActionTypeInfo",
+ dict(
+ name = _subjects.str,
+ ),
+)
+
+# buildifier: disable=name-conventions
+_ActionTypeSetFactory = generate_factory(
+ ActionTypeSetInfo,
+ "ActionTypeInfo",
+ dict(
+ actions = ProviderDepset(_ActionTypeFactory),
+ ),
+)
+
+# buildifier: disable=name-conventions
+_MutuallyExclusiveCategoryFactory = generate_factory(
+ MutuallyExclusiveCategoryInfo,
+ "MutuallyExclusiveCategoryInfo",
+ dict(name = _subjects.str),
+)
+
+_FEATURE_FLAGS = dict(
+ name = _subjects.str,
+ enabled = _subjects.bool,
+ args = None,
+ implies = None,
+ requires_any_of = None,
+ mutually_exclusive = ProviderSequence(_MutuallyExclusiveCategoryFactory),
+ overridable = _subjects.bool,
+ external = _subjects.bool,
+ overrides = None,
+)
+
+# Break the dependency loop.
+# buildifier: disable=name-conventions
+_FakeFeatureFactory = generate_factory(
+ FeatureInfo,
+ "FeatureInfo",
+ _FEATURE_FLAGS,
+)
+
+# buildifier: disable=name-conventions
+_FeatureSetFactory = generate_factory(
+ FeatureSetInfo,
+ "FeatureSetInfo",
+ dict(features = ProviderDepset(_FakeFeatureFactory)),
+)
+
+# buildifier: disable=name-conventions
+_FeatureConstraintFactory = generate_factory(
+ FeatureConstraintInfo,
+ "FeatureConstraintInfo",
+ dict(
+ all_of = ProviderDepset(_FakeFeatureFactory),
+ none_of = ProviderDepset(_FakeFeatureFactory),
+ ),
+)
+
+_NESTED_ARGS_FLAGS = dict(
+ nested = None,
+ files = _subjects.depset_file,
+ iterate_over = optional_subject(_subjects.str),
+ legacy_flag_group = unknown_subject,
+ requires_types = _subjects.dict,
+ unwrap_options = _subjects.collection,
+)
+
+# buildifier: disable=name-conventions
+_FakeNestedArgsFactory = generate_factory(
+ NestedArgsInfo,
+ "NestedArgsInfo",
+ _NESTED_ARGS_FLAGS,
+)
+
+# buildifier: disable=name-conventions
+_NestedArgsFactory = generate_factory(
+ NestedArgsInfo,
+ "NestedArgsInfo",
+ _NESTED_ARGS_FLAGS | dict(
+ nested = ProviderSequence(_FakeNestedArgsFactory),
+ ),
+)
+
+# buildifier: disable=name-conventions
+_ArgsFactory = generate_factory(
+ ArgsInfo,
+ "ArgsInfo",
+ dict(
+ actions = ProviderDepset(_ActionTypeFactory),
+ env = _subjects.dict,
+ files = _subjects.depset_file,
+ # Use .factory so it's not inlined.
+ nested = optional_subject(_NestedArgsFactory.factory),
+ requires_any_of = ProviderSequence(_FeatureConstraintFactory),
+ ),
+)
+
+# buildifier: disable=name-conventions
+_ArgsListFactory = generate_factory(
+ ArgsListInfo,
+ "ArgsListInfo",
+ dict(
+ args = ProviderSequence(_ArgsFactory),
+ by_action = lambda values, *, meta: dict_key_subject(struct_subject(
+ args = _subjects.collection,
+ files = _subjects.depset_file,
+ ))({value.action: value for value in values}, meta = meta),
+ files = _subjects.depset_file,
+ ),
+)
+
+# buildifier: disable=name-conventions
+_FeatureFactory = generate_factory(
+ FeatureInfo,
+ "FeatureInfo",
+ _FEATURE_FLAGS | dict(
+ # Use .factory so it's not inlined.
+ args = _ArgsListFactory.factory,
+ implies = ProviderDepset(_FakeFeatureFactory),
+ requires_any_of = ProviderSequence(_FeatureSetFactory),
+ overrides = optional_subject(_FakeFeatureFactory.factory),
+ ),
+)
+
+# buildifier: disable=name-conventions
+_ToolFactory = generate_factory(
+ ToolInfo,
+ "ToolInfo",
+ dict(
+ exe = _subjects.file,
+ runfiles = runfiles_subject,
+ requires_any_of = ProviderSequence(_FeatureConstraintFactory),
+ execution_requirements = _subjects.collection,
+ ),
+)
+
+# buildifier: disable=name-conventions
+_ActionTypeConfigFactory = generate_factory(
+ ActionTypeConfigInfo,
+ "ActionTypeConfigInfo",
+ dict(
+ action_type = _ActionTypeFactory,
+ tools = ProviderSequence(_ToolFactory),
+ args = ProviderSequence(_ArgsFactory),
+ implies = ProviderDepset(_FeatureFactory),
+ files = runfiles_subject,
+ ),
+)
+
+# buildifier: disable=name-conventions
+_ActionTypeConfigSetFactory = generate_factory(
+ ActionTypeConfigSetInfo,
+ "ActionTypeConfigSetInfo",
+ dict(
+ configs = dict_key_subject(_ActionTypeConfigFactory.factory),
+ ),
+)
+
+# buildifier: disable=name-conventions
+_ToolchainConfigFactory = generate_factory(
+ ToolchainConfigInfo,
+ "ToolchainConfigInfo",
+ dict(
+ features = ProviderDepset(_FeatureFactory),
+ action_type_configs = dict_key_subject(_ActionTypeConfigFactory.factory),
+ args = ProviderSequence(_ArgsFactory),
+ files = dict_key_subject(_subjects.depset_file),
+ ),
+)
+
+FACTORIES = [
+ _ActionTypeFactory,
+ _ActionTypeSetFactory,
+ _NestedArgsFactory,
+ _ArgsFactory,
+ _ArgsListFactory,
+ _MutuallyExclusiveCategoryFactory,
+ _FeatureFactory,
+ _FeatureConstraintFactory,
+ _FeatureSetFactory,
+ _ToolFactory,
+ _ActionTypeConfigSetFactory,
+ _ToolchainConfigFactory,
+]
+
+result_fn_wrapper = _result_fn_wrapper
+
+subjects = struct(
+ **(structs.to_dict(_subjects) | dict(
+ unknown = unknown_subject,
+ result = result_subject,
+ optional = optional_subject,
+ struct = struct_subject,
+ runfiles = runfiles_subject,
+ dict_key = dict_key_subject,
+ ) | {factory.name: factory.factory for factory in FACTORIES})
+)
diff --git a/tests/rule_based_toolchain/testdata/BUILD b/tests/rule_based_toolchain/testdata/BUILD
new file mode 100644
index 0000000..4bfb3e6
--- /dev/null
+++ b/tests/rule_based_toolchain/testdata/BUILD
@@ -0,0 +1,32 @@
+load("@bazel_skylib//rules:native_binary.bzl", "native_binary")
+
+package(default_visibility = ["//tests/rule_based_toolchain:__subpackages__"])
+
+exports_files(
+ glob(
+ ["*"],
+ exclude = ["BUILD"],
+ ),
+)
+
+native_binary(
+ name = "bin_wrapper",
+ src = "bin_wrapper.sh",
+ out = "bin_wrapper",
+ data = [":bin"],
+)
+
+filegroup(
+ name = "multiple",
+ srcs = [
+ "multiple1",
+ "multiple2",
+ ],
+)
+
+# Analysis_test is unable to depend on source files directly, but it can depend
+# on a filegroup containing a single file.
+filegroup(
+ name = "bin_filegroup",
+ srcs = ["bin"],
+)
diff --git a/tests/rule_based_toolchain/testdata/bin b/tests/rule_based_toolchain/testdata/bin
new file mode 100755
index 0000000..ff29c83
--- /dev/null
+++ b/tests/rule_based_toolchain/testdata/bin
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+echo "Running unwrapped tool"
diff --git a/tests/rule_based_toolchain/testdata/bin_wrapper.sh b/tests/rule_based_toolchain/testdata/bin_wrapper.sh
new file mode 100755
index 0000000..ce615a2
--- /dev/null
+++ b/tests/rule_based_toolchain/testdata/bin_wrapper.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+echo "Running tool wrapper"
diff --git a/tests/rule_based_toolchain/testdata/file1 b/tests/rule_based_toolchain/testdata/file1
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/rule_based_toolchain/testdata/file1
diff --git a/tests/rule_based_toolchain/testdata/file2 b/tests/rule_based_toolchain/testdata/file2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/rule_based_toolchain/testdata/file2
diff --git a/tests/rule_based_toolchain/testdata/multiple1 b/tests/rule_based_toolchain/testdata/multiple1
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/rule_based_toolchain/testdata/multiple1
diff --git a/tests/rule_based_toolchain/testdata/multiple2 b/tests/rule_based_toolchain/testdata/multiple2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/rule_based_toolchain/testdata/multiple2
diff --git a/tests/rule_based_toolchain/tool/BUILD b/tests/rule_based_toolchain/tool/BUILD
new file mode 100644
index 0000000..d16ded6
--- /dev/null
+++ b/tests/rule_based_toolchain/tool/BUILD
@@ -0,0 +1,26 @@
+load("@rules_testing//lib:util.bzl", "util")
+load("//cc/toolchains:tool.bzl", "cc_tool")
+load("//tests/rule_based_toolchain:analysis_test_suite.bzl", "analysis_test_suite")
+load(":tool_test.bzl", "TARGETS", "TESTS")
+
+util.helper_target(
+ cc_tool,
+ name = "tool",
+ src = "//tests/rule_based_toolchain/testdata:bin_wrapper.sh",
+ data = ["//tests/rule_based_toolchain/testdata:bin"],
+ execution_requirements = ["requires-network"],
+ requires_any_of = ["//tests/rule_based_toolchain/features:direct_constraint"],
+)
+
+util.helper_target(
+ cc_tool,
+ name = "wrapped_tool",
+ src = "//tests/rule_based_toolchain/testdata:bin_wrapper",
+ visibility = ["//tests/rule_based_toolchain:__subpackages__"],
+)
+
+analysis_test_suite(
+ name = "test_suite",
+ targets = TARGETS,
+ tests = TESTS,
+)
diff --git a/tests/rule_based_toolchain/tool/tool_test.bzl b/tests/rule_based_toolchain/tool/tool_test.bzl
new file mode 100644
index 0000000..8e9b68a
--- /dev/null
+++ b/tests/rule_based_toolchain/tool/tool_test.bzl
@@ -0,0 +1,122 @@
+# 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.
+"""Tests for the cc_args rule."""
+
+load(
+ "//cc:cc_toolchain_config_lib.bzl",
+ legacy_with_feature_set = "with_feature_set",
+)
+load("//cc/toolchains:cc_toolchain_info.bzl", "ToolInfo")
+load("//cc/toolchains/impl:collect.bzl", _collect_tools = "collect_tools")
+load("//cc/toolchains/impl:legacy_converter.bzl", "convert_tool")
+load("//tests/rule_based_toolchain:subjects.bzl", "result_fn_wrapper", "subjects")
+
+visibility("private")
+
+collect_tools = result_fn_wrapper(_collect_tools)
+collection_result = subjects.result(subjects.collection)
+
+collect_tool = result_fn_wrapper(
+ lambda ctx, target, fail: _collect_tools(ctx, [target], fail = fail)[0],
+)
+tool_result = subjects.result(subjects.ToolInfo)
+
+# Generated by native_binary.
+_BIN_WRAPPER_SYMLINK = "tests/rule_based_toolchain/testdata/bin_wrapper"
+_BIN_WRAPPER = "tests/rule_based_toolchain/testdata/bin_wrapper.sh"
+_BIN = "tests/rule_based_toolchain/testdata/bin"
+
+def _tool_test(env, targets):
+ tool = env.expect.that_target(targets.tool).provider(ToolInfo)
+ tool.exe().short_path_equals(_BIN_WRAPPER)
+ tool.execution_requirements().contains_exactly(["requires-network"])
+ tool.runfiles().contains_exactly([
+ _BIN_WRAPPER,
+ _BIN,
+ ])
+ tool.requires_any_of().contains_exactly([targets.direct_constraint.label])
+
+ legacy = convert_tool(tool.actual)
+ env.expect.that_file(legacy.tool).equals(tool.actual.exe)
+ env.expect.that_collection(legacy.execution_requirements).contains_exactly(["requires-network"])
+ env.expect.that_collection(legacy.with_features).contains_exactly([
+ legacy_with_feature_set(
+ features = ["feature_name"],
+ not_features = ["simple2"],
+ ),
+ ])
+
+def _wrapped_tool_includes_runfiles_test(env, targets):
+ tool = env.expect.that_target(targets.wrapped_tool).provider(ToolInfo)
+ tool.exe().short_path_equals(_BIN_WRAPPER_SYMLINK)
+ tool.runfiles().contains_exactly([
+ _BIN_WRAPPER_SYMLINK,
+ _BIN,
+ ])
+
+def _collect_tools_collects_tools_test(env, targets):
+ env.expect.that_value(
+ value = collect_tools(env.ctx, [targets.tool, targets.wrapped_tool]),
+ factory = collection_result,
+ ).ok().contains_exactly(
+ [targets.tool[ToolInfo], targets.wrapped_tool[ToolInfo]],
+ ).in_order()
+
+def _collect_tools_collects_binaries_test(env, targets):
+ tool_wrapper = env.expect.that_value(
+ value = collect_tool(env.ctx, targets.bin_wrapper),
+ factory = tool_result,
+ ).ok()
+ tool_wrapper.label().equals(targets.bin_wrapper.label)
+ tool_wrapper.exe().short_path_equals(_BIN_WRAPPER_SYMLINK)
+ tool_wrapper.runfiles().contains_exactly([
+ _BIN_WRAPPER_SYMLINK,
+ _BIN,
+ ])
+
+def _collect_tools_collects_single_files_test(env, targets):
+ bin = env.expect.that_value(
+ value = collect_tool(env.ctx, targets.bin_filegroup),
+ factory = tool_result,
+ expr = "bin_filegroup",
+ ).ok()
+ bin.label().equals(targets.bin_filegroup.label)
+ bin.exe().short_path_equals(_BIN)
+ bin.runfiles().contains_exactly([_BIN])
+
+def _collect_tools_fails_on_non_binary_test(env, targets):
+ env.expect.that_value(
+ value = collect_tools(env.ctx, [targets.multiple]),
+ factory = collection_result,
+ expr = "multiple_non_binary",
+ ).err()
+
+TARGETS = [
+ "//tests/rule_based_toolchain/features:direct_constraint",
+ "//tests/rule_based_toolchain/tool:tool",
+ "//tests/rule_based_toolchain/tool:wrapped_tool",
+ "//tests/rule_based_toolchain/testdata:bin_wrapper",
+ "//tests/rule_based_toolchain/testdata:multiple",
+ "//tests/rule_based_toolchain/testdata:bin_filegroup",
+]
+
+# @unsorted-dict-items
+TESTS = {
+ "tool_test": _tool_test,
+ "wrapped_tool_includes_runfiles_test": _wrapped_tool_includes_runfiles_test,
+ "collect_tools_collects_tools_test": _collect_tools_collects_tools_test,
+ "collect_tools_collects_binaries_test": _collect_tools_collects_binaries_test,
+ "collect_tools_collects_single_files_test": _collect_tools_collects_single_files_test,
+ "collect_tools_fails_on_non_binary_test": _collect_tools_fails_on_non_binary_test,
+}
diff --git a/tests/rule_based_toolchain/toolchain_config/BUILD b/tests/rule_based_toolchain/toolchain_config/BUILD
new file mode 100644
index 0000000..0068963
--- /dev/null
+++ b/tests/rule_based_toolchain/toolchain_config/BUILD
@@ -0,0 +1,197 @@
+load("@rules_testing//lib:util.bzl", "util")
+load("//cc/toolchains:action_type_config.bzl", "cc_action_type_config")
+load("//cc/toolchains:args.bzl", "cc_args")
+load("//cc/toolchains:feature.bzl", "cc_feature")
+load("//cc/toolchains:feature_set.bzl", "cc_feature_set")
+load("//cc/toolchains:tool.bzl", "cc_tool")
+load("//cc/toolchains/impl:external_feature.bzl", "cc_external_feature")
+load("//cc/toolchains/impl:toolchain_config.bzl", "cc_legacy_file_group", "cc_toolchain_config")
+load("//tests/rule_based_toolchain:analysis_test_suite.bzl", "analysis_test_suite")
+load(":toolchain_config_test.bzl", "TARGETS", "TESTS")
+
+util.helper_target(
+ cc_feature_set,
+ name = "all_simple_features",
+ all_of = [
+ ":simple_feature",
+ "simple_feature2",
+ ],
+)
+
+util.helper_target(
+ cc_external_feature,
+ name = "builtin_feature",
+ feature_name = "builtin_feature",
+ overridable = True,
+)
+
+util.helper_target(
+ cc_args,
+ name = "c_compile_args",
+ actions = ["//tests/rule_based_toolchain/actions:c_compile"],
+ args = ["c_compile_args"],
+ data = ["//tests/rule_based_toolchain/testdata:file1"],
+)
+
+util.helper_target(
+ cc_args,
+ name = "cpp_compile_args",
+ actions = ["//tests/rule_based_toolchain/actions:cpp_compile"],
+ args = ["cpp_compile_args"],
+ env = {"CPP_COMPILE": "1"},
+)
+
+util.helper_target(
+ cc_toolchain_config,
+ name = "collects_files_toolchain_config",
+ action_type_configs = [":compile_config"],
+ args = [":c_compile_args"],
+ compiler = "gcc-4.1.1",
+ skip_experimental_flag_validation_for_test = True,
+ target_cpu = "k8",
+ target_libc = "glibc-2.2.2",
+ target_system_name = "local",
+ toolchain_features = [":compile_feature"],
+)
+
+util.helper_target(
+ cc_legacy_file_group,
+ name = "collects_files_c_compile",
+ actions = ["//tests/rule_based_toolchain/actions:c_compile"],
+ config = ":collects_files_toolchain_config",
+)
+
+util.helper_target(
+ cc_legacy_file_group,
+ name = "collects_files_cpp_compile",
+ actions = ["//tests/rule_based_toolchain/actions:cpp_compile"],
+ config = ":collects_files_toolchain_config",
+)
+
+util.helper_target(
+ cc_args,
+ name = "compile_args",
+ actions = ["//tests/rule_based_toolchain/actions:all_compile"],
+ args = ["compile_args"],
+ data = ["//tests/rule_based_toolchain/testdata:file2"],
+)
+
+util.helper_target(
+ cc_action_type_config,
+ name = "compile_config",
+ action_types = ["//tests/rule_based_toolchain/actions:all_compile"],
+ args = [":cpp_compile_args"],
+ tools = [
+ "//tests/rule_based_toolchain/tool:wrapped_tool",
+ ],
+)
+
+util.helper_target(
+ cc_feature,
+ name = "compile_feature",
+ args = [":compile_args"],
+ enabled = True,
+ feature_name = "compile_feature",
+)
+
+util.helper_target(
+ cc_action_type_config,
+ name = "c_compile_config",
+ action_types = ["//tests/rule_based_toolchain/actions:c_compile"],
+ implies = [":simple_feature"],
+ tools = [
+ "//tests/rule_based_toolchain/tool:wrapped_tool",
+ ],
+)
+
+util.helper_target(
+ cc_feature,
+ name = "implies_simple_feature",
+ args = [":c_compile_args"],
+ enabled = True,
+ feature_name = "implies",
+ implies = [":simple_feature"],
+)
+
+util.helper_target(
+ cc_feature,
+ name = "overrides_feature",
+ args = [":c_compile_args"],
+ enabled = True,
+ overrides = ":builtin_feature",
+)
+
+util.helper_target(
+ cc_args,
+ name = "requires_all_simple_args",
+ actions = ["//tests/rule_based_toolchain/actions:c_compile"],
+ args = ["--foo"],
+ requires_any_of = [":all_simple_features"],
+)
+
+util.helper_target(
+ cc_feature,
+ name = "requires_all_simple_feature",
+ args = [":c_compile_args"],
+ enabled = True,
+ feature_name = "requires_any_simple",
+ requires_any_of = [":all_simple_features"],
+)
+
+util.helper_target(
+ cc_tool,
+ name = "requires_all_simple_tool",
+ src = "//tests/rule_based_toolchain/testdata:bin_wrapper.sh",
+ requires_any_of = [":all_simple_features"],
+)
+
+util.helper_target(
+ cc_action_type_config,
+ name = "requires_all_simple_action_type_config",
+ action_types = ["//tests/rule_based_toolchain/actions:c_compile"],
+ tools = [":requires_all_simple_tool"],
+)
+
+util.helper_target(
+ cc_feature,
+ name = "requires_any_simple_feature",
+ args = [":c_compile_args"],
+ enabled = True,
+ feature_name = "requires_any_simple",
+ requires_any_of = [
+ ":simple_feature",
+ ":simple_feature2",
+ ],
+)
+
+util.helper_target(
+ cc_feature,
+ name = "same_feature_name",
+ args = [":c_compile_args"],
+ enabled = False,
+ feature_name = "simple_feature",
+ visibility = ["//tests/rule_based_toolchain:__subpackages__"],
+)
+
+util.helper_target(
+ cc_feature,
+ name = "simple_feature",
+ args = [":c_compile_args"],
+ enabled = False,
+ feature_name = "simple_feature",
+)
+
+util.helper_target(
+ cc_feature,
+ name = "simple_feature2",
+ args = [":c_compile_args"],
+ enabled = False,
+ feature_name = "simple_feature2",
+ visibility = ["//tests/rule_based_toolchain:__subpackages__"],
+)
+
+analysis_test_suite(
+ name = "test_suite",
+ targets = TARGETS,
+ tests = TESTS,
+)
diff --git a/tests/rule_based_toolchain/toolchain_config/toolchain_config_test.bzl b/tests/rule_based_toolchain/toolchain_config/toolchain_config_test.bzl
new file mode 100644
index 0000000..e188772
--- /dev/null
+++ b/tests/rule_based_toolchain/toolchain_config/toolchain_config_test.bzl
@@ -0,0 +1,293 @@
+# 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.
+"""Tests for the cc_toolchain_config rule."""
+
+load(
+ "//cc:cc_toolchain_config_lib.bzl",
+ legacy_action_config = "action_config",
+ legacy_env_entry = "env_entry",
+ legacy_env_set = "env_set",
+ legacy_feature = "feature",
+ legacy_flag_group = "flag_group",
+ legacy_flag_set = "flag_set",
+ legacy_tool = "tool",
+)
+load("//cc/toolchains:cc_toolchain_info.bzl", "ActionTypeInfo", "ToolchainConfigInfo")
+load("//cc/toolchains/impl:legacy_converter.bzl", "convert_toolchain")
+load("//cc/toolchains/impl:toolchain_config_info.bzl", _toolchain_config_info = "toolchain_config_info")
+load("//tests/rule_based_toolchain:subjects.bzl", "result_fn_wrapper", "subjects")
+
+visibility("private")
+
+toolchain_config_info = result_fn_wrapper(_toolchain_config_info)
+
+_COLLECTED_CPP_COMPILE_FILES = [
+ # From :compile_config's tool
+ "tests/rule_based_toolchain/testdata/bin",
+ "tests/rule_based_toolchain/testdata/bin_wrapper",
+ # From :compile_feature's args
+ "tests/rule_based_toolchain/testdata/file2",
+]
+
+_COLLECTED_C_COMPILE_FILES = _COLLECTED_CPP_COMPILE_FILES + [
+ # From :c_compile_args
+ "tests/rule_based_toolchain/testdata/file1",
+]
+
+def _expect_that_toolchain(env, expr = None, **kwargs):
+ return env.expect.that_value(
+ value = toolchain_config_info(label = Label("//:toolchain"), **kwargs),
+ expr = expr,
+ factory = subjects.result(subjects.ToolchainConfigInfo),
+ )
+
+def _empty_toolchain_valid_test(env, _targets):
+ _expect_that_toolchain(env).ok()
+
+def _duplicate_feature_names_invalid_test(env, targets):
+ _expect_that_toolchain(
+ env,
+ features = [targets.simple_feature, targets.same_feature_name],
+ expr = "duplicate_feature_name",
+ ).err().contains_all_of([
+ "The feature name simple_feature was defined by",
+ targets.same_feature_name.label,
+ targets.simple_feature.label,
+ ])
+
+ # Overriding a feature gives it the same name. Ensure this isn't blocked.
+ _expect_that_toolchain(
+ env,
+ features = [targets.builtin_feature, targets.overrides_feature],
+ expr = "override_feature",
+ ).ok()
+
+def _duplicate_action_type_invalid_test(env, targets):
+ _expect_that_toolchain(
+ env,
+ features = [targets.simple_feature],
+ action_type_configs = [targets.compile_config, targets.c_compile_config],
+ ).err().contains_all_of([
+ "The action type %s is configured by" % targets.c_compile.label,
+ targets.compile_config.label,
+ targets.c_compile_config.label,
+ ])
+
+def _action_config_implies_missing_feature_invalid_test(env, targets):
+ _expect_that_toolchain(
+ env,
+ features = [targets.simple_feature],
+ action_type_configs = [targets.c_compile_config],
+ expr = "action_type_config_with_implies",
+ ).ok()
+
+ _expect_that_toolchain(
+ env,
+ features = [],
+ action_type_configs = [targets.c_compile_config],
+ expr = "action_type_config_missing_implies",
+ ).err().contains(
+ "%s implies the feature %s" % (targets.c_compile_config.label, targets.simple_feature.label),
+ )
+
+def _feature_config_implies_missing_feature_invalid_test(env, targets):
+ _expect_that_toolchain(
+ env,
+ expr = "feature_with_implies",
+ features = [targets.simple_feature, targets.implies_simple_feature],
+ ).ok()
+
+ _expect_that_toolchain(
+ env,
+ features = [targets.implies_simple_feature],
+ expr = "feature_missing_implies",
+ ).err().contains(
+ "%s implies the feature %s" % (targets.implies_simple_feature.label, targets.simple_feature.label),
+ )
+
+def _feature_missing_requirements_invalid_test(env, targets):
+ _expect_that_toolchain(
+ env,
+ features = [targets.requires_any_simple_feature, targets.simple_feature],
+ expr = "requires_any_simple_has_simple",
+ ).ok()
+ _expect_that_toolchain(
+ env,
+ features = [targets.requires_any_simple_feature, targets.simple_feature2],
+ expr = "requires_any_simple_has_simple2",
+ ).ok()
+ _expect_that_toolchain(
+ env,
+ features = [targets.requires_any_simple_feature],
+ expr = "requires_any_simple_has_none",
+ ).err().contains(
+ "It is impossible to enable %s" % targets.requires_any_simple_feature.label,
+ )
+
+ _expect_that_toolchain(
+ env,
+ features = [targets.requires_all_simple_feature, targets.simple_feature, targets.simple_feature2],
+ expr = "requires_all_simple_has_both",
+ ).ok()
+ _expect_that_toolchain(
+ env,
+ features = [targets.requires_all_simple_feature, targets.simple_feature],
+ expr = "requires_all_simple_has_simple",
+ ).err().contains(
+ "It is impossible to enable %s" % targets.requires_all_simple_feature.label,
+ )
+ _expect_that_toolchain(
+ env,
+ features = [targets.requires_all_simple_feature, targets.simple_feature2],
+ expr = "requires_all_simple_has_simple2",
+ ).err().contains(
+ "It is impossible to enable %s" % targets.requires_all_simple_feature.label,
+ )
+
+def _args_missing_requirements_invalid_test(env, targets):
+ _expect_that_toolchain(
+ env,
+ args = [targets.requires_all_simple_args],
+ features = [targets.simple_feature, targets.simple_feature2],
+ expr = "has_both",
+ ).ok()
+ _expect_that_toolchain(
+ env,
+ args = [targets.requires_all_simple_args],
+ features = [targets.simple_feature],
+ expr = "has_only_one",
+ ).err().contains(
+ "It is impossible to enable %s" % targets.requires_all_simple_args.label,
+ )
+
+def _tool_missing_requirements_invalid_test(env, targets):
+ _expect_that_toolchain(
+ env,
+ action_type_configs = [targets.requires_all_simple_action_type_config],
+ features = [targets.simple_feature, targets.simple_feature2],
+ expr = "has_both",
+ ).ok()
+ _expect_that_toolchain(
+ env,
+ action_type_configs = [targets.requires_all_simple_action_type_config],
+ features = [targets.simple_feature],
+ expr = "has_only_one",
+ ).err().contains(
+ "It is impossible to enable %s" % targets.requires_all_simple_tool.label,
+ )
+
+def _toolchain_collects_files_test(env, targets):
+ tc = env.expect.that_target(
+ targets.collects_files_toolchain_config,
+ ).provider(ToolchainConfigInfo)
+ tc.files().get(targets.c_compile[ActionTypeInfo]).contains_exactly(_COLLECTED_C_COMPILE_FILES)
+ tc.files().get(targets.cpp_compile[ActionTypeInfo]).contains_exactly(_COLLECTED_CPP_COMPILE_FILES)
+
+ env.expect.that_target(
+ targets.collects_files_c_compile,
+ ).default_outputs().contains_exactly(_COLLECTED_C_COMPILE_FILES)
+ env.expect.that_target(
+ targets.collects_files_cpp_compile,
+ ).default_outputs().contains_exactly(_COLLECTED_CPP_COMPILE_FILES)
+
+ legacy = convert_toolchain(tc.actual)
+ env.expect.that_collection(legacy.features).contains_exactly([
+ legacy_feature(
+ name = "compile_feature",
+ enabled = True,
+ flag_sets = [legacy_flag_set(
+ actions = ["c_compile", "cpp_compile"],
+ flag_groups = [
+ legacy_flag_group(flags = ["compile_args"]),
+ ],
+ )],
+ ),
+ legacy_feature(
+ name = "implied_by_always_enabled",
+ enabled = True,
+ flag_sets = [legacy_flag_set(
+ actions = ["c_compile"],
+ flag_groups = [
+ legacy_flag_group(flags = ["c_compile_args"]),
+ ],
+ )],
+ ),
+ legacy_feature(
+ name = "implied_by_cpp_compile",
+ enabled = False,
+ flag_sets = [legacy_flag_set(
+ actions = ["cpp_compile"],
+ flag_groups = [
+ legacy_flag_group(flags = ["cpp_compile_args"]),
+ ],
+ )],
+ env_sets = [legacy_env_set(
+ actions = ["cpp_compile"],
+ env_entries = [legacy_env_entry(key = "CPP_COMPILE", value = "1")],
+ )],
+ ),
+ ]).in_order()
+
+ exe = tc.action_type_configs().get(
+ targets.c_compile[ActionTypeInfo],
+ ).actual.tools[0].exe
+ env.expect.that_collection(legacy.action_configs).contains_exactly([
+ legacy_action_config(
+ action_name = "c_compile",
+ enabled = True,
+ tools = [legacy_tool(tool = exe)],
+ ),
+ legacy_action_config(
+ action_name = "cpp_compile",
+ enabled = True,
+ tools = [legacy_tool(tool = exe)],
+ implies = ["implied_by_cpp_compile"],
+ ),
+ ]).in_order()
+
+TARGETS = [
+ "//tests/rule_based_toolchain/actions:c_compile",
+ "//tests/rule_based_toolchain/actions:cpp_compile",
+ ":builtin_feature",
+ ":compile_config",
+ ":collects_files_c_compile",
+ ":collects_files_cpp_compile",
+ ":collects_files_toolchain_config",
+ ":compile_feature",
+ ":c_compile_args",
+ ":c_compile_config",
+ ":implies_simple_feature",
+ ":overrides_feature",
+ ":requires_any_simple_feature",
+ ":requires_all_simple_feature",
+ ":requires_all_simple_args",
+ ":requires_all_simple_action_type_config",
+ ":requires_all_simple_tool",
+ ":simple_feature",
+ ":simple_feature2",
+ ":same_feature_name",
+]
+
+# @unsorted-dict-items
+TESTS = {
+ "empty_toolchain_valid_test": _empty_toolchain_valid_test,
+ "duplicate_feature_names_fail_validation_test": _duplicate_feature_names_invalid_test,
+ "duplicate_action_type_invalid_test": _duplicate_action_type_invalid_test,
+ "action_config_implies_missing_feature_invalid_test": _action_config_implies_missing_feature_invalid_test,
+ "feature_config_implies_missing_feature_invalid_test": _feature_config_implies_missing_feature_invalid_test,
+ "feature_missing_requirements_invalid_test": _feature_missing_requirements_invalid_test,
+ "args_missing_requirements_invalid_test": _args_missing_requirements_invalid_test,
+ "tool_missing_requirements_invalid_test": _tool_missing_requirements_invalid_test,
+ "toolchain_collects_files_test": _toolchain_collects_files_test,
+}
diff --git a/tests/rule_based_toolchain/variables/BUILD b/tests/rule_based_toolchain/variables/BUILD
new file mode 100644
index 0000000..5f7a5a6
--- /dev/null
+++ b/tests/rule_based_toolchain/variables/BUILD
@@ -0,0 +1,148 @@
+load("//cc/toolchains:format.bzl", "format_arg")
+load("//cc/toolchains:nested_args.bzl", "cc_nested_args")
+load("//cc/toolchains/impl:variables.bzl", "cc_builtin_variables", "cc_variable", "types")
+load("//tests/rule_based_toolchain:analysis_test_suite.bzl", "analysis_test_suite")
+load(":variables_test.bzl", "TARGETS", "TESTS")
+
+cc_variable(
+ name = "str",
+ type = types.string,
+)
+
+cc_variable(
+ name = "optional_list",
+ type = types.option(types.list(types.string)),
+)
+
+cc_variable(
+ name = "str_list",
+ type = types.list(types.string),
+)
+
+cc_variable(
+ name = "str_option",
+ type = types.option(types.string),
+)
+
+cc_variable(
+ name = "struct",
+ actions = ["//tests/rule_based_toolchain/actions:c_compile"],
+ type = types.struct(
+ nested_str = types.string,
+ nested_str_list = types.list(types.string),
+ ),
+)
+
+cc_variable(
+ name = "struct_list",
+ actions = ["//tests/rule_based_toolchain/actions:c_compile"],
+ type = types.list(types.struct(
+ nested_str = types.string,
+ nested_str_list = types.list(types.string),
+ )),
+)
+
+cc_variable(
+ name = "struct_list.nested_str_list",
+ type = types.unknown,
+)
+
+# Dots in the name confuse the test rules.
+# It would end up generating targets.struct_list.nested_str_list.
+alias(
+ name = "nested_str_list",
+ actual = ":struct_list.nested_str_list",
+)
+
+cc_nested_args(
+ name = "simple_str",
+ args = [format_arg("%s", ":str")],
+)
+
+cc_nested_args(
+ name = "list_not_allowed",
+ args = [format_arg("%s", ":str_list")],
+)
+
+cc_nested_args(
+ name = "iterate_over_list",
+ args = [format_arg("%s")],
+ iterate_over = ":str_list",
+)
+
+cc_nested_args(
+ name = "iterate_over_non_list",
+ args = ["--foo"],
+ iterate_over = ":str",
+)
+
+cc_nested_args(
+ name = "str_not_a_bool",
+ args = ["--foo"],
+ requires_true = ":str",
+)
+
+cc_nested_args(
+ name = "str_equal",
+ args = ["--foo"],
+ requires_equal = ":str",
+ requires_equal_value = "bar",
+)
+
+cc_nested_args(
+ name = "inner_iter",
+ args = [format_arg("%s")],
+ iterate_over = ":struct_list.nested_str_list",
+)
+
+cc_nested_args(
+ name = "outer_iter",
+ iterate_over = ":struct_list",
+ nested = [":inner_iter"],
+)
+
+cc_nested_args(
+ name = "bad_inner_iter",
+ args = [format_arg("%s", ":struct_list.nested_str_list")],
+)
+
+cc_nested_args(
+ name = "bad_outer_iter",
+ iterate_over = ":struct_list",
+ nested = [":bad_inner_iter"],
+)
+
+cc_nested_args(
+ name = "bad_nested_optional",
+ args = [format_arg("%s", ":str_option")],
+)
+
+cc_nested_args(
+ name = "good_nested_optional",
+ args = [format_arg("%s", ":str_option")],
+ requires_not_none = ":str_option",
+)
+
+cc_nested_args(
+ name = "optional_list_iter",
+ args = ["--foo"],
+ iterate_over = ":optional_list",
+)
+
+cc_builtin_variables(
+ name = "variables",
+ srcs = [
+ ":optional_list",
+ ":str",
+ ":str_list",
+ ":str_option",
+ ":struct",
+ ":struct_list",
+ ],
+)
+
+analysis_test_suite(
+ name = "test_suite",
+ targets = TARGETS,
+ tests = TESTS,
+)
diff --git a/tests/rule_based_toolchain/variables/variables_test.bzl b/tests/rule_based_toolchain/variables/variables_test.bzl
new file mode 100644
index 0000000..98a64fd
--- /dev/null
+++ b/tests/rule_based_toolchain/variables/variables_test.bzl
@@ -0,0 +1,200 @@
+# 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.
+"""Tests for variables rule."""
+
+load("//cc/toolchains:cc_toolchain_info.bzl", "ActionTypeInfo", "BuiltinVariablesInfo", "NestedArgsInfo", "VariableInfo")
+load("//cc/toolchains/impl:args_utils.bzl", _validate_nested_args = "validate_nested_args")
+load(
+ "//cc/toolchains/impl:nested_args.bzl",
+ "FORMAT_ARGS_ERR",
+ "REQUIRES_TRUE_ERR",
+)
+load("//cc/toolchains/impl:variables.bzl", "types", _get_type = "get_type")
+load("//tests/rule_based_toolchain:subjects.bzl", "result_fn_wrapper", "subjects")
+
+visibility("private")
+
+get_type = result_fn_wrapper(_get_type)
+validate_nested_args = result_fn_wrapper(_validate_nested_args)
+
+_ARGS_LABEL = Label("//:args")
+_NESTED_LABEL = Label("//:nested_vars")
+
+def _type(target):
+ return target[VariableInfo].type
+
+def _types_represent_correctly_test(env, targets):
+ env.expect.that_str(_type(targets.str_list)["repr"]).equals("List[string]")
+ env.expect.that_str(_type(targets.str_option)["repr"]).equals("Option[string]")
+ env.expect.that_str(_type(targets.struct)["repr"]).equals("struct(nested_str=string, nested_str_list=List[string])")
+ env.expect.that_str(_type(targets.struct_list)["repr"]).equals("List[struct(nested_str=string, nested_str_list=List[string])]")
+
+def _get_types_test(env, targets):
+ c_compile = targets.c_compile[ActionTypeInfo]
+ cpp_compile = targets.cpp_compile[ActionTypeInfo]
+ variables = targets.variables[BuiltinVariablesInfo].variables
+
+ def expect_type(key, overrides = {}, expr = None, actions = []):
+ return env.expect.that_value(
+ get_type(
+ variables = variables,
+ overrides = overrides,
+ args_label = _ARGS_LABEL,
+ nested_label = _NESTED_LABEL,
+ actions = actions,
+ name = key,
+ ),
+ # It's not a string, it's a complex recursive type, but string
+ # supports .equals, which is all we care about.
+ factory = subjects.result(subjects.str),
+ expr = expr or key,
+ )
+
+ expect_type("unknown").err().contains(
+ """The variable unknown does not exist. Did you mean one of the following?
+optional_list
+str
+str_list
+""",
+ )
+
+ expect_type("str").ok().equals(types.string)
+ expect_type("str.invalid").err().equals("""Attempted to access "str.invalid", but "str" was not a struct - it had type string.""")
+
+ expect_type("str_option").ok().equals(types.option(types.string))
+
+ expect_type("str_list").ok().equals(types.list(types.string))
+
+ expect_type("str_list.invalid").err().equals("""Attempted to access "str_list.invalid", but "str_list" was not a struct - it had type List[string].""")
+
+ expect_type("struct").ok().equals(_type(targets.struct))
+
+ expect_type("struct.nested_str_list").ok().equals(types.list(types.string))
+
+ expect_type("struct_list").ok().equals(_type(targets.struct_list))
+
+ expect_type("struct_list.nested_str_list").err().equals("""Attempted to access "struct_list.nested_str_list", but "struct_list" was not a struct - it had type List[struct(nested_str=string, nested_str_list=List[string])]. Maybe you meant to use iterate_over.""")
+
+ expect_type("struct.unknown").err().equals("""Unable to find "unknown" in "struct", which had the following attributes:
+nested_str: string
+nested_str_list: List[string]""")
+
+ expect_type("struct", actions = [c_compile]).ok()
+ expect_type("struct", actions = [c_compile, cpp_compile]).err().equals(
+ "The variable %s is inaccessible from the action %s. This is required because it is referenced in %s, which is included by %s, which references that action" % (targets.struct.label, cpp_compile.label, _NESTED_LABEL, _ARGS_LABEL),
+ )
+
+ expect_type("struct.nested_str_list", actions = [c_compile]).ok()
+ expect_type("struct.nested_str_list", actions = [c_compile, cpp_compile]).err()
+
+ # Simulate someone doing iterate_over = struct_list.
+ expect_type(
+ "struct_list",
+ overrides = {"struct_list": _type(targets.struct)},
+ expr = "struct_list_override",
+ ).ok().equals(_type(targets.struct))
+
+ expect_type(
+ "struct_list.nested_str_list",
+ overrides = {"struct_list": _type(targets.struct)},
+ ).ok().equals(types.list(types.string))
+
+ expect_type(
+ "struct_list.nested_str_list",
+ overrides = {
+ "struct_list": _type(targets.struct),
+ "struct_list.nested_str_list": types.string,
+ },
+ ).ok().equals(types.string)
+
+def _variable_validation_test(env, targets):
+ c_compile = targets.c_compile[ActionTypeInfo]
+ cpp_compile = targets.cpp_compile[ActionTypeInfo]
+ variables = targets.variables[BuiltinVariablesInfo].variables
+
+ def _expect_validated(target, expr = None, actions = []):
+ return env.expect.that_value(
+ validate_nested_args(
+ nested_args = target[NestedArgsInfo],
+ variables = variables,
+ actions = actions,
+ label = _ARGS_LABEL,
+ ),
+ expr = expr,
+ # Type is Result[None]
+ factory = subjects.result(subjects.unknown),
+ )
+
+ _expect_validated(targets.simple_str, expr = "simple_str").ok()
+ _expect_validated(targets.list_not_allowed).err().equals(
+ FORMAT_ARGS_ERR + ", but str_list has type List[string]",
+ )
+ _expect_validated(targets.iterate_over_list, expr = "iterate_over_list").ok()
+ _expect_validated(targets.iterate_over_non_list, expr = "iterate_over_non_list").err().equals(
+ "Attempting to iterate over str, but it was not a list - it was a string",
+ )
+ _expect_validated(targets.str_not_a_bool, expr = "str_not_a_bool").err().equals(
+ REQUIRES_TRUE_ERR + ", but str has type string",
+ )
+ _expect_validated(targets.str_equal, expr = "str_equal").ok()
+ _expect_validated(targets.inner_iter, expr = "inner_iter_standalone").err().equals(
+ 'Attempted to access "struct_list.nested_str_list", but "struct_list" was not a struct - it had type List[struct(nested_str=string, nested_str_list=List[string])]. Maybe you meant to use iterate_over.',
+ )
+
+ _expect_validated(targets.outer_iter, actions = [c_compile], expr = "outer_iter_valid_action").ok()
+ _expect_validated(targets.outer_iter, actions = [c_compile, cpp_compile], expr = "outer_iter_missing_action").err().equals(
+ "The variable %s is inaccessible from the action %s. This is required because it is referenced in %s, which is included by %s, which references that action" % (targets.struct_list.label, cpp_compile.label, targets.outer_iter.label, _ARGS_LABEL),
+ )
+
+ _expect_validated(targets.bad_outer_iter, expr = "bad_outer_iter").err().equals(
+ FORMAT_ARGS_ERR + ", but struct_list.nested_str_list has type List[string]",
+ )
+
+ _expect_validated(targets.optional_list_iter, expr = "optional_list_iter").ok()
+
+ _expect_validated(targets.bad_nested_optional, expr = "bad_nested_optional").err().equals(
+ FORMAT_ARGS_ERR + ", but str_option has type Option[string]",
+ )
+ _expect_validated(targets.good_nested_optional, expr = "good_nested_optional").ok()
+
+TARGETS = [
+ "//tests/rule_based_toolchain/actions:c_compile",
+ "//tests/rule_based_toolchain/actions:cpp_compile",
+ ":bad_nested_optional",
+ ":bad_outer_iter",
+ ":good_nested_optional",
+ ":inner_iter",
+ ":iterate_over_list",
+ ":iterate_over_non_list",
+ ":list_not_allowed",
+ ":nested_str_list",
+ ":optional_list_iter",
+ ":outer_iter",
+ ":simple_str",
+ ":str",
+ ":str_equal",
+ ":str_list",
+ ":str_not_a_bool",
+ ":str_option",
+ ":struct",
+ ":struct_list",
+ ":variables",
+]
+
+# @unsorted-dict-items
+TESTS = {
+ "types_represent_correctly_test": _types_represent_correctly_test,
+ "get_types_test": _get_types_test,
+ "variable_validation_test": _variable_validation_test,
+}
diff --git a/tests/system_library/BUILD b/tests/system_library/BUILD
index abc1392..3c8d577 100644
--- a/tests/system_library/BUILD
+++ b/tests/system_library/BUILD
@@ -1,3 +1,17 @@
+# 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.
+
sh_test(
name = "system_library_test",
size = "small",
diff --git a/tools/migration/BUILD b/tools/migration/BUILD
index b1dfafb..1550c15 100644
--- a/tools/migration/BUILD
+++ b/tools/migration/BUILD
@@ -13,11 +13,7 @@
# limitations under the License.
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
-
-# Go rules
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", "go_test")
-
-# Python rules
load("@rules_python//python:defs.bzl", "py_binary", "py_library", "py_test")
package(default_visibility = ["//visibility:public"])
@@ -33,6 +29,7 @@ py_binary(
"//third_party/com/github/bazelbuild/bazel/src/main/protobuf:crosstool_config_py_pb2",
"@io_abseil_py//absl:app",
"@io_abseil_py//absl/flags",
+ #internal proto upb dep,
],
)
@@ -62,6 +59,7 @@ py_binary(
"//third_party/com/github/bazelbuild/bazel/src/main/protobuf:crosstool_config_py_pb2",
"@io_abseil_py//absl:app",
"@io_abseil_py//absl/flags",
+ #internal proto upb dep,
],
)
@@ -74,6 +72,7 @@ py_binary(
"//third_party/com/github/bazelbuild/bazel/src/main/protobuf:crosstool_config_py_pb2",
"@io_abseil_py//absl:app",
"@io_abseil_py//absl/flags",
+ #internal proto upb dep,
],
)
@@ -132,11 +131,6 @@ filegroup(
],
)
-exports_files([
- "cc_toolchain_config_comparator.bzl",
- "ctoolchain_compare.bzl",
-])
-
bzl_library(
name = "ctoolchain_compare_bzl",
srcs = ["ctoolchain_compare.bzl"],
diff --git a/tools/migration/ctoolchain_comparator_lib_test.py b/tools/migration/ctoolchain_comparator_lib_test.py
index c81ff47..1a3a270 100644
--- a/tools/migration/ctoolchain_comparator_lib_test.py
+++ b/tools/migration/ctoolchain_comparator_lib_test.py
@@ -13,11 +13,13 @@
# limitations under the License.
import unittest
+
+from py import mock
+
from google.protobuf import text_format
from third_party.com.github.bazelbuild.bazel.src.main.protobuf import crosstool_config_pb2
from tools.migration.ctoolchain_comparator_lib import compare_ctoolchains
-from py import mock
try:
# Python 2
from cStringIO import StringIO
@@ -60,34 +62,53 @@ class CtoolchainComparatorLibTest(unittest.TestCase):
error_toolchain_identifier = (
"Difference in 'toolchain_identifier' field:\n"
"Value before change:\t'first-id'\n"
- "Value after change:\t'second-id'\n")
- error_host_system_name = ("Difference in 'host_system_name' field:\n"
- "Value before change:\t'first-host'\n"
- "Value after change:\t'second-host'\n")
- error_target_system_name = ("Difference in 'target_system_name' field:\n"
- "Value before change:\t'first-target'\n"
- "Value after change:\t'second-target'\n")
- error_target_cpu = ("Difference in 'target_cpu' field:\n"
- "Value before change:\t'first-cpu'\n"
- "Value after change:\t'second-cpu'\n")
- error_target_libc = ("Difference in 'target_libc' field:\n"
- "Value before change:\t'first-libc'\n"
- "Value after change:\t'second-libc'\n")
- error_compiler = ("Difference in 'compiler' field:\n"
- "Value before change:\t'first-compiler'\n"
- "Value after change:\t'second-compiler'\n")
- error_abi_version = ("Difference in 'abi_version' field:\n"
- "Value before change:\t'first-abi'\n"
- "Value after change:\t'second-abi'\n")
- error_abi_libc_version = ("Difference in 'abi_libc_version' field:\n"
- "Value before change:\t'first-abi-libc'\n"
- "Value after change:\t'second-abi-libc'\n")
- error_builtin_sysroot = ("Difference in 'builtin_sysroot' field:\n"
- "Value before change is set to 'sysroot'\n"
- "Value after change is not set\n")
- error_cc_target_os = ("Difference in 'cc_target_os' field:\n"
- "Value before change is not set\n"
- "Value after change is set to 'os'\n")
+ "Value after change:\t'second-id'\n"
+ )
+ error_host_system_name = (
+ "Difference in 'host_system_name' field:\n"
+ "Value before change:\t'first-host'\n"
+ "Value after change:\t'second-host'\n"
+ )
+ error_target_system_name = (
+ "Difference in 'target_system_name' field:\n"
+ "Value before change:\t'first-target'\n"
+ "Value after change:\t'second-target'\n"
+ )
+ error_target_cpu = (
+ "Difference in 'target_cpu' field:\n"
+ "Value before change:\t'first-cpu'\n"
+ "Value after change:\t'second-cpu'\n"
+ )
+ error_target_libc = (
+ "Difference in 'target_libc' field:\n"
+ "Value before change:\t'first-libc'\n"
+ "Value after change:\t'second-libc'\n"
+ )
+ error_compiler = (
+ "Difference in 'compiler' field:\n"
+ "Value before change:\t'first-compiler'\n"
+ "Value after change:\t'second-compiler'\n"
+ )
+ error_abi_version = (
+ "Difference in 'abi_version' field:\n"
+ "Value before change:\t'first-abi'\n"
+ "Value after change:\t'second-abi'\n"
+ )
+ error_abi_libc_version = (
+ "Difference in 'abi_libc_version' field:\n"
+ "Value before change:\t'first-abi-libc'\n"
+ "Value after change:\t'second-abi-libc'\n"
+ )
+ error_builtin_sysroot = (
+ "Difference in 'builtin_sysroot' field:\n"
+ "Value before change is set to 'sysroot'\n"
+ "Value after change is not set\n"
+ )
+ error_cc_target_os = (
+ "Difference in 'cc_target_os' field:\n"
+ "Value before change is not set\n"
+ "Value after change is set to 'os'\n"
+ )
mock_stdout = StringIO()
with mock.patch("sys.stdout", mock_stdout):
compare_ctoolchains(first, second)
@@ -127,20 +148,26 @@ class CtoolchainComparatorLibTest(unittest.TestCase):
path: "/a/b/c"
}
""")
- error_only_first = ("* List before change contains entries for the "
- "following tools that the list after the change "
- "doesn't:\n[only_first]\n")
- error_only_second = ("* List after change contains entries for the "
- "following tools that the list before the change "
- "doesn't:\n"
- "[\n"
- "\tonly_second_1\n"
- "\tonly_second_2\n"
- "]\n")
- error_paths_differ = ("* Path for tool 'paths_differ' differs before and "
- "after the change:\n"
- "Value before change:\t'/path/first'\n"
- "Value after change:\t'/path/second'\n")
+ error_only_first = (
+ "* List before change contains entries for the "
+ "following tools that the list after the change "
+ "doesn't:\n[only_first]\n"
+ )
+ error_only_second = (
+ "* List after change contains entries for the "
+ "following tools that the list before the change "
+ "doesn't:\n"
+ "[\n"
+ "\tonly_second_1\n"
+ "\tonly_second_2\n"
+ "]\n"
+ )
+ error_paths_differ = (
+ "* Path for tool 'paths_differ' differs before and "
+ "after the change:\n"
+ "Value before change:\t'/path/first'\n"
+ "Value after change:\t'/path/second'\n"
+ )
mock_stdout = StringIO()
with mock.patch("sys.stdout", mock_stdout):
compare_ctoolchains(first, second)
@@ -173,20 +200,26 @@ class CtoolchainComparatorLibTest(unittest.TestCase):
value: "val"
}
""")
- error_only_first = ("* List before change contains entries for the "
- "following variables that the list after the "
- "change doesn't:\n[only_first]\n")
- error_only_second = ("* List after change contains entries for the "
- "following variables that the list before the "
- "change doesn't:\n"
- "[\n"
- "\tonly_second_1\n"
- "\tonly_second_2\n"
- "]\n")
- error_value_differs = ("* Value for variable 'value_differs' differs before"
- " and after the change:\n"
- "Value before change:\t'first_value'\n"
- "Value after change:\t'second_value'\n")
+ error_only_first = (
+ "* List before change contains entries for the "
+ "following variables that the list after the "
+ "change doesn't:\n[only_first]\n"
+ )
+ error_only_second = (
+ "* List after change contains entries for the "
+ "following variables that the list before the "
+ "change doesn't:\n"
+ "[\n"
+ "\tonly_second_1\n"
+ "\tonly_second_2\n"
+ "]\n"
+ )
+ error_value_differs = (
+ "* Value for variable 'value_differs' differs before"
+ " and after the change:\n"
+ "Value before change:\t'first_value'\n"
+ "Value after change:\t'second_value'\n"
+ )
mock_stdout = StringIO()
with mock.patch("sys.stdout", mock_stdout):
compare_ctoolchains(first, second)
@@ -203,17 +236,19 @@ class CtoolchainComparatorLibTest(unittest.TestCase):
cxx_builtin_include_directory: "d/e/f"
cxx_builtin_include_directory: "a/b/c"
""")
- expect_error = ("Difference in 'cxx_builtin_include_directory' field:\n"
- "List of elements before change:\n"
- "[\n"
- "\ta/b/c\n"
- "\td/e/f\n"
- "]\n"
- "List of elements after change:\n"
- "[\n"
- "\td/e/f\n"
- "\ta/b/c\n"
- "]\n")
+ expect_error = (
+ "Difference in 'cxx_builtin_include_directory' field:\n"
+ "List of elements before change:\n"
+ "[\n"
+ "\ta/b/c\n"
+ "\td/e/f\n"
+ "]\n"
+ "List of elements after change:\n"
+ "[\n"
+ "\td/e/f\n"
+ "\ta/b/c\n"
+ "]\n"
+ )
mock_stdout = StringIO()
with mock.patch("sys.stdout", mock_stdout):
compare_ctoolchains(first, second)
@@ -259,32 +294,40 @@ class CtoolchainComparatorLibTest(unittest.TestCase):
extension: '.if.lib'
}
""")
- error_only_first = ("* List before change contains entries for the "
- "following categories that the list after the "
- "change doesn't:\n[dynamic_library]\n")
- error_only_second = ("* List after change contains entries for the "
- "following categories that the list before the "
- "change doesn't:\n"
- "[\n"
- "\tinterface_library\n"
- "\tstatic_library\n"
- "]\n")
- error_extension_differs = ("* Value for category 'object_file' differs "
- "before and after the change:\n"
- "Value before change:"
- "\tprefix:''"
- "\textension:'.obj1'\n"
- "Value after change:"
- "\tprefix:''"
- "\textension:'.obj2'\n")
- error_prefix_differs = ("* Value for category 'executable' differs "
- "before and after the change:\n"
- "Value before change:"
- "\tprefix:'first'"
- "\textension:'.exe'\n"
- "Value after change:"
- "\tprefix:'second'"
- "\textension:'.exe'\n")
+ error_only_first = (
+ "* List before change contains entries for the "
+ "following categories that the list after the "
+ "change doesn't:\n[dynamic_library]\n"
+ )
+ error_only_second = (
+ "* List after change contains entries for the "
+ "following categories that the list before the "
+ "change doesn't:\n"
+ "[\n"
+ "\tinterface_library\n"
+ "\tstatic_library\n"
+ "]\n"
+ )
+ error_extension_differs = (
+ "* Value for category 'object_file' differs "
+ "before and after the change:\n"
+ "Value before change:"
+ "\tprefix:''"
+ "\textension:'.obj1'\n"
+ "Value after change:"
+ "\tprefix:''"
+ "\textension:'.obj2'\n"
+ )
+ error_prefix_differs = (
+ "* Value for category 'executable' differs "
+ "before and after the change:\n"
+ "Value before change:"
+ "\tprefix:'first'"
+ "\textension:'.exe'\n"
+ "Value after change:"
+ "\tprefix:'second'"
+ "\textension:'.exe'\n"
+ )
mock_stdout = StringIO()
with mock.patch("sys.stdout", mock_stdout):
compare_ctoolchains(first, second)
@@ -326,12 +369,16 @@ class CtoolchainComparatorLibTest(unittest.TestCase):
name: 'feature2'
}
""")
- error_only_first = ("* List before change contains entries for the "
- "following features that the list after the "
- "change doesn't:\n[feature1]\n")
- error_only_second = ("* List after change contains entries for the "
- "following features that the list before the "
- "change doesn't:\n[feature2]\n")
+ error_only_first = (
+ "* List before change contains entries for the "
+ "following features that the list after the "
+ "change doesn't:\n[feature1]\n"
+ )
+ error_only_second = (
+ "* List after change contains entries for the "
+ "following features that the list before the "
+ "change doesn't:\n[feature2]\n"
+ )
mock_stdout = StringIO()
with mock.patch("sys.stdout", mock_stdout):
compare_ctoolchains(first, second)
@@ -354,8 +401,9 @@ class CtoolchainComparatorLibTest(unittest.TestCase):
mock_stdout = StringIO()
with mock.patch("sys.stdout", mock_stdout):
compare_ctoolchains(first, second)
- self.assertIn("* Feature 'feature' differs before and after",
- mock_stdout.getvalue())
+ self.assertIn(
+ "* Feature 'feature' differs before and after", mock_stdout.getvalue()
+ )
def test_feature_provides(self):
first = make_toolchain("""
@@ -373,8 +421,10 @@ class CtoolchainComparatorLibTest(unittest.TestCase):
mock_stdout = StringIO()
with mock.patch("sys.stdout", mock_stdout):
compare_ctoolchains(first, second)
- self.assertIn("* Feature 'feature' differs before and after the change:",
- mock_stdout.getvalue())
+ self.assertIn(
+ "* Feature 'feature' differs before and after the change:",
+ mock_stdout.getvalue(),
+ )
def test_feature_provides_preserves_order(self):
first = make_toolchain("""
@@ -394,8 +444,10 @@ class CtoolchainComparatorLibTest(unittest.TestCase):
mock_stdout = StringIO()
with mock.patch("sys.stdout", mock_stdout):
compare_ctoolchains(first, second)
- self.assertIn("* Feature 'feature' differs before and after the change:",
- mock_stdout.getvalue())
+ self.assertIn(
+ "* Feature 'feature' differs before and after the change:",
+ mock_stdout.getvalue(),
+ )
def test_feature_implies(self):
first = make_toolchain("""
@@ -412,8 +464,10 @@ class CtoolchainComparatorLibTest(unittest.TestCase):
mock_stdout = StringIO()
with mock.patch("sys.stdout", mock_stdout):
compare_ctoolchains(first, second)
- self.assertIn("* Feature 'feature' differs before and after the change:",
- mock_stdout.getvalue())
+ self.assertIn(
+ "* Feature 'feature' differs before and after the change:",
+ mock_stdout.getvalue(),
+ )
def test_feature_implies_preserves_order(self):
first = make_toolchain("""
@@ -433,8 +487,10 @@ class CtoolchainComparatorLibTest(unittest.TestCase):
mock_stdout = StringIO()
with mock.patch("sys.stdout", mock_stdout):
compare_ctoolchains(first, second)
- self.assertIn("* Feature 'feature' differs before and after the change:",
- mock_stdout.getvalue())
+ self.assertIn(
+ "* Feature 'feature' differs before and after the change:",
+ mock_stdout.getvalue(),
+ )
def test_feature_requires_preserves_list_order(self):
first = make_toolchain("""
@@ -462,8 +518,10 @@ class CtoolchainComparatorLibTest(unittest.TestCase):
mock_stdout = StringIO()
with mock.patch("sys.stdout", mock_stdout):
compare_ctoolchains(first, second)
- self.assertIn("* Feature 'feature' differs before and after the change:",
- mock_stdout.getvalue())
+ self.assertIn(
+ "* Feature 'feature' differs before and after the change:",
+ mock_stdout.getvalue(),
+ )
def test_feature_requires_ignores_required_features_order(self):
first = make_toolchain("""
@@ -509,8 +567,10 @@ class CtoolchainComparatorLibTest(unittest.TestCase):
mock_stdout = StringIO()
with mock.patch("sys.stdout", mock_stdout):
compare_ctoolchains(first, second)
- self.assertIn("* Feature 'feature' differs before and after the change:",
- mock_stdout.getvalue())
+ self.assertIn(
+ "* Feature 'feature' differs before and after the change:",
+ mock_stdout.getvalue(),
+ )
def test_action_config_ignores_requires(self):
first = make_toolchain("""
@@ -555,8 +615,10 @@ class CtoolchainComparatorLibTest(unittest.TestCase):
mock_stdout = StringIO()
with mock.patch("sys.stdout", mock_stdout):
compare_ctoolchains(first, second)
- self.assertIn("* Feature 'feature' differs before and after the change:",
- mock_stdout.getvalue())
+ self.assertIn(
+ "* Feature 'feature' differs before and after the change:",
+ mock_stdout.getvalue(),
+ )
def test_env_set_ignores_actions_order(self):
first = make_toolchain("""
@@ -616,8 +678,10 @@ class CtoolchainComparatorLibTest(unittest.TestCase):
mock_stdout = StringIO()
with mock.patch("sys.stdout", mock_stdout):
compare_ctoolchains(first, second)
- self.assertIn("* Feature 'feature' differs before and after the change:",
- mock_stdout.getvalue())
+ self.assertIn(
+ "* Feature 'feature' differs before and after the change:",
+ mock_stdout.getvalue(),
+ )
def test_env_set_env_entries_differ(self):
first = make_toolchain("""
@@ -645,8 +709,10 @@ class CtoolchainComparatorLibTest(unittest.TestCase):
mock_stdout = StringIO()
with mock.patch("sys.stdout", mock_stdout):
compare_ctoolchains(first, second)
- self.assertIn("* Feature 'feature' differs before and after the change:",
- mock_stdout.getvalue())
+ self.assertIn(
+ "* Feature 'feature' differs before and after the change:",
+ mock_stdout.getvalue(),
+ )
def test_feature_preserves_env_set_order(self):
first = make_toolchain("""
@@ -686,8 +752,10 @@ class CtoolchainComparatorLibTest(unittest.TestCase):
mock_stdout = StringIO()
with mock.patch("sys.stdout", mock_stdout):
compare_ctoolchains(first, second)
- self.assertIn("* Feature 'feature' differs before and after the change:",
- mock_stdout.getvalue())
+ self.assertIn(
+ "* Feature 'feature' differs before and after the change:",
+ mock_stdout.getvalue(),
+ )
def test_action_config_ignores_env_set(self):
first = make_toolchain("""
@@ -987,10 +1055,13 @@ class CtoolchainComparatorLibTest(unittest.TestCase):
mock_stdout = StringIO()
with mock.patch("sys.stdout", mock_stdout):
compare_ctoolchains(first, second)
- self.assertIn("* Feature 'feature' differs before and after",
- mock_stdout.getvalue())
- self.assertIn("* Action config 'config' differs before and after",
- mock_stdout.getvalue())
+ self.assertIn(
+ "* Feature 'feature' differs before and after", mock_stdout.getvalue()
+ )
+ self.assertIn(
+ "* Action config 'config' differs before and after",
+ mock_stdout.getvalue(),
+ )
def test_flag_group_preserves_flags_order(self):
first = make_toolchain("""
@@ -1036,10 +1107,13 @@ class CtoolchainComparatorLibTest(unittest.TestCase):
mock_stdout = StringIO()
with mock.patch("sys.stdout", mock_stdout):
compare_ctoolchains(first, second)
- self.assertIn("* Feature 'feature' differs before and after",
- mock_stdout.getvalue())
- self.assertIn("* Action config 'config' differs before and after",
- mock_stdout.getvalue())
+ self.assertIn(
+ "* Feature 'feature' differs before and after", mock_stdout.getvalue()
+ )
+ self.assertIn(
+ "* Action config 'config' differs before and after",
+ mock_stdout.getvalue(),
+ )
def test_flag_group_iterate_over_differs(self):
first = make_toolchain("""
@@ -1081,10 +1155,13 @@ class CtoolchainComparatorLibTest(unittest.TestCase):
mock_stdout = StringIO()
with mock.patch("sys.stdout", mock_stdout):
compare_ctoolchains(first, second)
- self.assertIn("* Feature 'feature' differs before and after",
- mock_stdout.getvalue())
- self.assertIn("* Action config 'config' differs before and after",
- mock_stdout.getvalue())
+ self.assertIn(
+ "* Feature 'feature' differs before and after", mock_stdout.getvalue()
+ )
+ self.assertIn(
+ "* Action config 'config' differs before and after",
+ mock_stdout.getvalue(),
+ )
def test_flag_group_expand_if_true_differs(self):
first = make_toolchain("""
@@ -1126,10 +1203,13 @@ class CtoolchainComparatorLibTest(unittest.TestCase):
mock_stdout = StringIO()
with mock.patch("sys.stdout", mock_stdout):
compare_ctoolchains(first, second)
- self.assertIn("* Feature 'feature' differs before and after",
- mock_stdout.getvalue())
- self.assertIn("* Action config 'config' differs before and after",
- mock_stdout.getvalue())
+ self.assertIn(
+ "* Feature 'feature' differs before and after", mock_stdout.getvalue()
+ )
+ self.assertIn(
+ "* Action config 'config' differs before and after",
+ mock_stdout.getvalue(),
+ )
def test_flag_group_expand_if_false_differs(self):
first = make_toolchain("""
@@ -1171,10 +1251,13 @@ class CtoolchainComparatorLibTest(unittest.TestCase):
mock_stdout = StringIO()
with mock.patch("sys.stdout", mock_stdout):
compare_ctoolchains(first, second)
- self.assertIn("* Feature 'feature' differs before and after",
- mock_stdout.getvalue())
- self.assertIn("* Action config 'config' differs before and after",
- mock_stdout.getvalue())
+ self.assertIn(
+ "* Feature 'feature' differs before and after", mock_stdout.getvalue()
+ )
+ self.assertIn(
+ "* Action config 'config' differs before and after",
+ mock_stdout.getvalue(),
+ )
def test_flag_group_expand_if_all_available_differs(self):
first = make_toolchain("""
@@ -1216,10 +1299,13 @@ class CtoolchainComparatorLibTest(unittest.TestCase):
mock_stdout = StringIO()
with mock.patch("sys.stdout", mock_stdout):
compare_ctoolchains(first, second)
- self.assertIn("* Feature 'feature' differs before and after",
- mock_stdout.getvalue())
- self.assertIn("* Action config 'config' differs before and after",
- mock_stdout.getvalue())
+ self.assertIn(
+ "* Feature 'feature' differs before and after", mock_stdout.getvalue()
+ )
+ self.assertIn(
+ "* Action config 'config' differs before and after",
+ mock_stdout.getvalue(),
+ )
def test_flag_group_expand_if_none_available_differs(self):
first = make_toolchain("""
@@ -1261,10 +1347,13 @@ class CtoolchainComparatorLibTest(unittest.TestCase):
mock_stdout = StringIO()
with mock.patch("sys.stdout", mock_stdout):
compare_ctoolchains(first, second)
- self.assertIn("* Feature 'feature' differs before and after",
- mock_stdout.getvalue())
- self.assertIn("* Action config 'config' differs before and after",
- mock_stdout.getvalue())
+ self.assertIn(
+ "* Feature 'feature' differs before and after", mock_stdout.getvalue()
+ )
+ self.assertIn(
+ "* Action config 'config' differs before and after",
+ mock_stdout.getvalue(),
+ )
def test_flag_group_expand_if_all_available_ignores_order(self):
first = make_toolchain("""
@@ -1410,10 +1499,13 @@ class CtoolchainComparatorLibTest(unittest.TestCase):
mock_stdout = StringIO()
with mock.patch("sys.stdout", mock_stdout):
compare_ctoolchains(first, second)
- self.assertIn("* Feature 'feature' differs before and after",
- mock_stdout.getvalue())
- self.assertIn("* Action config 'config' differs before and after",
- mock_stdout.getvalue())
+ self.assertIn(
+ "* Feature 'feature' differs before and after", mock_stdout.getvalue()
+ )
+ self.assertIn(
+ "* Action config 'config' differs before and after",
+ mock_stdout.getvalue(),
+ )
def test_flag_group_flag_groups_differ(self):
first = make_toolchain("""
@@ -1467,10 +1559,13 @@ class CtoolchainComparatorLibTest(unittest.TestCase):
mock_stdout = StringIO()
with mock.patch("sys.stdout", mock_stdout):
compare_ctoolchains(first, second)
- self.assertIn("* Feature 'feature' differs before and after",
- mock_stdout.getvalue())
- self.assertIn("* Action config 'config' differs before and after",
- mock_stdout.getvalue())
+ self.assertIn(
+ "* Feature 'feature' differs before and after", mock_stdout.getvalue()
+ )
+ self.assertIn(
+ "* Action config 'config' differs before and after",
+ mock_stdout.getvalue(),
+ )
def test_action_configs_not_ordered(self):
first = make_toolchain("""
@@ -1505,12 +1600,16 @@ class CtoolchainComparatorLibTest(unittest.TestCase):
config_name: 'action2'
}
""")
- error_only_first = ("* List before change contains entries for the "
- "following action_configs that the list after the "
- "change doesn't:\n[action1]\n")
- error_only_second = ("* List after change contains entries for the "
- "following action_configs that the list before the "
- "change doesn't:\n[action2]\n")
+ error_only_first = (
+ "* List before change contains entries for the "
+ "following action_configs that the list after the "
+ "change doesn't:\n[action1]\n"
+ )
+ error_only_second = (
+ "* List after change contains entries for the "
+ "following action_configs that the list before the "
+ "change doesn't:\n[action2]\n"
+ )
mock_stdout = StringIO()
with mock.patch("sys.stdout", mock_stdout):
compare_ctoolchains(first, second)
@@ -1533,8 +1632,10 @@ class CtoolchainComparatorLibTest(unittest.TestCase):
mock_stdout = StringIO()
with mock.patch("sys.stdout", mock_stdout):
compare_ctoolchains(first, second)
- self.assertIn("* Action config 'config' differs before and after",
- mock_stdout.getvalue())
+ self.assertIn(
+ "* Action config 'config' differs before and after",
+ mock_stdout.getvalue(),
+ )
def test_action_config_action_name(self):
first = make_toolchain("""
@@ -1552,8 +1653,10 @@ class CtoolchainComparatorLibTest(unittest.TestCase):
mock_stdout = StringIO()
with mock.patch("sys.stdout", mock_stdout):
compare_ctoolchains(first, second)
- self.assertIn("* Action config 'config' differs before and after",
- mock_stdout.getvalue())
+ self.assertIn(
+ "* Action config 'config' differs before and after",
+ mock_stdout.getvalue(),
+ )
def test_action_config_tool_tool_path_differs(self):
first = make_toolchain("""
@@ -1575,8 +1678,10 @@ class CtoolchainComparatorLibTest(unittest.TestCase):
mock_stdout = StringIO()
with mock.patch("sys.stdout", mock_stdout):
compare_ctoolchains(first, second)
- self.assertIn("* Action config 'config' differs before and after",
- mock_stdout.getvalue())
+ self.assertIn(
+ "* Action config 'config' differs before and after",
+ mock_stdout.getvalue(),
+ )
def test_action_config_tool_execution_requirements_differ(self):
first = make_toolchain("""
@@ -1598,8 +1703,10 @@ class CtoolchainComparatorLibTest(unittest.TestCase):
mock_stdout = StringIO()
with mock.patch("sys.stdout", mock_stdout):
compare_ctoolchains(first, second)
- self.assertIn("* Action config 'config' differs before and after",
- mock_stdout.getvalue())
+ self.assertIn(
+ "* Action config 'config' differs before and after",
+ mock_stdout.getvalue(),
+ )
def test_action_config_tool_execution_requirements_ignores_order(self):
first = make_toolchain("""
@@ -1641,8 +1748,10 @@ class CtoolchainComparatorLibTest(unittest.TestCase):
mock_stdout = StringIO()
with mock.patch("sys.stdout", mock_stdout):
compare_ctoolchains(first, second)
- self.assertIn("* Action config 'config' differs before and after",
- mock_stdout.getvalue())
+ self.assertIn(
+ "* Action config 'config' differs before and after",
+ mock_stdout.getvalue(),
+ )
def test_action_config_implies_preserves_order(self):
first = make_toolchain("""
@@ -1662,8 +1771,10 @@ class CtoolchainComparatorLibTest(unittest.TestCase):
mock_stdout = StringIO()
with mock.patch("sys.stdout", mock_stdout):
compare_ctoolchains(first, second)
- self.assertIn("* Action config 'config' differs before and after",
- mock_stdout.getvalue())
+ self.assertIn(
+ "* Action config 'config' differs before and after",
+ mock_stdout.getvalue(),
+ )
def test_unused_tool_path(self):
first = make_toolchain("""
@@ -1705,5 +1816,6 @@ class CtoolchainComparatorLibTest(unittest.TestCase):
compare_ctoolchains(first, second)
self.assertIn("No difference", mock_stdout.getvalue())
+
if __name__ == "__main__":
unittest.main()