Skip to content

Commit 678575a

Browse files
committed
Route rendered JSON diagnostics from rustc through Cargo
This commit updates Cargo's behavior when it is printing "human readable" error messages when paired with pipelined compilation. In this mode Cargo needs to switch rustc into JSON mode to get a JSON directive message when metadata is ready, but we still want to produce human readable error messages if they crop up. As a result parse a bit of JSON coming out of rustc, if any, and ensure that everything is configured correctly along the way.
1 parent b59ba27 commit 678575a

File tree

1 file changed

+82
-16
lines changed

1 file changed

+82
-16
lines changed

src/cargo/core/compiler/mod.rs

Lines changed: 82 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use std::fs;
1717
use std::path::{Path, PathBuf};
1818
use std::sync::Arc;
1919

20-
use failure::Error;
20+
use failure::{bail, Error};
2121
use log::debug;
2222
use same_file::is_same_file;
2323
use serde::Serialize;
@@ -217,6 +217,43 @@ fn rustc<'a, 'cfg>(
217217
let fingerprint_dir = cx.files().fingerprint_dir(unit);
218218
let rmeta_produced = cx.rmeta_required(unit);
219219

220+
// If this unit is producing a required rmeta file then we need to know
221+
// when the rmeta file is ready so we can signal to the rest of Cargo that
222+
// it can continue dependant compilations. To do this we are currently
223+
// required to switch the compiler into JSON message mode, but we still
224+
// want to present human readable errors as well. (this rabbit hole just
225+
// goes and goes)
226+
//
227+
// All that means is that if we're not already in JSON mode we need to
228+
// switch to JSON mode, ensure that rustc error messages can be rendered
229+
// prettily, and then when parsing JSON messages from rustc we need to
230+
// internally understand that we should extract the `rendered` field and
231+
// present it if we can.
232+
let extract_rendered_errors = if rmeta_produced {
233+
match cx.bcx.build_config.message_format {
234+
MessageFormat::Json => false,
235+
MessageFormat::Human => {
236+
rustc
237+
.arg("--error-format=json")
238+
.arg("--json-rendered=termcolor")
239+
.arg("-Zunstable-options");
240+
true
241+
}
242+
243+
// FIXME(rust-lang/rust#60419): right now we have no way of turning
244+
// on JSON messages from the compiler and also asking the rendered
245+
// field to be in the `short` format.
246+
MessageFormat::Short => {
247+
bail!(
248+
"currently `--message-format short` is incompatible with \
249+
pipelined compilation"
250+
);
251+
}
252+
}
253+
} else {
254+
false
255+
};
256+
220257
return Ok(Work::new(move |state| {
221258
// Only at runtime have we discovered what the extra -L and -l
222259
// arguments are for native libraries, so we process those here. We
@@ -277,7 +314,9 @@ fn rustc<'a, 'cfg>(
277314
&target,
278315
mode,
279316
&mut |line| on_stdout_line(state, line, package_id, &target),
280-
&mut |line| on_stderr_line(state, line, package_id, &target),
317+
&mut |line| {
318+
on_stderr_line(state, line, package_id, &target, extract_rendered_errors)
319+
},
281320
)
282321
.map_err(internal_if_simple_exit_code)
283322
.chain_err(|| format!("Could not compile `{}`.", name))?;
@@ -659,7 +698,7 @@ fn rustdoc<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoResult
659698
rustdoc
660699
.exec_with_streaming(
661700
&mut |line| on_stdout_line(state, line, package_id, &target),
662-
&mut |line| on_stderr_line(state, line, package_id, &target),
701+
&mut |line| on_stderr_line(state, line, package_id, &target, false),
663702
false,
664703
)
665704
.chain_err(|| format!("Could not document `{}`.", name))?;
@@ -1054,22 +1093,49 @@ fn on_stderr_line(
10541093
line: &str,
10551094
package_id: PackageId,
10561095
target: &Target,
1096+
extract_rendered_errors: bool,
10571097
) -> CargoResult<()> {
1098+
// We primarily want to use this function to process JSON messages from
1099+
// rustc. The compiler should always print one JSON message per line, and
1100+
// otherwise it may have other output intermingled (think RUST_LOG or
1101+
// something like that), so skip over everything that doesn't look like a
1102+
// JSON message.
1103+
if !line.starts_with('{') {
1104+
state.stderr(line);
1105+
return Ok(());
1106+
}
1107+
1108+
let compiler_message: Box<serde_json::value::RawValue> = serde_json::from_str(line)
1109+
.map_err(|_| internal(&format!("compiler produced invalid json: `{}`", line)))?;
1110+
1111+
// In some modes of compilation Cargo switches the compiler to JSON mode but
1112+
// the user didn't request that so we still want to print pretty rustc
1113+
// colorized errors. In those cases (`extract_rendered_errors`) we take a
1114+
// look at the JSON blob we go, see if it's a relevant diagnostics, and if
1115+
// so forward just that diagnostic for us to print.
1116+
if extract_rendered_errors {
1117+
#[derive(serde::Deserialize)]
1118+
struct CompilerError {
1119+
rendered: String,
1120+
}
1121+
if let Ok(error) = serde_json::from_str::<CompilerError>(compiler_message.get()) {
1122+
state.stderr(&error.rendered);
1123+
return Ok(());
1124+
}
1125+
}
1126+
1127+
// And failing all that above we should have a legitimate JSON diagnostic
1128+
// from the compiler, so wrap it in an external Cargo JSON message
1129+
// indicating which package it came from and then emit it.
1130+
let msg = machine_message::emit(&machine_message::FromCompiler {
1131+
package_id,
1132+
target,
1133+
message: compiler_message,
1134+
});
1135+
10581136
// Switch json lines from rustc/rustdoc that appear on stderr to instead.
10591137
// We want the stdout of Cargo to always be machine parseable as stderr has
10601138
// our colorized human-readable messages.
1061-
if line.starts_with('{') {
1062-
let compiler_message = serde_json::from_str(line)
1063-
.map_err(|_| internal(&format!("compiler produced invalid json: `{}`", line)))?;
1064-
1065-
let msg = machine_message::emit(&machine_message::FromCompiler {
1066-
package_id,
1067-
target,
1068-
message: compiler_message,
1069-
});
1070-
state.stdout(&msg);
1071-
} else {
1072-
state.stderr(line);
1073-
}
1139+
state.stdout(&msg);
10741140
Ok(())
10751141
}

0 commit comments

Comments
 (0)