# 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") args = collect_args_lists(ctx.attr.args, ctx.label) feature = FeatureInfo( label = ctx.label, name = name, # Unused field, but leave it just in case we want to reuse it in the # future. enabled = False, args = args, 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, allowlist_include_directories = args.allowlist_include_directories, ) 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", ... ) """, ), "args": attr.label_list( 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_toolchain_config.enabled_features`) 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", 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", overrides = "//cc/toolchains/features:supports_pic ) """, )