1# Copyright 2024 The Bazel Authors. All rights reserved. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14"""Implementation of the cc_feature rule.""" 15 16load( 17 "//cc/toolchains/impl:collect.bzl", 18 "collect_args_lists", 19 "collect_features", 20 "collect_provider", 21) 22load( 23 ":cc_toolchain_info.bzl", 24 "ArgsListInfo", 25 "FeatureConstraintInfo", 26 "FeatureInfo", 27 "FeatureSetInfo", 28 "MutuallyExclusiveCategoryInfo", 29) 30 31def _cc_feature_impl(ctx): 32 if bool(ctx.attr.feature_name) == (ctx.attr.overrides != None): 33 fail("Exactly one of 'feature_name' and 'overrides' are required") 34 35 if ctx.attr.overrides == None: 36 overrides = None 37 38 # In the future, we may consider making feature_name optional, 39 # defaulting to ctx.label.name. However, starting that way would make it 40 # very difficult if we did want to later change that. 41 name = ctx.attr.feature_name 42 else: 43 overrides = ctx.attr.overrides[FeatureInfo] 44 if not overrides.overridable: 45 fail("Attempting to override %s, which is not overridable" % overrides.label) 46 name = overrides.name 47 48 # In the following scenario: 49 # cc_args(name = "foo", env = {"FOO": "BAR"}, args = ["--foo"]) 50 # cc_action_config(name = "ac", args=[":foo"]) 51 52 # We will translate this into providers roughly equivalent to the following: 53 # cc_args(name = "implied_by_ac_env", env = {"FOO": "BAR"}, args = ["--foo"]) 54 # cc_feature(name = "implied_by_ac", args = [":implied_by_ac_env"]) 55 # cc_action_config( 56 # name = "c_compile", 57 # implies = [":implied_by_c_compile"] 58 # ) 59 60 # The reason for this is because although the legacy providers support 61 # flag_sets in action_config, they don't support env sets. 62 if name.startswith("implied_by_"): 63 fail("Feature names starting with 'implied_by' are reserved") 64 65 args = collect_args_lists(ctx.attr.args, ctx.label) 66 feature = FeatureInfo( 67 label = ctx.label, 68 name = name, 69 # Unused field, but leave it just in case we want to reuse it in the 70 # future. 71 enabled = False, 72 args = args, 73 implies = collect_features(ctx.attr.implies), 74 requires_any_of = tuple(collect_provider( 75 ctx.attr.requires_any_of, 76 FeatureSetInfo, 77 )), 78 mutually_exclusive = tuple(collect_provider( 79 ctx.attr.mutually_exclusive, 80 MutuallyExclusiveCategoryInfo, 81 )), 82 external = False, 83 overridable = False, 84 overrides = overrides, 85 allowlist_include_directories = args.allowlist_include_directories, 86 ) 87 88 return [ 89 feature, 90 FeatureSetInfo(label = ctx.label, features = depset([feature])), 91 FeatureConstraintInfo( 92 label = ctx.label, 93 all_of = depset([feature]), 94 none_of = depset([]), 95 ), 96 MutuallyExclusiveCategoryInfo(label = ctx.label, name = name), 97 ] 98 99cc_feature = rule( 100 implementation = _cc_feature_impl, 101 # @unsorted-dict-items 102 attrs = { 103 "feature_name": attr.string( 104 doc = """The name of the feature that this rule implements. 105 106The feature name is a string that will be used in the `features` attribute of 107rules to enable them (eg. `cc_binary(..., features = ["opt"])`. 108 109While two features with the same `feature_name` may not be bound to the same 110toolchain, they can happily live alongside each other in the same BUILD file. 111 112Example: 113``` 114cc_feature( 115 name = "sysroot_macos", 116 feature_name = "sysroot", 117 ... 118) 119 120cc_feature( 121 name = "sysroot_linux", 122 feature_name = "sysroot", 123 ... 124) 125``` 126""", 127 ), 128 "args": attr.label_list( 129 doc = """A list of `cc_args` or `cc_args_list` labels that are expanded when this feature is enabled.""", 130 providers = [ArgsListInfo], 131 ), 132 "requires_any_of": attr.label_list( 133 doc = """A list of feature sets that define toolchain compatibility. 134 135If *at least one* of the listed `cc_feature_set`s are fully satisfied (all 136features exist in the toolchain AND are currently enabled), this feature is 137deemed compatible and may be enabled. 138 139Note: Even if `cc_feature.requires_any_of` is satisfied, a feature is not 140enabled unless another mechanism (e.g. command-line flags, `cc_feature.implies`, 141`cc_toolchain_config.enabled_features`) signals that the feature should actually 142be enabled. 143""", 144 providers = [FeatureSetInfo], 145 ), 146 "implies": attr.label_list( 147 providers = [FeatureSetInfo], 148 doc = """List of features enabled along with this feature. 149 150Warning: If any of the features cannot be enabled, this feature is 151silently disabled. 152""", 153 ), 154 "mutually_exclusive": attr.label_list( 155 providers = [MutuallyExclusiveCategoryInfo], 156 doc = """A list of things that this feature is mutually exclusive with. 157 158It can be either: 159* A feature, in which case the two features are mutually exclusive. 160* A `cc_mutually_exclusive_category`, in which case all features that write 161 `mutually_exclusive = [":category"]` are mutually exclusive with each other. 162 163If this feature has a side-effect of implementing another feature, it can be 164useful to list that feature here to ensure they aren't enabled at the same time. 165""", 166 ), 167 "overrides": attr.label( 168 providers = [FeatureInfo], 169 doc = """A declaration that this feature overrides a known feature. 170 171In the example below, if you missed the "overrides" attribute, it would complain 172that the feature "opt" was defined twice. 173 174Example: 175``` 176load("//cc/toolchains:feature.bzl", "cc_feature") 177 178cc_feature( 179 name = "opt", 180 feature_name = "opt", 181 args = [":size_optimized"], 182 overrides = "//cc/toolchains/features:opt", 183) 184``` 185""", 186 ), 187 }, 188 provides = [ 189 FeatureInfo, 190 FeatureSetInfo, 191 FeatureConstraintInfo, 192 MutuallyExclusiveCategoryInfo, 193 ], 194 doc = """A dynamic set of toolchain flags that create a singular [feature](https://bazel.build/docs/cc-toolchain-config-reference#features) definition. 195 196A feature is basically a dynamically toggleable `cc_args_list`. There are a variety of 197dependencies and compatibility requirements that must be satisfied to enable a 198`cc_feature`. Once those conditions are met, the arguments in [`cc_feature.args`](#cc_feature-args) 199are expanded and added to the command-line. 200 201A feature may be enabled or disabled through the following mechanisms: 202* Via command-line flags, or a `.bazelrc` file via the 203 [`--features` flag](https://bazel.build/reference/command-line-reference#flag--features) 204* Through inter-feature relationships (via [`cc_feature.implies`](#cc_feature-implies)) where one 205 feature may implicitly enable another. 206* Individual rules (e.g. `cc_library`) or `package` definitions may elect to manually enable or 207 disable features through the 208 [`features` attribute](https://bazel.build/reference/be/common-definitions#common.features). 209 210Note that a feature may alternate between enabled and disabled dynamically over the course of a 211build. Because of their toggleable nature, it's generally best to avoid adding arguments to a 212`cc_toolchain` as a `cc_feature` unless strictly necessary. Instead, prefer to express arguments 213via [`cc_toolchain.args`](#cc_toolchain-args) whenever possible. 214 215You should use a `cc_feature` when any of the following apply: 216* You need the flags to be dynamically toggled over the course of a build. 217* You want build files to be able to configure the flags in question. For example, a 218 binary might specify `features = ["optimize_for_size"]` to create a small 219 binary instead of optimizing for performance. 220* You need to carry forward Starlark toolchain behaviors. If you're migrating a 221 complex Starlark-based toolchain definition to these rules, many of the 222 workflows and flags were likely based on features. 223 224If you only need to configure flags via the Bazel command-line, instead 225consider adding a 226[`bool_flag`](https://github.com/bazelbuild/bazel-skylib/tree/main/doc/common_settings_doc.md#bool_flag) 227paired with a [`config_setting`](https://bazel.build/reference/be/general#config_setting) 228and then make your `cc_args` rule `select` on the `config_setting`. 229 230For more details about how Bazel handles features, see the official Bazel 231documentation at 232https://bazel.build/docs/cc-toolchain-config-reference#features. 233 234Example: 235``` 236load("//cc/toolchains:feature.bzl", "cc_feature") 237 238# A feature that enables LTO, which may be incompatible when doing interop with various 239# languages (e.g. rust, go), or may need to be disabled for particular `cc_binary` rules 240# for various reasons. 241cc_feature( 242 name = "lto", 243 feature_name = "lto", 244 args = [":lto_args"], 245) 246``` 247""", 248) 249