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_action_config and pw_cc_tool.""" 15 16load( 17 ":providers.bzl", 18 "PwActionConfigInfo", 19 "PwActionConfigSetInfo", 20 "PwActionNameSetInfo", 21 "PwFeatureConstraintInfo", 22 "PwFeatureSetInfo", 23 "PwFlagSetInfo", 24 "PwToolInfo", 25) 26 27def _bin_to_files(target, extra_files = []): 28 if not target: 29 return depset(extra_files) 30 info = target[DefaultInfo] 31 exe = info.files_to_run.executable 32 if not exe: 33 return depset(extra_files) 34 return depset( 35 [exe] + extra_files, 36 transitive = [info.files, info.data_runfiles.files], 37 ) 38 39def _pw_cc_tool_impl(ctx): 40 """Implementation for pw_cc_tool.""" 41 42 # Remaps empty strings to `None` to match behavior of the default values. 43 exe = ctx.executable.tool or None 44 path = ctx.attr.path or None 45 46 if (exe == None) == (path == None): 47 fail("Exactly one of tool and path must be provided. Prefer tool") 48 49 tool = PwToolInfo( 50 label = ctx.label, 51 exe = exe, 52 path = path, 53 files = _bin_to_files(ctx.attr.tool, ctx.files.additional_files), 54 requires_any_of = tuple([fc[PwFeatureConstraintInfo] for fc in ctx.attr.requires_any_of]), 55 execution_requirements = tuple(ctx.attr.execution_requirements), 56 ) 57 58 return [ 59 tool, 60 DefaultInfo(files = tool.files), 61 ] 62 63pw_cc_tool = rule( 64 implementation = _pw_cc_tool_impl, 65 attrs = { 66 "additional_files": attr.label_list( 67 allow_files = True, 68 doc = """Additional files that are required for this tool to correctly operate. 69 70These files are propagated up to the `pw_cc_toolchain` so you typically won't 71need to explicitly specify the `*_files` attributes on a `pw_cc_toolchain`. 72""", 73 ), 74 "execution_requirements": attr.string_list( 75 doc = "A list of strings that provide hints for execution environment compatibility (e.g. `requires-darwin`).", 76 ), 77 "path": attr.string( 78 doc = """An absolute path to a binary to use for this tool. 79 80Relative paths are also supported, but they are relative to the 81`pw_cc_toolchain` that uses this tool rather than relative to this `pw_cc_tool` 82rule. 83 84WARNING: This method of listing a tool is NOT recommended, and is provided as an 85escape hatch for edge cases. Prefer using `tool` whenever possible. 86""", 87 ), 88 "requires_any_of": attr.label_list( 89 providers = [PwFeatureConstraintInfo], 90 doc = """This will be enabled when any of the constraints are met. 91 92If omitted, this tool will be enabled unconditionally. 93""", 94 ), 95 "tool": attr.label( 96 allow_files = True, 97 executable = True, 98 cfg = "exec", 99 doc = """The underlying tool that this rule represents. 100 101This attribute is a label rather than a simple file path. This means that the 102file must be referenced relative to the BUILD file that exports it. For example: 103 104 @llvm_toolchain//:bin/clang 105 ^ ^ ^ 106 Where: 107 108 * `@llvm_toolchain` is the repository. 109 * `//` is the directory of the BUILD file that exports the file of interest. 110 * `bin/clang` is the path of the actual binary relative to the BUILD file of 111 interest. 112""", 113 ), 114 }, 115 provides = [PwToolInfo, DefaultInfo], 116 doc = """Declares a singular tool that can be bound to action configs. 117 118`pw_cc_tool` rules are intended to be consumed exclusively by 119`pw_cc_action_config` rules. These rules declare an underlying tool that can 120be used to fulfill various actions. Many actions may reuse a shared tool. 121 122Example: 123 124 # A project-provided tool. 125 pw_cc_tool( 126 name = "clang_tool", 127 tool = "@llvm_toolchain//:bin/clang", 128 ) 129 130 # A tool expected to be preinstalled on a user's machine. 131 pw_cc_tool( 132 name = "clang_tool", 133 path = "/usr/bin/clang", 134 ) 135""", 136) 137 138def _generate_action_config(ctx, action_name, **kwargs): 139 flag_sets = [] 140 for fs in ctx.attr.flag_sets: 141 provided_fs = fs[PwFlagSetInfo] 142 if action_name in provided_fs.actions: 143 flag_sets.append(provided_fs) 144 145 return PwActionConfigInfo( 146 action_name = action_name, 147 flag_sets = tuple(flag_sets), 148 **kwargs 149 ) 150 151def _pw_cc_action_config_impl(ctx): 152 """Implementation for pw_cc_tool.""" 153 if not ctx.attr.tools: 154 fail("Action configs are not valid unless they specify at least one `pw_cc_tool` in `tools`") 155 156 action_names = depset(transitive = [ 157 action[PwActionNameSetInfo].actions 158 for action in ctx.attr.action_names 159 ]).to_list() 160 if not action_names: 161 fail("Action configs are not valid unless they specify at least one action name in `action_names`") 162 163 # Check that the listed flag sets apply to at least one action in this group 164 # of action configs. 165 for fs in ctx.attr.flag_sets: 166 provided_fs = fs[PwFlagSetInfo] 167 flag_set_applies = False 168 for action in action_names: 169 if action in provided_fs.actions: 170 flag_set_applies = True 171 if not flag_set_applies: 172 fail("{} listed as a flag set to apply to {}, but none of the actions match".format( 173 fs.label, 174 ctx.label, 175 )) 176 177 tools = [] 178 for tool in ctx.attr.tools: 179 exe = tool[DefaultInfo].files_to_run.executable 180 if PwToolInfo in tool: 181 tools.append(tool[PwToolInfo]) 182 elif exe != None: 183 tools.append(PwToolInfo( 184 label = ctx.label, 185 exe = exe, 186 path = None, 187 files = _bin_to_files(tool), 188 requires_any_of = tuple(), 189 execution_requirements = tuple(), 190 )) 191 else: 192 fail("Expected either a pw_cc_tool or a binary, but %s is neither" % tool.label) 193 194 tools = tuple(tools) 195 files = depset(transitive = [tool.files for tool in tools]) 196 197 common_kwargs = dict( 198 label = ctx.label, 199 tools = tools, 200 implies_features = depset(transitive = [ 201 ft_set[PwFeatureSetInfo].features 202 for ft_set in ctx.attr.implies 203 ]), 204 implies_action_configs = depset([]), 205 enabled = ctx.attr.enabled, 206 files = files, 207 ) 208 action_configs = [ 209 _generate_action_config(ctx, action, **common_kwargs) 210 for action in action_names 211 ] 212 213 return [ 214 PwActionConfigSetInfo( 215 label = ctx.label, 216 action_configs = depset(action_configs), 217 ), 218 DefaultInfo(files = files), 219 ] 220 221pw_cc_action_config = rule( 222 implementation = _pw_cc_action_config_impl, 223 attrs = { 224 "action_names": attr.label_list( 225 providers = [PwActionNameSetInfo], 226 mandatory = True, 227 doc = """A list of action names to apply this action to. 228 229See @pw_toolchain//actions:all for valid options. 230""", 231 ), 232 "enabled": attr.bool( 233 default = True, 234 doc = """Whether or not this action config is enabled by default. 235 236Note: This defaults to `True` since it's assumed that most listed action configs 237will be enabled and used by default. This is the opposite of Bazel's native 238default. 239""", 240 ), 241 "flag_sets": attr.label_list( 242 providers = [PwFlagSetInfo], 243 doc = """Labels that point to `pw_cc_flag_set`s that are 244unconditionally bound to the specified actions. 245 246Note: The flags in the `pw_cc_flag_set` are only bound to matching action names. 247If an action is listed in this rule's `action_names`, but is NOT listed in the 248`pw_cc_flag_set`'s `actions`, the flag will not be applied to that action. 249""", 250 ), 251 "implies": attr.label_list( 252 providers = [PwFeatureSetInfo], 253 doc = "Features that should be enabled when this action is used.", 254 ), 255 "tools": attr.label_list( 256 mandatory = True, 257 cfg = "exec", 258 doc = """The tool to use for the specified actions. 259 260A tool can be a `pw_cc_tool`, or a binary. 261 262If multiple tools are specified, the first tool that has `with_features` that 263satisfy the currently enabled feature set is used. 264""", 265 ), 266 }, 267 provides = [PwActionConfigSetInfo], 268 doc = """Declares the configuration and selection of `pw_cc_tool` rules. 269 270Action configs are bound to a toolchain through `action_configs`, and are the 271driving mechanism for controlling toolchain tool invocation/behavior. 272 273Action configs define three key things: 274 275* Which tools to invoke for a given type of action. 276* Tool features and compatibility. 277* `pw_cc_flag_set`s that are unconditionally bound to a tool invocation. 278 279Examples: 280 281 pw_cc_action_config( 282 name = "ar", 283 action_names = ["@pw_toolchain//actions:all_ar_actions"], 284 implies = [ 285 "@pw_toolchain//features/legacy:archiver_flags", 286 "@pw_toolchain//features/legacy:linker_param_file", 287 ], 288 tools = [":ar_tool"], 289 ) 290 291 pw_cc_action_config( 292 name = "clang", 293 action_names = [ 294 "@pw_toolchain//actions:all_asm_actions", 295 "@pw_toolchain//actions:all_c_compiler_actions", 296 ], 297 tools = [":clang_tool"], 298 ) 299""", 300) 301