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