From 35f882918cdb6c2b31639677556997cd62fd1a4c Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Tue, 21 May 2024 16:43:22 +0000 Subject: [PATCH] Use `Errored` in dependency building to get more details --- src/dependencies.rs | 143 ++++++++++++++++------- tests/integrations/dep-fail/Cargo.stdout | 15 +-- 2 files changed, 107 insertions(+), 51 deletions(-) diff --git a/src/dependencies.rs b/src/dependencies.rs index 6b3e3035..a8a7f125 100644 --- a/src/dependencies.rs +++ b/src/dependencies.rs @@ -1,8 +1,8 @@ //! Use `cargo` to build dependencies and make them available in your tests +use bstr::ByteSlice; use cargo_metadata::{camino::Utf8PathBuf, BuildScript, DependencyKind}; use cargo_platform::Cfg; -use color_eyre::eyre::{bail, eyre, Result}; use std::{ collections::{HashMap, HashSet}, ffi::OsString, @@ -31,33 +31,58 @@ pub struct Dependencies { pub dependencies: Vec<(String, Vec)>, } -fn cfgs(config: &Config) -> Result> { +fn cfgs(config: &Config) -> Result, Errored> { let Some(cfg) = &config.program.cfg_flag else { return Ok(vec![]); }; let mut cmd = config.program.build(&config.out_dir); cmd.arg(cfg); cmd.arg("--target").arg(config.target.as_ref().unwrap()); - let output = cmd.output()?; - let stdout = String::from_utf8(output.stdout)?; + let output = match cmd.output() { + Ok(o) => o, + Err(e) => { + return Err(Errored { + command: cmd, + stderr: e.to_string().into_bytes(), + stdout: vec![], + errors: vec![], + }) + } + }; if !output.status.success() { - let stderr = String::from_utf8(output.stderr)?; - bail!( - "failed to obtain `cfg` information from {cmd:?}:\nstderr:\n{stderr}\n\nstdout:{stdout}" - ); + return Err(Errored { + command: cmd, + stderr: output.stderr, + stdout: output.stdout, + errors: vec![], + }); } let mut cfgs = vec![]; + let stdout = String::from_utf8(output.stdout).map_err(|e| Errored { + command: Command::new("processing cfg information from rustc as utf8"), + errors: vec![], + stderr: e.to_string().into_bytes(), + stdout: vec![], + })?; for line in stdout.lines() { - cfgs.push(Cfg::from_str(line)?); + cfgs.push(Cfg::from_str(line).map_err(|e| Errored { + command: Command::new("parsing cfgs from rustc output"), + errors: vec![], + stderr: e.to_string().into_bytes(), + stdout: vec![], + })?); } Ok(cfgs) } /// Compiles dependencies and returns the crate names and corresponding rmeta files. -fn build_dependencies_inner(config: &Config, info: &DependencyBuilder) -> Result { +fn build_dependencies_inner( + config: &Config, + info: &DependencyBuilder, +) -> Result { let mut build = info.program.build(&config.out_dir); build.arg(&info.crate_manifest_path); @@ -76,22 +101,34 @@ fn build_dependencies_inner(config: &Config, info: &DependencyBuilder) -> Result set_locking(&mut build); build.arg("--message-format=json"); - let output = build.output()?; + let output = match build.output() { + Err(e) => { + return Err(Errored { + command: build, + stderr: e.to_string().into_bytes(), + stdout: vec![], + errors: vec![], + }) + } + Ok(o) => o, + }; if !output.status.success() { - let stdout = String::from_utf8(output.stdout)?; - let stderr = String::from_utf8(output.stderr)?; - bail!("failed to compile dependencies:\ncommand: {build:?}\nstderr:\n{stderr}\n\nstdout:{stdout}"); + return Err(Errored { + command: build, + stderr: output.stderr, + stdout: output.stdout, + errors: vec![], + }); } // Collect all artifacts generated let artifact_output = output.stdout; - let artifact_output = String::from_utf8(artifact_output)?; let mut import_paths: HashSet = HashSet::new(); let mut import_libs: HashSet = HashSet::new(); let mut artifacts = HashMap::new(); for line in artifact_output.lines() { - let Ok(message) = serde_json::from_str::(line) else { + let Ok(message) = serde_json::from_slice::(line) else { continue; }; match message { @@ -137,24 +174,42 @@ fn build_dependencies_inner(config: &Config, info: &DependencyBuilder) -> Result .arg(&info.crate_manifest_path); info.program.apply_env(&mut metadata); set_locking(&mut metadata); - let output = metadata.output()?; + let output = match metadata.output() { + Err(e) => { + return Err(Errored { + command: metadata, + errors: vec![], + stderr: e.to_string().into_bytes(), + stdout: vec![], + }) + } + Ok(output) => output, + }; if !output.status.success() { - let stdout = String::from_utf8(output.stdout)?; - let stderr = String::from_utf8(output.stderr)?; - bail!("failed to run cargo-metadata:\nstderr:\n{stderr}\n\nstdout:{stdout}"); + return Err(Errored { + command: metadata, + stderr: output.stderr, + stdout: output.stdout, + errors: vec![], + }); } let output = output.stdout; - let output = String::from_utf8(output)?; let cfg = cfgs(config)?; for line in output.lines() { - if !line.starts_with('{') { + if !line.starts_with(b"{") { continue; } - let metadata: cargo_metadata::Metadata = serde_json::from_str(line)?; + let metadata: cargo_metadata::Metadata = + serde_json::from_slice(line).map_err(|err| Errored { + command: Command::new("decoding cargo metadata json"), + errors: vec![], + stderr: err.to_string().into_bytes(), + stdout: vec![], + })?; // Only take artifacts that are defined in the Cargo.toml // First, find the root artifact @@ -182,11 +237,13 @@ fn build_dependencies_inner(config: &Config, info: &DependencyBuilder) -> Result if p.name != dep.name { continue; } - if dep.path.as_ref().is_some_and(|path| p.manifest_path.parent().unwrap() == path) || dep.req.matches(&p.version) { - return ( - p, - dep.rename.clone().unwrap_or_else(|| p.name.clone()), - ) + if dep + .path + .as_ref() + .is_some_and(|path| p.manifest_path.parent().unwrap() == path) + || dep.req.matches(&p.version) + { + return (p, dep.rename.clone().unwrap_or_else(|| p.name.clone())); } } panic!("dep not found: {dep:#?}") @@ -199,7 +256,12 @@ fn build_dependencies_inner(config: &Config, info: &DependencyBuilder) -> Result // Return the name chosen in `Cargo.toml` and the path to the corresponding artifact match artifacts.remove(id) { Some(Ok(artifacts)) => Some(Ok((name.replace('-', "_"), artifacts))), - Some(Err(what)) => Some(Err(eyre!("`ui_test` does not support crates that appear as both build-dependencies and core dependencies: {id}: {what}"))), + Some(Err(what)) => Some(Err(Errored { + command: Command::new(what), + errors: vec![], + stderr: id.to_string().into_bytes(), + stdout: "`ui_test` does not support crates that appear as both build-dependencies and core dependencies".as_bytes().into(), + })), None => { if name == root.name { // If there are no artifacts, this is the root crate and it is being built as a binary/test @@ -207,12 +269,12 @@ fn build_dependencies_inner(config: &Config, info: &DependencyBuilder) -> Result // and types declared in the root crate. None } else { - panic!("no artifact found for `{name}`(`{id}`):`\n{artifact_output}") + panic!("no artifact found for `{name}`(`{id}`):`\n{}", artifact_output.to_str().unwrap()) } } } }) - .collect::>>()?; + .collect::, Errored>>()?; let import_paths = import_paths.into_iter().collect(); let import_libs = import_libs.into_iter().collect(); return Ok(Dependencies { @@ -222,7 +284,12 @@ fn build_dependencies_inner(config: &Config, info: &DependencyBuilder) -> Result }); } - bail!("no json found in cargo-metadata output") + Err(Errored { + command: Command::new("looking for json in cargo-metadata output"), + errors: vec![], + stderr: vec![], + stdout: vec![], + }) } /// Build the dependencies. @@ -269,12 +336,7 @@ impl Flag for DependencyBuilder { impl Build for DependencyBuilder { fn build(&self, build_manager: &BuildManager<'_>) -> Result, Errored> { - build_dependencies(build_manager.config(), self).map_err(|e| Errored { - command: Command::new(format!("{:?}", self.description())), - errors: vec![], - stderr: format!("{e:?}").into_bytes(), - stdout: vec![], - }) + build_dependencies(build_manager.config(), self) } fn description(&self) -> String { @@ -284,7 +346,10 @@ impl Build for DependencyBuilder { /// Compile dependencies and return the right flags /// to find the dependencies. -pub fn build_dependencies(config: &Config, info: &DependencyBuilder) -> Result> { +pub fn build_dependencies( + config: &Config, + info: &DependencyBuilder, +) -> Result, Errored> { let dependencies = build_dependencies_inner(config, info)?; let mut args = vec![]; for (name, artifacts) in dependencies.dependencies { diff --git a/tests/integrations/dep-fail/Cargo.stdout b/tests/integrations/dep-fail/Cargo.stdout index a619b05e..03cdc7de 100644 --- a/tests/integrations/dep-fail/Cargo.stdout +++ b/tests/integrations/dep-fail/Cargo.stdout @@ -7,27 +7,18 @@ Building dependencies ... FAILED tests/ui/basic_test.rs ... FAILED FAILED TEST: tests/ui/basic_test.rs -command: "\"Building dependencies\"" +command: "$CMD" "build" "--target-dir" "$DIR/tests/integrations/dep-fail/target/ui" "--manifest-path" "tested_crate/Cargo.toml" "--target=x86_64-unknown-linux-gnu" "--message-format=json" full stderr: -failed to compile dependencies: -command: "$CMD" "build" "--target-dir" "$DIR/tests/integrations/dep-fail/target/ui" "--manifest-path" "tested_crate/Cargo.toml" "--target=x86_64-unknown-linux-gnu" "--message-format=json" -stderr: - Blocking waiting for file lock on package cache Compiling tested_crate v0.1.0-dev ($DIR/tests/integrations/dep-fail/tested_crate) error: could not compile `tested_crate` (lib) due to 2 previous errors - -stdout:{"reason":"compiler-message","package_id":"path+file://$DIR/tests/integrations/dep-fail/tested_crate#0.1.0-dev","manifest_path":"$DIR/tests/integrations/dep-fail/tested_crate/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"tested_crate","src_path":"$DIR/tests/integrations/dep-fail/tested_crate/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"message":{"rendered":"error: this file contains an unclosed delimiter\n --> src/lib.rs:LL:CC\n |\n1 | pub trait A {\n | - ^\n | |\n | unclosed delimiter\n\n","$message_type":"diagnostic","children":[],"code":null,"level":"error","message":"this file contains an unclosed delimiter","spans":[{"byte_end":13,"byte_start":12,"column_end":14,"column_start":13,"expansion":null,"file_name":"src/lib.rs","is_primary":false,"label":"unclosed delimiter","line_end":1,"line_start":1,"suggested_replacement":null,"suggestion_applicability":null,"text":[{"highlight_end":14,"highlight_start":13,"text":"pub trait A {"}]},{"byte_end":14,"byte_start":14,"column_end":15,"column_start":15,"expansion":null,"file_name":"src/lib.rs","is_primary":true,"label":null,"line_end":1,"line_start":1,"suggested_replacement":null,"suggestion_applicability":null,"text":[{"highlight_end":15,"highlight_start":15,"text":"pub trait A {"}]}]}} +full stdout: +{"reason":"compiler-message","package_id":"path+file://$DIR/tests/integrations/dep-fail/tested_crate#0.1.0-dev","manifest_path":"$DIR/tests/integrations/dep-fail/tested_crate/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"tested_crate","src_path":"$DIR/tests/integrations/dep-fail/tested_crate/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"message":{"rendered":"error: this file contains an unclosed delimiter\n --> src/lib.rs:LL:CC\n |\n1 | pub trait A {\n | - ^\n | |\n | unclosed delimiter\n\n","$message_type":"diagnostic","children":[],"code":null,"level":"error","message":"this file contains an unclosed delimiter","spans":[{"byte_end":13,"byte_start":12,"column_end":14,"column_start":13,"expansion":null,"file_name":"src/lib.rs","is_primary":false,"label":"unclosed delimiter","line_end":1,"line_start":1,"suggested_replacement":null,"suggestion_applicability":null,"text":[{"highlight_end":14,"highlight_start":13,"text":"pub trait A {"}]},{"byte_end":14,"byte_start":14,"column_end":15,"column_start":15,"expansion":null,"file_name":"src/lib.rs","is_primary":true,"label":null,"line_end":1,"line_start":1,"suggested_replacement":null,"suggestion_applicability":null,"text":[{"highlight_end":15,"highlight_start":15,"text":"pub trait A {"}]}]}} {"reason":"compiler-message","package_id":"path+file://$DIR/tests/integrations/dep-fail/tested_crate#0.1.0-dev","manifest_path":"$DIR/tests/integrations/dep-fail/tested_crate/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"tested_crate","src_path":"$DIR/tests/integrations/dep-fail/tested_crate/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"message":{"rendered":"error: aborting due to 1 previous error\n\n","$message_type":"diagnostic","children":[],"code":null,"level":"error","message":"aborting due to 1 previous error","spans":[]}} {"reason":"build-finished","success":false} -Location: - $DIR/src/dependencies.rs:LL:CC -full stdout: - - FAILURES: tests/ui/basic_test.rs