1use std::ffi::OsStr;
7use std::fmt::{Debug, Formatter};
8use std::path::Path;
9use std::process::{Command, CommandArgs, CommandEnvs, ExitStatus, Output, Stdio};
10
11use build_helper::ci::CiEnv;
12use build_helper::drop_bomb::DropBomb;
13
14use crate::Build;
15
16#[derive(Debug, Copy, Clone)]
18pub enum BehaviorOnFailure {
19 Exit,
21 DelayFail,
23 Ignore,
25}
26
27#[derive(Debug, Copy, Clone)]
30pub enum OutputMode {
31 Print,
33 Capture,
35}
36
37impl OutputMode {
38 pub fn captures(&self) -> bool {
39 match self {
40 OutputMode::Print => false,
41 OutputMode::Capture => true,
42 }
43 }
44
45 pub fn stdio(&self) -> Stdio {
46 match self {
47 OutputMode::Print => Stdio::inherit(),
48 OutputMode::Capture => Stdio::piped(),
49 }
50 }
51}
52
53pub struct BootstrapCommand {
67 command: Command,
68 pub failure_behavior: BehaviorOnFailure,
69 pub run_always: bool,
71 drop_bomb: DropBomb,
74}
75
76impl BootstrapCommand {
77 #[track_caller]
78 pub fn new<S: AsRef<OsStr>>(program: S) -> Self {
79 Command::new(program).into()
80 }
81
82 pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self {
83 self.command.arg(arg.as_ref());
84 self
85 }
86
87 pub fn args<I, S>(&mut self, args: I) -> &mut Self
88 where
89 I: IntoIterator<Item = S>,
90 S: AsRef<OsStr>,
91 {
92 self.command.args(args);
93 self
94 }
95
96 pub fn env<K, V>(&mut self, key: K, val: V) -> &mut Self
97 where
98 K: AsRef<OsStr>,
99 V: AsRef<OsStr>,
100 {
101 self.command.env(key, val);
102 self
103 }
104
105 pub fn get_envs(&self) -> CommandEnvs<'_> {
106 self.command.get_envs()
107 }
108
109 pub fn get_args(&self) -> CommandArgs<'_> {
110 self.command.get_args()
111 }
112
113 pub fn env_remove<K: AsRef<OsStr>>(&mut self, key: K) -> &mut Self {
114 self.command.env_remove(key);
115 self
116 }
117
118 pub fn current_dir<P: AsRef<Path>>(&mut self, dir: P) -> &mut Self {
119 self.command.current_dir(dir);
120 self
121 }
122
123 #[must_use]
124 pub fn delay_failure(self) -> Self {
125 Self { failure_behavior: BehaviorOnFailure::DelayFail, ..self }
126 }
127
128 #[allow(dead_code)]
129 pub fn fail_fast(self) -> Self {
130 Self { failure_behavior: BehaviorOnFailure::Exit, ..self }
131 }
132
133 #[must_use]
134 pub fn allow_failure(self) -> Self {
135 Self { failure_behavior: BehaviorOnFailure::Ignore, ..self }
136 }
137
138 pub fn run_always(&mut self) -> &mut Self {
139 self.run_always = true;
140 self
141 }
142
143 #[track_caller]
146 pub fn run(&mut self, builder: &Build) -> bool {
147 builder.run(self, OutputMode::Print, OutputMode::Print).is_success()
148 }
149
150 #[track_caller]
152 pub fn run_capture(&mut self, builder: &Build) -> CommandOutput {
153 builder.run(self, OutputMode::Capture, OutputMode::Capture)
154 }
155
156 #[track_caller]
158 pub fn run_capture_stdout(&mut self, builder: &Build) -> CommandOutput {
159 builder.run(self, OutputMode::Capture, OutputMode::Print)
160 }
161
162 pub fn as_command_mut(&mut self) -> &mut Command {
165 self.mark_as_executed();
168 &mut self.command
169 }
170
171 pub fn mark_as_executed(&mut self) {
174 self.drop_bomb.defuse();
175 }
176
177 pub fn get_created_location(&self) -> std::panic::Location<'static> {
179 self.drop_bomb.get_created_location()
180 }
181
182 pub fn force_coloring_in_ci(&mut self) {
184 if CiEnv::is_ci() {
185 self.env("TERM", "xterm").args(["--color", "always"]);
191 }
192 }
193}
194
195impl Debug for BootstrapCommand {
196 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
197 write!(f, "{:?}", self.command)?;
198 write!(f, " (failure_mode={:?})", self.failure_behavior)
199 }
200}
201
202impl From<Command> for BootstrapCommand {
203 #[track_caller]
204 fn from(command: Command) -> Self {
205 let program = command.get_program().to_owned();
206
207 Self {
208 command,
209 failure_behavior: BehaviorOnFailure::Exit,
210 run_always: false,
211 drop_bomb: DropBomb::arm(program),
212 }
213 }
214}
215
216enum CommandStatus {
218 Finished(ExitStatus),
220 DidNotStart,
222}
223
224#[track_caller]
227#[must_use]
228pub fn command<S: AsRef<OsStr>>(program: S) -> BootstrapCommand {
229 BootstrapCommand::new(program)
230}
231
232pub struct CommandOutput {
234 status: CommandStatus,
235 stdout: Option<Vec<u8>>,
236 stderr: Option<Vec<u8>>,
237}
238
239impl CommandOutput {
240 #[must_use]
241 pub fn did_not_start(stdout: OutputMode, stderr: OutputMode) -> Self {
242 Self {
243 status: CommandStatus::DidNotStart,
244 stdout: match stdout {
245 OutputMode::Print => None,
246 OutputMode::Capture => Some(vec![]),
247 },
248 stderr: match stderr {
249 OutputMode::Print => None,
250 OutputMode::Capture => Some(vec![]),
251 },
252 }
253 }
254
255 #[must_use]
256 pub fn from_output(output: Output, stdout: OutputMode, stderr: OutputMode) -> Self {
257 Self {
258 status: CommandStatus::Finished(output.status),
259 stdout: match stdout {
260 OutputMode::Print => None,
261 OutputMode::Capture => Some(output.stdout),
262 },
263 stderr: match stderr {
264 OutputMode::Print => None,
265 OutputMode::Capture => Some(output.stderr),
266 },
267 }
268 }
269
270 #[must_use]
271 pub fn is_success(&self) -> bool {
272 match self.status {
273 CommandStatus::Finished(status) => status.success(),
274 CommandStatus::DidNotStart => false,
275 }
276 }
277
278 #[must_use]
279 pub fn is_failure(&self) -> bool {
280 !self.is_success()
281 }
282
283 #[allow(dead_code)]
284 pub fn status(&self) -> Option<ExitStatus> {
285 match self.status {
286 CommandStatus::Finished(status) => Some(status),
287 CommandStatus::DidNotStart => None,
288 }
289 }
290
291 #[must_use]
292 pub fn stdout(&self) -> String {
293 String::from_utf8(
294 self.stdout.clone().expect("Accessing stdout of a command that did not capture stdout"),
295 )
296 .expect("Cannot parse process stdout as UTF-8")
297 }
298
299 #[must_use]
300 pub fn stdout_if_present(&self) -> Option<String> {
301 self.stdout.as_ref().and_then(|s| String::from_utf8(s.clone()).ok())
302 }
303
304 #[must_use]
305 pub fn stdout_if_ok(&self) -> Option<String> {
306 if self.is_success() { Some(self.stdout()) } else { None }
307 }
308
309 #[must_use]
310 pub fn stderr(&self) -> String {
311 String::from_utf8(
312 self.stderr.clone().expect("Accessing stderr of a command that did not capture stderr"),
313 )
314 .expect("Cannot parse process stderr as UTF-8")
315 }
316
317 #[must_use]
318 pub fn stderr_if_present(&self) -> Option<String> {
319 self.stderr.as_ref().and_then(|s| String::from_utf8(s.clone()).ok())
320 }
321}
322
323impl Default for CommandOutput {
324 fn default() -> Self {
325 Self {
326 status: CommandStatus::Finished(ExitStatus::default()),
327 stdout: Some(vec![]),
328 stderr: Some(vec![]),
329 }
330 }
331}
332
333#[allow(unused)]
336pub trait FormatShortCmd {
337 fn format_short_cmd(&self) -> String;
338}
339
340impl FormatShortCmd for BootstrapCommand {
341 fn format_short_cmd(&self) -> String {
342 self.command.format_short_cmd()
343 }
344}
345
346impl FormatShortCmd for Command {
347 fn format_short_cmd(&self) -> String {
348 let program = Path::new(self.get_program());
349 let mut line = vec![program.file_name().unwrap().to_str().unwrap()];
350 line.extend(self.get_args().map(|arg| arg.to_str().unwrap()));
351 line.join(" ")
352 }
353}