diff --git a/src/lib.rs b/src/lib.rs index 8a204cdc..5e864d91 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,7 +56,8 @@ //! .spawn() //! .unwrap(); //! -//! for message in cargo_metadata::parse_messages(command.stdout.take().unwrap()) { +//! let reader = std::io::BufReader::new(command.stdout.take().unwrap()); +//! for message in cargo_metadata::Message::parse_stream(reader) { //! match message.unwrap() { //! Message::CompilerMessage(msg) => { //! println!("{:?}", msg); @@ -95,8 +96,10 @@ use semver::Version; pub use dependency::{Dependency, DependencyKind}; use diagnostic::Diagnostic; pub use errors::{Error, Result}; +#[allow(deprecated)] +pub use messages::parse_messages; pub use messages::{ - parse_messages, Artifact, ArtifactProfile, BuildFinished, BuildScript, CompilerMessage, Message, + Artifact, ArtifactProfile, BuildFinished, BuildScript, CompilerMessage, Message, MessageIter, }; mod dependency; @@ -319,9 +322,12 @@ pub struct Package { impl Package { /// Full path to the license file if one is present in the manifest pub fn license_file(&self) -> Option { - self.license_file - .as_ref() - .map(|file| self.manifest_path.parent().unwrap_or(&self.manifest_path).join(file)) + self.license_file.as_ref().map(|file| { + self.manifest_path + .parent() + .unwrap_or(&self.manifest_path) + .join(file) + }) } /// Full path to the readme file if one is present in the manifest @@ -493,7 +499,7 @@ impl MetadataCommand { /// Parses `cargo metadata` output. `data` must have been /// produced by a command built with `cargo_command`. - pub fn parse>(data : T) -> Result { + pub fn parse>(data: T) -> Result { let meta = serde_json::from_str(data.as_ref())?; Ok(meta) } diff --git a/src/messages.rs b/src/messages.rs index a5bc1616..720abe26 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -1,7 +1,7 @@ use super::{Diagnostic, PackageId, Target}; use serde_json; use std::fmt; -use std::io::Read; +use std::io::{self, BufRead, Lines, Read}; use std::path::PathBuf; /// Profile settings used to determine which compiler flags to use for a @@ -110,23 +110,51 @@ pub enum Message { /// This is emitted at the end of the build as the last message. /// Added in Rust 1.44. BuildFinished(BuildFinished), + /// A line of text which isn't a cargo or compiler message. + /// Line separator is not included + TextLine(String), #[doc(hidden)] #[serde(other)] Unknown, } +impl 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_stream(input: R) -> MessageIter { + MessageIter { + lines: input.lines(), + } + } +} + impl fmt::Display for CompilerMessage { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.message) } } +/// An iterator of Messages. +pub struct MessageIter { + lines: Lines, +} + +impl Iterator for MessageIter { + type Item = io::Result; + fn next(&mut self) -> Option { + let line = self.lines.next()?; + let message = line.map(|it| serde_json::from_str(&it).unwrap_or(Message::TextLine(it))); + Some(message) + } +} + /// An iterator of Message. type MessageIterator = serde_json::StreamDeserializer<'static, serde_json::de::IoRead, Message>; /// Creates an iterator of Message from a Read outputting a stream of JSON /// messages. For usage information, look at the top-level documentation. +#[deprecated(note = "Use Message::parse_stream instead")] pub fn parse_messages(input: R) -> MessageIterator { serde_json::Deserializer::from_reader(input).into_iter::() } diff --git a/tests/test_samples.rs b/tests/test_samples.rs index 3ba730c4..e4c6e19b 100644 --- a/tests/test_samples.rs +++ b/tests/test_samples.rs @@ -474,3 +474,24 @@ fn current_dir() { let namedep = meta.packages.iter().find(|p| p.name == "namedep").unwrap(); assert!(namedep.name.starts_with("namedep")); } + +#[test] +fn parse_stream_is_robust() { + // Proc macros can print stuff to stdout, which naturally breaks JSON messages. + // Let's check that we don't die horribly in this case, and report an error. + let json_output = r##"{"reason":"compiler-artifact","package_id":"chatty 0.1.0 (path+file:///chatty-macro/chatty)","target":{"kind":["proc-macro"],"crate_types":["proc-macro"],"name":"chatty","src_path":"/chatty-macro/chatty/src/lib.rs","edition":"2018","doctest":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/chatty-macro/target/debug/deps/libchatty-f2adcff24cdf3bb2.so"],"executable":null,"fresh":false} +Evil proc macro was here! +{"reason":"compiler-artifact","package_id":"chatty-macro 0.1.0 (path+file:///chatty-macro)","target":{"kind":["lib"],"crate_types":["lib"],"name":"chatty-macro","src_path":"/chatty-macro/src/lib.rs","edition":"2018","doctest":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/chatty-macro/target/debug/libchatty_macro.rlib","/chatty-macro/target/debug/deps/libchatty_macro-cb5956ed52a11fb6.rmeta"],"executable":null,"fresh":false} +"##; + let mut n_messages = 0; + let mut text = String::new(); + for message in cargo_metadata::Message::parse_stream(json_output.as_bytes()) { + let message = message.unwrap(); + match message { + cargo_metadata::Message::TextLine(line) => text = line, + _ => n_messages += 1, + } + } + assert_eq!(n_messages, 2); + assert_eq!(text, "Evil proc macro was here!"); +}