1use std::collections::HashMap;
11use std::ffi::{OsStr, OsString};
12use std::fmt::{Debug, Formatter};
13use std::fs::File;
14use std::hash::Hash;
15use std::io::{BufWriter, Write};
16use std::panic::Location;
17use std::path::Path;
18use std::process;
19use std::process::{
20 Child, ChildStderr, ChildStdout, Command, CommandArgs, CommandEnvs, ExitStatus, Output, Stdio,
21};
22use std::sync::{Arc, Mutex};
23use std::time::{Duration, Instant};
24
25use build_helper::ci::CiEnv;
26use build_helper::drop_bomb::DropBomb;
27use build_helper::exit;
28
29use crate::PathBuf;
30use crate::core::config::DryRun;
31#[cfg(feature = "tracing")]
32use crate::trace_cmd;
33
34#[derive(Debug, Copy, Clone)]
36pub enum BehaviorOnFailure {
37 Exit,
39 DelayFail,
41 Ignore,
43}
44
45#[derive(Debug, Copy, Clone)]
48pub enum OutputMode {
49 Print,
51 Capture,
53}
54
55impl OutputMode {
56 pub fn captures(&self) -> bool {
57 match self {
58 OutputMode::Print => false,
59 OutputMode::Capture => true,
60 }
61 }
62
63 pub fn stdio(&self) -> Stdio {
64 match self {
65 OutputMode::Print => Stdio::inherit(),
66 OutputMode::Capture => Stdio::piped(),
67 }
68 }
69}
70
71#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)]
72pub struct CommandFingerprint {
73 program: OsString,
74 args: Vec<OsString>,
75 envs: Vec<(OsString, Option<OsString>)>,
76 cwd: Option<PathBuf>,
77}
78
79impl CommandFingerprint {
80 pub fn format_short_cmd(&self) -> String {
83 let program = Path::new(&self.program);
84 let mut line = vec![program.file_name().unwrap().to_str().unwrap().to_owned()];
85 line.extend(self.args.iter().map(|arg| arg.to_string_lossy().into_owned()));
86 line.extend(self.cwd.iter().map(|p| p.to_string_lossy().into_owned()));
87 line.join(" ")
88 }
89}
90
91#[derive(Default, Clone)]
92pub struct CommandProfile {
93 pub traces: Vec<ExecutionTrace>,
94}
95
96#[derive(Default)]
97pub struct CommandProfiler {
98 stats: Mutex<HashMap<CommandFingerprint, CommandProfile>>,
99}
100
101impl CommandProfiler {
102 pub fn record_execution(&self, key: CommandFingerprint, start_time: Instant) {
103 let mut stats = self.stats.lock().unwrap();
104 let entry = stats.entry(key).or_default();
105 entry.traces.push(ExecutionTrace::Executed { duration: start_time.elapsed() });
106 }
107
108 pub fn record_cache_hit(&self, key: CommandFingerprint) {
109 let mut stats = self.stats.lock().unwrap();
110 let entry = stats.entry(key).or_default();
111 entry.traces.push(ExecutionTrace::CacheHit);
112 }
113
114 pub fn report_summary(&self, start_time: Instant) {
115 let pid = process::id();
116 let filename = format!("bootstrap-profile-{pid}.txt");
117
118 let file = match File::create(&filename) {
119 Ok(f) => f,
120 Err(e) => {
121 eprintln!("Failed to create profiler output file: {e}");
122 return;
123 }
124 };
125
126 let mut writer = BufWriter::new(file);
127 let stats = self.stats.lock().unwrap();
128
129 let mut entries: Vec<_> = stats
130 .iter()
131 .map(|(key, profile)| {
132 let max_duration = profile
133 .traces
134 .iter()
135 .filter_map(|trace| match trace {
136 ExecutionTrace::Executed { duration, .. } => Some(*duration),
137 _ => None,
138 })
139 .max();
140
141 (key, profile, max_duration)
142 })
143 .collect();
144
145 entries.sort_by(|a, b| b.2.cmp(&a.2));
146
147 let total_bootstrap_duration = start_time.elapsed();
148
149 let total_fingerprints = entries.len();
150 let mut total_cache_hits = 0;
151 let mut total_execution_duration = Duration::ZERO;
152 let mut total_saved_duration = Duration::ZERO;
153
154 for (key, profile, max_duration) in &entries {
155 writeln!(writer, "Command: {:?}", key.format_short_cmd()).unwrap();
156
157 let mut hits = 0;
158 let mut runs = 0;
159 let mut command_total_duration = Duration::ZERO;
160
161 for trace in &profile.traces {
162 match trace {
163 ExecutionTrace::CacheHit => {
164 hits += 1;
165 }
166 ExecutionTrace::Executed { duration, .. } => {
167 runs += 1;
168 command_total_duration += *duration;
169 }
170 }
171 }
172
173 total_cache_hits += hits;
174 total_execution_duration += command_total_duration;
175 total_saved_duration += command_total_duration * hits as u32;
181
182 let command_vs_bootstrap = if total_bootstrap_duration > Duration::ZERO {
183 100.0 * command_total_duration.as_secs_f64()
184 / total_bootstrap_duration.as_secs_f64()
185 } else {
186 0.0
187 };
188
189 let duration_str = match max_duration {
190 Some(d) => format!("{d:.2?}"),
191 None => "-".into(),
192 };
193
194 writeln!(
195 writer,
196 "Summary: {runs} run(s), {hits} hit(s), max_duration={duration_str} total_duration: {command_total_duration:.2?} ({command_vs_bootstrap:.2?}% of total)\n"
197 )
198 .unwrap();
199 }
200
201 let overhead_time = total_bootstrap_duration
202 .checked_sub(total_execution_duration)
203 .unwrap_or(Duration::ZERO);
204
205 writeln!(writer, "\n=== Aggregated Summary ===").unwrap();
206 writeln!(writer, "Total unique commands (fingerprints): {total_fingerprints}").unwrap();
207 writeln!(writer, "Total time spent in command executions: {total_execution_duration:.2?}")
208 .unwrap();
209 writeln!(writer, "Total bootstrap time: {total_bootstrap_duration:.2?}").unwrap();
210 writeln!(writer, "Time spent outside command executions: {overhead_time:.2?}").unwrap();
211 writeln!(writer, "Total cache hits: {total_cache_hits}").unwrap();
212 writeln!(writer, "Estimated time saved due to cache hits: {total_saved_duration:.2?}")
213 .unwrap();
214
215 println!("Command profiler report saved to {filename}");
216 }
217}
218
219#[derive(Clone)]
220pub enum ExecutionTrace {
221 CacheHit,
222 Executed { duration: Duration },
223}
224
225pub struct BootstrapCommand {
242 command: Command,
243 pub failure_behavior: BehaviorOnFailure,
244 pub run_in_dry_run: bool,
246 drop_bomb: DropBomb,
249 should_cache: bool,
250}
251
252impl<'a> BootstrapCommand {
253 #[track_caller]
254 pub fn new<S: AsRef<OsStr>>(program: S) -> Self {
255 Command::new(program).into()
256 }
257 pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self {
258 self.command.arg(arg.as_ref());
259 self
260 }
261
262 pub fn do_not_cache(&mut self) -> &mut Self {
263 self.should_cache = false;
264 self
265 }
266
267 pub fn args<I, S>(&mut self, args: I) -> &mut Self
268 where
269 I: IntoIterator<Item = S>,
270 S: AsRef<OsStr>,
271 {
272 self.command.args(args);
273 self
274 }
275
276 pub fn env<K, V>(&mut self, key: K, val: V) -> &mut Self
277 where
278 K: AsRef<OsStr>,
279 V: AsRef<OsStr>,
280 {
281 self.command.env(key, val);
282 self
283 }
284
285 pub fn get_envs(&self) -> CommandEnvs<'_> {
286 self.command.get_envs()
287 }
288
289 pub fn get_args(&self) -> CommandArgs<'_> {
290 self.command.get_args()
291 }
292
293 pub fn env_remove<K: AsRef<OsStr>>(&mut self, key: K) -> &mut Self {
294 self.command.env_remove(key);
295 self
296 }
297
298 pub fn current_dir<P: AsRef<Path>>(&mut self, dir: P) -> &mut Self {
299 self.command.current_dir(dir);
300 self
301 }
302
303 pub fn stdin(&mut self, stdin: std::process::Stdio) -> &mut Self {
304 self.command.stdin(stdin);
305 self
306 }
307
308 #[must_use]
309 pub fn delay_failure(self) -> Self {
310 Self { failure_behavior: BehaviorOnFailure::DelayFail, ..self }
311 }
312
313 pub fn fail_fast(self) -> Self {
314 Self { failure_behavior: BehaviorOnFailure::Exit, ..self }
315 }
316
317 #[must_use]
318 pub fn allow_failure(self) -> Self {
319 Self { failure_behavior: BehaviorOnFailure::Ignore, ..self }
320 }
321
322 pub fn run_in_dry_run(&mut self) -> &mut Self {
323 self.run_in_dry_run = true;
324 self
325 }
326
327 #[track_caller]
330 pub fn run(&mut self, exec_ctx: impl AsRef<ExecutionContext>) -> bool {
331 exec_ctx.as_ref().run(self, OutputMode::Print, OutputMode::Print).is_success()
332 }
333
334 #[track_caller]
336 pub fn run_capture(&mut self, exec_ctx: impl AsRef<ExecutionContext>) -> CommandOutput {
337 exec_ctx.as_ref().run(self, OutputMode::Capture, OutputMode::Capture)
338 }
339
340 #[track_caller]
342 pub fn run_capture_stdout(&mut self, exec_ctx: impl AsRef<ExecutionContext>) -> CommandOutput {
343 exec_ctx.as_ref().run(self, OutputMode::Capture, OutputMode::Print)
344 }
345
346 #[track_caller]
348 pub fn start_capture(
349 &'a mut self,
350 exec_ctx: impl AsRef<ExecutionContext>,
351 ) -> DeferredCommand<'a> {
352 exec_ctx.as_ref().start(self, OutputMode::Capture, OutputMode::Capture)
353 }
354
355 #[track_caller]
357 pub fn start_capture_stdout(
358 &'a mut self,
359 exec_ctx: impl AsRef<ExecutionContext>,
360 ) -> DeferredCommand<'a> {
361 exec_ctx.as_ref().start(self, OutputMode::Capture, OutputMode::Print)
362 }
363
364 #[track_caller]
367 pub fn stream_capture_stdout(
368 &'a mut self,
369 exec_ctx: impl AsRef<ExecutionContext>,
370 ) -> Option<StreamingCommand> {
371 exec_ctx.as_ref().stream(self, OutputMode::Capture, OutputMode::Print)
372 }
373
374 pub fn mark_as_executed(&mut self) {
377 self.drop_bomb.defuse();
378 }
379
380 pub fn get_created_location(&self) -> std::panic::Location<'static> {
382 self.drop_bomb.get_created_location()
383 }
384
385 pub fn force_coloring_in_ci(&mut self) {
387 if CiEnv::is_ci() {
388 self.env("TERM", "xterm").args(["--color", "always"]);
394 }
395 }
396
397 pub fn fingerprint(&self) -> CommandFingerprint {
398 let command = &self.command;
399 CommandFingerprint {
400 program: command.get_program().into(),
401 args: command.get_args().map(OsStr::to_os_string).collect(),
402 envs: command
403 .get_envs()
404 .map(|(k, v)| (k.to_os_string(), v.map(|val| val.to_os_string())))
405 .collect(),
406 cwd: command.get_current_dir().map(Path::to_path_buf),
407 }
408 }
409}
410
411impl Debug for BootstrapCommand {
412 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
413 write!(f, "{:?}", self.command)?;
414 write!(f, " (failure_mode={:?})", self.failure_behavior)
415 }
416}
417
418impl From<Command> for BootstrapCommand {
419 #[track_caller]
420 fn from(command: Command) -> Self {
421 let program = command.get_program().to_owned();
422 Self {
423 should_cache: true,
424 command,
425 failure_behavior: BehaviorOnFailure::Exit,
426 run_in_dry_run: false,
427 drop_bomb: DropBomb::arm(program),
428 }
429 }
430}
431
432#[derive(Clone, PartialEq)]
434enum CommandStatus {
435 Finished(ExitStatus),
437 DidNotStart,
439}
440
441#[track_caller]
444#[must_use]
445pub fn command<S: AsRef<OsStr>>(program: S) -> BootstrapCommand {
446 BootstrapCommand::new(program)
447}
448
449#[derive(Clone, PartialEq)]
451pub struct CommandOutput {
452 status: CommandStatus,
453 stdout: Option<Vec<u8>>,
454 stderr: Option<Vec<u8>>,
455}
456
457impl CommandOutput {
458 #[must_use]
459 pub fn did_not_start(stdout: OutputMode, stderr: OutputMode) -> Self {
460 Self {
461 status: CommandStatus::DidNotStart,
462 stdout: match stdout {
463 OutputMode::Print => None,
464 OutputMode::Capture => Some(vec![]),
465 },
466 stderr: match stderr {
467 OutputMode::Print => None,
468 OutputMode::Capture => Some(vec![]),
469 },
470 }
471 }
472
473 #[must_use]
474 pub fn from_output(output: Output, stdout: OutputMode, stderr: OutputMode) -> Self {
475 Self {
476 status: CommandStatus::Finished(output.status),
477 stdout: match stdout {
478 OutputMode::Print => None,
479 OutputMode::Capture => Some(output.stdout),
480 },
481 stderr: match stderr {
482 OutputMode::Print => None,
483 OutputMode::Capture => Some(output.stderr),
484 },
485 }
486 }
487
488 #[must_use]
489 pub fn is_success(&self) -> bool {
490 match self.status {
491 CommandStatus::Finished(status) => status.success(),
492 CommandStatus::DidNotStart => false,
493 }
494 }
495
496 #[must_use]
497 pub fn is_failure(&self) -> bool {
498 !self.is_success()
499 }
500
501 pub fn status(&self) -> Option<ExitStatus> {
502 match self.status {
503 CommandStatus::Finished(status) => Some(status),
504 CommandStatus::DidNotStart => None,
505 }
506 }
507
508 #[must_use]
509 pub fn stdout(&self) -> String {
510 String::from_utf8(
511 self.stdout.clone().expect("Accessing stdout of a command that did not capture stdout"),
512 )
513 .expect("Cannot parse process stdout as UTF-8")
514 }
515
516 #[must_use]
517 pub fn stdout_if_present(&self) -> Option<String> {
518 self.stdout.as_ref().and_then(|s| String::from_utf8(s.clone()).ok())
519 }
520
521 #[must_use]
522 pub fn stdout_if_ok(&self) -> Option<String> {
523 if self.is_success() { Some(self.stdout()) } else { None }
524 }
525
526 #[must_use]
527 pub fn stderr(&self) -> String {
528 String::from_utf8(
529 self.stderr.clone().expect("Accessing stderr of a command that did not capture stderr"),
530 )
531 .expect("Cannot parse process stderr as UTF-8")
532 }
533
534 #[must_use]
535 pub fn stderr_if_present(&self) -> Option<String> {
536 self.stderr.as_ref().and_then(|s| String::from_utf8(s.clone()).ok())
537 }
538}
539
540impl Default for CommandOutput {
541 fn default() -> Self {
542 Self {
543 status: CommandStatus::Finished(ExitStatus::default()),
544 stdout: Some(vec![]),
545 stderr: Some(vec![]),
546 }
547 }
548}
549
550#[derive(Clone, Default)]
551pub struct ExecutionContext {
552 dry_run: DryRun,
553 verbose: u8,
554 pub fail_fast: bool,
555 delayed_failures: Arc<Mutex<Vec<String>>>,
556 command_cache: Arc<CommandCache>,
557 profiler: Arc<CommandProfiler>,
558}
559
560#[derive(Default)]
561pub struct CommandCache {
562 cache: Mutex<HashMap<CommandFingerprint, CommandOutput>>,
563}
564
565enum CommandState<'a> {
566 Cached(CommandOutput),
567 Deferred {
568 process: Option<Result<Child, std::io::Error>>,
569 command: &'a mut BootstrapCommand,
570 stdout: OutputMode,
571 stderr: OutputMode,
572 executed_at: &'a Location<'a>,
573 fingerprint: CommandFingerprint,
574 start_time: Instant,
575 #[cfg(feature = "tracing")]
576 _span_guard: tracing::span::EnteredSpan,
577 },
578}
579
580pub struct StreamingCommand {
581 child: Child,
582 pub stdout: Option<ChildStdout>,
583 pub stderr: Option<ChildStderr>,
584 fingerprint: CommandFingerprint,
585 start_time: Instant,
586 #[cfg(feature = "tracing")]
587 _span_guard: tracing::span::EnteredSpan,
588}
589
590#[must_use]
591pub struct DeferredCommand<'a> {
592 state: CommandState<'a>,
593}
594
595impl CommandCache {
596 pub fn get(&self, key: &CommandFingerprint) -> Option<CommandOutput> {
597 self.cache.lock().unwrap().get(key).cloned()
598 }
599
600 pub fn insert(&self, key: CommandFingerprint, output: CommandOutput) {
601 self.cache.lock().unwrap().insert(key, output);
602 }
603}
604
605impl ExecutionContext {
606 pub fn new() -> Self {
607 ExecutionContext::default()
608 }
609
610 pub fn dry_run(&self) -> bool {
611 match self.dry_run {
612 DryRun::Disabled => false,
613 DryRun::SelfCheck | DryRun::UserSelected => true,
614 }
615 }
616
617 pub fn profiler(&self) -> &CommandProfiler {
618 &self.profiler
619 }
620
621 pub fn get_dry_run(&self) -> &DryRun {
622 &self.dry_run
623 }
624
625 pub fn verbose(&self, f: impl Fn()) {
626 if self.is_verbose() {
627 f()
628 }
629 }
630
631 pub fn is_verbose(&self) -> bool {
632 self.verbose > 0
633 }
634
635 pub fn fail_fast(&self) -> bool {
636 self.fail_fast
637 }
638
639 pub fn set_dry_run(&mut self, value: DryRun) {
640 self.dry_run = value;
641 }
642
643 pub fn set_verbose(&mut self, value: u8) {
644 self.verbose = value;
645 }
646
647 pub fn set_fail_fast(&mut self, value: bool) {
648 self.fail_fast = value;
649 }
650
651 pub fn add_to_delay_failure(&self, message: String) {
652 self.delayed_failures.lock().unwrap().push(message);
653 }
654
655 pub fn report_failures_and_exit(&self) {
656 let failures = self.delayed_failures.lock().unwrap();
657 if failures.is_empty() {
658 return;
659 }
660 eprintln!("\n{} command(s) did not execute successfully:\n", failures.len());
661 for failure in &*failures {
662 eprintln!(" - {failure}");
663 }
664 exit!(1);
665 }
666
667 #[track_caller]
671 pub fn start<'a>(
672 &self,
673 command: &'a mut BootstrapCommand,
674 stdout: OutputMode,
675 stderr: OutputMode,
676 ) -> DeferredCommand<'a> {
677 let fingerprint = command.fingerprint();
678
679 #[cfg(feature = "tracing")]
680 let span_guard = trace_cmd!(command);
681
682 if let Some(cached_output) = self.command_cache.get(&fingerprint) {
683 command.mark_as_executed();
684 self.verbose(|| println!("Cache hit: {command:?}"));
685 self.profiler.record_cache_hit(fingerprint);
686 return DeferredCommand { state: CommandState::Cached(cached_output) };
687 }
688
689 let created_at = command.get_created_location();
690 let executed_at = std::panic::Location::caller();
691
692 if self.dry_run() && !command.run_in_dry_run {
693 return DeferredCommand {
694 state: CommandState::Deferred {
695 process: None,
696 command,
697 stdout,
698 stderr,
699 executed_at,
700 fingerprint,
701 start_time: Instant::now(),
702 #[cfg(feature = "tracing")]
703 _span_guard: span_guard,
704 },
705 };
706 }
707
708 self.verbose(|| {
709 println!("running: {command:?} (created at {created_at}, executed at {executed_at})")
710 });
711
712 let cmd = &mut command.command;
713 cmd.stdout(stdout.stdio());
714 cmd.stderr(stderr.stdio());
715
716 let start_time = Instant::now();
717
718 let child = cmd.spawn();
719
720 DeferredCommand {
721 state: CommandState::Deferred {
722 process: Some(child),
723 command,
724 stdout,
725 stderr,
726 executed_at,
727 fingerprint,
728 start_time,
729 #[cfg(feature = "tracing")]
730 _span_guard: span_guard,
731 },
732 }
733 }
734
735 #[track_caller]
739 pub fn run(
740 &self,
741 command: &mut BootstrapCommand,
742 stdout: OutputMode,
743 stderr: OutputMode,
744 ) -> CommandOutput {
745 self.start(command, stdout, stderr).wait_for_output(self)
746 }
747
748 fn fail(&self, message: &str, output: CommandOutput) -> ! {
749 if self.is_verbose() {
750 println!("{message}");
751 } else {
752 let (stdout, stderr) = (output.stdout_if_present(), output.stderr_if_present());
753 if stdout.is_some() || stderr.is_some() {
757 if let Some(stdout) = output.stdout_if_present().take_if(|s| !s.trim().is_empty()) {
758 println!("STDOUT:\n{stdout}\n");
759 }
760 if let Some(stderr) = output.stderr_if_present().take_if(|s| !s.trim().is_empty()) {
761 println!("STDERR:\n{stderr}\n");
762 }
763 println!("Command has failed. Rerun with -v to see more details.");
764 } else {
765 println!("Command has failed. Rerun with -v to see more details.");
766 }
767 }
768 exit!(1);
769 }
770
771 pub fn stream(
775 &self,
776 command: &mut BootstrapCommand,
777 stdout: OutputMode,
778 stderr: OutputMode,
779 ) -> Option<StreamingCommand> {
780 command.mark_as_executed();
781 if !command.run_in_dry_run && self.dry_run() {
782 return None;
783 }
784
785 #[cfg(feature = "tracing")]
786 let span_guard = trace_cmd!(command);
787
788 let start_time = Instant::now();
789 let fingerprint = command.fingerprint();
790 let cmd = &mut command.command;
791 cmd.stdout(stdout.stdio());
792 cmd.stderr(stderr.stdio());
793 let child = cmd.spawn();
794 let mut child = match child {
795 Ok(child) => child,
796 Err(e) => panic!("failed to execute command: {cmd:?}\nERROR: {e}"),
797 };
798
799 let stdout = child.stdout.take();
800 let stderr = child.stderr.take();
801 Some(StreamingCommand {
802 child,
803 stdout,
804 stderr,
805 fingerprint,
806 start_time,
807 #[cfg(feature = "tracing")]
808 _span_guard: span_guard,
809 })
810 }
811}
812
813impl AsRef<ExecutionContext> for ExecutionContext {
814 fn as_ref(&self) -> &ExecutionContext {
815 self
816 }
817}
818
819impl StreamingCommand {
820 pub fn wait(
821 mut self,
822 exec_ctx: impl AsRef<ExecutionContext>,
823 ) -> Result<ExitStatus, std::io::Error> {
824 let exec_ctx = exec_ctx.as_ref();
825 let output = self.child.wait();
826 exec_ctx.profiler().record_execution(self.fingerprint, self.start_time);
827 output
828 }
829}
830
831impl<'a> DeferredCommand<'a> {
832 pub fn wait_for_output(self, exec_ctx: impl AsRef<ExecutionContext>) -> CommandOutput {
833 match self.state {
834 CommandState::Cached(output) => output,
835 CommandState::Deferred {
836 process,
837 command,
838 stdout,
839 stderr,
840 executed_at,
841 fingerprint,
842 start_time,
843 #[cfg(feature = "tracing")]
844 _span_guard,
845 } => {
846 let exec_ctx = exec_ctx.as_ref();
847
848 let output =
849 Self::finish_process(process, command, stdout, stderr, executed_at, exec_ctx);
850
851 #[cfg(feature = "tracing")]
852 drop(_span_guard);
853
854 if (!exec_ctx.dry_run() || command.run_in_dry_run)
855 && output.status().is_some()
856 && command.should_cache
857 {
858 exec_ctx.command_cache.insert(fingerprint.clone(), output.clone());
859 exec_ctx.profiler.record_execution(fingerprint.clone(), start_time);
860 }
861
862 output
863 }
864 }
865 }
866
867 pub fn finish_process(
868 mut process: Option<Result<Child, std::io::Error>>,
869 command: &mut BootstrapCommand,
870 stdout: OutputMode,
871 stderr: OutputMode,
872 executed_at: &'a std::panic::Location<'a>,
873 exec_ctx: &ExecutionContext,
874 ) -> CommandOutput {
875 command.mark_as_executed();
876
877 let process = match process.take() {
878 Some(p) => p,
879 None => return CommandOutput::default(),
880 };
881
882 let created_at = command.get_created_location();
883
884 let mut message = String::new();
885
886 let output = match process {
887 Ok(child) => match child.wait_with_output() {
888 Ok(result) if result.status.success() => {
889 CommandOutput::from_output(result, stdout, stderr)
891 }
892 Ok(result) => {
893 use std::fmt::Write;
895
896 writeln!(
897 message,
898 r#"
899Command {command:?} did not execute successfully.
900Expected success, got {}
901Created at: {created_at}
902Executed at: {executed_at}"#,
903 result.status,
904 )
905 .unwrap();
906
907 let output = CommandOutput::from_output(result, stdout, stderr);
908
909 if stdout.captures() {
910 writeln!(message, "\nSTDOUT ----\n{}", output.stdout().trim()).unwrap();
911 }
912 if stderr.captures() {
913 writeln!(message, "\nSTDERR ----\n{}", output.stderr().trim()).unwrap();
914 }
915
916 output
917 }
918 Err(e) => {
919 use std::fmt::Write;
921
922 writeln!(
923 message,
924 "\n\nCommand {command:?} did not execute successfully.\
925 \nIt was not possible to execute the command: {e:?}"
926 )
927 .unwrap();
928
929 CommandOutput::did_not_start(stdout, stderr)
930 }
931 },
932 Err(e) => {
933 use std::fmt::Write;
935
936 writeln!(
937 message,
938 "\n\nCommand {command:?} did not execute successfully.\
939 \nIt was not possible to execute the command: {e:?}"
940 )
941 .unwrap();
942
943 CommandOutput::did_not_start(stdout, stderr)
944 }
945 };
946
947 if !output.is_success() {
948 match command.failure_behavior {
949 BehaviorOnFailure::DelayFail => {
950 if exec_ctx.fail_fast {
951 exec_ctx.fail(&message, output);
952 }
953 exec_ctx.add_to_delay_failure(message);
954 }
955 BehaviorOnFailure::Exit => {
956 exec_ctx.fail(&message, output);
957 }
958 BehaviorOnFailure::Ignore => {
959 }
963 }
964 }
965
966 output
967 }
968}