diff --git a/rust/defs.bzl b/rust/defs.bzl index 4f2ef72582..71014cfed6 100644 --- a/rust/defs.bzl +++ b/rust/defs.bzl @@ -27,6 +27,7 @@ load( _rust_clippy = "rust_clippy", _rust_clippy_action = "rust_clippy_action", _rust_clippy_aspect = "rust_clippy_aspect", + _rust_create_clippy_action = "rust_create_clippy_action", ) load("//rust/private:common.bzl", _rust_common = "rust_common") load( @@ -123,6 +124,7 @@ capture_clippy_output = _capture_clippy_output rust_clippy_action = struct( action = _rust_clippy_action, + create_action = _rust_create_clippy_action, get_clippy_ready_crate_info = _get_clippy_ready_crate_info, ) # See @rules_rust//rust/private:clippy.bzl for a complete description. diff --git a/rust/private/clippy.bzl b/rust/private/clippy.bzl index 318c05af8d..119b15266f 100644 --- a/rust/private/clippy.bzl +++ b/rust/private/clippy.bzl @@ -101,10 +101,7 @@ def get_clippy_ready_crate_info(target, aspect_ctx = None): else: return None -def rust_clippy_action(ctx, clippy_executable, process_wrapper, crate_info, config, output = None, success_marker = None, cap_at_warnings = False, extra_clippy_flags = [], error_format = None, clippy_diagnostics_file = None): - """Run clippy with the specified parameters. - - Args: +_RUST_CLIPPY_ACTION_MACRO_ARGS = """ ctx (ctx): The aspect's context object. This function should not read ctx.attr, but it might read ctx.rule.attr clippy_executable (File): The clippy executable to run process_wrapper (File): An executable process wrapper that can run clippy, usually @rules_rust//utils/process_wrapper @@ -112,14 +109,36 @@ def rust_clippy_action(ctx, clippy_executable, process_wrapper, crate_info, conf config (File): The clippy configuration file. Reference: https://doc.rust-lang.org/clippy/configuration.html#configuring-clippy output (File): The output file for clippy stdout/stderr. If None, no output will be captured success_marker (File): A file that will be written if clippy succeeds + exit_code_file (File): A file that will contain clippy's exit code + forward_clippy_exit_code (bool): If set, let the action exit with the same exit code as clippy cap_at_warnings (bool): If set, it will cap all reports as warnings, allowing the build to continue even with clippy failures extra_clippy_flags (List[str]): A list of extra options to pass to clippy. If not set, every warnings will be turned into errors error_format (str): Which error format to use. Must be acceptable by rustc: https://doc.rust-lang.org/beta/rustc/command-line-arguments.html#--error-format-control-how-errors-are-produced clippy_diagnostics_file (File): File to output diagnostics to. If None, no diagnostics will be written +""" + +def rust_create_clippy_action( + ctx, + clippy_executable, + process_wrapper, + crate_info, + config, + output = None, + success_marker = None, + exit_code_file = None, + forward_clippy_exit_code = True, + cap_at_warnings = False, + extra_clippy_flags = [], + error_format = None, + clippy_diagnostics_file = None): + """Calculate inputs and arguments to run clippy with the specified parameters. + + Args: {args} Returns: - None - """ + A struct indicating how to run clippy. The fields of the struct match the fields of `ctx.action.run`. + """.format(args = _RUST_CLIPPY_ACTION_MACRO_ARGS) + print("BL: first line flags ={}".format(extra_clippy_flags)) toolchain = find_toolchain(ctx) cc_toolchain, feature_configuration = find_cc_toolchain(ctx) @@ -186,6 +205,8 @@ def rust_clippy_action(ctx, clippy_executable, process_wrapper, crate_info, conf if crate_info.is_test: args.rustc_flags.add("--test") + print("BL: inside flags ={}".format(extra_clippy_flags)) + # Then append the clippy flags specified from the command line, so they override what is # specified on the library. clippy_flags += extra_clippy_flags @@ -200,6 +221,13 @@ def rust_clippy_action(ctx, clippy_executable, process_wrapper, crate_info, conf args.process_wrapper_flags.add("--touch-file", success_marker) outputs.append(success_marker) + if forward_clippy_exit_code == False: + args.process_wrapper_flags.add("--forward-exit-code", "false") + + if exit_code_file != None: + args.process_wrapper_flags.add("--exit-code-file", exit_code_file) + outputs.append(exit_code_file) + if clippy_flags or lint_files: args.rustc_flags.add_all(clippy_flags) else: @@ -223,7 +251,7 @@ def rust_clippy_action(ctx, clippy_executable, process_wrapper, crate_info, conf env["CLIPPY_CONF_DIR"] = "${{pwd}}/{}".format(config.dirname) compile_inputs = depset([config], transitive = [compile_inputs]) - ctx.actions.run( + return struct( executable = process_wrapper, inputs = compile_inputs, outputs = outputs + [x for x in [clippy_diagnostics_file] if x], @@ -235,6 +263,56 @@ def rust_clippy_action(ctx, clippy_executable, process_wrapper, crate_info, conf toolchain = "@rules_rust//rust:toolchain_type", ) +def rust_clippy_action( + ctx, + clippy_executable, + process_wrapper, + crate_info, + config, + output = None, + success_marker = None, + exit_code_file = None, + forward_clippy_exit_code = True, + cap_at_warnings = False, + extra_clippy_flags = [], + error_format = None, + clippy_diagnostics_file = None): + """Run clippy with the specified parameters. + + Args: {args} + + Returns: + None + """.format(args = _RUST_CLIPPY_ACTION_MACRO_ARGS) + print("BL: flags ={}".format(extra_clippy_flags)) + clippy_action = rust_create_clippy_action( + ctx = ctx, + clippy_executable = clippy_executable, + process_wrapper = process_wrapper, + crate_info = crate_info, + config = config, + output = output, + success_marker = success_marker, + exit_code_file = exit_code_file, + forward_clippy_exit_code = forward_clippy_exit_code, + cap_at_warnings = cap_at_warnings, + extra_clippy_flags = extra_clippy_flags, + error_format = error_format, + clippy_diagnostics_file = clippy_diagnostics_file, + ) + + ctx.actions.run( + executable = clippy_action.executable, + inputs = clippy_action.inputs, + outputs = clippy_action.outputs, + env = clippy_action.env, + tools = clippy_action.tools, + arguments = clippy_action.arguments, + mnemonic = clippy_action.mnemonic, + progress_message = clippy_action.progress_message, + toolchain = clippy_action.toolchain, + ) + def _clippy_aspect_impl(target, ctx): # Exit early if a target already has a clippy output group. This # can be useful for rules which always want to inhibit clippy. diff --git a/util/process_wrapper/main.rs b/util/process_wrapper/main.rs index 39a6d6db16..14971b36c3 100644 --- a/util/process_wrapper/main.rs +++ b/util/process_wrapper/main.rs @@ -22,6 +22,7 @@ use std::collections::HashMap; use std::fmt; use std::fs::{copy, OpenOptions}; use std::io; +use std::io::Write; use std::process::{exit, Command, ExitStatus, Stdio}; use tinyjson::JsonValue; @@ -215,7 +216,7 @@ fn main() -> Result<(), ProcessWrapperError> { .create(true) .truncate(true) .write(true) - .open(tf) + .open(&tf) .map_err(|e| ProcessWrapperError(format!("failed to create touch file: {}", e)))?; } if let Some((copy_source, copy_dest)) = opts.copy_output { @@ -228,7 +229,23 @@ fn main() -> Result<(), ProcessWrapperError> { } } - exit(code) + if let Some(ecf) = opts.exit_code_file { + let exit_code_file = OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .open(&ecf) + .map_err(|e| ProcessWrapperError(format!("failed to create exit code file: {}", e)))?; + let mut writer = io::LineWriter::new(exit_code_file); + writeln!(writer, "{}", code) + .map_err(|e| ProcessWrapperError(format!("failed to write exit code to file: {}", e)))?; + } + + if opts.forward_exit_code { + exit(code) + } else { + exit(0) + } } #[cfg(test)] diff --git a/util/process_wrapper/options.rs b/util/process_wrapper/options.rs index 2f252cadc7..b50fa9ff8b 100644 --- a/util/process_wrapper/options.rs +++ b/util/process_wrapper/options.rs @@ -44,6 +44,10 @@ 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, + // If set, exit with the same code as the child process. + pub(crate) forward_exit_code: bool, + // If set, writes the exit code of the process into this file. + pub(crate) exit_code_file: Option, // If set, it configures rustc to emit an rmeta file and then // quit. pub(crate) rustc_quit_on_rmeta: bool, @@ -64,6 +68,8 @@ pub(crate) fn options() -> Result { let mut stdout_file = None; let mut stderr_file = None; let mut output_file = None; + let mut forward_exit_code_raw = 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(); @@ -102,6 +108,16 @@ pub(crate) fn options() -> Result { "Log all unprocessed subprocess stderr in this file.", &mut output_file, ); + flags.define_flag( + "--forward-exit-code", + "If set, the process_wrapper will exit with the same exit code as the subprocess if it had no internal errors. True by default, disable with `--forward-exit-code=false`", + &mut forward_exit_code_raw, + ); + flags.define_flag( + "--exit-code-file", + "Log the exit code of the process to this file.", + &mut exit_code_file, + ); flags.define_flag( "--rustc-quit-on-rmeta", "If enabled, this wrapper will terminate rustc after rmeta has been emitted.", @@ -179,6 +195,8 @@ pub(crate) fn options() -> Result { }) .transpose()?; + // As we want `true` by default, we accept anything except `"false"` as `"true"` + let forward_exit_code = forward_exit_code_raw.is_some_and(|s| s != "false"); let rustc_quit_on_rmeta = rustc_quit_on_rmeta_raw.is_some_and(|s| s == "true"); let rustc_output_format = rustc_output_format_raw .map(|v| match v.as_str() { @@ -227,6 +245,8 @@ pub(crate) fn options() -> Result { stdout_file, stderr_file, output_file, + forward_exit_code, + exit_code_file, rustc_quit_on_rmeta, rustc_output_format, })