ninja_xtask/commands/
mod.rs1use std::{
2 fmt::Debug,
3 io::{self, Read},
4 process::{Child, Output},
5 thread::{self, JoinHandle},
6};
7
8use serde_json::{Value, json};
9
10mod stage;
11pub use stage::*;
12
13mod build;
14pub use build::*;
15
16use crate::{CheckFlags, Exit, WithJson};
17
18#[derive(Debug)]
19pub struct Cmd {
20 pub name: &'static str,
21 pub result: Result<Output, io::Error>,
22 pub flags: CheckFlags,
23}
24
25pub trait CmdExt {
26 fn into_cmd(self, name: &'static str, checkflags: Option<CheckFlags>) -> Cmd;
27}
28
29impl CmdExt for Result<Output, io::Error> {
30 fn into_cmd(self, name: &'static str, checkflags: Option<CheckFlags>) -> Cmd {
31 Cmd {
32 name,
33 result: self,
34 flags: checkflags.unwrap_or_default(),
35 }
36 }
37}
38
39impl From<Cmd> for Exit<WithJson<()>> {
40 fn from(cmd: Cmd) -> Self {
41 let Cmd {
42 name: task,
43 result: did_it_spawn,
44 flags,
45 } = cmd;
46
47 let output = match did_it_spawn {
48 Ok(output) => output,
49 Err(err_spawning) => {
50 let json = flags.contains(CheckFlags::JSON).then(|| {
51 json!({
52 "task": task,
53 "status": "failed to spawn",
54 "error": &err_spawning.to_string(),
55 })
56 });
57 let msg = format!("{task} failed: {err_spawning}");
58 return match json {
59 Some(json) => Exit::IO(WithJson {
60 value: String::new(),
61 json: Some(json),
62 }),
63 None => Self::IO(WithJson {
64 value: msg,
65 json: None,
66 }),
67 };
68 }
69 };
70
71 let status = output.status;
72 let json = flags.contains(CheckFlags::JSON).then(|| {
73 let payload = String::from_utf8_lossy(&output.stdout)
74 .lines()
75 .filter(|line| line.starts_with("{"))
76 .map(serde_json::from_str::<Value>)
77 .map(|json| json.unwrap_or_else(|err| json!({"unparsable": &err.to_string()})))
78 .filter(|json| {
80 json.as_object().is_some_and(|fields| {
81 fields.get("reason").is_some_and(|reason| {
82 [Some("build-finished"), Some("compiler-message")]
83 .contains(&reason.as_str())
84 }) || fields.contains_key("event") })
86 })
87 .collect::<Value>();
88 json!({
89 "task": task,
90 "status": &status.to_string(),
91 "payload": payload,
92 })
93 });
94 let stdout = String::from_utf8_lossy(&output.stdout);
95 let stderr = String::from_utf8_lossy(&output.stderr);
96
97 match (status.success(), json) {
98 (true, Some(json)) => Exit::Ok(WithJson {
99 value: (),
100 json: Some(json),
101 }),
102 (true, None) => {
103 println!("{task}: OK");
104 Self::Ok(WithJson {
105 value: (),
106 json: None,
107 })
108 }
109 (false, Some(json)) => Exit::Error(WithJson {
110 value: String::new(),
111 json: Some(json),
112 }),
113 (false, None) => Self::Error(WithJson {
114 value: format!(
115 "====== {task} exited with {status} ======\n-- stdout: --\n{stdout}\n\n-- stderr: --\n{stderr}",
116 status = output.status
117 ),
118 json: None,
119 }),
120 }
121 }
122}
123
124#[derive(Debug)]
125pub struct Spawned {
126 pub name: &'static str,
127 pub child: Result<Child, io::Error>,
128 pub stdout: JoinHandle<Vec<u8>>,
129 pub stderr: JoinHandle<Vec<u8>>,
130 pub flags: CheckFlags,
131}
132
133impl Spawned {
134 pub fn wait(self) -> Cmd {
135 match self.child {
136 Ok(mut child) => {
137 let status = child.wait();
138 let stdout = self.stdout.join().unwrap();
139 let stderr = self.stderr.join().unwrap();
140 match status {
141 Ok(exit_status) => {
142 let output = Output {
143 status: exit_status,
144 stdout,
145 stderr,
146 };
147 Ok(output).into_cmd(self.name, Some(self.flags))
148 }
149 Err(error_waiting) => Cmd {
150 name: self.name,
151 result: Err(error_waiting),
152 flags: self.flags,
153 },
154 }
155 }
156 Err(error_spawning) => {
157 let _ = self.stdout.join().unwrap();
158 let _ = self.stderr.join().unwrap();
159 Cmd {
160 name: self.name,
161 result: Err(error_spawning),
162 flags: self.flags,
163 }
164 }
165 }
166 }
167}
168
169pub trait SpawnedExt {
170 fn into_spawned(self, name: &'static str, flags: Option<CheckFlags>) -> Spawned;
171}
172
173impl SpawnedExt for Result<Child, io::Error> {
174 fn into_spawned(mut self, name: &'static str, flags: Option<CheckFlags>) -> Spawned {
175 let stdout_pipe = self.as_mut().ok().and_then(|child| child.stdout.take());
176 let stderr_pipe = self.as_mut().ok().and_then(|child| child.stderr.take());
177
178 let stdout_reader = thread::spawn(|| {
179 let mut buf = Vec::<u8>::with_capacity(65536);
180 if let Some(mut stdout) = stdout_pipe {
181 stdout.read_to_end(&mut buf).unwrap(); }
183 buf
184 });
185
186 let stderr_reader = thread::spawn(|| {
187 let mut buf = Vec::<u8>::with_capacity(65536);
188 if let Some(mut stderr) = stderr_pipe {
189 stderr.read_to_end(&mut buf).unwrap(); }
191 buf
192 });
193
194 Spawned {
195 name,
196 child: self,
197 stdout: stdout_reader,
198 stderr: stderr_reader,
199 flags: flags.unwrap_or_default(),
200 }
201 }
202}
203
204impl FromIterator<Spawned> for Exit<WithJson<()>> {
205 fn from_iter<I: IntoIterator<Item = Spawned>>(spawns: I) -> Self {
206 spawns.into_iter().map(Exit::from).collect()
207 }
208}
209
210impl From<Spawned> for Exit<WithJson<()>> {
211 fn from(spawn: Spawned) -> Self {
212 spawn.wait().into()
213 }
214}