Skip to content

Commit 8ffc960

Browse files
committed
Auto merge of #6432 - ehuss:fix-metabuild-json, r=alexcrichton
Fix metabuild compile errors with --message-format=json. If an error occurs while compiling a metabuild target with `--message-format=json`, it would panic because it was unable to serialize `Target`. This change makes it so that it places a fake "metabuild.rs" string in the `src_path` in this situation. I'm very unhappy with this solution, but I'm unable to think of something better. Changing `src_path` to an `Option` (or something) would break existing tools (this might break, but maybe not catastrophically?). I tried implementing something that resets the `src_path` to the correct path in the target dir after the workspace is configured, but it felt very brittle – you have to fix up after all dependencies are downloaded, and there's not a good place to ensure that happens correctly. Any alternate ideas? This adds a `with_json_contains_unordered` to help with tests.
2 parents c684d02 + 48d56a4 commit 8ffc960

File tree

5 files changed

+136
-11
lines changed

5 files changed

+136
-11
lines changed

src/cargo/core/compiler/mod.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -689,10 +689,9 @@ fn rustdoc<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoResult
689689
// second is the cwd that rustc should operate in.
690690
fn path_args(bcx: &BuildContext<'_, '_>, unit: &Unit<'_>) -> (PathBuf, PathBuf) {
691691
let ws_root = bcx.ws.root();
692-
let src = if unit.target.is_custom_build() && unit.pkg.manifest().metabuild().is_some() {
693-
unit.pkg.manifest().metabuild_path(bcx.ws.target_dir())
694-
} else {
695-
unit.target.src_path().path().to_path_buf()
692+
let src = match unit.target.src_path() {
693+
TargetSourcePath::Path(path) => path.to_path_buf(),
694+
TargetSourcePath::Metabuild => unit.pkg.manifest().metabuild_path(bcx.ws.target_dir()),
696695
};
697696
assert!(src.is_absolute());
698697
if unit.pkg.package_id().source_id().is_path() {

src/cargo/core/manifest.rs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -222,10 +222,10 @@ pub enum TargetSourcePath {
222222
}
223223

224224
impl TargetSourcePath {
225-
pub fn path(&self) -> &Path {
225+
pub fn path(&self) -> Option<&Path> {
226226
match self {
227-
TargetSourcePath::Path(path) => path.as_ref(),
228-
TargetSourcePath::Metabuild => panic!("metabuild not expected"),
227+
TargetSourcePath::Path(path) => Some(path.as_ref()),
228+
TargetSourcePath::Metabuild => None,
229229
}
230230
}
231231

@@ -268,19 +268,25 @@ struct SerializedTarget<'a> {
268268
/// See https://doc.rust-lang.org/reference/linkage.html
269269
crate_types: Vec<&'a str>,
270270
name: &'a str,
271-
src_path: &'a PathBuf,
271+
src_path: Option<&'a PathBuf>,
272272
edition: &'a str,
273273
#[serde(rename = "required-features", skip_serializing_if = "Option::is_none")]
274274
required_features: Option<Vec<&'a str>>,
275275
}
276276

277277
impl ser::Serialize for Target {
278278
fn serialize<S: ser::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
279+
let src_path = match &self.src_path {
280+
TargetSourcePath::Path(p) => Some(p),
281+
// Unfortunately getting the correct path would require access to
282+
// target_dir, which is not available here.
283+
TargetSourcePath::Metabuild => None,
284+
};
279285
SerializedTarget {
280286
kind: &self.kind,
281287
crate_types: self.rustc_crate_types(),
282288
name: &self.name,
283-
src_path: &self.src_path.path().to_path_buf(),
289+
src_path: src_path,
284290
edition: &self.edition.to_string(),
285291
required_features: self
286292
.required_features
@@ -301,7 +307,7 @@ compact_debug! {
301307
Target::lib_target(
302308
&self.name,
303309
kinds.clone(),
304-
self.src_path().path().to_path_buf(),
310+
self.src_path().path().unwrap().to_path_buf(),
305311
self.edition,
306312
),
307313
format!("lib_target({:?}, {:?}, {:?}, {:?})",

src/cargo/ops/cargo_test.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ fn run_doc_tests(
148148
config.shell().status("Doc-tests", target.name())?;
149149
let mut p = compilation.rustdoc_process(package, target)?;
150150
p.arg("--test")
151-
.arg(target.src_path().path())
151+
.arg(target.src_path().path().unwrap())
152152
.arg("--crate-name")
153153
.arg(&target.crate_name());
154154

tests/testsuite/metabuild.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,5 +699,78 @@ fn metabuild_json_artifact() {
699699
let p = basic_project();
700700
p.cargo("build --message-format=json")
701701
.masquerade_as_nightly_cargo()
702+
.with_json_contains_unordered(
703+
r#"
704+
{
705+
"executable": null,
706+
"features": [],
707+
"filenames": [
708+
"[..]/foo/target/debug/build/foo-[..]/metabuild-foo[EXE]"
709+
],
710+
"fresh": false,
711+
"package_id": "foo [..]",
712+
"profile": "{...}",
713+
"reason": "compiler-artifact",
714+
"target": {
715+
"crate_types": [
716+
"bin"
717+
],
718+
"edition": "2018",
719+
"kind": [
720+
"custom-build"
721+
],
722+
"name": "metabuild-foo",
723+
"src_path": "[..]/foo/target/.metabuild/metabuild-foo-[..].rs"
724+
}
725+
}
726+
727+
{
728+
"cfgs": [],
729+
"env": [],
730+
"linked_libs": [],
731+
"linked_paths": [],
732+
"package_id": "foo [..]",
733+
"reason": "build-script-executed"
734+
}
735+
"#,
736+
)
737+
.run();
738+
}
739+
740+
#[test]
741+
fn metabuild_failed_build_json() {
742+
let p = basic_project();
743+
// Modify the metabuild dep so that it fails to compile.
744+
p.change_file("mb/src/lib.rs", "");
745+
p.cargo("build --message-format=json")
746+
.masquerade_as_nightly_cargo()
747+
.with_status(101)
748+
.with_json_contains_unordered(
749+
r#"
750+
{
751+
"message": {
752+
"children": "{...}",
753+
"code": "{...}",
754+
"level": "error",
755+
"message": "cannot find function `metabuild` in module `mb`",
756+
"rendered": "[..]",
757+
"spans": "{...}"
758+
},
759+
"package_id": "foo [..]",
760+
"reason": "compiler-message",
761+
"target": {
762+
"crate_types": [
763+
"bin"
764+
],
765+
"edition": "2018",
766+
"kind": [
767+
"custom-build"
768+
],
769+
"name": "metabuild-foo",
770+
"src_path": null
771+
}
772+
}
773+
"#,
774+
)
702775
.run();
703776
}

tests/testsuite/support/mod.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,7 @@ pub struct Execs {
550550
expect_stderr_unordered: Vec<String>,
551551
expect_neither_contains: Vec<String>,
552552
expect_json: Option<Vec<Value>>,
553+
expect_json_contains_unordered: Vec<Value>,
553554
stream_output: bool,
554555
}
555556

@@ -689,6 +690,24 @@ impl Execs {
689690
self
690691
}
691692

693+
/// Verify JSON output contains the given objects (in any order) somewhere
694+
/// in its output.
695+
///
696+
/// CAUTION: Be very careful when using this. Make sure every object is
697+
/// unique (not a subset of one another). Also avoid using objects that
698+
/// could possibly match multiple output lines unless you're very sure of
699+
/// what you are doing.
700+
///
701+
/// See `with_json` for more detail.
702+
pub fn with_json_contains_unordered(&mut self, expected: &str) -> &mut Self {
703+
self.expect_json_contains_unordered.extend(
704+
expected
705+
.split("\n\n")
706+
.map(|line| line.parse().expect("line to be a valid JSON value")),
707+
);
708+
self
709+
}
710+
692711
/// Forward subordinate process stdout/stderr to the terminal.
693712
/// Useful for printf debugging of the tests.
694713
/// CAUTION: CI will fail if you leave this in your test!
@@ -941,6 +960,33 @@ impl Execs {
941960
self.match_json(obj, line)?;
942961
}
943962
}
963+
964+
if !self.expect_json_contains_unordered.is_empty() {
965+
let stdout = str::from_utf8(&actual.stdout)
966+
.map_err(|_| "stdout was not utf8 encoded".to_owned())?;
967+
let mut lines = stdout
968+
.lines()
969+
.filter(|line| line.starts_with('{'))
970+
.collect::<Vec<_>>();
971+
for obj in &self.expect_json_contains_unordered {
972+
match lines
973+
.iter()
974+
.position(|line| self.match_json(obj, line).is_ok())
975+
{
976+
Some(index) => lines.remove(index),
977+
None => {
978+
return Err(format!(
979+
"Did not find expected JSON:\n\
980+
{}\n\
981+
Remaining available output:\n\
982+
{}\n",
983+
serde_json::to_string_pretty(obj).unwrap(),
984+
lines.join("\n")
985+
));
986+
}
987+
};
988+
}
989+
}
944990
Ok(())
945991
}
946992

@@ -1322,6 +1368,7 @@ pub fn execs() -> Execs {
13221368
expect_stderr_unordered: Vec::new(),
13231369
expect_neither_contains: Vec::new(),
13241370
expect_json: None,
1371+
expect_json_contains_unordered: Vec::new(),
13251372
stream_output: false,
13261373
}
13271374
}

0 commit comments

Comments
 (0)