From 5ac406e65059a3485ce0d9468481738fc6ab3abe Mon Sep 17 00:00:00 2001 From: Nilstrieb <48135649+Nilstrieb@users.noreply.github.com> Date: Tue, 3 Jan 2023 20:38:15 +0100 Subject: [PATCH] Support `--script` properly... hopefully --- src/build.rs | 189 ++++++++++++++++++++++++++++++---------- src/lib.rs | 19 ++-- tests/always_success.sh | 3 + tests/minimize.rs | 26 ++++++ 4 files changed, 184 insertions(+), 53 deletions(-) create mode 100755 tests/always_success.sh diff --git a/src/build.rs b/src/build.rs index 2e5b554..439655f 100644 --- a/src/build.rs +++ b/src/build.rs @@ -36,6 +36,7 @@ impl Debug for Verify { #[derive(Debug)] struct BuildInner { mode: BuildMode, + lint_mode: BuildMode, input_path: PathBuf, verify: Verify, env: Vec, @@ -49,7 +50,6 @@ enum BuildMode { Cargo { /// May be something like `miri run`. subcommand: Vec, - diag_subcommand: Vec, }, Script(PathBuf), Rustc, @@ -73,15 +73,24 @@ impl Build { BuildMode::Script(script.clone()) } else { let subcommand = split_args(&options.cargo_subcmd); - let diag_subcommand = options - .diagnostics_cargo_subcmd + BuildMode::Cargo { subcommand } + }; + + let lint_mode = if options.rustc { + BuildMode::Rustc + } else if let Some(script) = options + .script_path_lints + .as_ref() + .or(options.script_path.as_ref()) + { + BuildMode::Script(script.clone()) + } else { + let subcommand = options + .cargo_subcmd_lints .as_deref() .map(split_args) - .unwrap_or_else(|| subcommand.clone()); - BuildMode::Cargo { - subcommand, - diag_subcommand, - } + .unwrap_or_else(|| split_args(&options.cargo_subcmd)); + BuildMode::Cargo { subcommand } }; let verify = if options.no_verify { @@ -95,6 +104,7 @@ impl Build { Ok(Self { inner: Rc::new(BuildInner { mode, + lint_mode, input_path: options.path.clone(), verify, env: options.env.clone(), @@ -126,10 +136,7 @@ impl Build { } let (is_ice, output) = match &inner.mode { - BuildMode::Cargo { - subcommand, - diag_subcommand: _, - } => { + BuildMode::Cargo { subcommand } => { let mut cmd = self.cmd("cargo"); cmd.args(subcommand); @@ -154,21 +161,6 @@ impl Build { output, ) } - BuildMode::Script(script_path) => { - let mut cmd = self.cmd(script_path); - - cmd.args(&inner.extra_args); - - for env in &inner.env { - cmd.env(&env.key, &env.value); - } - - let outputs = cmd.output().context("spawning script")?; - - let output = String::from_utf8(outputs.stderr)?; - - (outputs.status.success(), output) - } BuildMode::Rustc => { let mut cmd = self.cmd("rustc"); cmd.args(["--edition", "2021"]); @@ -194,6 +186,23 @@ impl Build { output, ) } + BuildMode::Script(script_path) => { + let mut cmd = self.cmd(script_path); + + cmd.args(&inner.extra_args); + + for env in &inner.env { + cmd.env(&env.key, &env.value); + } + + let outputs = cmd + .output() + .with_context(|| format!("spawning script: `{cmd:?}`"))?; + + let output = String::from_utf8(outputs.stderr)?; + + (outputs.status.success(), output) + } }; let reproduces_issue = match inner.verify { @@ -213,14 +222,30 @@ impl Build { pub fn get_diags(&self) -> Result<(Vec, Vec)> { let inner = &self.inner; - let diags = match &inner.mode { - BuildMode::Cargo { - subcommand: _, - diag_subcommand, - } => { + fn grab_cargo_diags(output: &str) -> Result> { + let messages = serde_json::Deserializer::from_str(output) + .into_iter::() + .collect::, _>>()?; + + Ok(messages + .into_iter() + .filter(|msg| msg.reason == "compiler-message") + .flat_map(|msg| msg.message) + .collect()) + } + + fn grab_rustc_diags(output: &str) -> Result> { + serde_json::Deserializer::from_str(&output) + .into_iter::() + .collect::>() + .map_err(Into::into) + } + + let diags = match &inner.lint_mode { + BuildMode::Cargo { subcommand } => { let mut cmd = self.cmd("cargo"); - cmd.args(diag_subcommand); + cmd.args(subcommand); cmd.arg("--message-format=json"); @@ -233,15 +258,7 @@ impl Build { let cmd_output = cmd.output()?; let output = String::from_utf8(cmd_output.stdout)?; - let messages = serde_json::Deserializer::from_str(&output) - .into_iter::() - .collect::, _>>()?; - - messages - .into_iter() - .filter(|msg| msg.reason == "compiler-message") - .flat_map(|msg| msg.message) - .collect() + grab_cargo_diags(&output)? } BuildMode::Rustc => { let mut cmd = self.cmd("rustc"); @@ -255,13 +272,31 @@ impl Build { let output = cmd.output()?.stderr; let output = String::from_utf8(output)?; - let diags = serde_json::Deserializer::from_str(&output) - .into_iter::() - .collect::>()?; - - diags + grab_rustc_diags(&output)? + } + BuildMode::Script(script_path) => { + let mut cmd = self.cmd(script_path); + + cmd.args(&inner.extra_args); + + for env in &inner.env { + cmd.env(&env.key, &env.value); + } + + let outputs = cmd + .output() + .with_context(|| format!("spawning script: `{cmd:?}`"))?; + + let stderr = String::from_utf8(outputs.stderr)?; + let stdout = String::from_utf8(outputs.stdout)?; + + let (output, mode) = read_script_output(&stdout, &stderr); + + match mode { + LintMode::Rustc => grab_rustc_diags(output)?, + LintMode::Cargo => grab_cargo_diags(output)?, + } } - BuildMode::Script(_) => todo!(), }; let mut suggestions = Vec::new(); @@ -329,3 +364,61 @@ pub struct CargoJsonCompileMessage { fn split_args(s: &str) -> Vec { s.split_whitespace().map(ToString::to_string).collect() } + +#[derive(Debug, PartialEq, Eq)] +enum LintMode { + Rustc, + Cargo, +} + +fn read_script_output<'a>(stdout: &'a str, stderr: &'a str) -> (&'a str, LintMode) { + let is_marked_output = |output: &str| { + let first_line = output.lines().next(); + match first_line { + None => None, + Some(line) if line.contains("minimize-fmt-cargo") => Some(LintMode::Cargo), + Some(line) if line.contains("minimize-fmt-rustc") => Some(LintMode::Rustc), + Some(_) => None, + } + }; + + is_marked_output(stdout) + .map(|mode| (stdout, mode)) + .or(is_marked_output(stderr).map(|mode| (stderr, mode))) + .unwrap_or_else(|| (stdout, LintMode::Cargo)) +} + +#[cfg(test)] +mod tests { + use crate::build::LintMode; + + use super::read_script_output; + + #[test] + fn script_output_default() { + let (output, mode) = read_script_output("uwu", "owo"); + assert_eq!(output, "uwu"); + assert_eq!(mode, LintMode::Cargo); + } + + #[test] + fn script_output_rustc_stderr() { + let (output, mode) = read_script_output("wrong", "minimize-fmt-rustc"); + assert_eq!(output, "minimize-fmt-rustc"); + assert_eq!(mode, LintMode::Rustc); + } + + #[test] + fn script_output_cargo_stderr() { + let (output, mode) = read_script_output("wrong", "minimize-fmt-cargo"); + assert_eq!(output, "minimize-fmt-cargo"); + assert_eq!(mode, LintMode::Cargo); + } + + #[test] + fn script_output_rustc_stdout() { + let (output, mode) = read_script_output("minimize-fmt-rustc", "wrong"); + assert_eq!(output, "minimize-fmt-rustc"); + assert_eq!(mode, LintMode::Rustc); + } +} diff --git a/src/lib.rs b/src/lib.rs index 7920958..94e64f8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,7 +42,7 @@ pub struct Options { /// The cargo subcommand used to get diagnostics like the dead_code lint from the compiler, seperated by whitespace. /// Defaults to the value of `--cargo-subcmd`. #[arg(long)] - pub diagnostics_cargo_subcmd: Option, + pub cargo_subcmd_lints: Option, /// To disable colored output. #[arg(long)] @@ -75,11 +75,19 @@ pub struct Options { #[arg(default_value = "src")] pub path: PathBuf, - /// NOTE: This is currently broken. /// A path to a script that is run to check whether code reproduces. When it exits with code 0, the - /// problem reproduces. + /// problem reproduces. If `--script-path-lints` isn't set, this script is also run to get lints. + /// For lints, the `MINIMIZE_LINTS` environment variable will be set to `1`. + /// The first line of the lint stdout or stderr can be `minimize-fmt-rustc` or `minimize-fmt-cargo` to show whether the rustc or wrapper cargo + /// lint format and which output stream is used. Defaults to cargo and stdout. #[arg(long)] pub script_path: Option, + + /// A path to a script that is run to get lints. + /// The first line of stdout or stderr must be `minimize-fmt-rustc` or `minimize-fmt-cargo` to show whether the rustc or wrapper cargo + /// lint format and which output stream is used. Defaults to cargo and stdout. + #[arg(long)] + pub script_path_lints: Option, } #[derive(Debug, Clone)] @@ -137,10 +145,9 @@ pub fn init_recommended_tracing_subscriber(default_level: Level) { impl Default for Options { fn default() -> Self { Self { - script_path: None, extra_args: None, cargo_subcmd: "build".into(), - diagnostics_cargo_subcmd: None, + cargo_subcmd_lints: None, no_color: false, rustc: false, no_verify: false, @@ -148,6 +155,8 @@ impl Default for Options { env: Vec::new(), project_dir: None, path: PathBuf::from("/the/wrong/path/you/need/to/change/it"), + script_path: None, + script_path_lints: None, } } } diff --git a/tests/always_success.sh b/tests/always_success.sh new file mode 100755 index 0000000..885970b --- /dev/null +++ b/tests/always_success.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +exit 0 # we reproduce! aren't we great?! \ No newline at end of file diff --git a/tests/minimize.rs b/tests/minimize.rs index 8b4d032..c7f3774 100644 --- a/tests/minimize.rs +++ b/tests/minimize.rs @@ -1,5 +1,7 @@ mod helper; +use std::path::Path; + use anyhow::Result; use helper::run_test; @@ -44,3 +46,27 @@ fn unused() -> Result<()> { }, ) } + +#[test] +#[cfg_attr(windows, ignore)] +fn custom_script_success() -> Result<()> { + let script_path = Path::new(file!()) + .parent() + .unwrap() + .join("always_success.sh") + .canonicalize()?; + + run_test( + r##" + fn main() {} + "##, + r##" + fn main() { + loop {} + } + "##, + |opts| { + opts.script_path = Some(script_path); + }, + ) +}