aboutsummaryrefslogtreecommitdiff
path: root/pw_toolchain_bazel/cc_toolchain/private/flag_set.bzl
blob: 23094e864f5c4b4d4affe4580d0bda0057577fd3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# Copyright 2023 The Pigweed Authors
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
"""Implementation of pw_cc_flag_set and pw_cc_flag_group."""

load(
    "@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl",
    "FlagGroupInfo",
    "FlagSetInfo",
    "flag_group",
    "flag_set",
)

def _pw_cc_flag_group_impl(ctx):
    """Implementation for pw_cc_flag_group."""

    # If these are empty strings, they are handled differently than if they
    # are None. Rather than an explicit error or breakage, there's just silent
    # behavioral differences. Ideally, these attributes default to `None`, but
    # that is not supported with string types. Since these have no practical
    # meaning if they are empty strings, just remap empty strings to `None`.
    #
    # A minimal reproducer of this behavior with some useful analysis is
    # provided here:
    #
    #     https://github.com/armandomontanez/bazel_reproducers/tree/main/flag_group_with_empty_strings
    iterate_over = ctx.attr.iterate_over if ctx.attr.iterate_over else None
    expand_if = ctx.attr.expand_if_available if ctx.attr.expand_if_available else None
    expand_if_not = ctx.attr.expand_if_not_available if ctx.attr.expand_if_not_available else None
    return flag_group(
        flags = ctx.attr.flags,
        iterate_over = iterate_over,
        expand_if_available = expand_if,
        expand_if_not_available = expand_if_not,
    )

pw_cc_flag_group = rule(
    implementation = _pw_cc_flag_group_impl,
    attrs = {
        "flags": attr.string_list(
            doc = """List of flags provided by this rule.

For extremely complex expressions of flags that require nested flag groups with
multiple layers of expansion, prefer creating a custom rule in Starlark that
provides `FlagGroupInfo` or `FlagSetInfo`.
""",
        ),
        "iterate_over": attr.string(
            doc = """Expands `flags` for items in the named list.

Toolchain actions have various variables accessible as names that can be used
to guide flag expansions. For variables that are lists, `iterate_over` must be
used to expand the list into a series of flags.

Note that `iterate_over` is the string name of a build variable, and not an
actual list. Valid options are listed at:

    https://bazel.build/docs/cc-toolchain-config-reference#cctoolchainconfiginfo-build-variables

Note that the flag expansion stamps out the entire list of flags in `flags`
once for each item in the list.

Example:

    # Expands each path in `system_include_paths` to a series of `-isystem`
    # includes.
    #
    # Example input:
    #     system_include_paths = ["/usr/local/include", "/usr/include"]
    #
    # Expected result:
    #     "-isystem /usr/local/include -isystem /usr/include"
    pw_cc_flag_group(
        name = "system_include_paths",
        flags = ["-isystem", "%{system_include_paths}"],
        iterate_over = "system_include_paths",
    )
""",
        ),
        "expand_if_available": attr.string(
            doc = "Expands the expression in `flags` if the specified build variable is set.",
        ),
        "expand_if_not_available": attr.string(
            doc = "Expands the expression in `flags` if the specified build variable is NOT set.",
        ),
    },
    provides = [FlagGroupInfo],
    doc = """Declares an (optionally parametric) ordered set of flags.

`pw_cc_flag_group` rules are expected to be consumed exclusively by
`pw_cc_flag_set` rules. Though simple lists of flags can be expressed by
populating `flags` on a `pw_cc_flag_set`, `pw_cc_flag_group` provides additional
power in the following two ways:

    1. Iteration and conditional expansion. Using `iterate_over`,
       `expand_if_available`, and `expand_if_not_available`, more complex flag
       expressions can be made. This is critical for implementing things like
       the `libraries_to_link` feature, where library names are transformed
       into flags that end up in the final link invocation.

       Note: `expand_if_equal`, `expand_if_true`, and `expand_if_false` are not
       yet supported.

    2. Flags are tool-independent. A `pw_cc_flag_group` expresses ordered flags
       that may be reused across various `pw_cc_flag_set` rules. This is useful
       for cases where multiple `pw_cc_flag_set` rules must be created to
       implement a feature for which flags are slightly different depending on
       the action (e.g. compile vs link). Common flags can be expressed in a
       shared `pw_cc_flag_group`, and the differences can be relegated to
       separate `pw_cc_flag_group` instances.

Examples:

    pw_cc_flag_group(
        name = "user_compile_flag_expansion",
        flags = ["%{user_compile_flags}"],
        iterate_over = "user_compile_flags",
        expand_if_available = "user_compile_flags",
    )

    # This flag_group might be referenced from various FDO-related
    # `pw_cc_flag_set` rules. More importantly, the flag sets pulling this in
    # may apply to different sets of actions.
    pw_cc_flag_group(
        name = "fdo_profile_correction",
        flags = ["-fprofile-correction"],
        expand_if_available = "fdo_profile_path",
    )
""",
)

def _pw_cc_flag_set_impl(ctx):
    """Implementation for pw_cc_flag_set."""
    if ctx.attr.flags and ctx.attr.flag_groups:
        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))
    flag_groups = []
    if ctx.attr.flags:
        flag_groups.append(flag_group(flags = ctx.attr.flags))
    elif ctx.attr.flag_groups:
        for dep in ctx.attr.flag_groups:
            if not dep[FlagGroupInfo]:
                fail("{} in `flag_groups` of {} does not provide FlagGroupInfo".format(dep.label, ctx.label))

        flag_groups = [dep[FlagGroupInfo] for dep in ctx.attr.flag_groups]
    return flag_set(
        actions = ctx.attr.actions,
        flag_groups = flag_groups,
    )

pw_cc_flag_set = rule(
    implementation = _pw_cc_flag_set_impl,
    attrs = {
        "actions": attr.string_list(
            mandatory = True,
            # inclusive-language: disable
            doc = """A list of action names that this flag set applies to.

Valid choices are listed here:

    https://github.com/bazelbuild/bazel/blob/master/tools/build_defs/cc/action_names.bzl

It is possible for some needed action names to not be enumerated in this list,
so there is not rigid validation for these strings. Prefer using constants
rather than manually typing action names.
""",
            # inclusive-language: enable
        ),
        "flag_groups": attr.label_list(
            doc = """Labels pointing to `pw_cc_flag_group` rules.

This is intended to be compatible with any other rules that provide
`FlagGroupInfo`. These are evaluated in order, with earlier flag groups
appearing earlier in the invocation of the underlying tool.

Note: `flag_groups` and `flags` are mutually exclusive.
""",
        ),
        "flags": attr.string_list(
            doc = """Flags that should be applied to the specified actions.

These are evaluated in order, with earlier flags appearing earlier in the
invocation of the underlying tool. If you need expansion logic, prefer
enumerating flags in a `pw_cc_flag_group` or create a custom rule that provides
`FlagGroupInfo`.

Note: `flags` and `flag_groups` are mutually exclusive.
""",
        ),
    },
    provides = [FlagSetInfo],
    doc = """Declares an ordered set of flags bound to a set of actions.

Flag sets can be attached to a `pw_cc_toolchain` via `action_config_flag_sets`.

Examples:

    pw_cc_flag_set(
        name = "warnings_as_errors",
        flags = ["-Werror"],
    )

    pw_cc_flag_set(
        name = "layering_check",
        flag_groups = [
            ":strict_module_headers",
            ":dependent_module_map_files",
        ],
    )

Note: In the vast majority of cases, alphabetical sorting is not desirable for
the `flags` and `flag_groups` attributes. Buildifier shouldn't ever try to sort
these, but in the off chance it starts to these members should be listed as
exceptions in the `SortableDenylist`.
""",
)