xref: /aosp_15_r20/external/bazelbuild-rules_cc/cc/toolchains/feature.bzl (revision eed53cd41c5909d05eedc7ad9720bb158fd93452)
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