Skip to content

Commit 422f5ec

Browse files
committed
Add setting clippy_output_diagnostics to match rustc_output_[...]
Right now, capturing clippy's diagnostics is in a weird place. `capture_clippy_output` exists, but it has drastically different behavior from `rustc_output_diagnostics` in that it captures the rendered error output and hides it from the terminal, whereas `rustc_output_diagnostics` captures the raw JSON and still prints out the rendered diagnostics. The loss of the JSON is a bit of an issue. With that, the only way to get JSON output from the command-line is to set `clippy_error_format`, which will necessarily *clear the analysis cache* and rebuild all the targets, meaning that you can't get both rendered output *and* JSON output without either rebuilds in-between or relying on custom transitions. This adds the missing equivalent to `rustc_output_diagnostics` in the form of a new option, `clippy_output_diagnostics`. In an ideal world, I'd imagine this would be the *only* option to capture clippy output (since for most intents and purposes, it's strictly a superset of `capture_clippy_output`), but using a separate option and output group preserves backwards compatibility while retaining the parallel naming of `rustc_output_diagnostics` vs `clippy_output_diagnostics`, as well as the output groups `rustc_output` vs `clippy_output`.
1 parent 52b93ae commit 422f5ec

File tree

6 files changed

+93
-5
lines changed

6 files changed

+93
-5
lines changed

rust/private/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ bzl_library(
2828
":rules_cc_bzl_lib",
2929
"//rust/platform:bzl_lib",
3030
"@bazel_skylib//lib:paths",
31+
"@bazel_skylib//lib:structs",
3132
"@bazel_skylib//rules:common_settings",
3233
],
3334
)

rust/private/clippy.bzl

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,15 @@
1414

1515
"""A module defining clippy rules"""
1616

17+
load("@bazel_skylib//lib:structs.bzl", "structs")
1718
load("//rust/private:common.bzl", "rust_common")
18-
load("//rust/private:providers.bzl", "CaptureClippyOutputInfo", "ClippyInfo", "LintsInfo")
19+
load(
20+
"//rust/private:providers.bzl",
21+
"CaptureClippyOutputInfo",
22+
"ClippyInfo",
23+
"ClippyOutputDiagnosticsInfo",
24+
"LintsInfo",
25+
)
1926
load(
2027
"//rust/private:rustc.bzl",
2128
"collect_deps",
@@ -146,6 +153,13 @@ def _clippy_aspect_impl(target, ctx):
146153
"_clippy_error_format" if use_clippy_error_format else "_error_format",
147154
)
148155

156+
clippy_diagnostics = None
157+
if ctx.attr._clippy_output_diagnostics[ClippyOutputDiagnosticsInfo].output_diagnostics:
158+
clippy_diagnostics = ctx.actions.declare_file(ctx.label.name + ".clippy.diagnostics", sibling = crate_info.output)
159+
crate_info_dict = structs.to_dict(crate_info)
160+
crate_info_dict["rustc_output"] = clippy_diagnostics
161+
crate_info = rust_common.create_crate_info(**crate_info_dict)
162+
149163
args, env = construct_arguments(
150164
ctx = ctx,
151165
attr = ctx.rule.attr,
@@ -165,6 +179,7 @@ def _clippy_aspect_impl(target, ctx):
165179
build_flags_files = build_flags_files,
166180
emit = ["dep-info", "metadata"],
167181
skip_expanding_rustc_env = True,
182+
use_json_output = bool(clippy_diagnostics),
168183
error_format = error_format,
169184
)
170185

@@ -217,7 +232,7 @@ def _clippy_aspect_impl(target, ctx):
217232
ctx.actions.run(
218233
executable = ctx.executable._process_wrapper,
219234
inputs = compile_inputs,
220-
outputs = [clippy_out],
235+
outputs = [clippy_out] + [x for x in [clippy_diagnostics] if x],
221236
env = env,
222237
tools = [toolchain.clippy_driver],
223238
arguments = args.all,
@@ -226,8 +241,12 @@ def _clippy_aspect_impl(target, ctx):
226241
toolchain = "@rules_rust//rust:toolchain_type",
227242
)
228243

244+
output_group_info = {"clippy_checks": depset([clippy_out])}
245+
if clippy_diagnostics:
246+
output_group_info["clippy_output"] = depset([clippy_diagnostics])
247+
229248
return [
230-
OutputGroupInfo(clippy_checks = depset([clippy_out])),
249+
OutputGroupInfo(**output_group_info),
231250
ClippyInfo(output = depset([clippy_out])),
232251
]
233252

@@ -255,6 +274,10 @@ rust_clippy_aspect = aspect(
255274
doc = "Arguments to pass to clippy",
256275
default = Label("//rust/settings:clippy_flags"),
257276
),
277+
"_clippy_output_diagnostics": attr.label(
278+
doc = "Value of the `clippy_output_diagnostics` build setting",
279+
default = "//rust/settings:clippy_output_diagnostics",
280+
),
258281
"_config": attr.label(
259282
doc = "The `clippy.toml` file used for configuration",
260283
allow_single_file = True,
@@ -395,3 +418,25 @@ capture_clippy_output = rule(
395418
implementation = _capture_clippy_output_impl,
396419
build_setting = config.bool(flag = True),
397420
)
421+
422+
def _clippy_output_diagnostics_impl(ctx):
423+
"""Implementation of the `clippy_output_diagnostics` rule
424+
425+
Args:
426+
ctx (ctx): The rule's context object
427+
428+
Returns:
429+
list: A list containing the CaptureClippyOutputInfo provider
430+
"""
431+
return [ClippyOutputDiagnosticsInfo(output_diagnostics = ctx.build_setting_value)]
432+
433+
clippy_output_diagnostics = rule(
434+
doc = (
435+
"Setting this flag from the command line with `--@rules_rust//rust/settings:clippy_output_diagnostics` " +
436+
"makes rules_rust save lippy json output (suitable for consumption by rust-analyzer) in a file, " +
437+
"available from the `clippy_output` output group. This is the clippy equivalent of " +
438+
"`@rules_rust//settings:rustc_output_diagnostics`."
439+
),
440+
implementation = _clippy_output_diagnostics_impl,
441+
build_setting = config.bool(flag = True),
442+
)

rust/private/providers.bzl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,11 @@ CaptureClippyOutputInfo = provider(
135135
fields = {"capture_output": "Value of the `capture_clippy_output` build setting"},
136136
)
137137

138+
ClippyOutputDiagnosticsInfo = provider(
139+
doc = "Value of the `clippy_output_diagnostics` build setting",
140+
fields = {"output_diagnostics": "Value of the `clippy_output_diagnostics` build setting"},
141+
)
142+
138143
ClippyInfo = provider(
139144
doc = "Provides information on a clippy run.",
140145
fields = {

rust/settings/BUILD.bazel

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ load(
55
"clippy_error_format",
66
"clippy_flag",
77
"clippy_flags",
8+
"clippy_output_diagnostics",
89
"clippy_toml",
910
"codegen_units",
1011
"error_format",
@@ -58,6 +59,8 @@ clippy_flag()
5859

5960
clippy_flags()
6061

62+
clippy_output_diagnostics()
63+
6164
clippy_toml()
6265

6366
codegen_units()

rust/settings/settings.bzl

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ load(
1414
_capture_clippy_output = "capture_clippy_output",
1515
_clippy_flag = "clippy_flag",
1616
_clippy_flags = "clippy_flags",
17+
_clippy_output_diagnostics = "clippy_output_diagnostics",
1718
)
1819
load("//rust/private:lto.bzl", "rust_lto_flag")
1920
load(
@@ -331,6 +332,20 @@ def rustc_output_diagnostics():
331332
visibility = ["//visibility:public"],
332333
)
333334

335+
# buildifier: disable=unnamed-macro
336+
def clippy_output_diagnostics():
337+
"""A flag to enable the `clippy_output_diagnostics` setting.
338+
339+
If this flag is true, rules_rust will save clippy json output (suitable for consumption
340+
by rust-analyzer) in a file, available from the `clippy_output` output group. This is the
341+
clippy equivalent of `rustc_output_diagnostics`.
342+
"""
343+
_clippy_output_diagnostics(
344+
name = "clippy_output_diagnostics",
345+
build_setting_default = False,
346+
visibility = ["//visibility:public"],
347+
)
348+
334349
# buildifier: disable=unnamed-macro
335350
def clippy_flags():
336351
"""This setting may be used to pass extra options to clippy from the command line.

test/unit/clippy/clippy_test.bzl

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22

33
load("@bazel_skylib//lib:unittest.bzl", "analysistest")
44
load("//rust:defs.bzl", "rust_clippy_aspect")
5-
load("//test/unit:common.bzl", "assert_argv_contains", "assert_list_contains_adjacent_elements")
5+
load("//test/unit:common.bzl", "assert_argv_contains", "assert_argv_contains_prefix_suffix", "assert_list_contains_adjacent_elements")
66

77
def _find_clippy_action(actions):
88
for action in actions:
99
if action.mnemonic == "Clippy":
1010
return action
1111
fail("Failed to find Clippy action")
1212

13-
def _clippy_aspect_action_has_flag_impl(ctx, flags):
13+
def _clippy_aspect_action_has_flag_impl(ctx, flags, *, prefix_suffix_flags = []):
1414
env = analysistest.begin(ctx)
1515
target = analysistest.target_under_test(env)
1616

@@ -23,6 +23,8 @@ def _clippy_aspect_action_has_flag_impl(ctx, flags):
2323
clippy_action,
2424
flag,
2525
)
26+
for (prefix, suffix) in prefix_suffix_flags:
27+
assert_argv_contains_prefix_suffix(env, clippy_action, prefix, suffix)
2628

2729
clippy_checks = target[OutputGroupInfo].clippy_checks.to_list()
2830
if len(clippy_checks) != 1:
@@ -123,6 +125,17 @@ clippy_aspect_with_clippy_error_format_test = make_clippy_aspect_unittest(
123125
},
124126
)
125127

128+
clippy_aspect_with_output_diagnostics_test = make_clippy_aspect_unittest(
129+
lambda ctx: _clippy_aspect_action_has_flag_impl(
130+
ctx,
131+
["--error-format=json", "--output-file"],
132+
prefix_suffix_flags = [("", "/ok_library.clippy.diagnostics")],
133+
),
134+
config_settings = {
135+
str(Label("//rust/settings:clippy_output_diagnostics")): True,
136+
},
137+
)
138+
126139
def clippy_test_suite(name):
127140
"""Entry-point macro called from the BUILD file.
128141
@@ -165,6 +178,11 @@ def clippy_test_suite(name):
165178
target_under_test = Label("//test/clippy:ok_library"),
166179
)
167180

181+
clippy_aspect_with_output_diagnostics_test(
182+
name = "clippy_aspect_with_output_diagnostics_test",
183+
target_under_test = Label("//test/clippy:ok_library"),
184+
)
185+
168186
native.test_suite(
169187
name = name,
170188
tests = [
@@ -176,5 +194,6 @@ def clippy_test_suite(name):
176194
":test_clippy_aspect_with_explicit_flags_test",
177195
":clippy_aspect_without_clippy_error_format_test",
178196
":clippy_aspect_with_clippy_error_format_test",
197+
":clippy_aspect_with_output_diagnostics_test",
179198
],
180199
)

0 commit comments

Comments
 (0)