aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGoogler <noreply@google.com>2024-02-27 04:44:55 -0800
committerCopybara-Service <copybara-worker@google.com>2024-02-27 04:45:26 -0800
commit2b6cdcfe88536f255c76687c895a3b666e2a2521 (patch)
treea069c8bbfc8567efc89ede4708ce6ec1b69bbacc
parentc5493f9b2cd691608d417d6ef9c8110aec59cbf7 (diff)
downloadbazelbuild-rules_cc-2b6cdcfe88536f255c76687c895a3b666e2a2521.tar.gz
BEGIN_PUBLIC
Implement cc_feature for the rule based toolchain. END_PUBLIC PiperOrigin-RevId: 610712498 Change-Id: I2539825f0f4cf7f234a2310de6af0662aeb0ea2c
-rw-r--r--cc/toolchains/args.bzl2
-rw-r--r--cc/toolchains/cc_toolchain_info.bzl3
-rw-r--r--cc/toolchains/feature.bzl243
-rw-r--r--cc/toolchains/impl/collect.bzl6
-rw-r--r--tests/rule_based_toolchain/features/BUILD55
-rw-r--r--tests/rule_based_toolchain/features/features_test.bzl80
-rw-r--r--tests/rule_based_toolchain/subjects.bzl5
7 files changed, 389 insertions, 5 deletions
diff --git a/cc/toolchains/args.bzl b/cc/toolchains/args.bzl
index 3409d00..a19c16a 100644
--- a/cc/toolchains/args.bzl
+++ b/cc/toolchains/args.bzl
@@ -56,7 +56,7 @@ def _cc_args_impl(ctx):
args = tuple([args]),
files = files,
by_action = tuple([
- struct(action = action, args = [args], files = files)
+ struct(action = action, args = tuple([args]), files = files)
for action in actions.to_list()
]),
),
diff --git a/cc/toolchains/cc_toolchain_info.bzl b/cc/toolchains/cc_toolchain_info.bzl
index ed2bdb6..c4b2344 100644
--- a/cc/toolchains/cc_toolchain_info.bzl
+++ b/cc/toolchains/cc_toolchain_info.bzl
@@ -89,7 +89,8 @@ FeatureInfo = provider(
"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.",
- "known": "(bool) Whether the feature is a known feature. Known features are assumed to be defined elsewhere.",
+ "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",
},
)
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/impl/collect.bzl b/cc/toolchains/impl/collect.bzl
index 5865d27..3fab3e6 100644
--- a/cc/toolchains/impl/collect.bzl
+++ b/cc/toolchains/impl/collect.bzl
@@ -144,7 +144,11 @@ def collect_args_lists(targets, label):
args = tuple(args),
files = depset(transitive = transitive_files),
by_action = tuple([
- struct(action = k, args = v.args, files = depset(transitive = v.transitive_files))
+ struct(
+ action = k,
+ args = tuple(v.args),
+ files = depset(transitive = v.transitive_files),
+ )
for k, v in by_action.items()
]),
)
diff --git a/tests/rule_based_toolchain/features/BUILD b/tests/rule_based_toolchain/features/BUILD
new file mode 100644
index 0000000..0d9f5cd
--- /dev/null
+++ b/tests/rule_based_toolchain/features/BUILD
@@ -0,0 +1,55 @@
+load("@rules_testing//lib:util.bzl", "util")
+load("//cc/toolchains:args.bzl", "cc_args")
+load("//cc/toolchains:feature.bzl", "cc_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"],
+ additional_files = ["//tests/rule_based_toolchain/testdata:file1"],
+ args = ["c"],
+)
+
+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 = "requires",
+ args = [":c_compile"],
+ enabled = True,
+ feature_name = "requires",
+ requires_any_of = [":simple"],
+)
+
+util.helper_target(
+ cc_feature,
+ name = "implies",
+ args = [":c_compile"],
+ enabled = True,
+ feature_name = "implies",
+ implies = [":simple"],
+)
+
+util.helper_target(
+ cc_feature,
+ name = "mutual_exclusion_feature",
+ args = [":c_compile"],
+ enabled = True,
+ feature_name = "mutual_exclusion",
+ mutually_exclusive = [":simple"],
+)
+
+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..75ae7ee
--- /dev/null
+++ b/tests/rule_based_toolchain/features/features_test.bzl
@@ -0,0 +1,80 @@
+# 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",
+ "ArgsInfo",
+ "FeatureInfo",
+ "MutuallyExclusiveCategoryInfo",
+)
+
+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]])
+
+def _feature_collects_requirements_test(env, targets):
+ env.expect.that_target(targets.requires).provider(
+ FeatureInfo,
+ ).requires_any_of().contains_exactly([
+ targets.simple.label,
+ ])
+
+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 = [
+ ":c_compile",
+ ":simple",
+ ":requires",
+ ":implies",
+ ":mutual_exclusion_feature",
+]
+
+# @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,
+}
diff --git a/tests/rule_based_toolchain/subjects.bzl b/tests/rule_based_toolchain/subjects.bzl
index 5fcb8a1..23f5ec7 100644
--- a/tests/rule_based_toolchain/subjects.bzl
+++ b/tests/rule_based_toolchain/subjects.bzl
@@ -71,7 +71,8 @@ _FEATURE_FLAGS = dict(
implies = None,
requires_any_of = None,
mutually_exclusive = ProviderSequence(_MutuallyExclusiveCategoryFactory),
- known = _subjects.bool,
+ overridable = _subjects.bool,
+ external = _subjects.bool,
overrides = None,
)
@@ -146,7 +147,7 @@ _FeatureFactory = generate_factory(
args = _ArgsListFactory.factory,
implies = ProviderDepset(_FakeFeatureFactory),
requires_any_of = ProviderSequence(_FeatureSetFactory),
- overrides = optional_subject(_FakeFeatureFactory),
+ overrides = optional_subject(_FakeFeatureFactory.factory),
),
)