Skip to content

Commit

Permalink
Support JSON with rustdoc.
Browse files Browse the repository at this point in the history
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
ehuss committed Aug 9, 2018
1 parent ebaf212 commit f3faa97
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 31 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 json_stdout,
&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 json_stdout,
&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 json_stdout(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(())
}
30 changes: 30 additions & 0 deletions tests/testsuite/doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1421,3 +1421,33 @@ 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", "asdf").build();

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

0 comments on commit f3faa97

Please sign in to comment.