|
| 1 | +//! A `cargo-metadata`-equivalent for non-Cargo build systems. |
| 2 | +use std::{io, process::Command}; |
| 3 | + |
| 4 | +use crossbeam_channel::Sender; |
| 5 | +use paths::Utf8PathBuf; |
| 6 | +use project_model::ProjectJsonData; |
| 7 | +use serde::{Deserialize, Serialize}; |
| 8 | + |
| 9 | +use crate::command::{CommandHandle, ParseFromLine}; |
| 10 | + |
| 11 | +/// A command wrapper for getting a `rust-project.json`. |
| 12 | +/// |
| 13 | +/// This is analogous to `cargo-metadata`, but for non-Cargo build systems. |
| 14 | +pub struct JsonWorkspace { |
| 15 | + command: Vec<String>, |
| 16 | + sender: Sender<DiscoverProjectMessage>, |
| 17 | +} |
| 18 | + |
| 19 | +#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] |
| 20 | +#[serde(rename_all = "camelCase")] |
| 21 | +pub enum JsonArguments { |
| 22 | + Path(Utf8PathBuf), |
| 23 | + Label(String), |
| 24 | +} |
| 25 | + |
| 26 | +impl JsonWorkspace { |
| 27 | + /// Create a new [JsonWorkspace]. |
| 28 | + pub fn new(sender: Sender<DiscoverProjectMessage>, command: Vec<String>) -> Self { |
| 29 | + Self { sender, command } |
| 30 | + } |
| 31 | + |
| 32 | + /// Spawn the command inside [JsonWorkspace] and report progress, if any. |
| 33 | + pub fn spawn(&self, arg: JsonArguments) -> io::Result<JsonWorkspaceHandle> { |
| 34 | + let command = &self.command[0]; |
| 35 | + let args = &self.command[1..]; |
| 36 | + |
| 37 | + let mut cmd = Command::new(command); |
| 38 | + cmd.args(args); |
| 39 | + |
| 40 | + let arg = serde_json::to_string(&arg)?; |
| 41 | + cmd.arg(arg); |
| 42 | + |
| 43 | + Ok(JsonWorkspaceHandle { _handle: CommandHandle::spawn(cmd, self.sender.clone())? }) |
| 44 | + } |
| 45 | +} |
| 46 | + |
| 47 | +/// A handle to a spawned [JsonWorkspace]. |
| 48 | +#[derive(Debug)] |
| 49 | +pub struct JsonWorkspaceHandle { |
| 50 | + _handle: CommandHandle<DiscoverProjectMessage>, |
| 51 | +} |
| 52 | + |
| 53 | +/// An enum containing either progress messages or the materialized rust-project. |
| 54 | +#[derive(Debug, Clone, Deserialize, Serialize)] |
| 55 | +#[serde(tag = "type")] |
| 56 | +pub enum DiscoverProjectMessage { |
| 57 | + Error { message: String, context: Option<String> }, |
| 58 | + Progress { message: String }, |
| 59 | + Finished(FinishedOutput), |
| 60 | +} |
| 61 | + |
| 62 | +#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] |
| 63 | +pub struct FinishedOutput { |
| 64 | + pub project: ProjectJsonData, |
| 65 | + pub buildfile: Utf8PathBuf, |
| 66 | +} |
| 67 | + |
| 68 | +impl ParseFromLine for DiscoverProjectMessage { |
| 69 | + fn from_line(line: &str, _error: &mut String) -> Option<Self> { |
| 70 | + let Ok(value) = serde_json::from_str::<serde_json::Value>(line) else { |
| 71 | + return Some(DiscoverProjectMessage::Error { message: line.to_owned(), context: None }); |
| 72 | + }; |
| 73 | + |
| 74 | + if let Ok(project) = serde_json::from_value::<FinishedOutput>(value.clone()) { |
| 75 | + return Some(DiscoverProjectMessage::Finished(project)); |
| 76 | + } |
| 77 | + |
| 78 | + if let Some(message) = value.pointer("/fields/message") { |
| 79 | + return Some(DiscoverProjectMessage::Progress { |
| 80 | + message: message.as_str().unwrap().to_owned(), |
| 81 | + }); |
| 82 | + } |
| 83 | + |
| 84 | + if let Some(error) = value.pointer("/fields/error") { |
| 85 | + if let Some(source) = value.pointer("/fields/source") { |
| 86 | + return Some(DiscoverProjectMessage::Error { |
| 87 | + message: error.as_str().unwrap().to_owned(), |
| 88 | + context: Some(source.as_str().unwrap().to_owned()), |
| 89 | + }); |
| 90 | + } |
| 91 | + } |
| 92 | + |
| 93 | + None |
| 94 | + } |
| 95 | + |
| 96 | + fn from_eof() -> Option<Self> { |
| 97 | + None |
| 98 | + } |
| 99 | +} |
0 commit comments