Skip to content

Add function for generating customisable clippy action #3405

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 44 additions & 8 deletions rust/private/clippy.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,30 @@ def _clippy_aspect_impl(target, ctx):
if hasattr(target[OutputGroupInfo], "clippy_checks"):
return []

output_format = "stderr-to-file" if ctx.attr._capture_output[CaptureClippyOutputInfo].capture_output else "stderr-and-touch-file"

clippy_info = rust_clippy_action(target, ctx, output_format, capture_exit_code=False)

return [
OutputGroupInfo(clippy_checks = clippy_info.output),
clippy_info,
]

def rust_clippy_action(target, ctx, output_format, capture_exit_code):
"""Generate an action to run clippy.

Args:
target (Target): The target the aspect is running on.
ctx (ctx, optional): The aspect's context object.
output_format (str): One of "stderr-and-touch-file", "stderr-to-file", or "json-to-file"
capture_exit_code (boolean): If true, write the exit code to a file and exit 0.

Returns:
ClippyInfo: A `ClippyInfo` provider.
"""
crate_info = _get_clippy_ready_crate_info(target, ctx)
if not crate_info:
return [ClippyInfo(output = depset([]))]
return ClippyInfo(output = depset([]))

toolchain = find_toolchain(ctx)
cc_toolchain, feature_configuration = find_cc_toolchain(ctx)
Expand Down Expand Up @@ -171,7 +192,7 @@ def _clippy_aspect_impl(target, ctx):

# For remote execution purposes, the clippy_out file must be a sibling of crate_info.output
# or rustc may fail to create intermediate output files because the directory does not exist.
if ctx.attr._capture_output[CaptureClippyOutputInfo].capture_output:
if output_format == "stderr-to-file":
clippy_out = ctx.actions.declare_file(ctx.label.name + ".clippy.out", sibling = crate_info.output)
args.process_wrapper_flags.add("--stderr-file", clippy_out)

Expand All @@ -181,7 +202,7 @@ def _clippy_aspect_impl(target, ctx):
# If we are capturing the output, we want the build system to be able to keep going
# and consume the output. Some clippy lints are denials, so we cap everything at warn.
args.rustc_flags.add("--cap-lints=warn")
else:
elif output_format == "stderr-and-touch-file":
# A marker file indicating clippy has executed successfully.
# This file is necessary because "ctx.actions.run" mandates an output.
clippy_out = ctx.actions.declare_file(ctx.label.name + ".clippy.ok", sibling = crate_info.output)
Expand All @@ -196,6 +217,24 @@ def _clippy_aspect_impl(target, ctx):
# Bazel will consider the execution result of the aspect to be "success",
# and Clippy won't be re-triggered unless the source file is modified.
args.rustc_flags.add("-Dwarnings")
elif output_format == "json-to-file":
clippy_out = ctx.actions.declare_file(ctx.label.name + ".clippy.out", sibling = crate_info.output)
args.process_wrapper_flags.add("--stderr-file", clippy_out)

if clippy_flags or lint_files:
args.rustc_flags.add_all(clippy_flags)
args.rustc_flags.add("--error-format=json")
else:
fail("clippy output_format must be one of stderr-to-file|stderr-and-touch-file|json-to-file but was {}".format(output_format))

outputs = [clippy_out]

if capture_exit_code:
exit_code_file = ctx.actions.declare_file(ctx.label.name + ".clippy.exit_code", sibling = crate_info.output)
args.process_wrapper_flags.add("--exit-code-file", exit_code_file)
outputs.append(exit_code_file)
else:
exit_code_file = None

# Upstream clippy requires one of these two filenames or it silently uses
# the default config. Enforce the naming so users are not confused.
Expand All @@ -208,7 +247,7 @@ def _clippy_aspect_impl(target, ctx):
ctx.actions.run(
executable = ctx.executable._process_wrapper,
inputs = compile_inputs,
outputs = [clippy_out],
outputs = outputs,
env = env,
tools = [toolchain.clippy_driver],
arguments = args.all,
Expand All @@ -217,10 +256,7 @@ def _clippy_aspect_impl(target, ctx):
toolchain = "@rules_rust//rust:toolchain_type",
)

return [
OutputGroupInfo(clippy_checks = depset([clippy_out])),
ClippyInfo(output = depset([clippy_out])),
]
return ClippyInfo(output = depset([clippy_out]), exit_code_file = exit_code_file)

# Example: Run the clippy checker on all targets in the codebase.
# bazel build --aspects=@rules_rust//rust:defs.bzl%rust_clippy_aspect \
Expand Down
1 change: 1 addition & 0 deletions rust/private/providers.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ ClippyInfo = provider(
doc = "Provides information on a clippy run.",
fields = {
"output": "File with the clippy output.",
"exit_code_file": "File with clippy's exit code, or None if the action is expected to fail on failure.",
},
)

Expand Down
8 changes: 7 additions & 1 deletion util/process_wrapper/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,13 @@ fn main() -> Result<(), ProcessWrapperError> {
}
}

exit(code)
if let Some(exit_code_file) = opts.exit_code_file {
std::fs::write(exit_code_file, format!("{code}"))
.map_err(|e| ProcessWrapperError(format!("failed to write exit code file: {}", e)))?;
Ok(())
} else {
exit(code);
}
}

#[cfg(test)]
Expand Down
9 changes: 9 additions & 0 deletions util/process_wrapper/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ pub(crate) struct Options {
// If set, also logs all unprocessed output from the rustc output to this file.
// Meant to be used to get json output out of rustc for tooling usage.
pub(crate) output_file: Option<String>,
// If set, output the exit code of the subprocess to this file in ASCII, and unconditionally exit 0.
pub(crate) exit_code_file: Option<String>,
// If set, it configures rustc to emit an rmeta file and then
// quit.
pub(crate) rustc_quit_on_rmeta: bool,
Expand All @@ -64,6 +66,7 @@ pub(crate) fn options() -> Result<Options, OptionError> {
let mut stdout_file = None;
let mut stderr_file = None;
let mut output_file = None;
let mut exit_code_file = None;
let mut rustc_quit_on_rmeta_raw = None;
let mut rustc_output_format_raw = None;
let mut flags = Flags::new();
Expand Down Expand Up @@ -101,6 +104,11 @@ pub(crate) fn options() -> Result<Options, OptionError> {
"Log all unprocessed subprocess stderr in this file.",
&mut output_file,
);
flags.define_flag(
"--exit-code-file",
"Log the process's exit code to this file as ASCII. Unconditionally exit with code 0 if this is set.",
&mut exit_code_file,
);
flags.define_flag(
"--rustc-quit-on-rmeta",
"If enabled, this wrapper will terminate rustc after rmeta has been emitted.",
Expand Down Expand Up @@ -210,6 +218,7 @@ pub(crate) fn options() -> Result<Options, OptionError> {
stdout_file,
stderr_file,
output_file,
exit_code_file,
rustc_quit_on_rmeta,
rustc_output_format,
})
Expand Down
Loading