xref: /aosp_15_r20/external/pigweed/pw_toolchain_bazel/cc_toolchain/private/feature.bzl (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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