• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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