Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add message-format=json support, second take #64

Merged
merged 13 commits into from
Jan 24, 2019
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ readme = "README.md"

[dependencies]
error-chain = {version = "0.12.0", default-features = false}
serde = "1.0.59"
serde_derive = "1.0.59"
serde = "1.0.79"
serde_derive = "1.0.79"
serde_json = "1.0.1"

[dependencies.semver]
Expand Down
3 changes: 2 additions & 1 deletion src/dependency.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ pub enum DependencyKind {
/// Those used in build scripts only
Build,
#[doc(hidden)]
DoNotMatchExhaustively,
#[serde(other)]
Unknown,
oli-obk marked this conversation as resolved.
Show resolved Hide resolved
}

impl Default for DependencyKind {
Expand Down
158 changes: 158 additions & 0 deletions src/diagnostic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
//! This module contains `Diagnostic` and the types/functions it uses for deserialization.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you put these types into their own crate so we can reuse them from the compiler and from rustfix? (maybe rustc_diagnostics)


use std::fmt;

/// The error code associated to this diagnostic.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiagnosticCode {
/// The code itself.
pub code: String,
/// An explanation for the code
pub explanation: Option<String>,
#[doc(hidden)]
#[serde(skip)]
__do_not_match_exhaustively: (),
}

/// A line of code associated with the Diagnostic
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiagnosticSpanLine {
/// The line of code associated with the error
pub text: String,
/// Start of the section of the line to highlight. 1-based, character offset in self.text
pub highlight_start: usize,
/// End of the section of the line to highlight. 1-based, character offset in self.text
pub highlight_end: usize,
#[doc(hidden)]
#[serde(skip)]
__do_not_match_exhaustively: (),
}

/// Macro expansion information associated with a diagnostic.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiagnosticSpanMacroExpansion {
/// span where macro was applied to generate this code; note that
/// this may itself derive from a macro (if
/// `span.expansion.is_some()`)
pub span: DiagnosticSpan,

/// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]")
pub macro_decl_name: String,

/// span where macro was defined (if known)
pub def_site_span: Option<DiagnosticSpan>,
#[doc(hidden)]
#[serde(skip)]
__do_not_match_exhaustively: (),
}

/// A section of the source code associated with a Diagnostic
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiagnosticSpan {
/// The file name or the macro name this diagnostic comes from.
pub file_name: String,
/// The byte offset in the file where this diagnostic starts from.
pub byte_start: u32,
/// The byte offset in the file where this diagnostic ends.
pub byte_end: u32,
/// 1-based. The line in the file.
pub line_start: usize,
/// 1-based. The line in the file.
pub line_end: usize,
/// 1-based, character offset.
pub column_start: usize,
/// 1-based, character offset.
pub column_end: usize,
/// Is this a "primary" span -- meaning the point, or one of the points,
/// where the error occurred?
konstin marked this conversation as resolved.
Show resolved Hide resolved
///
/// There are rare cases where multiple spans are marked as primary,
/// e.g. "immutable borrow occurs here" and "mutable borrow ends here" can
/// be two separate spans both "primary". Top (parent) messages should
/// always have at least one primary span, unless it has 0 spans. Child
/// messages may have 0 or more primary spans.
pub is_primary: bool,
/// Source text from the start of line_start to the end of line_end.
pub text: Vec<DiagnosticSpanLine>,
/// Label that should be placed at this location (if any)
pub label: Option<String>,
/// If we are suggesting a replacement, this will contain text
/// that should be sliced in atop this span.
pub suggested_replacement: Option<String>,
/// If the suggestion is approximate
pub suggestion_applicability: Option<Applicability>,
/// Macro invocations that created the code at this span, if any.
pub expansion: Option<Box<DiagnosticSpanMacroExpansion>>,
#[doc(hidden)]
#[serde(skip)]
__do_not_match_exhaustively: (),
}

/// Whether a suggestion can be safely applied.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Applicability {
/// The suggested replacement can be applied automatically safely
MachineApplicable,
/// The suggested replacement has placeholders that will need to be manually
/// replaced.
HasPlaceholders,
/// The suggested replacement may be incorrect in some circumstances. Needs
/// human review.
MaybeIncorrect,
/// The suggested replacement will probably not work.
Unspecified,
#[doc(hidden)]
#[serde(other)]
Unknown,
}

/// The diagnostic level
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum DiagnosticLevel {
/// Internal compiler error
#[serde(rename = "error: internal compiler error")]
Ice,
/// Error
Error,
/// Warning
Warning,
/// Note
Note,
/// Help
Help,
#[doc(hidden)]
#[serde(other)]
Unknown,
}

/// A diagnostic message generated by rustc
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Diagnostic {
/// The error message of this diagnostic.
pub message: String,
/// The associated error code for this diagnostic
pub code: Option<DiagnosticCode>,
/// "error: internal compiler error", "error", "warning", "note", "help"
pub level: DiagnosticLevel,
/// A list of source code spans this diagnostic is associated with.
pub spans: Vec<DiagnosticSpan>,
Copy link
Contributor

@ehuss ehuss Jan 23, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a note that this may be empty? Some messages don't have a span (like "main not found"), and child messages sometimes don't have spans when they are attached to their parent.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's clear that that Vec can be empty

/// Associated diagnostic messages.
pub children: Vec<Diagnostic>,
/// The message as rustc would render it
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changed in 1.23. Before 1.23 it was used by suggested replacements.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just assume that everyone uses a reasonably recent compiler version?

pub rendered: Option<String>,
#[doc(hidden)]
#[serde(skip)]
__do_not_match_exhaustively: (),
}

impl fmt::Display for Diagnostic {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(ref rendered) = self.rendered {
f.write_str(rendered)?;
} else {
f.write_str("cargo didn't render this message")?;
}
Ok(())
}
}
44 changes: 42 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
#![deny(missing_docs)]
//! Structured access to the output of `cargo metadata`
//! Structured access to the output of `cargo metadata` and `cargo --message-format=json`.
//! Usually used from within a `cargo-*` executable
//!
//! See the [cargo book](https://doc.rust-lang.org/cargo/index.html) for
//! details on cargo itself.
//!
//! ## Examples
//!
//! With [`std::env::args()`](https://doc.rust-lang.org/std/env/fn.args.html):
Expand Down Expand Up @@ -66,7 +69,6 @@
//! # // This should be kept in sync with the equivalent example in the readme.
//! # extern crate cargo_metadata;
//! # extern crate clap;
//!
//! let matches = clap::App::new("myapp")
//! .arg(
//! clap::Arg::with_name("manifest-path")
Expand Down Expand Up @@ -122,6 +124,38 @@
//! .unwrap();
//! # }
//! ```
//!
//! Parse message-format output:
//!
//! ```
//! # extern crate cargo_metadata;
//! use std::process::{Stdio, Command};
//! use cargo_metadata::Message;
//!
//! let mut command = Command::new("cargo")
//! .args(&["build", "--message-format=json"])
//! .stdout(Stdio::piped())
//! .spawn()
//! .unwrap();
//!
//! for message in cargo_metadata::parse_messages(command.stdout.take().unwrap()) {
//! match message.unwrap() {
//! Message::CompilerMessage(msg) => {
//! println!("{:?}", msg);
//! },
//! Message::CompilerArtifact(artifact) => {
//! println!("{:?}", artifact);
//! },
//! Message::BuildScriptExecuted(script) => {
//! println!("{:?}", script);
//! },
//! _ => () // Unknown message
//! }
//! }
//!
//! let output = command.wait().expect("Couldn't get cargo's exit status");
//! ```

#[macro_use]
extern crate error_chain;
extern crate semver;
Expand All @@ -140,10 +174,16 @@ use std::str::from_utf8;
use semver::Version;

pub use dependency::{Dependency, DependencyKind};
use diagnostic::Diagnostic;
pub use errors::{Error, ErrorKind, Result};
pub use messages::{
parse_messages, Artifact, ArtifactProfile, BuildScript, CompilerMessage, Message,
};

mod dependency;
mod diagnostic;
mod errors;
mod messages;

/// An "opaque" identifier for a package.
/// It is possible to inspect the `repr` field, if the need arises, but its
Expand Down
109 changes: 109 additions & 0 deletions src/messages.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
use super::{Diagnostic, PackageId, Target};
use serde_json;
use std::fmt;
use std::io::Read;
use std::path::PathBuf;

/// Profile settings used to determine which compiler flags to use for a
/// target.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ArtifactProfile {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would make sense to share these types with cargo. cc #63

We could have a dep graph like
grafik

where each crate reexports its dependencies

/// Optimization level. Possible values are 0-3, s or z.
pub opt_level: String,
/// The amount of debug info. 0 for none, 1 for limited, 2 for full
pub debuginfo: Option<u32>,
/// State of the `cfg(debug_assertions)` directive, enabling macros like
/// `debug_assert!`
pub debug_assertions: bool,
/// State of the overflow checks.
pub overflow_checks: bool,
/// Whether this profile is a test
pub test: bool,
#[doc(hidden)]
#[serde(skip)]
__do_not_match_exhaustively: (),
}

/// A compiler-generated file.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Artifact {
/// The package this artifact belongs to
pub package_id: PackageId,
/// The target this artifact was compiled for
pub target: Target,
/// The profile this artifact was compiled with
pub profile: ArtifactProfile,
/// The enabled features for this artifact
pub features: Vec<String>,
/// The full paths to the generated artifacts
pub filenames: Vec<PathBuf>,
/// If true, then the files were already generated
pub fresh: bool,
#[doc(hidden)]
#[serde(skip)]
__do_not_match_exhaustively: (),
}

/// Message left by the compiler
// TODO: Better name. This one comes from machine_message.rs
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompilerMessage {
/// The package this message belongs to
pub package_id: PackageId,
/// The target this message is aimed at
pub target: Target,
/// The message the compiler sent.
pub message: Diagnostic,
#[doc(hidden)]
#[serde(skip)]
__do_not_match_exhaustively: (),
}

/// Output of a build script execution.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BuildScript {
/// The package this build script execution belongs to
pub package_id: PackageId,
/// The libs to link
pub linked_libs: Vec<PathBuf>,
/// The paths to search when resolving libs
pub linked_paths: Vec<PathBuf>,
/// Various `--cfg` flags to pass to the compiler
pub cfgs: Vec<PathBuf>,
/// The environment variables to add to the compilation
pub env: Vec<(String, String)>,
#[doc(hidden)]
#[serde(skip)]
__do_not_match_exhaustively: (),
}

/// A cargo message
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "reason", rename_all = "kebab-case")]
pub enum Message {
/// The compiler generated an artifact
CompilerArtifact(Artifact),
/// The compiler wants to display a message
CompilerMessage(CompilerMessage),
/// A build script successfully executed.
BuildScriptExecuted(BuildScript),
#[doc(hidden)]
#[serde(other)]
Unknown,
}

impl fmt::Display for CompilerMessage {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.message)
}
}

/// An iterator of Message.
type MessageIterator<R> =
serde_json::StreamDeserializer<'static, serde_json::de::IoRead<R>, Message>;

/// Creates an iterator of Message from a Read outputting a stream of JSON
/// messages. For usage information, look at the top-level documentation.
pub fn parse_messages<R: Read>(input: R) -> MessageIterator<R> {
serde_json::Deserializer::from_reader(input).into_iter::<Message>()
}
Loading