In JSON output, emit a directive after metadata is generated.

To implement pipelining, Cargo needs to know when metadata generation is
finished. This commit adds code to do that. Unfortunately, metadata file
writing currently occurs very late during compilation, so pipelining
won't produce a speed-up. Moving metadata file writing earlier will be a
follow-up.

The change involves splitting the existing `Emitter::emit` method in
two: `Emitter::emit_diagnostic` and `Emitter::emit_directive`.

The JSON directives look like this:
```
{"directive":"metadata file written: liba.rmeta"}
```
The functionality is behind the `-Z emit-directives` option, and also
requires `--error-format=json`.
This commit is contained in:
Nicholas Nethercote 2019-04-15 08:26:08 +10:00
parent 91d5b764ea
commit 7bcb0cffb6
10 changed files with 85 additions and 25 deletions

View File

@ -1468,6 +1468,8 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options,
the same values as the target option of the same name"),
allow_features: Option<Vec<String>> = (None, parse_opt_comma_list, [TRACKED],
"only allow the listed language features to be enabled in code (space separated)"),
emit_directives: bool = (false, parse_bool, [UNTRACKED],
"emit build directives if producing JSON output"),
}
pub fn default_lib_output() -> CrateType {

View File

@ -300,7 +300,7 @@ impl CodegenBackend for LlvmCodegenBackend {
sess: &Session,
dep_graph: &DepGraph,
outputs: &OutputFilenames,
) -> Result<(), ErrorReported>{
) -> Result<(), ErrorReported> {
use rustc::util::common::time;
let (codegen_results, work_products) =
ongoing_codegen.downcast::

View File

@ -119,8 +119,14 @@ fn link_binary_output<'a, B: ArchiveBuilder<'a>>(sess: &'a Session,
.tempdir_in(out_filename.parent().unwrap())
.unwrap_or_else(|err| sess.fatal(&format!("couldn't create a temp dir: {}", err)));
let metadata = emit_metadata(sess, codegen_results, &metadata_tmpdir);
if let Err(e) = fs::rename(metadata, &out_filename) {
sess.fatal(&format!("failed to write {}: {}", out_filename.display(), e));
match fs::rename(&metadata, &out_filename) {
Ok(_) => {
if sess.opts.debugging_opts.emit_directives {
sess.parse_sess.span_diagnostic.maybe_emit_json_directive(
format!("metadata file written: {}", out_filename.display()));
}
}
Err(e) => sess.fatal(&format!("failed to write {}: {}", out_filename.display(), e)),
}
}

View File

@ -1726,7 +1726,7 @@ impl SharedEmitter {
}
impl Emitter for SharedEmitter {
fn emit(&mut self, db: &DiagnosticBuilder<'_>) {
fn emit_diagnostic(&mut self, db: &DiagnosticBuilder<'_>) {
drop(self.sender.send(SharedEmitterMessage::Diagnostic(Diagnostic {
msg: db.message(),
code: db.code.clone(),
@ -1865,7 +1865,7 @@ impl<B: ExtraBackendMethods> OngoingCodegen<B> {
self.wait_for_signal_to_codegen_item();
self.check_for_errors(tcx.sess);
// These are generally cheap and won't through off scheduling.
// These are generally cheap and won't throw off scheduling.
let cost = 0;
submit_codegened_module_to_llvm(&self.backend, tcx, module, cost);
}

View File

@ -50,7 +50,11 @@ const ANONYMIZED_LINE_NUM: &str = "LL";
/// Emitter trait for emitting errors.
pub trait Emitter {
/// Emit a structured diagnostic.
fn emit(&mut self, db: &DiagnosticBuilder<'_>);
fn emit_diagnostic(&mut self, db: &DiagnosticBuilder<'_>);
/// Emit a JSON directive. The default is to do nothing; this should only
/// be emitted with --error-format=json.
fn maybe_emit_json_directive(&mut self, _directive: String) {}
/// Checks if should show explanations about "rustc --explain"
fn should_show_explain(&self) -> bool {
@ -59,7 +63,7 @@ pub trait Emitter {
}
impl Emitter for EmitterWriter {
fn emit(&mut self, db: &DiagnosticBuilder<'_>) {
fn emit_diagnostic(&mut self, db: &DiagnosticBuilder<'_>) {
let mut primary_span = db.span.clone();
let mut children = db.children.clone();
let mut suggestions: &[_] = &[];

View File

@ -294,9 +294,16 @@ impl error::Error for ExplicitBug {
pub use diagnostic::{Diagnostic, SubDiagnostic, DiagnosticStyledString, DiagnosticId};
pub use diagnostic_builder::DiagnosticBuilder;
/// A handler deals with errors; certain errors
/// (fatal, bug, unimpl) may cause immediate exit,
/// others log errors for later reporting.
/// A handler deals with two kinds of compiler output.
/// - Errors: certain errors (fatal, bug, unimpl) may cause immediate exit,
/// others log errors for later reporting.
/// - Directives: with --error-format=json, the compiler produces directives
/// that indicate when certain actions have completed, which are useful for
/// Cargo. They may change at any time and should not be considered a public
/// API.
///
/// This crate's name (rustc_errors) doesn't encompass the directives, because
/// directives were added much later.
pub struct Handler {
pub flags: HandlerFlags,
@ -736,7 +743,7 @@ impl Handler {
}
pub fn force_print_db(&self, mut db: DiagnosticBuilder<'_>) {
self.emitter.borrow_mut().emit(&db);
self.emitter.borrow_mut().emit_diagnostic(&db);
db.cancel();
}
@ -761,14 +768,17 @@ impl Handler {
// Only emit the diagnostic if we haven't already emitted an equivalent
// one:
if self.emitted_diagnostics.borrow_mut().insert(diagnostic_hash) {
self.emitter.borrow_mut().emit(db);
self.emitter.borrow_mut().emit_diagnostic(db);
if db.is_error() {
self.bump_err_count();
}
}
}
}
pub fn maybe_emit_json_directive(&self, directive: String) {
self.emitter.borrow_mut().maybe_emit_json_directive(directive);
}
}
#[derive(Copy, PartialEq, Clone, Hash, Debug, RustcEncodable, RustcDecodable)]
pub enum Level {

View File

@ -79,7 +79,7 @@ impl JsonEmitter {
}
impl Emitter for JsonEmitter {
fn emit(&mut self, db: &DiagnosticBuilder<'_>) {
fn emit_diagnostic(&mut self, db: &DiagnosticBuilder<'_>) {
let data = Diagnostic::from_diagnostic_builder(db, self);
let result = if self.pretty {
writeln!(&mut self.dst, "{}", as_pretty_json(&data))
@ -90,6 +90,18 @@ impl Emitter for JsonEmitter {
panic!("failed to print diagnostics: {:?}", e);
}
}
fn maybe_emit_json_directive(&mut self, directive: String) {
let data = Directive { directive };
let result = if self.pretty {
writeln!(&mut self.dst, "{}", as_pretty_json(&data))
} else {
writeln!(&mut self.dst, "{}", as_json(&data))
};
if let Err(e) = result {
panic!("failed to print message: {:?}", e);
}
}
}
// The following data types are provided just for serialisation.
@ -168,6 +180,12 @@ struct DiagnosticCode {
explanation: Option<&'static str>,
}
#[derive(RustcEncodable)]
struct Directive {
/// The directive itself.
directive: String,
}
impl Diagnostic {
fn from_diagnostic_builder(db: &DiagnosticBuilder<'_>,
je: &JsonEmitter)
@ -200,7 +218,7 @@ impl Diagnostic {
let buf = BufWriter::default();
let output = buf.clone();
je.json_rendered.new_emitter(Box::new(buf), Some(je.sm.clone()), false)
.ui_testing(je.ui_testing).emit(db);
.ui_testing(je.ui_testing).emit_diagnostic(db);
let output = Arc::try_unwrap(output.0).unwrap().into_inner().unwrap();
let output = String::from_utf8(output).unwrap();

View File

@ -0,0 +1,12 @@
// ignore-tidy-linelength
// compile-flags:--emit=metadata --error-format=json -Z emit-directives
// compile-pass
//
// Normalization is required to eliminated minor path and filename differences
// across platforms.
// normalize-stderr-test: "metadata file written: .*/emit-directives" -> "metadata file written: .../emit-directives"
// normalize-stderr-test: "emit-directives(\.\w*)?/a(\.\w*)?" -> "emit-directives/a"
// A very basic test for the emission of build directives in JSON output.
fn main() {}

View File

@ -0,0 +1 @@
{"directive":"metadata file written: .../emit-directives/a"}

View File

@ -17,6 +17,12 @@ struct Diagnostic {
rendered: Option<String>,
}
#[derive(Deserialize)]
struct Directive {
#[allow(dead_code)]
directive: String,
}
#[derive(Deserialize, Clone)]
struct DiagnosticSpan {
file_name: String,
@ -67,16 +73,17 @@ pub fn extract_rendered(output: &str) -> String {
.lines()
.filter_map(|line| {
if line.starts_with('{') {
match serde_json::from_str::<Diagnostic>(line) {
Ok(diagnostic) => diagnostic.rendered,
Err(error) => {
print!(
"failed to decode compiler output as json: \
`{}`\nline: {}\noutput: {}",
error, line, output
);
panic!()
}
if let Ok(diagnostic) = serde_json::from_str::<Diagnostic>(line) {
diagnostic.rendered
} else if let Ok(_directive) = serde_json::from_str::<Directive>(line) {
// Swallow the directive.
None
} else {
print!(
"failed to decode compiler output as json: line: {}\noutput: {}",
line, output
);
panic!()
}
} else {
// preserve non-JSON lines, such as ICEs