diff --git a/CHANGELOG.md b/CHANGELOG.md index c63acd7..c567f58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +## [0.18.0] - 2024-10-13 + +- **BREAKING** Extend `CommandError::ExecutionFailed` and `PrepareError` to optionally include stderr + ## [0.17.0] - 2024-06-26 ### Added diff --git a/Cargo.toml b/Cargo.toml index a0ea64c..3a5f29a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustwide" -version = "0.17.0" +version = "0.18.0" edition = "2018" build = "build.rs" diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index 95dd4ad..a4512b8 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -71,8 +71,13 @@ pub enum CommandError { Timeout(u64), /// The command failed to execute. - #[error("command failed: {0}")] - ExecutionFailed(ExitStatus), + #[error("command failed: {status}\n\n{stderr}")] + ExecutionFailed { + /// the exit status we got from the command + status: ExitStatus, + /// the stderr output, if it was captured via `.run_capture()` + stderr: String, + }, /// Killing the underlying process after the timeout failed. #[error("{0}")] @@ -496,7 +501,10 @@ impl<'w, 'pl> Command<'w, 'pl> { if out.status.success() { Ok(out.into()) } else { - Err(CommandError::ExecutionFailed(out.status)) + Err(CommandError::ExecutionFailed { + status: out.status, + stderr: out.stderr.join("\n"), + }) } } } diff --git a/src/cmd/sandbox.rs b/src/cmd/sandbox.rs index 1a4ad2c..1d940f8 100644 --- a/src/cmd/sandbox.rs +++ b/src/cmd/sandbox.rs @@ -393,7 +393,7 @@ impl Container<'_> { // Return a different error if the container was killed due to an OOM if details.state.oom_killed { Err(match res { - Ok(_) | Err(CommandError::ExecutionFailed(_)) => CommandError::SandboxOOM, + Ok(_) | Err(CommandError::ExecutionFailed { .. }) => CommandError::SandboxOOM, Err(err) => err, }) } else { diff --git a/src/prepare.rs b/src/prepare.rs index 0a7a081..8239794 100644 --- a/src/prepare.rs +++ b/src/prepare.rs @@ -1,4 +1,4 @@ -use crate::cmd::Command; +use crate::cmd::{Command, CommandError}; use crate::{build::CratePatch, Crate, Toolchain, Workspace}; use anyhow::Context as _; use log::info; @@ -113,7 +113,8 @@ impl<'a> Prepare<'a> { .args(&["-Zno-index-update"]) .env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly"); } - let res = cmd + + match cmd .cd(self.source_dir) .process_lines(&mut |line, _| { if line.contains("failed to select a version for the requirement") { @@ -124,17 +125,17 @@ impl<'a> Prepare<'a> { missing_deps = true; } }) - .run(); - match res { - Err(_) if yanked_deps => { - return Err(PrepareError::YankedDependencies.into()); + .run_capture() + { + Ok(_) => Ok(()), + Err(CommandError::ExecutionFailed { status: _, stderr }) if yanked_deps => { + Err(PrepareError::YankedDependencies(stderr).into()) } - Err(_) if missing_deps => { - return Err(PrepareError::MissingDependencies.into()); + Err(CommandError::ExecutionFailed { status: _, stderr }) if missing_deps => { + Err(PrepareError::MissingDependencies(stderr).into()) } - other => other?, + Err(err) => Err(err.into()), } - Ok(()) } fn fetch_deps(&mut self) -> anyhow::Result<()> { @@ -161,17 +162,20 @@ pub(crate) fn fetch_deps( for target in fetch_build_std_targets { cmd = cmd.args(&["--target", target]); } - let res = cmd + + match cmd .process_lines(&mut |line, _| { if line.contains("failed to load source for dependency") { missing_deps = true; } }) - .run(); - match res { + .run_capture() + { Ok(_) => Ok(()), - Err(_) if missing_deps => Err(PrepareError::MissingDependencies.into()), - err => err.map_err(|e| e.into()), + Err(CommandError::ExecutionFailed { status: _, stderr }) if missing_deps => { + Err(PrepareError::MissingDependencies(stderr).into()) + } + Err(err) => Err(err.into()), } } @@ -381,11 +385,11 @@ pub enum PrepareError { #[error("invalid Cargo.toml syntax")] InvalidCargoTomlSyntax, /// Some of this crate's dependencies were yanked, preventing Crater from fetching them. - #[error("the crate depends on yanked dependencies")] - YankedDependencies, + #[error("the crate depends on yanked dependencies: \n\n{0}")] + YankedDependencies(String), /// Some of the dependencies do not exist anymore. - #[error("the crate depends on missing dependencies")] - MissingDependencies, + #[error("the crate depends on missing dependencies: \n\n{0}")] + MissingDependencies(String), } #[cfg(test)] diff --git a/tests/buildtest/mod.rs b/tests/buildtest/mod.rs index e313bb4..594df51 100644 --- a/tests/buildtest/mod.rs +++ b/tests/buildtest/mod.rs @@ -188,22 +188,30 @@ test_prepare_error!( InvalidCargoTomlSyntax ); -test_prepare_error!(test_yanked_deps, "yanked-deps", YankedDependencies); +test_prepare_error_stderr!( + test_yanked_deps, + "yanked-deps", + YankedDependencies, + r#"failed to select a version for the requirement `ring = "^0.2"`"# +); -test_prepare_error!( +test_prepare_error_stderr!( test_missing_deps_git, "missing-deps-git", - MissingDependencies + MissingDependencies, + "failed to get `not-a-git-repo` as a dependency of package `missing-deps v0.1.0" ); -test_prepare_error!( +test_prepare_error_stderr!( test_missing_deps_git_locked, "missing-deps-git-locked", - MissingDependencies + MissingDependencies, + "failed to get `not-a-git-repo` as a dependency of package `missing-deps-git-locked v0.1.0" ); -test_prepare_error!( +test_prepare_error_stderr!( test_missing_deps_registry, "missing-deps-registry", - MissingDependencies + MissingDependencies, + "error: no matching package named `macro` found" ); diff --git a/tests/buildtest/runner.rs b/tests/buildtest/runner.rs index 9bc6aff..a849d16 100644 --- a/tests/buildtest/runner.rs +++ b/tests/buildtest/runner.rs @@ -84,3 +84,25 @@ macro_rules! test_prepare_error { } }; } + +macro_rules! test_prepare_error_stderr { + ($name:ident, $krate:expr, $expected:ident, $expected_output:expr) => { + #[test] + fn $name() { + runner::run($krate, |run| { + let res = run.run( + rustwide::cmd::SandboxBuilder::new().enable_networking(false), + |_| Ok(()), + ); + if let Some(rustwide::PrepareError::$expected(output)) = + res.err().and_then(|err| err.downcast().ok()) + { + assert!(output.contains($expected_output), "output: {:?}", output); + } else { + panic!("didn't get the error {}", stringify!($expected)); + } + Ok(()) + }); + } + }; +}