Skip to content

Commit

Permalink
Auto merge of #5878 - ehuss:rustdoc-json, r=alexcrichton
Browse files Browse the repository at this point in the history
Support JSON with rustdoc.

This allows `cargo doc --message-format=json` to actually work.

Note that this explicitly does not attempt to support it for doctests for
several reasons:
- `rustdoc --test --error-format=json` does not work for some reason.
- Since the lib is usually compiled before running rustdoc, warnings/errors
  will be emitted correctly by rustc.
- I'm unaware of any errors/warnings `rustdoc --test` is capable of producing
  assuming the code passed `rustc`.
- The compilation of the tests themselves do not support JSON.
- libtest does not output json, so it's utility is limited.
  • Loading branch information
bors committed Aug 10, 2018
2 parents 1778152 + c28823f commit 578e253
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 40 deletions.
80 changes: 49 additions & 31 deletions src/cargo/core/compiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,36 +283,10 @@ fn rustc<'a, 'cfg>(
&package_id,
&target,
mode,
&mut |line| {
if !line.is_empty() {
Err(internal(&format!(
"compiler stdout is not empty: `{}`",
line
)))
} else {
Ok(())
}
},
&mut |line| {
// stderr from rustc can have a mix of JSON and non-JSON output
if line.starts_with('{') {
// Handle JSON lines
let compiler_message = serde_json::from_str(line).map_err(|_| {
internal(&format!("compiler produced invalid json: `{}`", line))
})?;

machine_message::emit(&machine_message::FromCompiler {
package_id: &package_id,
target: &target,
message: compiler_message,
});
} else {
// Forward non-JSON to stderr
writeln!(io::stderr(), "{}", line)?;
}
Ok(())
},
).map_err(Internal::new).chain_err(|| format!("Could not compile `{}`.", name))?;
&mut assert_is_empty,
&mut |line| json_stderr(line, &package_id, &target),
).map_err(Internal::new)
.chain_err(|| format!("Could not compile `{}`.", name))?;
} else if build_plan {
state.build_plan(buildkey, rustc.clone(), outputs.clone());
} else {
Expand Down Expand Up @@ -631,6 +605,10 @@ fn rustdoc<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoResult
rustdoc.arg("--cfg").arg(&format!("feature=\"{}\"", feat));
}

if bcx.build_config.json_messages() {
rustdoc.arg("--error-format").arg("json");
}

if let Some(ref args) = bcx.extra_args_for(unit) {
rustdoc.args(args);
}
Expand All @@ -642,6 +620,9 @@ fn rustdoc<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoResult
let name = unit.pkg.name().to_string();
let build_state = cx.build_state.clone();
let key = (unit.pkg.package_id().clone(), unit.kind);
let json_messages = bcx.build_config.json_messages();
let package_id = unit.pkg.package_id().clone();
let target = unit.target.clone();

let should_capture_output = cx.bcx.config.cli_unstable().compile_progress;

Expand All @@ -656,7 +637,14 @@ fn rustdoc<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoResult
}
state.running(&rustdoc);

let exec_result = if should_capture_output {
let exec_result = if json_messages {
rustdoc
.exec_with_streaming(
&mut assert_is_empty,
&mut |line| json_stderr(line, &package_id, &target),
false,
).map(drop)
} else if should_capture_output {
state.capture_output(rustdoc, false).map(drop)
} else {
rustdoc.exec()
Expand Down Expand Up @@ -999,3 +987,33 @@ impl Kind {
}
}
}

fn assert_is_empty(line: &str) -> CargoResult<()> {
if !line.is_empty() {
Err(internal(&format!(
"compiler stdout is not empty: `{}`",
line
)))
} else {
Ok(())
}
}

fn json_stderr(line: &str, package_id: &PackageId, target: &Target) -> CargoResult<()> {
// stderr from rustc/rustdoc can have a mix of JSON and non-JSON output
if line.starts_with('{') {
// Handle JSON lines
let compiler_message = serde_json::from_str(line)
.map_err(|_| internal(&format!("compiler produced invalid json: `{}`", line)))?;

machine_message::emit(&machine_message::FromCompiler {
package_id: package_id,
target: target,
message: compiler_message,
});
} else {
// Forward non-JSON to stderr
writeln!(io::stderr(), "{}", line)?;
}
Ok(())
}
47 changes: 38 additions & 9 deletions tests/testsuite/doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1359,22 +1359,22 @@ fn doc_private_items() {
assert_that(&foo.root().join("target/doc/foo/private/index.html"), existing_file());
}

const BAD_INTRA_LINK_LIB: &'static str = r#"
#![deny(intra_doc_link_resolution_failure)]
/// [bad_link]
pub fn foo() {}
"#;

#[test]
fn doc_cap_lints() {
if !is_nightly() {
// This can be removed once 1.29 is stable (rustdoc --cap-lints).
return;
}
let a = git::new("a", |p| {
p.file("Cargo.toml", &basic_lib_manifest("a")).file(
"src/lib.rs",
"
#![deny(intra_doc_link_resolution_failure)]
/// [bad_link]
pub fn foo() {}
",
)
p.file("Cargo.toml", &basic_lib_manifest("a"))
.file("src/lib.rs", BAD_INTRA_LINK_LIB)
}).unwrap();

let p = project()
Expand Down Expand Up @@ -1419,5 +1419,34 @@ fn doc_cap_lints() {
",
),
);
}

#[test]
fn doc_message_format() {
if !is_nightly() {
// This can be removed once 1.30 is stable (rustdoc --error-format stabilized).
return;
}
let p = project().file("src/lib.rs", BAD_INTRA_LINK_LIB).build();

assert_that(
p.cargo("doc --message-format=json"),
execs().with_status(101).with_json(
r#"
{
"message": {
"children": "{...}",
"code": "{...}",
"level": "error",
"message": "[..]",
"rendered": "[..]",
"spans": "{...}"
},
"package_id": "foo [..]",
"reason": "compiler-message",
"target": "{...}"
}
"#,
),
);
}

0 comments on commit 578e253

Please sign in to comment.