Skip to content

Commit

Permalink
Use Errored in dependency building to get more details
Browse files Browse the repository at this point in the history
  • Loading branch information
oli-obk committed May 21, 2024
1 parent 049f060 commit 35f8829
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 51 deletions.
143 changes: 104 additions & 39 deletions src/dependencies.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -31,33 +31,58 @@ pub struct Dependencies {
pub dependencies: Vec<(String, Vec<Utf8PathBuf>)>,
}

fn cfgs(config: &Config) -> Result<Vec<Cfg>> {
fn cfgs(config: &Config) -> Result<Vec<Cfg>, 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<Dependencies> {
fn build_dependencies_inner(
config: &Config,
info: &DependencyBuilder,
) -> Result<Dependencies, Errored> {
let mut build = info.program.build(&config.out_dir);
build.arg(&info.crate_manifest_path);

Expand All @@ -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<PathBuf> = HashSet::new();
let mut import_libs: HashSet<PathBuf> = HashSet::new();
let mut artifacts = HashMap::new();
for line in artifact_output.lines() {
let Ok(message) = serde_json::from_str::<cargo_metadata::Message>(line) else {
let Ok(message) = serde_json::from_slice::<cargo_metadata::Message>(line) else {
continue;
};
match message {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:#?}")
Expand All @@ -199,20 +256,25 @@ 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
// instead of a library. We simply add no artifacts, meaning you can't depend on functions
// 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::<Result<Vec<_>>>()?;
.collect::<Result<Vec<_>, Errored>>()?;
let import_paths = import_paths.into_iter().collect();
let import_libs = import_libs.into_iter().collect();
return Ok(Dependencies {
Expand All @@ -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.
Expand Down Expand Up @@ -269,12 +336,7 @@ impl Flag for DependencyBuilder {

impl Build for DependencyBuilder {
fn build(&self, build_manager: &BuildManager<'_>) -> Result<Vec<OsString>, 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 {
Expand All @@ -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<Vec<OsString>> {
pub fn build_dependencies(
config: &Config,
info: &DependencyBuilder,
) -> Result<Vec<OsString>, Errored> {
let dependencies = build_dependencies_inner(config, info)?;
let mut args = vec![];
for (name, artifacts) in dependencies.dependencies {
Expand Down
15 changes: 3 additions & 12 deletions tests/integrations/dep-fail/Cargo.stdout
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down

0 comments on commit 35f8829

Please sign in to comment.