xref: /aosp_15_r20/external/bazelbuild-rules_rust/rust/private/rustfmt.bzl (revision d4726bddaa87cc4778e7472feed243fa4b6c267f)
1*d4726bddSHONG Yifan"""A module defining rustfmt rules"""
2*d4726bddSHONG Yifan
3*d4726bddSHONG Yifanload(":common.bzl", "rust_common")
4*d4726bddSHONG Yifan
5*d4726bddSHONG Yifandef _get_rustfmt_ready_crate_info(target):
6*d4726bddSHONG Yifan    """Check that a target is suitable for rustfmt and extract the `CrateInfo` provider from it.
7*d4726bddSHONG Yifan
8*d4726bddSHONG Yifan    Args:
9*d4726bddSHONG Yifan        target (Target): The target the aspect is running on.
10*d4726bddSHONG Yifan
11*d4726bddSHONG Yifan    Returns:
12*d4726bddSHONG Yifan        CrateInfo, optional: A `CrateInfo` provider if clippy should be run or `None`.
13*d4726bddSHONG Yifan    """
14*d4726bddSHONG Yifan
15*d4726bddSHONG Yifan    # Ignore external targets
16*d4726bddSHONG Yifan    if target.label.workspace_name:
17*d4726bddSHONG Yifan        return None
18*d4726bddSHONG Yifan
19*d4726bddSHONG Yifan    # Obviously ignore any targets that don't contain `CrateInfo`
20*d4726bddSHONG Yifan    if rust_common.crate_info in target:
21*d4726bddSHONG Yifan        return target[rust_common.crate_info]
22*d4726bddSHONG Yifan    elif rust_common.test_crate_info in target:
23*d4726bddSHONG Yifan        return target[rust_common.test_crate_info].crate
24*d4726bddSHONG Yifan    else:
25*d4726bddSHONG Yifan        return None
26*d4726bddSHONG Yifan
27*d4726bddSHONG Yifandef _find_rustfmtable_srcs(crate_info, aspect_ctx = None):
28*d4726bddSHONG Yifan    """Parse a `CrateInfo` provider for rustfmt formattable sources.
29*d4726bddSHONG Yifan
30*d4726bddSHONG Yifan    Args:
31*d4726bddSHONG Yifan        crate_info (CrateInfo): A `CrateInfo` provider.
32*d4726bddSHONG Yifan        aspect_ctx (ctx, optional): The aspect's context object.
33*d4726bddSHONG Yifan
34*d4726bddSHONG Yifan    Returns:
35*d4726bddSHONG Yifan        list: A list of formattable sources (`File`).
36*d4726bddSHONG Yifan    """
37*d4726bddSHONG Yifan
38*d4726bddSHONG Yifan    # Targets with specific tags will not be formatted
39*d4726bddSHONG Yifan    if aspect_ctx:
40*d4726bddSHONG Yifan        ignore_tags = [
41*d4726bddSHONG Yifan            "no-format",
42*d4726bddSHONG Yifan            "no-rustfmt",
43*d4726bddSHONG Yifan            "norustfmt",
44*d4726bddSHONG Yifan        ]
45*d4726bddSHONG Yifan
46*d4726bddSHONG Yifan        for tag in ignore_tags:
47*d4726bddSHONG Yifan            if tag in aspect_ctx.rule.attr.tags:
48*d4726bddSHONG Yifan                return []
49*d4726bddSHONG Yifan
50*d4726bddSHONG Yifan    # Filter out any generated files
51*d4726bddSHONG Yifan    srcs = [src for src in crate_info.srcs.to_list() if src.is_source]
52*d4726bddSHONG Yifan
53*d4726bddSHONG Yifan    return srcs
54*d4726bddSHONG Yifan
55*d4726bddSHONG Yifandef _generate_manifest(edition, srcs, ctx):
56*d4726bddSHONG Yifan    workspace = ctx.label.workspace_name or ctx.workspace_name
57*d4726bddSHONG Yifan
58*d4726bddSHONG Yifan    # Gather the source paths to non-generated files
59*d4726bddSHONG Yifan    content = ctx.actions.args()
60*d4726bddSHONG Yifan    content.set_param_file_format("multiline")
61*d4726bddSHONG Yifan    content.add_all(srcs, format_each = workspace + "/%s")
62*d4726bddSHONG Yifan    content.add(edition)
63*d4726bddSHONG Yifan
64*d4726bddSHONG Yifan    # Write the rustfmt manifest
65*d4726bddSHONG Yifan    manifest = ctx.actions.declare_file(ctx.label.name + ".rustfmt")
66*d4726bddSHONG Yifan    ctx.actions.write(
67*d4726bddSHONG Yifan        output = manifest,
68*d4726bddSHONG Yifan        content = content,
69*d4726bddSHONG Yifan    )
70*d4726bddSHONG Yifan
71*d4726bddSHONG Yifan    return manifest
72*d4726bddSHONG Yifan
73*d4726bddSHONG Yifandef _perform_check(edition, srcs, ctx):
74*d4726bddSHONG Yifan    rustfmt_toolchain = ctx.toolchains[Label("//rust/rustfmt:toolchain_type")]
75*d4726bddSHONG Yifan
76*d4726bddSHONG Yifan    config = ctx.file._config
77*d4726bddSHONG Yifan    marker = ctx.actions.declare_file(ctx.label.name + ".rustfmt.ok")
78*d4726bddSHONG Yifan
79*d4726bddSHONG Yifan    args = ctx.actions.args()
80*d4726bddSHONG Yifan    args.add("--touch-file", marker)
81*d4726bddSHONG Yifan    args.add("--")
82*d4726bddSHONG Yifan    args.add(rustfmt_toolchain.rustfmt)
83*d4726bddSHONG Yifan    args.add("--config-path", config)
84*d4726bddSHONG Yifan    args.add("--edition", edition)
85*d4726bddSHONG Yifan    args.add("--check")
86*d4726bddSHONG Yifan    args.add_all(srcs)
87*d4726bddSHONG Yifan
88*d4726bddSHONG Yifan    ctx.actions.run(
89*d4726bddSHONG Yifan        executable = ctx.executable._process_wrapper,
90*d4726bddSHONG Yifan        inputs = srcs + [config],
91*d4726bddSHONG Yifan        outputs = [marker],
92*d4726bddSHONG Yifan        tools = [rustfmt_toolchain.all_files],
93*d4726bddSHONG Yifan        arguments = [args],
94*d4726bddSHONG Yifan        mnemonic = "Rustfmt",
95*d4726bddSHONG Yifan    )
96*d4726bddSHONG Yifan
97*d4726bddSHONG Yifan    return marker
98*d4726bddSHONG Yifan
99*d4726bddSHONG Yifandef _rustfmt_aspect_impl(target, ctx):
100*d4726bddSHONG Yifan    crate_info = _get_rustfmt_ready_crate_info(target)
101*d4726bddSHONG Yifan
102*d4726bddSHONG Yifan    if not crate_info:
103*d4726bddSHONG Yifan        return []
104*d4726bddSHONG Yifan
105*d4726bddSHONG Yifan    srcs = _find_rustfmtable_srcs(crate_info, ctx)
106*d4726bddSHONG Yifan
107*d4726bddSHONG Yifan    # If there are no formattable sources, do nothing.
108*d4726bddSHONG Yifan    if not srcs:
109*d4726bddSHONG Yifan        return []
110*d4726bddSHONG Yifan
111*d4726bddSHONG Yifan    edition = crate_info.edition
112*d4726bddSHONG Yifan
113*d4726bddSHONG Yifan    marker = _perform_check(edition, srcs, ctx)
114*d4726bddSHONG Yifan
115*d4726bddSHONG Yifan    return [
116*d4726bddSHONG Yifan        OutputGroupInfo(
117*d4726bddSHONG Yifan            rustfmt_checks = depset([marker]),
118*d4726bddSHONG Yifan        ),
119*d4726bddSHONG Yifan    ]
120*d4726bddSHONG Yifan
121*d4726bddSHONG Yifanrustfmt_aspect = aspect(
122*d4726bddSHONG Yifan    implementation = _rustfmt_aspect_impl,
123*d4726bddSHONG Yifan    doc = """\
124*d4726bddSHONG YifanThis aspect is used to gather information about a crate for use in rustfmt and perform rustfmt checks
125*d4726bddSHONG Yifan
126*d4726bddSHONG YifanOutput Groups:
127*d4726bddSHONG Yifan
128*d4726bddSHONG Yifan- `rustfmt_checks`: Executes `rustfmt --check` on the specified target.
129*d4726bddSHONG Yifan
130*d4726bddSHONG YifanThe build setting `@rules_rust//:rustfmt.toml` is used to control the Rustfmt [configuration settings][cs]
131*d4726bddSHONG Yifanused at runtime.
132*d4726bddSHONG Yifan
133*d4726bddSHONG Yifan[cs]: https://rust-lang.github.io/rustfmt/
134*d4726bddSHONG Yifan
135*d4726bddSHONG YifanThis aspect is executed on any target which provides the `CrateInfo` provider. However
136*d4726bddSHONG Yifanusers may tag a target with `no-rustfmt` or `no-format` to have it skipped. Additionally,
137*d4726bddSHONG Yifangenerated source files are also ignored by this aspect.
138*d4726bddSHONG Yifan""",
139*d4726bddSHONG Yifan    attrs = {
140*d4726bddSHONG Yifan        "_config": attr.label(
141*d4726bddSHONG Yifan            doc = "The `rustfmt.toml` file used for formatting",
142*d4726bddSHONG Yifan            allow_single_file = True,
143*d4726bddSHONG Yifan            default = Label("//:rustfmt.toml"),
144*d4726bddSHONG Yifan        ),
145*d4726bddSHONG Yifan        "_process_wrapper": attr.label(
146*d4726bddSHONG Yifan            doc = "A process wrapper for running rustfmt on all platforms",
147*d4726bddSHONG Yifan            cfg = "exec",
148*d4726bddSHONG Yifan            executable = True,
149*d4726bddSHONG Yifan            default = Label("//util/process_wrapper"),
150*d4726bddSHONG Yifan        ),
151*d4726bddSHONG Yifan    },
152*d4726bddSHONG Yifan    required_providers = [
153*d4726bddSHONG Yifan        [rust_common.crate_info],
154*d4726bddSHONG Yifan        [rust_common.test_crate_info],
155*d4726bddSHONG Yifan    ],
156*d4726bddSHONG Yifan    fragments = ["cpp"],
157*d4726bddSHONG Yifan    toolchains = [
158*d4726bddSHONG Yifan        str(Label("//rust/rustfmt:toolchain_type")),
159*d4726bddSHONG Yifan    ],
160*d4726bddSHONG Yifan)
161*d4726bddSHONG Yifan
162*d4726bddSHONG Yifandef _rustfmt_test_manifest_aspect_impl(target, ctx):
163*d4726bddSHONG Yifan    crate_info = _get_rustfmt_ready_crate_info(target)
164*d4726bddSHONG Yifan
165*d4726bddSHONG Yifan    if not crate_info:
166*d4726bddSHONG Yifan        return []
167*d4726bddSHONG Yifan
168*d4726bddSHONG Yifan    # Parse the edition to use for formatting from the target
169*d4726bddSHONG Yifan    edition = crate_info.edition
170*d4726bddSHONG Yifan
171*d4726bddSHONG Yifan    srcs = _find_rustfmtable_srcs(crate_info, ctx)
172*d4726bddSHONG Yifan    manifest = _generate_manifest(edition, srcs, ctx)
173*d4726bddSHONG Yifan
174*d4726bddSHONG Yifan    return [
175*d4726bddSHONG Yifan        OutputGroupInfo(
176*d4726bddSHONG Yifan            rustfmt_manifest = depset([manifest]),
177*d4726bddSHONG Yifan        ),
178*d4726bddSHONG Yifan    ]
179*d4726bddSHONG Yifan
180*d4726bddSHONG Yifan# This aspect contains functionality split out of `rustfmt_aspect` which broke when
181*d4726bddSHONG Yifan# `required_providers` was added to it. Aspects which have `required_providers` seems
182*d4726bddSHONG Yifan# to not function with attributes that also require providers.
183*d4726bddSHONG Yifan_rustfmt_test_manifest_aspect = aspect(
184*d4726bddSHONG Yifan    implementation = _rustfmt_test_manifest_aspect_impl,
185*d4726bddSHONG Yifan    doc = """\
186*d4726bddSHONG YifanThis aspect is used to gather information about a crate for use in `rustfmt_test`
187*d4726bddSHONG Yifan
188*d4726bddSHONG YifanOutput Groups:
189*d4726bddSHONG Yifan
190*d4726bddSHONG Yifan- `rustfmt_manifest`: A manifest used by rustfmt binaries to provide crate specific settings.
191*d4726bddSHONG Yifan""",
192*d4726bddSHONG Yifan    fragments = ["cpp"],
193*d4726bddSHONG Yifan    toolchains = [
194*d4726bddSHONG Yifan        str(Label("//rust/rustfmt:toolchain_type")),
195*d4726bddSHONG Yifan    ],
196*d4726bddSHONG Yifan)
197*d4726bddSHONG Yifan
198*d4726bddSHONG Yifandef _rustfmt_test_impl(ctx):
199*d4726bddSHONG Yifan    # The executable of a test target must be the output of an action in
200*d4726bddSHONG Yifan    # the rule implementation. This file is simply a symlink to the real
201*d4726bddSHONG Yifan    # rustfmt test runner.
202*d4726bddSHONG Yifan    is_windows = ctx.executable._runner.extension == ".exe"
203*d4726bddSHONG Yifan    runner = ctx.actions.declare_file("{}{}".format(
204*d4726bddSHONG Yifan        ctx.label.name,
205*d4726bddSHONG Yifan        ".exe" if is_windows else "",
206*d4726bddSHONG Yifan    ))
207*d4726bddSHONG Yifan
208*d4726bddSHONG Yifan    ctx.actions.symlink(
209*d4726bddSHONG Yifan        output = runner,
210*d4726bddSHONG Yifan        target_file = ctx.executable._runner,
211*d4726bddSHONG Yifan        is_executable = True,
212*d4726bddSHONG Yifan    )
213*d4726bddSHONG Yifan
214*d4726bddSHONG Yifan    crate_infos = [_get_rustfmt_ready_crate_info(target) for target in ctx.attr.targets]
215*d4726bddSHONG Yifan    srcs = [depset(_find_rustfmtable_srcs(crate_info)) for crate_info in crate_infos if crate_info]
216*d4726bddSHONG Yifan
217*d4726bddSHONG Yifan    # Some targets may be included in tests but tagged as "no-format". In this
218*d4726bddSHONG Yifan    # case, there will be no manifest.
219*d4726bddSHONG Yifan    manifests = [getattr(target[OutputGroupInfo], "rustfmt_manifest", None) for target in ctx.attr.targets]
220*d4726bddSHONG Yifan    manifests = depset(transitive = [manifest for manifest in manifests if manifest])
221*d4726bddSHONG Yifan
222*d4726bddSHONG Yifan    runfiles = ctx.runfiles(
223*d4726bddSHONG Yifan        transitive_files = depset(transitive = srcs + [manifests]),
224*d4726bddSHONG Yifan    )
225*d4726bddSHONG Yifan
226*d4726bddSHONG Yifan    runfiles = runfiles.merge(
227*d4726bddSHONG Yifan        ctx.attr._runner[DefaultInfo].default_runfiles,
228*d4726bddSHONG Yifan    )
229*d4726bddSHONG Yifan
230*d4726bddSHONG Yifan    workspace = ctx.label.workspace_name or ctx.workspace_name
231*d4726bddSHONG Yifan
232*d4726bddSHONG Yifan    return [
233*d4726bddSHONG Yifan        DefaultInfo(
234*d4726bddSHONG Yifan            files = depset([runner]),
235*d4726bddSHONG Yifan            runfiles = runfiles,
236*d4726bddSHONG Yifan            executable = runner,
237*d4726bddSHONG Yifan        ),
238*d4726bddSHONG Yifan        testing.TestEnvironment({
239*d4726bddSHONG Yifan            "RUSTFMT_MANIFESTS": ctx.configuration.host_path_separator.join([
240*d4726bddSHONG Yifan                workspace + "/" + manifest.short_path
241*d4726bddSHONG Yifan                for manifest in sorted(manifests.to_list())
242*d4726bddSHONG Yifan            ]),
243*d4726bddSHONG Yifan            "RUST_BACKTRACE": "1",
244*d4726bddSHONG Yifan        }),
245*d4726bddSHONG Yifan    ]
246*d4726bddSHONG Yifan
247*d4726bddSHONG Yifanrustfmt_test = rule(
248*d4726bddSHONG Yifan    implementation = _rustfmt_test_impl,
249*d4726bddSHONG Yifan    doc = "A test rule for performing `rustfmt --check` on a set of targets",
250*d4726bddSHONG Yifan    attrs = {
251*d4726bddSHONG Yifan        "targets": attr.label_list(
252*d4726bddSHONG Yifan            doc = "Rust targets to run `rustfmt --check` on.",
253*d4726bddSHONG Yifan            providers = [
254*d4726bddSHONG Yifan                [rust_common.crate_info],
255*d4726bddSHONG Yifan                [rust_common.test_crate_info],
256*d4726bddSHONG Yifan            ],
257*d4726bddSHONG Yifan            aspects = [_rustfmt_test_manifest_aspect],
258*d4726bddSHONG Yifan        ),
259*d4726bddSHONG Yifan        "_runner": attr.label(
260*d4726bddSHONG Yifan            doc = "The rustfmt test runner",
261*d4726bddSHONG Yifan            cfg = "exec",
262*d4726bddSHONG Yifan            executable = True,
263*d4726bddSHONG Yifan            default = Label("//tools/rustfmt:rustfmt_test"),
264*d4726bddSHONG Yifan        ),
265*d4726bddSHONG Yifan    },
266*d4726bddSHONG Yifan    test = True,
267*d4726bddSHONG Yifan)
268*d4726bddSHONG Yifan
269*d4726bddSHONG Yifandef _rustfmt_toolchain_impl(ctx):
270*d4726bddSHONG Yifan    make_variables = {
271*d4726bddSHONG Yifan        "RUSTFMT": ctx.file.rustfmt.path,
272*d4726bddSHONG Yifan    }
273*d4726bddSHONG Yifan
274*d4726bddSHONG Yifan    if ctx.attr.rustc:
275*d4726bddSHONG Yifan        make_variables.update({
276*d4726bddSHONG Yifan            "RUSTC": ctx.file.rustc.path,
277*d4726bddSHONG Yifan        })
278*d4726bddSHONG Yifan
279*d4726bddSHONG Yifan    make_variable_info = platform_common.TemplateVariableInfo(make_variables)
280*d4726bddSHONG Yifan
281*d4726bddSHONG Yifan    all_files = [ctx.file.rustfmt] + ctx.files.rustc_lib
282*d4726bddSHONG Yifan    if ctx.file.rustc:
283*d4726bddSHONG Yifan        all_files.append(ctx.file.rustc)
284*d4726bddSHONG Yifan
285*d4726bddSHONG Yifan    toolchain = platform_common.ToolchainInfo(
286*d4726bddSHONG Yifan        rustfmt = ctx.file.rustfmt,
287*d4726bddSHONG Yifan        rustc = ctx.file.rustc,
288*d4726bddSHONG Yifan        rustc_lib = depset(ctx.files.rustc_lib),
289*d4726bddSHONG Yifan        all_files = depset(all_files),
290*d4726bddSHONG Yifan        make_variables = make_variable_info,
291*d4726bddSHONG Yifan    )
292*d4726bddSHONG Yifan
293*d4726bddSHONG Yifan    return [
294*d4726bddSHONG Yifan        toolchain,
295*d4726bddSHONG Yifan        make_variable_info,
296*d4726bddSHONG Yifan    ]
297*d4726bddSHONG Yifan
298*d4726bddSHONG Yifanrustfmt_toolchain = rule(
299*d4726bddSHONG Yifan    doc = "A toolchain for [rustfmt](https://rust-lang.github.io/rustfmt/)",
300*d4726bddSHONG Yifan    implementation = _rustfmt_toolchain_impl,
301*d4726bddSHONG Yifan    attrs = {
302*d4726bddSHONG Yifan        "rustc": attr.label(
303*d4726bddSHONG Yifan            doc = "The location of the `rustc` binary. Can be a direct source or a filegroup containing one item.",
304*d4726bddSHONG Yifan            allow_single_file = True,
305*d4726bddSHONG Yifan            cfg = "exec",
306*d4726bddSHONG Yifan        ),
307*d4726bddSHONG Yifan        "rustc_lib": attr.label(
308*d4726bddSHONG Yifan            doc = "The libraries used by rustc during compilation.",
309*d4726bddSHONG Yifan            cfg = "exec",
310*d4726bddSHONG Yifan        ),
311*d4726bddSHONG Yifan        "rustfmt": attr.label(
312*d4726bddSHONG Yifan            doc = "The location of the `rustfmt` binary. Can be a direct source or a filegroup containing one item.",
313*d4726bddSHONG Yifan            allow_single_file = True,
314*d4726bddSHONG Yifan            cfg = "exec",
315*d4726bddSHONG Yifan            mandatory = True,
316*d4726bddSHONG Yifan        ),
317*d4726bddSHONG Yifan    },
318*d4726bddSHONG Yifan    toolchains = [
319*d4726bddSHONG Yifan        str(Label("@rules_rust//rust:toolchain_type")),
320*d4726bddSHONG Yifan    ],
321*d4726bddSHONG Yifan)
322*d4726bddSHONG Yifan
323*d4726bddSHONG Yifandef _current_rustfmt_toolchain_impl(ctx):
324*d4726bddSHONG Yifan    toolchain = ctx.toolchains[str(Label("@rules_rust//rust/rustfmt:toolchain_type"))]
325*d4726bddSHONG Yifan
326*d4726bddSHONG Yifan    return [
327*d4726bddSHONG Yifan        toolchain,
328*d4726bddSHONG Yifan        toolchain.make_variables,
329*d4726bddSHONG Yifan        DefaultInfo(
330*d4726bddSHONG Yifan            files = depset([
331*d4726bddSHONG Yifan                toolchain.rustfmt,
332*d4726bddSHONG Yifan            ]),
333*d4726bddSHONG Yifan            runfiles = ctx.runfiles(transitive_files = toolchain.all_files),
334*d4726bddSHONG Yifan        ),
335*d4726bddSHONG Yifan    ]
336*d4726bddSHONG Yifan
337*d4726bddSHONG Yifancurrent_rustfmt_toolchain = rule(
338*d4726bddSHONG Yifan    doc = "A rule for exposing the current registered `rustfmt_toolchain`.",
339*d4726bddSHONG Yifan    implementation = _current_rustfmt_toolchain_impl,
340*d4726bddSHONG Yifan    toolchains = [
341*d4726bddSHONG Yifan        str(Label("@rules_rust//rust/rustfmt:toolchain_type")),
342*d4726bddSHONG Yifan    ],
343*d4726bddSHONG Yifan)
344