Skip to content

Commit 7b31777

Browse files
authored
Update rustfmt to work with mixed generated srcs (bazelbuild#3983)
This change adds an aspect to better locate formattable sources for rustfmt. closes bazelbuild#3850
1 parent d5809d4 commit 7b31777

5 files changed

Lines changed: 514 additions & 45 deletions

File tree

rust/private/rustfmt.bzl

Lines changed: 79 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ def _find_rustfmtable_srcs(crate_info, aspect_ctx = None):
3535
list: A list of formattable sources (`File`).
3636
"""
3737

38+
crate_srcs = crate_info.srcs
39+
3840
# Targets with specific tags will not be formatted
3941
if aspect_ctx:
4042
ignore_tags = [
@@ -47,8 +49,10 @@ def _find_rustfmtable_srcs(crate_info, aspect_ctx = None):
4749
if tag.replace("-", "_").lower() in ignore_tags:
4850
return []
4951

52+
crate_srcs = depset(getattr(aspect_ctx.rule.files, "srcs", []), transitive = [crate_info.srcs])
53+
5054
# Filter out any generated files
51-
srcs = [src for src in crate_info.srcs.to_list() if src.is_source]
55+
srcs = [src for src in crate_srcs.to_list() if src.is_source]
5256

5357
return srcs
5458

@@ -97,27 +101,55 @@ def _perform_check(edition, srcs, ctx):
97101

98102
return marker
99103

104+
RustfmtTargetInfo = provider(
105+
doc = "A provider containing rustfmt formattable sources for a target.",
106+
fields = {
107+
"edition": "str: The Rust edition of the target.",
108+
"srcs": "list[File]: The formattable sources.",
109+
},
110+
)
111+
112+
def _rustfmt_srcs_aspect_impl(target, ctx):
113+
crate_info = _get_rustfmt_ready_crate_info(target)
114+
115+
if not crate_info:
116+
return []
117+
118+
srcs = _find_rustfmtable_srcs(crate_info, ctx)
119+
120+
return [
121+
RustfmtTargetInfo(
122+
srcs = srcs,
123+
edition = crate_info.edition,
124+
),
125+
]
126+
127+
rustfmt_srcs_aspect = aspect(
128+
implementation = _rustfmt_srcs_aspect_impl,
129+
doc = "This aspect collects formattable sources from a Rust target.",
130+
required_providers = [
131+
[rust_common.crate_info],
132+
[rust_common.test_crate_info],
133+
],
134+
fragments = ["cpp"],
135+
)
136+
100137
def _rustfmt_aspect_impl(target, ctx):
101138
# Exit early if a target already has a rustfmt output group. This
102139
# can be useful for rules which always want to inhibit rustfmt.
103140
if OutputGroupInfo in target:
104141
if hasattr(target[OutputGroupInfo], "rustfmt_checks"):
105142
return []
106143

107-
crate_info = _get_rustfmt_ready_crate_info(target)
108-
109-
if not crate_info:
144+
if RustfmtTargetInfo not in target:
110145
return []
111146

112-
srcs = _find_rustfmtable_srcs(crate_info, ctx)
147+
info = target[RustfmtTargetInfo]
113148

114-
# If there are no formattable sources, do nothing.
115-
if not srcs:
149+
if not info.srcs:
116150
return []
117151

118-
edition = crate_info.edition
119-
120-
marker = _perform_check(edition, srcs, ctx)
152+
marker = _perform_check(info.edition, info.srcs, ctx)
121153

122154
return [
123155
OutputGroupInfo(
@@ -160,48 +192,51 @@ generated source files are also ignored by this aspect.
160192
[rust_common.crate_info],
161193
[rust_common.test_crate_info],
162194
],
195+
requires = [rustfmt_srcs_aspect],
163196
fragments = ["cpp"],
164197
toolchains = [
165198
str(Label("//rust/rustfmt:toolchain_type")),
166199
],
167200
)
168201

169-
def _rustfmt_test_manifest_aspect_impl(target, ctx):
170-
crate_info = _get_rustfmt_ready_crate_info(target)
202+
_RustfmtTestManifestInfo = provider(
203+
doc = "A container for rustfmt manifest info to use in `rustfmt_test`",
204+
fields = {
205+
"manifest": "File: The manifest of formattable sources.",
206+
"srcs": "depset[File]: The formattable sources.",
207+
},
208+
)
171209

172-
if not crate_info:
210+
def _rustfmt_test_target_aspect_impl(target, ctx):
211+
if RustfmtTargetInfo not in target:
173212
return []
174213

175-
# Parse the edition to use for formatting from the target
176-
edition = crate_info.edition
177-
178-
srcs = _find_rustfmtable_srcs(crate_info, ctx)
179-
manifest = _generate_manifest(edition, srcs, ctx)
214+
info = target[RustfmtTargetInfo]
215+
manifest = _generate_manifest(info.edition, info.srcs, ctx)
180216

181217
return [
182-
OutputGroupInfo(
183-
rustfmt_manifest = depset([manifest]),
218+
_RustfmtTestManifestInfo(
219+
manifest = manifest,
220+
srcs = depset(info.srcs),
184221
),
185222
]
186223

187-
# This aspect contains functionality split out of `rustfmt_aspect` which broke when
188-
# `required_providers` was added to it. Aspects which have `required_providers` seems
189-
# to not function with attributes that also require providers.
190-
_rustfmt_test_manifest_aspect = aspect(
191-
implementation = _rustfmt_test_manifest_aspect_impl,
192-
doc = """\
193-
This aspect is used to gather information about a crate for use in `rustfmt_test`
194-
195-
Output Groups:
196-
197-
- `rustfmt_manifest`: A manifest used by rustfmt binaries to provide crate specific settings.
198-
""",
224+
_rustfmt_test_target_aspect = aspect(
225+
implementation = _rustfmt_test_target_aspect_impl,
226+
doc = """This aspect is used to gather information about a crate for use in `rustfmt_test`""",
227+
requires = [rustfmt_srcs_aspect],
199228
fragments = ["cpp"],
200229
toolchains = [
201230
str(Label("//rust/rustfmt:toolchain_type")),
202231
],
203232
)
204233

234+
def _rlocationpath(file, workspace_name):
235+
if file.short_path.startswith("../"):
236+
return file.short_path[len("../"):]
237+
238+
return "{}/{}".format(workspace_name, file.short_path)
239+
205240
def _rustfmt_test_impl(ctx):
206241
# The executable of a test target must be the output of an action in
207242
# the rule implementation. This file is simply a symlink to the real
@@ -218,24 +253,23 @@ def _rustfmt_test_impl(ctx):
218253
is_executable = True,
219254
)
220255

221-
crate_infos = [_get_rustfmt_ready_crate_info(target) for target in ctx.attr.targets]
222-
srcs = [depset(_find_rustfmtable_srcs(crate_info)) for crate_info in crate_infos if crate_info]
223-
224-
# Some targets may be included in tests but tagged as "no-format". In this
225-
# case, there will be no manifest.
226-
manifests = [getattr(target[OutputGroupInfo], "rustfmt_manifest", None) for target in ctx.attr.targets]
227-
manifests = depset(transitive = [manifest for manifest in manifests if manifest])
256+
srcs = []
257+
manifests = []
258+
for target in ctx.attr.targets:
259+
if _RustfmtTestManifestInfo not in target:
260+
continue
261+
info = target[_RustfmtTestManifestInfo]
262+
manifests.append(info.manifest)
263+
srcs.append(info.srcs)
228264

229265
runfiles = ctx.runfiles(
230-
transitive_files = depset(transitive = srcs + [manifests]),
266+
transitive_files = depset(manifests, transitive = srcs),
231267
)
232268

233269
runfiles = runfiles.merge(
234270
ctx.attr._runner[DefaultInfo].default_runfiles,
235271
)
236272

237-
workspace = ctx.label.workspace_name or ctx.workspace_name
238-
239273
return [
240274
DefaultInfo(
241275
files = depset([runner]),
@@ -245,8 +279,8 @@ def _rustfmt_test_impl(ctx):
245279
RunEnvironmentInfo(
246280
environment = {
247281
"RUSTFMT_MANIFESTS": ctx.configuration.host_path_separator.join([
248-
workspace + "/" + manifest.short_path
249-
for manifest in sorted(manifests.to_list())
282+
_rlocationpath(manifest, ctx.workspace_name)
283+
for manifest in manifests
250284
]),
251285
"RUST_BACKTRACE": "1",
252286
},
@@ -263,7 +297,7 @@ rustfmt_test = rule(
263297
[rust_common.crate_info],
264298
[rust_common.test_crate_info],
265299
],
266-
aspects = [_rustfmt_test_manifest_aspect],
300+
aspects = [_rustfmt_test_target_aspect],
267301
),
268302
"_runner": attr.label(
269303
doc = "The rustfmt test runner",

test/unit/rustfmt/BUILD.bazel

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
load(":rustfmt_unit_test.bzl", "rustfmt_unit_test_suite")
2+
3+
rustfmt_unit_test_suite(name = "rustfmt_unit_test_suite")

test/unit/rustfmt/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

test/unit/rustfmt/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
fn main() {}

0 commit comments

Comments
 (0)