1# Copyright 2023 The Pigweed Authors 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); you may not 4# use this file except in compliance with the License. You may obtain a copy of 5# the License at 6# 7# https://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, WITHOUT 11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12# License for the specific language governing permissions and limitations under 13# the License. 14"""Implementation of the pw_cc_feature and pw_cc_feature_set rules.""" 15 16load( 17 ":providers.bzl", 18 "PwBuiltinFeatureInfo", 19 "PwFeatureConstraintInfo", 20 "PwFeatureInfo", 21 "PwFeatureSetInfo", 22 "PwFlagSetInfo", 23 "PwMutuallyExclusiveCategoryInfo", 24) 25 26def _pw_cc_mutually_exclusive_category_impl(ctx): 27 return [PwMutuallyExclusiveCategoryInfo( 28 name = ctx.attr.category_name or str(ctx.label), 29 )] 30 31pw_cc_mutually_exclusive_category = rule( 32 implementation = _pw_cc_mutually_exclusive_category_impl, 33 provides = [PwMutuallyExclusiveCategoryInfo], 34 doc = """Creates a category of mutually exclusive features. 35 36Example: 37 38 pw_cc_mutually_exclusive_category( 39 name = "compilation_mode", 40 ) 41 42 pw_cc_feature(name = "opt", mutually_exclusive = [":compilation_mode"], ...) 43 pw_cc_feature(name = "dbg", mutually_exclusive = [":compilation_mode"], ...) 44 pw_cc_feature(name = "fastbuild", mutually_exclusive = [":compilation_mode"], ...) 45 46""", 47 attrs = { 48 "category_name": attr.string( 49 doc = """A backdoor to support old-style provides 50 51Not recommended to be used, as it can clash with other provides. 52""", 53 ), 54 }, 55) 56 57def _pw_cc_feature_set_impl(ctx): 58 if not ctx.attr.all_of: 59 fail("At least one feature must be specified in `all_of`") 60 features = depset(transitive = [attr[PwFeatureSetInfo].features for attr in ctx.attr.all_of]) 61 62 return [ 63 PwFeatureSetInfo(features = features), 64 PwFeatureConstraintInfo(all_of = features, none_of = depset([])), 65 ] 66 67pw_cc_feature_set = rule( 68 implementation = _pw_cc_feature_set_impl, 69 attrs = { 70 "all_of": attr.label_list( 71 providers = [PwFeatureSetInfo], 72 doc = """Features that must be enabled for this feature set to be deemed compatible with the current toolchain configuration.""", 73 ), 74 }, 75 provides = [PwFeatureSetInfo], 76 doc = """Defines a set of features. 77 78Example: 79 80 pw_cc_feature_set( 81 name = "thin_lto_requirements", 82 all_of = [ 83 ":thin_lto", 84 ":opt", 85 ], 86 ) 87""", 88) 89 90def _pw_cc_feature_impl(ctx): 91 name = ctx.attr.feature_name 92 if name.startswith("implied_by_"): 93 fail("Feature names starting with 'implied_by' are reserved") 94 95 implies_features = depset(transitive = [ 96 attr[PwFeatureSetInfo].features 97 for attr in ctx.attr.implies 98 ]) 99 requires = [req[PwFeatureSetInfo] for req in ctx.attr.requires_any_of] 100 101 overrides = None 102 if ctx.attr.overrides != None: 103 overrides = ctx.attr.overrides[PwFeatureInfo] 104 if overrides.name != name: 105 fail("%s is supposed to override %s, but they have different feature names" % (ctx.label, overrides.label)) 106 107 feature = PwFeatureInfo( 108 label = ctx.label, 109 name = name, 110 enabled = ctx.attr.enabled, 111 flag_sets = depset([ 112 fs[PwFlagSetInfo] 113 for fs in ctx.attr.flag_sets 114 ]), 115 implies_features = implies_features, 116 implies_action_configs = depset([]), 117 requires_any_of = tuple(requires), 118 provides = tuple([ 119 p[PwMutuallyExclusiveCategoryInfo].name 120 for p in ctx.attr.mutually_exclusive 121 ]), 122 known = False, 123 overrides = overrides, 124 ) 125 126 return [ 127 feature, 128 PwFeatureSetInfo(features = depset([feature])), 129 PwFeatureConstraintInfo(all_of = depset([feature]), none_of = depset([])), 130 PwMutuallyExclusiveCategoryInfo(name = name), 131 ] 132 133pw_cc_feature = rule( 134 implementation = _pw_cc_feature_impl, 135 attrs = { 136 "enabled": attr.bool( 137 mandatory = True, 138 doc = """Whether or not this feature is enabled by default.""", 139 ), 140 "feature_name": attr.string( 141 mandatory = True, 142 doc = """The name of the feature that this rule implements. 143 144Feature names are used to express feature dependencies and compatibility. 145Because features are tracked by string names rather than labels, there's great 146flexibility in swapping out feature implementations or overriding the built-in 147legacy features that Bazel silently binds to every toolchain. 148 149`feature_name` is used rather than `name` to distinguish between the rule 150name, and the intended final feature name. This allows similar rules to exist 151in the same package, even if slight differences are required. 152 153Example: 154 155 pw_cc_feature( 156 name = "sysroot_macos", 157 feature_name = "sysroot", 158 ... 159 ) 160 161 pw_cc_feature( 162 name = "sysroot_linux", 163 feature_name = "sysroot", 164 ... 165 ) 166 167While two features with the same `feature_name` may not be bound to the same 168toolchain, they can happily live alongside each other in the same BUILD file. 169""", 170 ), 171 "flag_sets": attr.label_list( 172 doc = """Flag sets that, when expanded, implement this feature.""", 173 providers = [PwFlagSetInfo], 174 ), 175 "implies": attr.label_list( 176 providers = [PwFeatureSetInfo], 177 doc = """List of features enabled along with this feature. 178 179Warning: If any of the named features cannot be enabled, this feature is 180silently disabled. 181""", 182 ), 183 "mutually_exclusive": attr.label_list( 184 providers = [PwMutuallyExclusiveCategoryInfo], 185 doc = """A list of things that this is mutually exclusive with. 186 187It can be either: 188* A feature, in which case the two features are mutually exclusive. 189* A `pw_cc_mutually_exclusive_category`, in which case all features that write 190 `mutually_exclusive = [":category"]` are mutually exclusive with each other. 191 192If this feature has a side-effect of implementing another feature, it can be 193useful to list that feature here to ensure they aren't enabled at the 194same time. 195""", 196 ), 197 "overrides": attr.label( 198 providers = [PwBuiltinFeatureInfo, PwFeatureInfo], 199 doc = """A declaration that this feature overrides a known feature. 200 201In the example below, if you missed the "overrides" attribute, it would complain 202that the feature "opt" was defined twice. 203 204Example: 205 206 pw_cc_feature( 207 name = "opt", 208 feature_name = "opt", 209 ... 210 overrides = "@pw_toolchain//features/well_known:opt", 211 ) 212 213""", 214 ), 215 "requires_any_of": attr.label_list( 216 doc = """A list of feature sets that define toolchain compatibility. 217 218If *at least one* of the listed `pw_cc_feature_set`s are fully satisfied (all 219features exist in the toolchain AND are currently enabled), this feature is 220deemed compatible and may be enabled. 221 222Note: Even if `pw_cc_feature.requires_any_of` is satisfied, a feature is not 223enabled unless another mechanism (e.g. command-line flags, 224`pw_cc_feature.implies`,`pw_cc_feature.enabled`) signals that the feature should 225actually be enabled. 226""", 227 providers = [PwFeatureSetInfo], 228 ), 229 }, 230 provides = [ 231 PwFeatureInfo, 232 PwFeatureSetInfo, 233 PwFeatureConstraintInfo, 234 PwMutuallyExclusiveCategoryInfo, 235 ], 236 doc = """Defines the implemented behavior of a C/C++ toolchain feature. 237 238This rule is effectively a wrapper for the `feature` constructor in 239@rules_cc//cc:cc_toolchain_config_lib.bzl. 240 241A feature is basically a dynamic flag set. There are a variety of dependencies 242and compatibility requirements that must be satisfied for the listed flag sets 243to be applied. 244 245A feature may be enabled or disabled through the following mechanisms: 246* Via command-line flags, or a `.bazelrc`. 247* Through inter-feature relationships (enabling one feature may implicitly 248 enable another). 249* Individual rules may elect to manually enable or disable features through the 250 builtin ``features`` attribute. 251 252Because of the dynamic nature of toolchain features, it's generally best to 253avoid enumerating features as part of your toolchain with the following 254exceptions: 255* You want the flags to be controllable via Bazel's CLI. For example, adding 256 `-v` to a compiler invocation is often too verbose to be useful for most 257 workflows, but can be instrumental when debugging obscure errors. By 258 expressing compiler verbosity as a feature, users may opt-in when necessary. 259* You need to carry forward Starlark toolchain behaviors. If you're migrating a 260 complex Starlark-based toolchain definition to these rules, many of the 261 workflows and flags were likely based on features. This rule exists to support 262 those existing structures. 263 264For more details about how Bazel handles features, see the official Bazel 265documentation at 266https://bazel.build/docs/cc-toolchain-config-reference#features. 267 268Examples: 269 270 # A feature that can be easily toggled to include extra compiler output to 271 # help debug things like include search path ordering and showing all the 272 # flags passed to the compiler. 273 # 274 # Add `--features=verbose_compiler_output` to your Bazel invocation to 275 # enable. 276 pw_cc_feature( 277 name = "verbose_compiler_output", 278 enabled = False, 279 feature_name = "verbose_compiler_output", 280 flag_sets = [":verbose_compiler_flags"], 281 ) 282 283 # This feature signals a capability, and doesn't have associated flags. 284 # 285 # For a list of well-known features, see: 286 # https://bazel.build/docs/cc-toolchain-config-reference#wellknown-features 287 pw_cc_feature( 288 name = "link_object_files", 289 enabled = True, 290 feature_name = "supports_start_end_lib", 291 ) 292""", 293) 294