| use std::io; |
| use std::io::BufRead; |
| use std::io::Read; |
| use std::process; |
| |
| use crate::error::*; |
| use crate::format; |
| |
| /// Messages returned from a cargo sub-command. |
| pub struct CommandMessages(InnerCommandMessages); |
| |
| struct InnerCommandMessages { |
| done: bool, |
| child: process::Child, |
| stdout: io::BufReader<process::ChildStdout>, |
| stderr: io::BufReader<process::ChildStderr>, |
| } |
| |
| impl CommandMessages { |
| /// Run the command, allowing iteration over ndjson messages. |
| pub fn with_command(mut cmd: process::Command) -> CargoResult<Self> { |
| let mut child = cmd |
| .stdout(process::Stdio::piped()) |
| .stderr(process::Stdio::piped()) |
| .spawn() |
| .map_err(|e| CargoError::new(ErrorKind::InvalidCommand).set_cause(e))?; |
| let stdout = child.stdout.take().expect("piped above"); |
| let stdout = io::BufReader::new(stdout); |
| let stderr = child.stderr.take().expect("piped above"); |
| let stderr = io::BufReader::new(stderr); |
| let msgs = InnerCommandMessages { |
| done: false, |
| child, |
| stdout, |
| stderr, |
| }; |
| Ok(CommandMessages(msgs)) |
| } |
| |
| #[inline] |
| fn next_msg(&mut self) -> CargoResult<Option<Message>> { |
| #![allow(clippy::branches_sharing_code)] |
| |
| let mut content = String::new(); |
| let len = self |
| .0 |
| .stdout |
| .read_line(&mut content) |
| .map_err(|e| CargoError::new(ErrorKind::InvalidOutput).set_cause(e))?; |
| if 0 < len { |
| Ok(Some(Message(content))) |
| } else { |
| let status = self |
| .0 |
| .child |
| .wait() |
| .map_err(|e| CargoError::new(ErrorKind::InvalidOutput).set_cause(e))?; |
| if !status.success() && !self.0.done { |
| self.0.done = true; |
| |
| let mut data = vec![]; |
| self.0 |
| .stderr |
| .read_to_end(&mut data) |
| .map_err(|e| CargoError::new(ErrorKind::InvalidOutput).set_cause(e))?; |
| let err = CargoError::new(ErrorKind::CommandFailed) |
| .set_context(String::from_utf8_lossy(&data)); |
| Err(err) |
| } else { |
| self.0.done = true; |
| Ok(None) |
| } |
| } |
| } |
| } |
| |
| impl Drop for CommandMessages { |
| fn drop(&mut self) { |
| if !self.0.done { |
| let _ = self.0.child.wait(); |
| } |
| } |
| } |
| |
| impl Iterator for CommandMessages { |
| type Item = CargoResult<Message>; |
| |
| #[inline] |
| fn next(&mut self) -> Option<CargoResult<Message>> { |
| match self.next_msg() { |
| Ok(Some(x)) => Some(Ok(x)), |
| Ok(None) => None, |
| Err(e) => Some(Err(e)), |
| } |
| } |
| } |
| |
| /// An individual message from a cargo sub-command. |
| #[derive(Debug, Clone, PartialEq, Eq)] |
| pub struct Message(String); |
| |
| impl Message { |
| /// Deserialize the message. |
| pub fn decode(&self) -> CargoResult<format::Message<'_>> { |
| self.decode_custom() |
| } |
| |
| /// Deserialize the message. |
| pub fn decode_custom<'a, T>(&'a self) -> CargoResult<T> |
| where |
| T: serde::Deserialize<'a>, |
| { |
| let data = serde_json::from_str(self.0.as_str()) |
| .map_err(|e| CargoError::new(ErrorKind::InvalidOutput).set_cause(e))?; |
| Ok(data) |
| } |
| } |