From f3faa976fa00f5dbb86bddd756fe5c73f1427a75 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Wed, 8 Aug 2018 23:52:05 -0700 Subject: [PATCH 1/2] 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. --- src/cargo/core/compiler/mod.rs | 80 +++++++++++++++++++++------------- tests/testsuite/doc.rs | 30 +++++++++++++ 2 files changed, 79 insertions(+), 31 deletions(-) diff --git a/src/cargo/core/compiler/mod.rs b/src/cargo/core/compiler/mod.rs index 3c9c35a0e8f..0d7a253a70a 100644 --- a/src/cargo/core/compiler/mod.rs +++ b/src/cargo/core/compiler/mod.rs @@ -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 { @@ -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); } @@ -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; @@ -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() @@ -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(()) +} diff --git a/tests/testsuite/doc.rs b/tests/testsuite/doc.rs index 871f459a593..edbac5aabe8 100644 --- a/tests/testsuite/doc.rs +++ b/tests/testsuite/doc.rs @@ -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": "{...}" + } + "#, + ), + ); +} From c28823fa008edc8676a6fad5012941da11386302 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Thu, 9 Aug 2018 15:43:34 -0700 Subject: [PATCH 2/2] Update for review comments. --- src/cargo/core/compiler/mod.rs | 6 +++--- tests/testsuite/doc.rs | 25 ++++++++++++------------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/cargo/core/compiler/mod.rs b/src/cargo/core/compiler/mod.rs index 0d7a253a70a..1427f958288 100644 --- a/src/cargo/core/compiler/mod.rs +++ b/src/cargo/core/compiler/mod.rs @@ -283,7 +283,7 @@ fn rustc<'a, 'cfg>( &package_id, &target, mode, - &mut json_stdout, + &mut assert_is_empty, &mut |line| json_stderr(line, &package_id, &target), ).map_err(Internal::new) .chain_err(|| format!("Could not compile `{}`.", name))?; @@ -640,7 +640,7 @@ fn rustdoc<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoResult let exec_result = if json_messages { rustdoc .exec_with_streaming( - &mut json_stdout, + &mut assert_is_empty, &mut |line| json_stderr(line, &package_id, &target), false, ).map(drop) @@ -988,7 +988,7 @@ impl Kind { } } -fn json_stdout(line: &str) -> CargoResult<()> { +fn assert_is_empty(line: &str) -> CargoResult<()> { if !line.is_empty() { Err(internal(&format!( "compiler stdout is not empty: `{}`", diff --git a/tests/testsuite/doc.rs b/tests/testsuite/doc.rs index edbac5aabe8..ca212bf2ff6 100644 --- a/tests/testsuite/doc.rs +++ b/tests/testsuite/doc.rs @@ -1359,6 +1359,13 @@ 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() { @@ -1366,15 +1373,8 @@ fn doc_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() @@ -1419,7 +1419,6 @@ fn doc_cap_lints() { ", ), ); - } #[test] @@ -1428,7 +1427,7 @@ fn doc_message_format() { // This can be removed once 1.30 is stable (rustdoc --error-format stabilized). return; } - let p = project().file("src/lib.rs", "asdf").build(); + let p = project().file("src/lib.rs", BAD_INTRA_LINK_LIB).build(); assert_that( p.cargo("doc --message-format=json"), @@ -1436,8 +1435,8 @@ fn doc_message_format() { r#" { "message": { - "children": [], - "code": null, + "children": "{...}", + "code": "{...}", "level": "error", "message": "[..]", "rendered": "[..]",