Skip to content

Commit

Permalink
Merge pull request #64 from konstin/parse_messages
Browse files Browse the repository at this point in the history
Add message-format=json support, second take
  • Loading branch information
oli-obk committed Jan 24, 2019
2 parents 2681ef8 + b199137 commit 85ecce7
Show file tree
Hide file tree
Showing 6 changed files with 345 additions and 18 deletions.
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,
}

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.

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?
///
/// 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>,
/// Associated diagnostic messages.
pub children: Vec<Diagnostic>,
/// The message as rustc would render it
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 {
/// 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

0 comments on commit 85ecce7

Please sign in to comment.