mirror of https://github.com/bazelbuild/rules_cc
BEGIN_PUBLIC
Implement cc_feature for the rule based toolchain. END_PUBLIC PiperOrigin-RevId: 610712498 Change-Id: I2539825f0f4cf7f234a2310de6af0662aeb0ea2c
This commit is contained in:
parent
c5493f9b2c
commit
2b6cdcfe88
|
@ -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()
|
||||
]),
|
||||
),
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
)
|
||||
|
|
|
@ -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
|
||||
)
|
||||
""",
|
||||
)
|
|
@ -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()
|
||||
]),
|
||||
)
|
||||
|
|
|
@ -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,
|
||||
)
|
|
@ -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,
|
||||
}
|
|
@ -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),
|
||||
),
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in New Issue