Googler | 2b6cdcf | 2024-02-27 04:44:55 -0800 | [diff] [blame] | 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 | |
| 16 | load( |
| 17 | "//cc/toolchains/impl:collect.bzl", |
| 18 | "collect_args_lists", |
| 19 | "collect_features", |
| 20 | "collect_provider", |
| 21 | ) |
| 22 | load( |
| 23 | ":cc_toolchain_info.bzl", |
| 24 | "ArgsListInfo", |
| 25 | "FeatureConstraintInfo", |
| 26 | "FeatureInfo", |
| 27 | "FeatureSetInfo", |
| 28 | "MutuallyExclusiveCategoryInfo", |
| 29 | ) |
| 30 | |
| 31 | def _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 | |
Googler | 66613ac | 2024-09-05 09:05:23 -0700 | [diff] [blame] | 65 | args = collect_args_lists(ctx.attr.args, ctx.label) |
Googler | 2b6cdcf | 2024-02-27 04:44:55 -0800 | [diff] [blame] | 66 | feature = FeatureInfo( |
| 67 | label = ctx.label, |
| 68 | name = name, |
Googler | 84fceed | 2024-08-01 19:19:23 -0700 | [diff] [blame] | 69 | # Unused field, but leave it just in case we want to reuse it in the |
| 70 | # future. |
| 71 | enabled = False, |
Googler | 66613ac | 2024-09-05 09:05:23 -0700 | [diff] [blame] | 72 | args = args, |
Googler | 2b6cdcf | 2024-02-27 04:44:55 -0800 | [diff] [blame] | 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, |
Googler | 66613ac | 2024-09-05 09:05:23 -0700 | [diff] [blame] | 85 | allowlist_include_directories = args.allowlist_include_directories, |
Googler | 2b6cdcf | 2024-02-27 04:44:55 -0800 | [diff] [blame] | 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 | |
| 99 | cc_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 | |
| 106 | The feature name is a string that will be used in the `features` attribute of |
| 107 | rules to enable them (eg. `cc_binary(..., features = ["opt"])`. |
| 108 | |
| 109 | While two features with the same `feature_name` may not be bound to the same |
| 110 | toolchain, they can happily live alongside each other in the same BUILD file. |
| 111 | |
| 112 | Example: |
Googler | 1af2140 | 2024-10-02 09:09:54 -0700 | [diff] [blame] | 113 | ``` |
| 114 | cc_feature( |
| 115 | name = "sysroot_macos", |
| 116 | feature_name = "sysroot", |
| 117 | ... |
| 118 | ) |
Googler | 2b6cdcf | 2024-02-27 04:44:55 -0800 | [diff] [blame] | 119 | |
Googler | 1af2140 | 2024-10-02 09:09:54 -0700 | [diff] [blame] | 120 | cc_feature( |
| 121 | name = "sysroot_linux", |
| 122 | feature_name = "sysroot", |
| 123 | ... |
| 124 | ) |
| 125 | ``` |
Googler | 2b6cdcf | 2024-02-27 04:44:55 -0800 | [diff] [blame] | 126 | """, |
| 127 | ), |
Googler | 2b6cdcf | 2024-02-27 04:44:55 -0800 | [diff] [blame] | 128 | "args": attr.label_list( |
Googler | 1af2140 | 2024-10-02 09:09:54 -0700 | [diff] [blame] | 129 | doc = """A list of `cc_args` or `cc_args_list` labels that are expanded when this feature is enabled.""", |
Googler | 2b6cdcf | 2024-02-27 04:44:55 -0800 | [diff] [blame] | 130 | providers = [ArgsListInfo], |
| 131 | ), |
| 132 | "requires_any_of": attr.label_list( |
| 133 | doc = """A list of feature sets that define toolchain compatibility. |
| 134 | |
| 135 | If *at least one* of the listed `cc_feature_set`s are fully satisfied (all |
| 136 | features exist in the toolchain AND are currently enabled), this feature is |
| 137 | deemed compatible and may be enabled. |
| 138 | |
| 139 | Note: Even if `cc_feature.requires_any_of` is satisfied, a feature is not |
| 140 | enabled unless another mechanism (e.g. command-line flags, `cc_feature.implies`, |
Googler | 84fceed | 2024-08-01 19:19:23 -0700 | [diff] [blame] | 141 | `cc_toolchain_config.enabled_features`) signals that the feature should actually |
| 142 | be enabled. |
Googler | 2b6cdcf | 2024-02-27 04:44:55 -0800 | [diff] [blame] | 143 | """, |
| 144 | providers = [FeatureSetInfo], |
| 145 | ), |
| 146 | "implies": attr.label_list( |
| 147 | providers = [FeatureSetInfo], |
| 148 | doc = """List of features enabled along with this feature. |
| 149 | |
| 150 | Warning: If any of the features cannot be enabled, this feature is |
| 151 | silently disabled. |
| 152 | """, |
| 153 | ), |
| 154 | "mutually_exclusive": attr.label_list( |
| 155 | providers = [MutuallyExclusiveCategoryInfo], |
Googler | 1af2140 | 2024-10-02 09:09:54 -0700 | [diff] [blame] | 156 | doc = """A list of things that this feature is mutually exclusive with. |
Googler | 2b6cdcf | 2024-02-27 04:44:55 -0800 | [diff] [blame] | 157 | |
| 158 | It 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 | |
| 163 | If this feature has a side-effect of implementing another feature, it can be |
Googler | 84fceed | 2024-08-01 19:19:23 -0700 | [diff] [blame] | 164 | useful to list that feature here to ensure they aren't enabled at the same time. |
Googler | 2b6cdcf | 2024-02-27 04:44:55 -0800 | [diff] [blame] | 165 | """, |
| 166 | ), |
| 167 | "overrides": attr.label( |
| 168 | providers = [FeatureInfo], |
| 169 | doc = """A declaration that this feature overrides a known feature. |
| 170 | |
| 171 | In the example below, if you missed the "overrides" attribute, it would complain |
| 172 | that the feature "opt" was defined twice. |
| 173 | |
| 174 | Example: |
Googler | 1af2140 | 2024-10-02 09:09:54 -0700 | [diff] [blame] | 175 | ``` |
| 176 | load("//cc/toolchains:feature.bzl", "cc_feature") |
Googler | 2b6cdcf | 2024-02-27 04:44:55 -0800 | [diff] [blame] | 177 | |
Googler | 1af2140 | 2024-10-02 09:09:54 -0700 | [diff] [blame] | 178 | cc_feature( |
| 179 | name = "opt", |
| 180 | feature_name = "opt", |
| 181 | args = [":size_optimized"], |
| 182 | overrides = "//cc/toolchains/features:opt", |
| 183 | ) |
| 184 | ``` |
Googler | 2b6cdcf | 2024-02-27 04:44:55 -0800 | [diff] [blame] | 185 | """, |
| 186 | ), |
| 187 | }, |
| 188 | provides = [ |
| 189 | FeatureInfo, |
| 190 | FeatureSetInfo, |
| 191 | FeatureConstraintInfo, |
| 192 | MutuallyExclusiveCategoryInfo, |
| 193 | ], |
Googler | 1af2140 | 2024-10-02 09:09:54 -0700 | [diff] [blame] | 194 | doc = """A dynamic set of toolchain flags that create a singular [feature](https://bazel.build/docs/cc-toolchain-config-reference#features) definition. |
Googler | 2b6cdcf | 2024-02-27 04:44:55 -0800 | [diff] [blame] | 195 | |
Googler | 1af2140 | 2024-10-02 09:09:54 -0700 | [diff] [blame] | 196 | A feature is basically a dynamically toggleable `cc_args_list`. There are a variety of |
| 197 | dependencies 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) |
| 199 | are expanded and added to the command-line. |
Googler | 2b6cdcf | 2024-02-27 04:44:55 -0800 | [diff] [blame] | 200 | |
| 201 | A feature may be enabled or disabled through the following mechanisms: |
Googler | 1af2140 | 2024-10-02 09:09:54 -0700 | [diff] [blame] | 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). |
Googler | 2b6cdcf | 2024-02-27 04:44:55 -0800 | [diff] [blame] | 209 | |
Googler | 1af2140 | 2024-10-02 09:09:54 -0700 | [diff] [blame] | 210 | Note that a feature may alternate between enabled and disabled dynamically over the course of a |
| 211 | build. 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 |
| 213 | via [`cc_toolchain.args`](#cc_toolchain-args) whenever possible. |
| 214 | |
| 215 | You 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 |
Googler | 2b6cdcf | 2024-02-27 04:44:55 -0800 | [diff] [blame] | 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 |
Googler | 1af2140 | 2024-10-02 09:09:54 -0700 | [diff] [blame] | 222 | workflows and flags were likely based on features. |
Googler | 2b6cdcf | 2024-02-27 04:44:55 -0800 | [diff] [blame] | 223 | |
Googler | 1af2140 | 2024-10-02 09:09:54 -0700 | [diff] [blame] | 224 | If you only need to configure flags via the Bazel command-line, instead |
| 225 | consider adding a |
| 226 | [`bool_flag`](https://github.com/bazelbuild/bazel-skylib/tree/main/doc/common_settings_doc.md#bool_flag) |
| 227 | paired with a [`config_setting`](https://bazel.build/reference/be/general#config_setting) |
| 228 | and then make your `cc_args` rule `select` on the `config_setting`. |
Googler | 2b6cdcf | 2024-02-27 04:44:55 -0800 | [diff] [blame] | 229 | |
| 230 | For more details about how Bazel handles features, see the official Bazel |
| 231 | documentation at |
| 232 | https://bazel.build/docs/cc-toolchain-config-reference#features. |
| 233 | |
Googler | 1af2140 | 2024-10-02 09:09:54 -0700 | [diff] [blame] | 234 | Example: |
| 235 | ``` |
| 236 | load("//cc/toolchains:feature.bzl", "cc_feature") |
Googler | 2b6cdcf | 2024-02-27 04:44:55 -0800 | [diff] [blame] | 237 | |
Googler | 1af2140 | 2024-10-02 09:09:54 -0700 | [diff] [blame] | 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. |
| 241 | cc_feature( |
| 242 | name = "lto", |
| 243 | feature_name = "lto", |
| 244 | args = [":lto_args"], |
| 245 | ) |
| 246 | ``` |
Googler | 2b6cdcf | 2024-02-27 04:44:55 -0800 | [diff] [blame] | 247 | """, |
| 248 | ) |