Skip to content

Commit 42a8c0a

Browse files
committed
Auto merge of #7214 - alexcrichton:json-diagnostics, r=ehuss
Add support for customizing JSON diagnostics from Cargo Cargo has of #7143 enabled pipelined compilation by default which affects how the compiler is invoked, especially with respect to JSON messages. This, in some testing, has proven to cause quite a few issues with rustbuild's current integration with Cargo. This commit is aimed at adding features to Cargo to solve this issue. This commit adds the ability to customize the stream of JSON messages coming from Cargo. The new feature for Cargo is that it now also mirrors rustc in how you can configure the JSON stream. Multiple `--message-format` arguments are now supported and the value specified is a comma-separated list of directives. In addition to the existing `human`, `short`, and `json` directives these new directives have been added: * `json-render-diagnostics` - instructs Cargo to render rustc diagnostics and only print out JSON messages for artifacts and Cargo things. * `json-diagnostic-short` - indicates that the `rendered` field of rustc diagnostics should use the "short" rendering. * `json-diagnostic-rendered-ansi` - indicates that the `rendered` field of rustc diagnostics should embed ansi color codes. The first option here, `json-render-diagnostics`, will be used by rustbuild unconditionally. Additionally `json-diagnostic-short` will be conditionally used based on the input to rustbuild itself. This should be enough for external tools to customize how Cargo is invoked and how all kinds of JSON diagnostics get printed, and it's thought that we can relatively easily tweak this as necessary to extend it and such.
2 parents 375de4a + 45699e9 commit 42a8c0a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+973
-227
lines changed

CHANGELOG.md

+11-1
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,20 @@
55

66
### Added
77

8+
- Cargo build pipelining has been enabled by default to leverage more idle CPU
9+
parallelism during builds.
10+
[#7143](https://github.com/rust-lang/cargo/pull/7143)
11+
- The `--message-format` option to Cargo can now be specified multiple times and
12+
accepts a comma-separated list of values. In addition to the previous values
13+
it also now accepts `json-diagnostic-short` and
14+
`json-diagnostic-rendered-ansi` which configures the output coming from rustc
15+
in `json` message mode.
16+
[#7214](https://github.com/rust-lang/cargo/pull/7214)
17+
818
### Changed
919

1020
### Fixed
11-
- (Nightly only): Fixed exponential blowup when using CARGO_BUILD_PIPELINING.
21+
- (Nightly only): Fixed exponential blowup when using `CARGO_BUILD_PIPELINING`.
1222
[#7062](https://github.com/rust-lang/cargo/pull/7062)
1323
- Fixed using the wrong directory when updating git repositories when using
1424
the `git-fetch-with-cli` config option, and the `GIT_DIR` environment

src/cargo/core/compiler/build_config.rs

+15-2
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,10 @@ impl BuildConfig {
112112
/// Whether or not the *user* wants JSON output. Whether or not rustc
113113
/// actually uses JSON is decided in `add_error_format`.
114114
pub fn emit_json(&self) -> bool {
115-
self.message_format == MessageFormat::Json
115+
match self.message_format {
116+
MessageFormat::Json { .. } => true,
117+
_ => false,
118+
}
116119
}
117120

118121
pub fn test(&self) -> bool {
@@ -123,7 +126,17 @@ impl BuildConfig {
123126
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
124127
pub enum MessageFormat {
125128
Human,
126-
Json,
129+
Json {
130+
/// Whether rustc diagnostics are rendered by cargo or included into the
131+
/// output stream.
132+
render_diagnostics: bool,
133+
/// Whether the `rendered` field of rustc diagnostics are using the
134+
/// "short" rendering.
135+
short: bool,
136+
/// Whether the `rendered` field of rustc diagnostics embed ansi color
137+
/// codes.
138+
ansi: bool,
139+
},
127140
Short,
128141
}
129142

src/cargo/core/compiler/mod.rs

+89-64
Original file line numberDiff line numberDiff line change
@@ -750,27 +750,47 @@ fn add_error_format_and_color(
750750
if pipelined {
751751
json.push_str(",artifacts");
752752
}
753-
if cx.bcx.build_config.message_format == MessageFormat::Short {
754-
json.push_str(",diagnostic-short");
753+
match cx.bcx.build_config.message_format {
754+
MessageFormat::Short | MessageFormat::Json { short: true, .. } => {
755+
json.push_str(",diagnostic-short");
756+
}
757+
_ => {}
755758
}
756759
cmd.arg(json);
757760
} else {
761+
let mut color = true;
758762
match cx.bcx.build_config.message_format {
759763
MessageFormat::Human => (),
760-
MessageFormat::Json => {
764+
MessageFormat::Json {
765+
ansi,
766+
short,
767+
render_diagnostics,
768+
} => {
761769
cmd.arg("--error-format").arg("json");
770+
// If ansi is explicitly requested, enable it. If we're
771+
// rendering diagnostics ourselves then also enable it because
772+
// we'll figure out what to do with the colors later.
773+
if ansi || render_diagnostics {
774+
cmd.arg("--json=diagnostic-rendered-ansi");
775+
}
776+
if short {
777+
cmd.arg("--json=diagnostic-short");
778+
}
779+
color = false;
762780
}
763781
MessageFormat::Short => {
764782
cmd.arg("--error-format").arg("short");
765783
}
766784
}
767785

768-
let color = if cx.bcx.config.shell().supports_color() {
769-
"always"
770-
} else {
771-
"never"
772-
};
773-
cmd.args(&["--color", color]);
786+
if color {
787+
let color = if cx.bcx.config.shell().supports_color() {
788+
"always"
789+
} else {
790+
"never"
791+
};
792+
cmd.args(&["--color", color]);
793+
}
774794
}
775795
Ok(())
776796
}
@@ -1090,9 +1110,8 @@ impl Kind {
10901110
}
10911111

10921112
struct OutputOptions {
1093-
/// Get the `"rendered"` field from the JSON output and display it on
1094-
/// stderr instead of the JSON message.
1095-
extract_rendered_messages: bool,
1113+
/// What format we're emitting from Cargo itself.
1114+
format: MessageFormat,
10961115
/// Look for JSON message that indicates .rmeta file is available for
10971116
/// pipelined compilation.
10981117
look_for_metadata_directive: bool,
@@ -1106,7 +1125,6 @@ struct OutputOptions {
11061125

11071126
impl OutputOptions {
11081127
fn new<'a>(cx: &Context<'a, '_>, unit: &Unit<'a>) -> OutputOptions {
1109-
let extract_rendered_messages = cx.bcx.build_config.message_format != MessageFormat::Json;
11101128
let look_for_metadata_directive = cx.rmeta_required(unit);
11111129
let color = cx.bcx.config.shell().supports_color();
11121130
let cache_cell = if cx.bcx.build_config.cache_messages() {
@@ -1118,7 +1136,7 @@ impl OutputOptions {
11181136
None
11191137
};
11201138
OutputOptions {
1121-
extract_rendered_messages,
1139+
format: cx.bcx.build_config.message_format,
11221140
look_for_metadata_directive,
11231141
color,
11241142
cache_cell,
@@ -1175,55 +1193,66 @@ fn on_stderr_line(
11751193
}
11761194
};
11771195

1178-
// In some modes of compilation Cargo switches the compiler to JSON mode
1179-
// but the user didn't request that so we still want to print pretty rustc
1180-
// colorized diagnostics. In those cases (`extract_rendered_messages`) we
1181-
// take a look at the JSON blob we go, see if it's a relevant diagnostics,
1182-
// and if so forward just that diagnostic for us to print.
1183-
if options.extract_rendered_messages {
1184-
#[derive(serde::Deserialize)]
1185-
struct CompilerMessage {
1186-
rendered: String,
1196+
// Depending on what we're emitting from Cargo itself, we figure out what to
1197+
// do with this JSON message.
1198+
match options.format {
1199+
// In the "human" output formats (human/short) or if diagnostic messages
1200+
// from rustc aren't being included in the output of Cargo's JSON
1201+
// messages then we extract the diagnostic (if present) here and handle
1202+
// it ourselves.
1203+
MessageFormat::Human
1204+
| MessageFormat::Short
1205+
| MessageFormat::Json {
1206+
render_diagnostics: true,
1207+
..
1208+
} => {
1209+
#[derive(serde::Deserialize)]
1210+
struct CompilerMessage {
1211+
rendered: String,
1212+
}
1213+
if let Ok(mut error) = serde_json::from_str::<CompilerMessage>(compiler_message.get()) {
1214+
// state.stderr will add a newline
1215+
if error.rendered.ends_with('\n') {
1216+
error.rendered.pop();
1217+
}
1218+
let rendered = if options.color {
1219+
error.rendered
1220+
} else {
1221+
// Strip only fails if the the Writer fails, which is Cursor
1222+
// on a Vec, which should never fail.
1223+
strip_ansi_escapes::strip(&error.rendered)
1224+
.map(|v| String::from_utf8(v).expect("utf8"))
1225+
.expect("strip should never fail")
1226+
};
1227+
state.stderr(rendered);
1228+
return Ok(());
1229+
}
11871230
}
1188-
if let Ok(mut error) = serde_json::from_str::<CompilerMessage>(compiler_message.get()) {
1189-
// state.stderr will add a newline
1190-
if error.rendered.ends_with('\n') {
1191-
error.rendered.pop();
1231+
1232+
// Remove color information from the rendered string. When pipelining is
1233+
// enabled and/or when cached messages are enabled we're always asking
1234+
// for ANSI colors from rustc, so unconditionally postprocess here and
1235+
// remove ansi color codes.
1236+
MessageFormat::Json { ansi: false, .. } => {
1237+
#[derive(serde::Deserialize, serde::Serialize)]
1238+
struct CompilerMessage {
1239+
rendered: String,
1240+
#[serde(flatten)]
1241+
other: std::collections::BTreeMap<String, serde_json::Value>,
11921242
}
1193-
let rendered = if options.color {
1194-
error.rendered
1195-
} else {
1196-
// Strip only fails if the the Writer fails, which is Cursor
1197-
// on a Vec, which should never fail.
1198-
strip_ansi_escapes::strip(&error.rendered)
1243+
if let Ok(mut error) = serde_json::from_str::<CompilerMessage>(compiler_message.get()) {
1244+
error.rendered = strip_ansi_escapes::strip(&error.rendered)
11991245
.map(|v| String::from_utf8(v).expect("utf8"))
1200-
.expect("strip should never fail")
1201-
};
1202-
state.stderr(rendered);
1203-
return Ok(());
1204-
}
1205-
} else {
1206-
// Remove color information from the rendered string. rustc has not
1207-
// included color in the past, so to avoid breaking anything, strip it
1208-
// out when --json=diagnostic-rendered-ansi is used. This runs
1209-
// unconditionally under the assumption that Cargo will eventually
1210-
// move to this as the default mode. Perhaps in the future, cargo
1211-
// could allow the user to enable/disable color (such as with a
1212-
// `--json` or `--color` or `--message-format` flag).
1213-
#[derive(serde::Deserialize, serde::Serialize)]
1214-
struct CompilerMessage {
1215-
rendered: String,
1216-
#[serde(flatten)]
1217-
other: std::collections::BTreeMap<String, serde_json::Value>,
1218-
}
1219-
if let Ok(mut error) = serde_json::from_str::<CompilerMessage>(compiler_message.get()) {
1220-
error.rendered = strip_ansi_escapes::strip(&error.rendered)
1221-
.map(|v| String::from_utf8(v).expect("utf8"))
1222-
.unwrap_or(error.rendered);
1223-
let new_line = serde_json::to_string(&error)?;
1224-
let new_msg: Box<serde_json::value::RawValue> = serde_json::from_str(&new_line)?;
1225-
compiler_message = new_msg;
1246+
.unwrap_or(error.rendered);
1247+
let new_line = serde_json::to_string(&error)?;
1248+
let new_msg: Box<serde_json::value::RawValue> = serde_json::from_str(&new_line)?;
1249+
compiler_message = new_msg;
1250+
}
12261251
}
1252+
1253+
// If ansi colors are desired then we should be good to go! We can just
1254+
// pass through this message as-is.
1255+
MessageFormat::Json { ansi: true, .. } => {}
12271256
}
12281257

12291258
// In some modes of execution we will execute rustc with `-Z
@@ -1274,12 +1303,8 @@ fn replay_output_cache(
12741303
color: bool,
12751304
) -> Work {
12761305
let target = target.clone();
1277-
let extract_rendered_messages = match format {
1278-
MessageFormat::Human | MessageFormat::Short => true,
1279-
MessageFormat::Json => false,
1280-
};
12811306
let mut options = OutputOptions {
1282-
extract_rendered_messages,
1307+
format,
12831308
look_for_metadata_directive: false,
12841309
color,
12851310
cache_cell: None,

src/cargo/util/command_prelude.rs

+65-24
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
use std::ffi::{OsStr, OsString};
2-
use std::fs;
3-
use std::path::PathBuf;
4-
51
use crate::core::compiler::{BuildConfig, MessageFormat};
62
use crate::core::Workspace;
73
use crate::ops::{CompileFilter, CompileOptions, NewOptions, Packages, VersionControl};
@@ -14,6 +10,10 @@ use crate::util::{
1410
};
1511
use crate::CargoResult;
1612
use clap::{self, SubCommand};
13+
use failure::bail;
14+
use std::ffi::{OsStr, OsString};
15+
use std::fs;
16+
use std::path::PathBuf;
1717

1818
pub use crate::core::compiler::CompileMode;
1919
pub use crate::{CliError, CliResult, Config};
@@ -134,13 +134,7 @@ pub trait AppExt: Sized {
134134
}
135135

136136
fn arg_message_format(self) -> Self {
137-
self._arg(
138-
opt("message-format", "Error format")
139-
.value_name("FMT")
140-
.case_insensitive(true)
141-
.possible_values(&["human", "json", "short"])
142-
.default_value("human"),
143-
)
137+
self._arg(multi_opt("message-format", "FMT", "Error format"))
144138
}
145139

146140
fn arg_build_plan(self) -> Self {
@@ -301,23 +295,70 @@ pub trait ArgMatchesExt {
301295
self._values_of("package"),
302296
)?;
303297

304-
let message_format = match self._value_of("message-format") {
305-
None => MessageFormat::Human,
306-
Some(f) => {
307-
if f.eq_ignore_ascii_case("json") {
308-
MessageFormat::Json
309-
} else if f.eq_ignore_ascii_case("human") {
310-
MessageFormat::Human
311-
} else if f.eq_ignore_ascii_case("short") {
312-
MessageFormat::Short
313-
} else {
314-
panic!("Impossible message format: {:?}", f)
298+
let mut message_format = None;
299+
let default_json = MessageFormat::Json {
300+
short: false,
301+
ansi: false,
302+
render_diagnostics: false,
303+
};
304+
for fmt in self._values_of("message-format") {
305+
for fmt in fmt.split(',') {
306+
let fmt = fmt.to_ascii_lowercase();
307+
match fmt.as_str() {
308+
"json" => {
309+
if message_format.is_some() {
310+
bail!("cannot specify two kinds of `message-format` arguments");
311+
}
312+
message_format = Some(default_json);
313+
}
314+
"human" => {
315+
if message_format.is_some() {
316+
bail!("cannot specify two kinds of `message-format` arguments");
317+
}
318+
message_format = Some(MessageFormat::Human);
319+
}
320+
"short" => {
321+
if message_format.is_some() {
322+
bail!("cannot specify two kinds of `message-format` arguments");
323+
}
324+
message_format = Some(MessageFormat::Short);
325+
}
326+
"json-render-diagnostics" => {
327+
if message_format.is_none() {
328+
message_format = Some(default_json);
329+
}
330+
match &mut message_format {
331+
Some(MessageFormat::Json {
332+
render_diagnostics, ..
333+
}) => *render_diagnostics = true,
334+
_ => bail!("cannot specify two kinds of `message-format` arguments"),
335+
}
336+
}
337+
"json-diagnostic-short" => {
338+
if message_format.is_none() {
339+
message_format = Some(default_json);
340+
}
341+
match &mut message_format {
342+
Some(MessageFormat::Json { short, .. }) => *short = true,
343+
_ => bail!("cannot specify two kinds of `message-format` arguments"),
344+
}
345+
}
346+
"json-diagnostic-rendered-ansi" => {
347+
if message_format.is_none() {
348+
message_format = Some(default_json);
349+
}
350+
match &mut message_format {
351+
Some(MessageFormat::Json { ansi, .. }) => *ansi = true,
352+
_ => bail!("cannot specify two kinds of `message-format` arguments"),
353+
}
354+
}
355+
s => bail!("invalid message format specifier: `{}`", s),
315356
}
316357
}
317-
};
358+
}
318359

319360
let mut build_config = BuildConfig::new(config, self.jobs()?, &self.target(), mode)?;
320-
build_config.message_format = message_format;
361+
build_config.message_format = message_format.unwrap_or(MessageFormat::Human);
321362
build_config.release = self._is_present("release");
322363
build_config.build_plan = self._is_present("build-plan");
323364
if build_config.build_plan {

0 commit comments

Comments
 (0)