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 feature = FeatureInfo( 66 label = ctx.label, 67 name = name, 68 enabled = ctx.attr.enabled, 69 args = collect_args_lists(ctx.attr.args, ctx.label), 70 implies = collect_features(ctx.attr.implies), 71 requires_any_of = tuple(collect_provider( 72 ctx.attr.requires_any_of, 73 FeatureSetInfo, 74 )), 75 mutually_exclusive = tuple(collect_provider( 76 ctx.attr.mutually_exclusive, 77 MutuallyExclusiveCategoryInfo, 78 )), 79 external = False, 80 overridable = False, 81 overrides = overrides, 82 ) 83 84 return [ 85 feature, 86 FeatureSetInfo(label = ctx.label, features = depset([feature])), 87 FeatureConstraintInfo( 88 label = ctx.label, 89 all_of = depset([feature]), 90 none_of = depset([]), 91 ), 92 MutuallyExclusiveCategoryInfo(label = ctx.label, name = name), 93 ] 94 95cc_feature = rule( 96 implementation = _cc_feature_impl, 97 # @unsorted-dict-items 98 attrs = { 99 "feature_name": attr.string( 100 doc = """The name of the feature that this rule implements. 101 102The feature name is a string that will be used in the `features` attribute of 103rules to enable them (eg. `cc_binary(..., features = ["opt"])`. 104 105While two features with the same `feature_name` may not be bound to the same 106toolchain, they can happily live alongside each other in the same BUILD file. 107 108Example: 109 110 cc_feature( 111 name = "sysroot_macos", 112 feature_name = "sysroot", 113 ... 114 ) 115 116 cc_feature( 117 name = "sysroot_linux", 118 feature_name = "sysroot", 119 ... 120 ) 121""", 122 ), 123 "enabled": attr.bool( 124 mandatory = True, 125 doc = """Whether or not this feature is enabled by default.""", 126 ), 127 "args": attr.label_list( 128 mandatory = True, 129 doc = """Args that, when expanded, implement this feature.""", 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_feature.enabled`) signals that the feature should actually be enabled. 142""", 143 providers = [FeatureSetInfo], 144 ), 145 "implies": attr.label_list( 146 providers = [FeatureSetInfo], 147 doc = """List of features enabled along with this feature. 148 149Warning: If any of the features cannot be enabled, this feature is 150silently disabled. 151""", 152 ), 153 "mutually_exclusive": attr.label_list( 154 providers = [MutuallyExclusiveCategoryInfo], 155 doc = """A list of things that this is mutually exclusive with. 156 157It can be either: 158* A feature, in which case the two features are mutually exclusive. 159* A `cc_mutually_exclusive_category`, in which case all features that write 160 `mutually_exclusive = [":category"]` are mutually exclusive with each other. 161 162If this feature has a side-effect of implementing another feature, it can be 163useful to list that feature here to ensure they aren't enabled at the 164same 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 176 cc_feature( 177 name = "opt", 178 feature_name = "opt", 179 ... 180 overrides = "@toolchain//features/well_known:opt", 181 ) 182 183""", 184 ), 185 }, 186 provides = [ 187 FeatureInfo, 188 FeatureSetInfo, 189 FeatureConstraintInfo, 190 MutuallyExclusiveCategoryInfo, 191 ], 192 doc = """Defines the implemented behavior of a C/C++ toolchain feature. 193 194A feature is basically a toggleable list of args. There are a variety of 195dependencies and compatibility requirements that must be satisfied for the 196listed args to be applied. 197 198A feature may be enabled or disabled through the following mechanisms: 199* Via command-line flags, or a `.bazelrc`. 200* Through inter-feature relationships (enabling one feature may implicitly 201 enable another). 202* Individual rules may elect to manually enable or disable features through the 203 builtin `features` attribute. 204 205Because of the toggleable nature of toolchain features, it's generally best to 206avoid defining features as part of your toolchain with the following exceptions: 207* You want build files to be able to configure compiler flags. For example, a 208 binary might specify `features = ["optimize_for_size"]` to create a small 209 binary instead of optimizing for performance. 210* You need to carry forward Starlark toolchain behaviors. If you're migrating a 211 complex Starlark-based toolchain definition to these rules, many of the 212 workflows and flags were likely based on features. This rule exists to support 213 those existing structures. 214 215If you want to be able to configure flags via the bazel command-line, instead 216consider making a bool_flag, and then making your `cc_args` `select` on those 217flags. 218 219For more details about how Bazel handles features, see the official Bazel 220documentation at 221https://bazel.build/docs/cc-toolchain-config-reference#features. 222 223Examples: 224 225 # A feature that can be easily toggled to optimize for size 226 cc_feature( 227 name = "optimize_for_size", 228 enabled = False, 229 feature_name = "optimize_for_size", 230 args = [":optimize_for_size_args"], 231 ) 232 233 # This feature signals a capability, and doesn't have associated flags. 234 # 235 # For a list of well-known features, see: 236 # https://bazel.build/docs/cc-toolchain-config-reference#wellknown-features 237 cc_feature( 238 name = "supports_pic", 239 enabled = True, 240 overrides = "//cc/toolchains/features:supports_pic 241 ) 242""", 243) 244