xref: /aosp_15_r20/external/bazelbuild-rules_rust/rust/private/unpretty.bzl (revision d4726bddaa87cc4778e7472feed243fa4b6c267f)
1*d4726bddSHONG Yifan"""A module defining Rust 'unpretty' rules"""
2*d4726bddSHONG Yifan
3*d4726bddSHONG Yifanload("//rust/private:common.bzl", "rust_common")
4*d4726bddSHONG Yifanload(
5*d4726bddSHONG Yifan    "//rust/private:rust.bzl",
6*d4726bddSHONG Yifan    "RUSTC_ATTRS",
7*d4726bddSHONG Yifan    "get_rust_test_flags",
8*d4726bddSHONG Yifan)
9*d4726bddSHONG Yifanload(
10*d4726bddSHONG Yifan    "//rust/private:rustc.bzl",
11*d4726bddSHONG Yifan    "collect_deps",
12*d4726bddSHONG Yifan    "collect_inputs",
13*d4726bddSHONG Yifan    "construct_arguments",
14*d4726bddSHONG Yifan)
15*d4726bddSHONG Yifanload(
16*d4726bddSHONG Yifan    "//rust/private:utils.bzl",
17*d4726bddSHONG Yifan    "determine_output_hash",
18*d4726bddSHONG Yifan    "find_cc_toolchain",
19*d4726bddSHONG Yifan    "find_toolchain",
20*d4726bddSHONG Yifan)
21*d4726bddSHONG Yifan
22*d4726bddSHONG Yifan# This list is determined by running the following command:
23*d4726bddSHONG Yifan#
24*d4726bddSHONG Yifan#   rustc +nightly -Zunpretty=
25*d4726bddSHONG Yifan#
26*d4726bddSHONG Yifan_UNPRETTY_MODES = [
27*d4726bddSHONG Yifan    "ast-tree,expanded",
28*d4726bddSHONG Yifan    "ast-tree",
29*d4726bddSHONG Yifan    "expanded,hygiene",
30*d4726bddSHONG Yifan    "expanded,identified",
31*d4726bddSHONG Yifan    "expanded",
32*d4726bddSHONG Yifan    "hir-tree",
33*d4726bddSHONG Yifan    "hir,identified",
34*d4726bddSHONG Yifan    "hir,typed",
35*d4726bddSHONG Yifan    "hir",
36*d4726bddSHONG Yifan    "identified",
37*d4726bddSHONG Yifan    "mir-cfg",
38*d4726bddSHONG Yifan    "mir",
39*d4726bddSHONG Yifan    "normal",
40*d4726bddSHONG Yifan]
41*d4726bddSHONG Yifan
42*d4726bddSHONG YifanRustUnprettyInfo = provider(
43*d4726bddSHONG Yifan    doc = "A provider describing the Rust unpretty mode.",
44*d4726bddSHONG Yifan    fields = {
45*d4726bddSHONG Yifan        "modes": "Depset[string]: Can be any of {}".format(["'{}'".format(m) for m in _UNPRETTY_MODES]),
46*d4726bddSHONG Yifan    },
47*d4726bddSHONG Yifan)
48*d4726bddSHONG Yifan
49*d4726bddSHONG Yifandef _rust_unpretty_flag_impl(ctx):
50*d4726bddSHONG Yifan    value = ctx.build_setting_value
51*d4726bddSHONG Yifan    invalid = []
52*d4726bddSHONG Yifan    for mode in value:
53*d4726bddSHONG Yifan        if mode not in _UNPRETTY_MODES:
54*d4726bddSHONG Yifan            invalid.append(mode)
55*d4726bddSHONG Yifan    if invalid:
56*d4726bddSHONG Yifan        fail("{} build setting allowed to take values [{}] but was set to unallowed values: {}".format(
57*d4726bddSHONG Yifan            ctx.label,
58*d4726bddSHONG Yifan            ", ".join(["'{}'".format(m) for m in _UNPRETTY_MODES]),
59*d4726bddSHONG Yifan            invalid,
60*d4726bddSHONG Yifan        ))
61*d4726bddSHONG Yifan
62*d4726bddSHONG Yifan    return RustUnprettyInfo(modes = depset(value))
63*d4726bddSHONG Yifan
64*d4726bddSHONG Yifanrust_unpretty_flag = rule(
65*d4726bddSHONG Yifan    doc = "A build setting which represents the Rust unpretty mode. The allowed values are {}".format(_UNPRETTY_MODES),
66*d4726bddSHONG Yifan    implementation = _rust_unpretty_flag_impl,
67*d4726bddSHONG Yifan    build_setting = config.string_list(
68*d4726bddSHONG Yifan        flag = True,
69*d4726bddSHONG Yifan        repeatable = True,
70*d4726bddSHONG Yifan    ),
71*d4726bddSHONG Yifan)
72*d4726bddSHONG Yifan
73*d4726bddSHONG Yifandef _nightly_unpretty_transition_impl(settings, attr):
74*d4726bddSHONG Yifan    mode = settings[str(Label("//rust/settings:unpretty"))]
75*d4726bddSHONG Yifan
76*d4726bddSHONG Yifan    # Use the presence of _unpretty_modes as a proxy for whether this is a rust_unpretty target.
77*d4726bddSHONG Yifan    if hasattr(attr, "_unpretty_modes") and hasattr(attr, "mode"):
78*d4726bddSHONG Yifan        mode = mode + [attr.mode]
79*d4726bddSHONG Yifan
80*d4726bddSHONG Yifan    return {
81*d4726bddSHONG Yifan        str(Label("//rust/settings:unpretty")): depset(mode).to_list(),
82*d4726bddSHONG Yifan        str(Label("//rust/toolchain/channel")): "nightly",
83*d4726bddSHONG Yifan    }
84*d4726bddSHONG Yifan
85*d4726bddSHONG Yifannightly_unpretty_transition = transition(
86*d4726bddSHONG Yifan    implementation = _nightly_unpretty_transition_impl,
87*d4726bddSHONG Yifan    inputs = [str(Label("//rust/settings:unpretty"))],
88*d4726bddSHONG Yifan    outputs = [
89*d4726bddSHONG Yifan        str(Label("//rust/settings:unpretty")),
90*d4726bddSHONG Yifan        str(Label("//rust/toolchain/channel")),
91*d4726bddSHONG Yifan    ],
92*d4726bddSHONG Yifan)
93*d4726bddSHONG Yifan
94*d4726bddSHONG Yifandef _get_unpretty_ready_crate_info(target, aspect_ctx):
95*d4726bddSHONG Yifan    """Check that a target is suitable for expansion and extract the `CrateInfo` provider from it.
96*d4726bddSHONG Yifan
97*d4726bddSHONG Yifan    Args:
98*d4726bddSHONG Yifan        target (Target): The target the aspect is running on.
99*d4726bddSHONG Yifan        aspect_ctx (ctx, optional): The aspect's context object.
100*d4726bddSHONG Yifan
101*d4726bddSHONG Yifan    Returns:
102*d4726bddSHONG Yifan        CrateInfo, optional: A `CrateInfo` provider if rust unpretty should be run or `None`.
103*d4726bddSHONG Yifan    """
104*d4726bddSHONG Yifan
105*d4726bddSHONG Yifan    # Ignore external targets
106*d4726bddSHONG Yifan    if target.label.workspace_root.startswith("external"):
107*d4726bddSHONG Yifan        return None
108*d4726bddSHONG Yifan
109*d4726bddSHONG Yifan    # Targets with specific tags will not be formatted
110*d4726bddSHONG Yifan    if aspect_ctx:
111*d4726bddSHONG Yifan        ignore_tags = [
112*d4726bddSHONG Yifan            "nounpretty",
113*d4726bddSHONG Yifan            "no-unpretty",
114*d4726bddSHONG Yifan            "no_unpretty",
115*d4726bddSHONG Yifan        ]
116*d4726bddSHONG Yifan
117*d4726bddSHONG Yifan        for tag in ignore_tags:
118*d4726bddSHONG Yifan            if tag in aspect_ctx.rule.attr.tags:
119*d4726bddSHONG Yifan                return None
120*d4726bddSHONG Yifan
121*d4726bddSHONG Yifan    # Obviously ignore any targets that don't contain `CrateInfo`
122*d4726bddSHONG Yifan    if rust_common.crate_info not in target:
123*d4726bddSHONG Yifan        return None
124*d4726bddSHONG Yifan
125*d4726bddSHONG Yifan    return target[rust_common.crate_info]
126*d4726bddSHONG Yifan
127*d4726bddSHONG Yifandef _rust_unpretty_aspect_impl(target, ctx):
128*d4726bddSHONG Yifan    crate_info = _get_unpretty_ready_crate_info(target, ctx)
129*d4726bddSHONG Yifan    if not crate_info:
130*d4726bddSHONG Yifan        return []
131*d4726bddSHONG Yifan
132*d4726bddSHONG Yifan    toolchain = find_toolchain(ctx)
133*d4726bddSHONG Yifan    cc_toolchain, feature_configuration = find_cc_toolchain(ctx)
134*d4726bddSHONG Yifan
135*d4726bddSHONG Yifan    dep_info, build_info, _ = collect_deps(
136*d4726bddSHONG Yifan        deps = crate_info.deps,
137*d4726bddSHONG Yifan        proc_macro_deps = crate_info.proc_macro_deps,
138*d4726bddSHONG Yifan        aliases = crate_info.aliases,
139*d4726bddSHONG Yifan    )
140*d4726bddSHONG Yifan
141*d4726bddSHONG Yifan    compile_inputs, out_dir, build_env_files, build_flags_files, linkstamp_outs, ambiguous_libs = collect_inputs(
142*d4726bddSHONG Yifan        ctx,
143*d4726bddSHONG Yifan        ctx.rule.file,
144*d4726bddSHONG Yifan        ctx.rule.files,
145*d4726bddSHONG Yifan        # Rust expand doesn't need to invoke transitive linking, therefore doesn't need linkstamps.
146*d4726bddSHONG Yifan        depset([]),
147*d4726bddSHONG Yifan        toolchain,
148*d4726bddSHONG Yifan        cc_toolchain,
149*d4726bddSHONG Yifan        feature_configuration,
150*d4726bddSHONG Yifan        crate_info,
151*d4726bddSHONG Yifan        dep_info,
152*d4726bddSHONG Yifan        build_info,
153*d4726bddSHONG Yifan    )
154*d4726bddSHONG Yifan
155*d4726bddSHONG Yifan    output_groups = {}
156*d4726bddSHONG Yifan    outputs = []
157*d4726bddSHONG Yifan
158*d4726bddSHONG Yifan    for mode in ctx.attr._unpretty_modes[RustUnprettyInfo].modes.to_list():
159*d4726bddSHONG Yifan        pretty_mode = mode.replace("-", "_")
160*d4726bddSHONG Yifan        mnemonic = "RustUnpretty{}".format("".join([
161*d4726bddSHONG Yifan            o.title()
162*d4726bddSHONG Yifan            for m in pretty_mode.split(",")
163*d4726bddSHONG Yifan            for o in m.split("_")
164*d4726bddSHONG Yifan        ]))
165*d4726bddSHONG Yifan
166*d4726bddSHONG Yifan        unpretty_out = ctx.actions.declare_file(
167*d4726bddSHONG Yifan            "{}.unpretty.{}.rs".format(ctx.label.name, pretty_mode.replace(",", ".")),
168*d4726bddSHONG Yifan            sibling = crate_info.output,
169*d4726bddSHONG Yifan        )
170*d4726bddSHONG Yifan
171*d4726bddSHONG Yifan        output_groups.update({"rust_unpretty_{}".format(pretty_mode.replace(",", "_")): depset([unpretty_out])})
172*d4726bddSHONG Yifan        outputs.append(unpretty_out)
173*d4726bddSHONG Yifan
174*d4726bddSHONG Yifan        rust_flags = []
175*d4726bddSHONG Yifan        if crate_info.is_test:
176*d4726bddSHONG Yifan            rust_flags = get_rust_test_flags(ctx.rule.attr)
177*d4726bddSHONG Yifan
178*d4726bddSHONG Yifan        args, env = construct_arguments(
179*d4726bddSHONG Yifan            ctx = ctx,
180*d4726bddSHONG Yifan            attr = ctx.rule.attr,
181*d4726bddSHONG Yifan            file = ctx.file,
182*d4726bddSHONG Yifan            toolchain = toolchain,
183*d4726bddSHONG Yifan            tool_path = toolchain.rustc.path,
184*d4726bddSHONG Yifan            cc_toolchain = cc_toolchain,
185*d4726bddSHONG Yifan            feature_configuration = feature_configuration,
186*d4726bddSHONG Yifan            crate_info = crate_info,
187*d4726bddSHONG Yifan            dep_info = dep_info,
188*d4726bddSHONG Yifan            linkstamp_outs = linkstamp_outs,
189*d4726bddSHONG Yifan            ambiguous_libs = ambiguous_libs,
190*d4726bddSHONG Yifan            output_hash = determine_output_hash(crate_info.root, ctx.label),
191*d4726bddSHONG Yifan            rust_flags = rust_flags,
192*d4726bddSHONG Yifan            out_dir = out_dir,
193*d4726bddSHONG Yifan            build_env_files = build_env_files,
194*d4726bddSHONG Yifan            build_flags_files = build_flags_files,
195*d4726bddSHONG Yifan            emit = ["dep-info", "metadata"],
196*d4726bddSHONG Yifan            skip_expanding_rustc_env = True,
197*d4726bddSHONG Yifan        )
198*d4726bddSHONG Yifan
199*d4726bddSHONG Yifan        args.process_wrapper_flags.add("--stdout-file", unpretty_out)
200*d4726bddSHONG Yifan
201*d4726bddSHONG Yifan        # Expand all macros and dump the source to stdout.
202*d4726bddSHONG Yifan        # Tracking issue: https://github.com/rust-lang/rust/issues/43364
203*d4726bddSHONG Yifan        args.rustc_flags.add("-Zunpretty={}".format(mode))
204*d4726bddSHONG Yifan
205*d4726bddSHONG Yifan        ctx.actions.run(
206*d4726bddSHONG Yifan            executable = ctx.executable._process_wrapper,
207*d4726bddSHONG Yifan            inputs = compile_inputs,
208*d4726bddSHONG Yifan            outputs = [unpretty_out],
209*d4726bddSHONG Yifan            env = env,
210*d4726bddSHONG Yifan            arguments = args.all,
211*d4726bddSHONG Yifan            mnemonic = mnemonic,
212*d4726bddSHONG Yifan            toolchain = "@rules_rust//rust:toolchain_type",
213*d4726bddSHONG Yifan        )
214*d4726bddSHONG Yifan
215*d4726bddSHONG Yifan    output_groups.update({"rust_unpretty": depset(outputs)})
216*d4726bddSHONG Yifan
217*d4726bddSHONG Yifan    return [
218*d4726bddSHONG Yifan        OutputGroupInfo(**output_groups),
219*d4726bddSHONG Yifan    ]
220*d4726bddSHONG Yifan
221*d4726bddSHONG Yifan# Example: Expand all rust targets in the codebase.
222*d4726bddSHONG Yifan#   bazel build --aspects=@rules_rust//rust:defs.bzl%rust_unpretty_aspect \
223*d4726bddSHONG Yifan#               --output_groups=expanded \
224*d4726bddSHONG Yifan#               //...
225*d4726bddSHONG Yifanrust_unpretty_aspect = aspect(
226*d4726bddSHONG Yifan    implementation = _rust_unpretty_aspect_impl,
227*d4726bddSHONG Yifan    fragments = ["cpp"],
228*d4726bddSHONG Yifan    attrs = {
229*d4726bddSHONG Yifan        "_unpretty_modes": attr.label(
230*d4726bddSHONG Yifan            doc = "The values to pass to `--unpretty`",
231*d4726bddSHONG Yifan            providers = [RustUnprettyInfo],
232*d4726bddSHONG Yifan            default = Label("//rust/settings:unpretty"),
233*d4726bddSHONG Yifan        ),
234*d4726bddSHONG Yifan    } | RUSTC_ATTRS,
235*d4726bddSHONG Yifan    toolchains = [
236*d4726bddSHONG Yifan        str(Label("//rust:toolchain_type")),
237*d4726bddSHONG Yifan        "@bazel_tools//tools/cpp:toolchain_type",
238*d4726bddSHONG Yifan    ],
239*d4726bddSHONG Yifan    required_providers = [rust_common.crate_info],
240*d4726bddSHONG Yifan    doc = """\
241*d4726bddSHONG YifanExecutes Rust expand on specified targets.
242*d4726bddSHONG Yifan
243*d4726bddSHONG YifanThis aspect applies to existing rust_library, rust_test, and rust_binary rules.
244*d4726bddSHONG Yifan
245*d4726bddSHONG YifanAs an example, if the following is defined in `examples/hello_lib/BUILD.bazel`:
246*d4726bddSHONG Yifan
247*d4726bddSHONG Yifan```python
248*d4726bddSHONG Yifanload("@rules_rust//rust:defs.bzl", "rust_library", "rust_test")
249*d4726bddSHONG Yifan
250*d4726bddSHONG Yifanrust_library(
251*d4726bddSHONG Yifan    name = "hello_lib",
252*d4726bddSHONG Yifan    srcs = ["src/lib.rs"],
253*d4726bddSHONG Yifan)
254*d4726bddSHONG Yifan
255*d4726bddSHONG Yifanrust_test(
256*d4726bddSHONG Yifan    name = "greeting_test",
257*d4726bddSHONG Yifan    srcs = ["tests/greeting.rs"],
258*d4726bddSHONG Yifan    deps = [":hello_lib"],
259*d4726bddSHONG Yifan)
260*d4726bddSHONG Yifan```
261*d4726bddSHONG Yifan
262*d4726bddSHONG YifanThen the targets can be expanded with the following command:
263*d4726bddSHONG Yifan
264*d4726bddSHONG Yifan```output
265*d4726bddSHONG Yifan$ bazel build --aspects=@rules_rust//rust:defs.bzl%rust_unpretty_aspect \
266*d4726bddSHONG Yifan              --output_groups=rust_unpretty_expanded //hello_lib:all
267*d4726bddSHONG Yifan```
268*d4726bddSHONG Yifan""",
269*d4726bddSHONG Yifan)
270*d4726bddSHONG Yifan
271*d4726bddSHONG Yifandef _rust_unpretty_rule_impl(ctx):
272*d4726bddSHONG Yifan    mode = ctx.attr.mode
273*d4726bddSHONG Yifan    output_group = "rust_unpretty_{}".format(mode.replace(",", "_").replace("-", "_"))
274*d4726bddSHONG Yifan    files = []
275*d4726bddSHONG Yifan    for target in ctx.attr.deps:
276*d4726bddSHONG Yifan        files.append(getattr(target[OutputGroupInfo], output_group))
277*d4726bddSHONG Yifan
278*d4726bddSHONG Yifan    return [DefaultInfo(files = depset(transitive = files))]
279*d4726bddSHONG Yifan
280*d4726bddSHONG Yifanrust_unpretty = rule(
281*d4726bddSHONG Yifan    implementation = _rust_unpretty_rule_impl,
282*d4726bddSHONG Yifan    cfg = nightly_unpretty_transition,
283*d4726bddSHONG Yifan    attrs = {
284*d4726bddSHONG Yifan        "deps": attr.label_list(
285*d4726bddSHONG Yifan            doc = "Rust targets to run unpretty on.",
286*d4726bddSHONG Yifan            providers = [rust_common.crate_info],
287*d4726bddSHONG Yifan            aspects = [rust_unpretty_aspect],
288*d4726bddSHONG Yifan        ),
289*d4726bddSHONG Yifan        "mode": attr.string(
290*d4726bddSHONG Yifan            doc = "The value to pass to `--unpretty`",
291*d4726bddSHONG Yifan            values = _UNPRETTY_MODES,
292*d4726bddSHONG Yifan            default = "expanded",
293*d4726bddSHONG Yifan        ),
294*d4726bddSHONG Yifan        "_allowlist_function_transition": attr.label(
295*d4726bddSHONG Yifan            default = Label("//tools/allowlists/function_transition_allowlist"),
296*d4726bddSHONG Yifan        ),
297*d4726bddSHONG Yifan        "_unpretty_modes": attr.label(
298*d4726bddSHONG Yifan            doc = "The values to pass to `--unpretty`",
299*d4726bddSHONG Yifan            providers = [RustUnprettyInfo],
300*d4726bddSHONG Yifan            default = Label("//rust/settings:unpretty"),
301*d4726bddSHONG Yifan        ),
302*d4726bddSHONG Yifan    },
303*d4726bddSHONG Yifan    doc = """\
304*d4726bddSHONG YifanExecutes rust unpretty on a specific target.
305*d4726bddSHONG Yifan
306*d4726bddSHONG YifanSimilar to `rust_unpretty_aspect`, but allows specifying a list of dependencies \
307*d4726bddSHONG Yifanwithin the build system.
308*d4726bddSHONG Yifan
309*d4726bddSHONG YifanFor example, given the following example targets:
310*d4726bddSHONG Yifan
311*d4726bddSHONG Yifan```python
312*d4726bddSHONG Yifanload("@rules_rust//rust:defs.bzl", "rust_library", "rust_test")
313*d4726bddSHONG Yifan
314*d4726bddSHONG Yifanrust_library(
315*d4726bddSHONG Yifan    name = "hello_lib",
316*d4726bddSHONG Yifan    srcs = ["src/lib.rs"],
317*d4726bddSHONG Yifan)
318*d4726bddSHONG Yifan
319*d4726bddSHONG Yifanrust_test(
320*d4726bddSHONG Yifan    name = "greeting_test",
321*d4726bddSHONG Yifan    srcs = ["tests/greeting.rs"],
322*d4726bddSHONG Yifan    deps = [":hello_lib"],
323*d4726bddSHONG Yifan)
324*d4726bddSHONG Yifan```
325*d4726bddSHONG Yifan
326*d4726bddSHONG YifanRust expand can be set as a build target with the following:
327*d4726bddSHONG Yifan
328*d4726bddSHONG Yifan```python
329*d4726bddSHONG Yifanload("@rules_rust//rust:defs.bzl", "rust_unpretty")
330*d4726bddSHONG Yifan
331*d4726bddSHONG Yifanrust_unpretty(
332*d4726bddSHONG Yifan    name = "hello_library_expand",
333*d4726bddSHONG Yifan    testonly = True,
334*d4726bddSHONG Yifan    deps = [
335*d4726bddSHONG Yifan        ":hello_lib",
336*d4726bddSHONG Yifan        ":greeting_test",
337*d4726bddSHONG Yifan    ],
338*d4726bddSHONG Yifan    mode = "expand",
339*d4726bddSHONG Yifan)
340*d4726bddSHONG Yifan```
341*d4726bddSHONG Yifan""",
342*d4726bddSHONG Yifan)
343