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