From ac0f3016cb79917fc5a26271ccbf0e2bf13a073d Mon Sep 17 00:00:00 2001 From: Borja Lorente Date: Fri, 12 Dec 2025 17:38:31 +0000 Subject: [PATCH 1/3] feat: Allow process_wrapper to capture exit codes instead of forwarding them --- rust/private/clippy.bzl | 11 ++++++++++- util/process_wrapper/main.rs | 21 +++++++++++++++++++-- util/process_wrapper/options.rs | 20 ++++++++++++++++++++ 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/rust/private/clippy.bzl b/rust/private/clippy.bzl index 318c05af8d..a9b50b50b7 100644 --- a/rust/private/clippy.bzl +++ b/rust/private/clippy.bzl @@ -101,7 +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): +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: @@ -112,6 +112,8 @@ 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 @@ -200,6 +202,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: 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, }) From 8fb6ffd40e582d5c8f83c5f83ab7f4387b5ef60e Mon Sep 17 00:00:00 2001 From: Borja Lorente Date: Mon, 15 Dec 2025 15:13:06 +0000 Subject: [PATCH 2/3] feat: Expose macro to create but not run the clippy action # Conflicts: # rust/private/clippy.bzl --- rust/defs.bzl | 2 ++ rust/private/clippy.bzl | 51 +++++++++++++++++++++++++++++++++++------ 2 files changed, 46 insertions(+), 7 deletions(-) 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 a9b50b50b7..0518f3e701 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, 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: +_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 @@ -118,10 +115,16 @@ def rust_clippy_action(ctx, clippy_executable, process_wrapper, crate_info, conf 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) toolchain = find_toolchain(ctx) cc_toolchain, feature_configuration = find_cc_toolchain(ctx) @@ -232,7 +235,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], @@ -244,6 +247,40 @@ 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, 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) + clippy_action = rust_create_clippy_action( + ctx, + clippy_executable, + process_wrapper, + crate_info, + config, + output, + success_marker, + cap_at_warnings, + extra_clippy_flags, + error_format, + 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. From 5d54226d48dfa38635b5cd5ca79c2000d00b2054 Mon Sep 17 00:00:00 2001 From: Borja Lorente Date: Mon, 15 Dec 2025 15:21:01 +0000 Subject: [PATCH 3/3] WIP --- rust/private/clippy.bzl | 58 ++++++++++++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/rust/private/clippy.bzl b/rust/private/clippy.bzl index 0518f3e701..119b15266f 100644 --- a/rust/private/clippy.bzl +++ b/rust/private/clippy.bzl @@ -117,7 +117,20 @@ _RUST_CLIPPY_ACTION_MACRO_ARGS = """ 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): +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} @@ -125,6 +138,7 @@ def rust_create_clippy_action(ctx, clippy_executable, process_wrapper, crate_inf Returns: 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) @@ -191,6 +205,8 @@ def rust_create_clippy_action(ctx, clippy_executable, process_wrapper, crate_inf 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 @@ -247,7 +263,20 @@ def rust_create_clippy_action(ctx, clippy_executable, process_wrapper, crate_inf toolchain = "@rules_rust//rust:toolchain_type", ) -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): +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} @@ -255,18 +284,21 @@ def rust_clippy_action(ctx, clippy_executable, process_wrapper, crate_info, conf Returns: None """.format(args = _RUST_CLIPPY_ACTION_MACRO_ARGS) + print("BL: flags ={}".format(extra_clippy_flags)) clippy_action = rust_create_clippy_action( - ctx, - clippy_executable, - process_wrapper, - crate_info, - config, - output, - success_marker, - cap_at_warnings, - extra_clippy_flags, - error_format, - clippy_diagnostics_file, + 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(