blob: c2addbe10957e73cc1b983c227f50ecc1793eec9 [file] [log] [blame]
# Copyright 2023 The Pigweed Authors
#
# 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
#
# https://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 pw_cc_feature and pw_cc_feature_set rules."""
load(
":providers.bzl",
"PwBuiltinFeatureInfo",
"PwFeatureConstraintInfo",
"PwFeatureInfo",
"PwFeatureSetInfo",
"PwFlagSetInfo",
"PwMutuallyExclusiveCategoryInfo",
)
def _pw_cc_mutually_exclusive_category_impl(ctx):
return [PwMutuallyExclusiveCategoryInfo(
name = ctx.attr.category_name or str(ctx.label),
)]
pw_cc_mutually_exclusive_category = rule(
implementation = _pw_cc_mutually_exclusive_category_impl,
provides = [PwMutuallyExclusiveCategoryInfo],
doc = """Creates a category of mutually exclusive features.
Example:
pw_cc_mutually_exclusive_category(
name = "compilation_mode",
)
pw_cc_feature(name = "opt", mutually_exclusive = [":compilation_mode"], ...)
pw_cc_feature(name = "dbg", mutually_exclusive = [":compilation_mode"], ...)
pw_cc_feature(name = "fastbuild", mutually_exclusive = [":compilation_mode"], ...)
""",
attrs = {
"category_name": attr.string(
doc = """A backdoor to support old-style provides
Not recommended to be used, as it can clash with other provides.
""",
),
},
)
def _pw_cc_feature_set_impl(ctx):
if not ctx.attr.all_of:
fail("At least one feature must be specified in `all_of`")
features = depset(transitive = [attr[PwFeatureSetInfo].features for attr in ctx.attr.all_of])
return [
PwFeatureSetInfo(features = features),
PwFeatureConstraintInfo(all_of = features, none_of = depset([])),
]
pw_cc_feature_set = rule(
implementation = _pw_cc_feature_set_impl,
attrs = {
"all_of": attr.label_list(
providers = [PwFeatureSetInfo],
doc = """Features that must be enabled for this feature set to be deemed compatible with the current toolchain configuration.""",
),
},
provides = [PwFeatureSetInfo],
doc = """Defines a set of features.
Example:
pw_cc_feature_set(
name = "thin_lto_requirements",
all_of = [
":thin_lto",
":opt",
],
)
""",
)
def _pw_cc_feature_impl(ctx):
name = ctx.attr.feature_name
if name.startswith("implied_by_"):
fail("Feature names starting with 'implied_by' are reserved")
implies_features = depset(transitive = [
attr[PwFeatureSetInfo].features
for attr in ctx.attr.implies
])
requires = [req[PwFeatureSetInfo] for req in ctx.attr.requires_any_of]
overrides = None
if ctx.attr.overrides != None:
overrides = ctx.attr.overrides[PwFeatureInfo]
if overrides.name != name:
fail("%s is supposed to override %s, but they have different feature names" % (ctx.label, overrides.label))
feature = PwFeatureInfo(
label = ctx.label,
name = name,
enabled = ctx.attr.enabled,
flag_sets = depset([
fs[PwFlagSetInfo]
for fs in ctx.attr.flag_sets
]),
implies_features = implies_features,
implies_action_configs = depset([]),
requires_any_of = tuple(requires),
provides = tuple([
p[PwMutuallyExclusiveCategoryInfo].name
for p in ctx.attr.mutually_exclusive
]),
known = False,
overrides = overrides,
)
return [
feature,
PwFeatureSetInfo(features = depset([feature])),
PwFeatureConstraintInfo(all_of = depset([feature]), none_of = depset([])),
PwMutuallyExclusiveCategoryInfo(name = name),
]
pw_cc_feature = rule(
implementation = _pw_cc_feature_impl,
attrs = {
"feature_name": attr.string(
mandatory = True,
doc = """The name of the feature that this rule implements.
Feature names are used to express feature dependencies and compatibility.
Because features are tracked by string names rather than labels, there's great
flexibility in swapping out feature implementations or overriding the built-in
legacy features that Bazel silently binds to every toolchain.
`feature_name` is used rather than `name` to distinguish between the rule
name, and the intended final feature name. This allows similar rules to exist
in the same package, even if slight differences are required.
Example:
pw_cc_feature(
name = "sysroot_macos",
feature_name = "sysroot",
...
)
pw_cc_feature(
name = "sysroot_linux",
feature_name = "sysroot",
...
)
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.
""",
),
"enabled": attr.bool(
mandatory = True,
doc = """Whether or not this feature is enabled by default.""",
),
"flag_sets": attr.label_list(
doc = """Flag sets that, when expanded, implement this feature.""",
providers = [PwFlagSetInfo],
),
"requires_any_of": attr.label_list(
doc = """A list of feature sets that define toolchain compatibility.
If *at least one* of the listed `pw_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 `pw_cc_feature.requires_any_of` is satisfied, a feature is not
enabled unless another mechanism (e.g. command-line flags,
`pw_cc_feature.implies`,`pw_cc_feature.enabled`) signals that the feature should
actually be enabled.
""",
providers = [PwFeatureSetInfo],
),
"implies": attr.label_list(
providers = [PwFeatureSetInfo],
doc = """List of features enabled along with this feature.
Warning: If any of the named features cannot be enabled, this feature is
silently disabled.
""",
),
"mutually_exclusive": attr.label_list(
providers = [PwMutuallyExclusiveCategoryInfo],
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 `pw_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 = [PwBuiltinFeatureInfo, PwFeatureInfo],
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:
pw_cc_feature(
name = "opt",
feature_name = "opt",
...
overrides = "@pw_toolchain//features/well_known:opt",
)
""",
),
},
provides = [
PwFeatureInfo,
PwFeatureSetInfo,
PwFeatureConstraintInfo,
PwMutuallyExclusiveCategoryInfo,
],
doc = """Defines the implemented behavior of a C/C++ toolchain feature.
This rule is effectively a wrapper for the `feature` constructor in
@rules_cc//cc:cc_toolchain_config_lib.bzl.
A feature is basically a dynamic flag set. There are a variety of dependencies
and compatibility requirements that must be satisfied for the listed flag sets
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 dynamic nature of toolchain features, it's generally best to
avoid enumerating features as part of your toolchain with the following
exceptions:
* You want the flags to be controllable via Bazel's CLI. For example, adding
`-v` to a compiler invocation is often too verbose to be useful for most
workflows, but can be instrumental when debugging obscure errors. By
expressing compiler verbosity as a feature, users may opt-in when necessary.
* 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.
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 include extra compiler output to
# help debug things like include search path ordering and showing all the
# flags passed to the compiler.
#
# Add `--features=verbose_compiler_output` to your Bazel invocation to
# enable.
pw_cc_feature(
name = "verbose_compiler_output",
enabled = False,
feature_name = "verbose_compiler_output",
flag_sets = [":verbose_compiler_flags"],
)
# 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
pw_cc_feature(
name = "link_object_files",
enabled = True,
feature_name = "supports_start_end_lib",
)
""",
)