xref: /aosp_15_r20/external/pigweed/pw_toolchain_bazel/cc_toolchain/private/flag_set.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 pw_cc_flag_set and pw_cc_flag_group."""
15
16load(
17    "@rules_cc//cc:cc_toolchain_config_lib.bzl",
18    "flag_group",
19)
20load(
21    ":providers.bzl",
22    "PwActionNameSetInfo",
23    "PwFeatureConstraintInfo",
24    "PwFlagGroupInfo",
25    "PwFlagSetInfo",
26)
27
28def _pw_cc_flag_group_impl(ctx):
29    """Implementation for pw_cc_flag_group."""
30
31    # If these are empty strings, they are handled differently than if they
32    # are None. Rather than an explicit error or breakage, there's just silent
33    # behavioral differences. Ideally, these attributes default to `None`, but
34    # that is not supported with string types. Since these have no practical
35    # meaning if they are empty strings, just remap empty strings to `None`.
36    #
37    # A minimal reproducer of this behavior with some useful analysis is
38    # provided here:
39    #
40    #     https://github.com/armandomontanez/bazel_reproducers/tree/main/flag_group_with_empty_strings
41    iterate_over = ctx.attr.iterate_over if ctx.attr.iterate_over else None
42    expand_if = ctx.attr.expand_if_available if ctx.attr.expand_if_available else None
43    expand_if_not = ctx.attr.expand_if_not_available if ctx.attr.expand_if_not_available else None
44    return flag_group(
45        flags = ctx.attr.flags,
46        iterate_over = iterate_over,
47        expand_if_available = expand_if,
48        expand_if_not_available = expand_if_not,
49    )
50
51pw_cc_flag_group = rule(
52    implementation = _pw_cc_flag_group_impl,
53    attrs = {
54        "expand_if_available": attr.string(
55            doc = "Expands the expression in `flags` if the specified build variable is set.",
56        ),
57        "expand_if_not_available": attr.string(
58            doc = "Expands the expression in `flags` if the specified build variable is NOT set.",
59        ),
60        "flags": attr.string_list(
61            doc = """List of flags provided by this rule.
62
63For extremely complex expressions of flags that require nested flag groups with
64multiple layers of expansion, prefer creating a custom rule in Starlark that
65provides `PwFlagGroupInfo` or `PwFlagSetInfo`.
66""",
67        ),
68        "iterate_over": attr.string(
69            doc = """Expands `flags` for items in the named list.
70
71Toolchain actions have various variables accessible as names that can be used
72to guide flag expansions. For variables that are lists, `iterate_over` must be
73used to expand the list into a series of flags.
74
75Note that `iterate_over` is the string name of a build variable, and not an
76actual list. Valid options are listed at:
77
78    https://bazel.build/docs/cc-toolchain-config-reference#cctoolchainconfiginfo-build-variables
79
80Note that the flag expansion stamps out the entire list of flags in `flags`
81once for each item in the list.
82
83Example:
84
85    # Expands each path in `system_include_paths` to a series of `-isystem`
86    # includes.
87    #
88    # Example input:
89    #     system_include_paths = ["/usr/local/include", "/usr/include"]
90    #
91    # Expected result:
92    #     "-isystem /usr/local/include -isystem /usr/include"
93    pw_cc_flag_group(
94        name = "system_include_paths",
95        flags = ["-isystem", "%{system_include_paths}"],
96        iterate_over = "system_include_paths",
97    )
98""",
99        ),
100    },
101    provides = [PwFlagGroupInfo],
102    doc = """Declares an (optionally parametric) ordered set of flags.
103
104`pw_cc_flag_group` rules are expected to be consumed exclusively by
105`pw_cc_flag_set` rules. Though simple lists of flags can be expressed by
106populating `flags` on a `pw_cc_flag_set`, `pw_cc_flag_group` provides additional
107power in the following two ways:
108
109    1. Iteration and conditional expansion. Using `iterate_over`,
110       `expand_if_available`, and `expand_if_not_available`, more complex flag
111       expressions can be made. This is critical for implementing things like
112       the `libraries_to_link` feature, where library names are transformed
113       into flags that end up in the final link invocation.
114
115       Note: `expand_if_equal`, `expand_if_true`, and `expand_if_false` are not
116       yet supported.
117
118    2. Flags are tool-independent. A `pw_cc_flag_group` expresses ordered flags
119       that may be reused across various `pw_cc_flag_set` rules. This is useful
120       for cases where multiple `pw_cc_flag_set` rules must be created to
121       implement a feature for which flags are slightly different depending on
122       the action (e.g. compile vs link). Common flags can be expressed in a
123       shared `pw_cc_flag_group`, and the differences can be relegated to
124       separate `pw_cc_flag_group` instances.
125
126Examples:
127
128    pw_cc_flag_group(
129        name = "user_compile_flag_expansion",
130        flags = ["%{user_compile_flags}"],
131        iterate_over = "user_compile_flags",
132        expand_if_available = "user_compile_flags",
133    )
134
135    # This flag_group might be referenced from various FDO-related
136    # `pw_cc_flag_set` rules. More importantly, the flag sets pulling this in
137    # may apply to different sets of actions.
138    pw_cc_flag_group(
139        name = "fdo_profile_correction",
140        flags = ["-fprofile-correction"],
141        expand_if_available = "fdo_profile_path",
142    )
143""",
144)
145
146def _pw_cc_flag_set_impl(ctx):
147    """Implementation for pw_cc_flag_set."""
148    if ctx.attr.flags and ctx.attr.flag_groups:
149        fail("{} specifies both `flag_groups` and `flags`, but only one can be specified. Consider splitting into two `pw_cc_flag_set` rules to make the intended order clearer.".format(ctx.label))
150    flag_groups = []
151    if ctx.attr.flags:
152        flag_groups.append(flag_group(flags = ctx.attr.flags))
153    elif ctx.attr.flag_groups:
154        for dep in ctx.attr.flag_groups:
155            if not dep[PwFlagGroupInfo]:
156                fail("{} in `flag_groups` of {} does not provide PwFlagGroupInfo".format(dep.label, ctx.label))
157
158        flag_groups = [dep[PwFlagGroupInfo] for dep in ctx.attr.flag_groups]
159
160    actions = depset(transitive = [
161        action[PwActionNameSetInfo].actions
162        for action in ctx.attr.actions
163    ]).to_list()
164    if not actions:
165        fail("Each pw_cc_flag_set must specify at least one action")
166
167    requires = [fc[PwFeatureConstraintInfo] for fc in ctx.attr.requires_any_of]
168    return [
169        PwFlagSetInfo(
170            label = ctx.label,
171            actions = tuple(actions),
172            requires_any_of = tuple(requires),
173            flag_groups = tuple(flag_groups),
174            env = ctx.attr.env,
175            env_expand_if_available = ctx.attr.env_expand_if_available,
176        ),
177    ]
178
179pw_cc_flag_set = rule(
180    implementation = _pw_cc_flag_set_impl,
181    attrs = {
182        "actions": attr.label_list(
183            providers = [PwActionNameSetInfo],
184            mandatory = True,
185            doc = """A list of action names that this flag set applies to.
186
187See @pw_toolchain//actions:all for valid options.
188""",
189        ),
190        "env": attr.string_dict(
191            doc = "Environment variables to be added to these actions",
192        ),
193        "env_expand_if_available": attr.string(
194            doc = "A build variable that needs to be available in order to expand the env entries",
195        ),
196        "flag_groups": attr.label_list(
197            doc = """Labels pointing to `pw_cc_flag_group` rules.
198
199This is intended to be compatible with any other rules that provide
200`PwFlagGroupInfo`. These are evaluated in order, with earlier flag groups
201appearing earlier in the invocation of the underlying tool.
202
203Note: `flag_groups` and `flags` are mutually exclusive.
204""",
205        ),
206        "flags": attr.string_list(
207            doc = """Flags that should be applied to the specified actions.
208
209These are evaluated in order, with earlier flags appearing earlier in the
210invocation of the underlying tool. If you need expansion logic, prefer
211enumerating flags in a `pw_cc_flag_group` or create a custom rule that provides
212`PwFlagGroupInfo`.
213
214Note: `flags` and `flag_groups` are mutually exclusive.
215""",
216        ),
217        "requires_any_of": attr.label_list(
218            providers = [PwFeatureConstraintInfo],
219            doc = """This will be enabled when any of the constraints are met.
220
221If omitted, this flag set will be enabled unconditionally.
222""",
223        ),
224    },
225    provides = [PwFlagSetInfo],
226    doc = """Declares an ordered set of flags bound to a set of actions.
227
228Flag sets can be attached to a `pw_cc_toolchain` via `flag_sets`.
229
230Examples:
231
232    pw_cc_flag_set(
233        name = "warnings_as_errors",
234        flags = ["-Werror"],
235    )
236
237    pw_cc_flag_set(
238        name = "layering_check",
239        flag_groups = [
240            ":strict_module_headers",
241            ":dependent_module_map_files",
242        ],
243    )
244
245Note: In the vast majority of cases, alphabetical sorting is not desirable for
246the `flags` and `flag_groups` attributes. Buildifier shouldn't ever try to sort
247these, but in the off chance it starts to these members should be listed as
248exceptions in the `SortableDenylist`.
249""",
250)
251