bootstrap/utils/
execution_context.rs1use std::panic::Location;
7use std::process::Child;
8use std::sync::{Arc, Mutex};
9
10use crate::core::config::DryRun;
11#[cfg(feature = "tracing")]
12use crate::trace_cmd;
13use crate::{BehaviorOnFailure, BootstrapCommand, CommandOutput, OutputMode, exit};
14
15#[derive(Clone, Default)]
16pub struct ExecutionContext {
17 dry_run: DryRun,
18 verbose: u8,
19 pub fail_fast: bool,
20 delayed_failures: Arc<Mutex<Vec<String>>>,
21}
22
23impl ExecutionContext {
24 pub fn new() -> Self {
25 ExecutionContext::default()
26 }
27
28 pub fn dry_run(&self) -> bool {
29 match self.dry_run {
30 DryRun::Disabled => false,
31 DryRun::SelfCheck | DryRun::UserSelected => true,
32 }
33 }
34
35 pub fn get_dry_run(&self) -> &DryRun {
36 &self.dry_run
37 }
38
39 pub fn verbose(&self, f: impl Fn()) {
40 if self.is_verbose() {
41 f()
42 }
43 }
44
45 pub fn is_verbose(&self) -> bool {
46 self.verbose > 0
47 }
48
49 pub fn fail_fast(&self) -> bool {
50 self.fail_fast
51 }
52
53 pub fn set_dry_run(&mut self, value: DryRun) {
54 self.dry_run = value;
55 }
56
57 pub fn set_verbose(&mut self, value: u8) {
58 self.verbose = value;
59 }
60
61 pub fn set_fail_fast(&mut self, value: bool) {
62 self.fail_fast = value;
63 }
64
65 pub fn add_to_delay_failure(&self, message: String) {
66 self.delayed_failures.lock().unwrap().push(message);
67 }
68
69 pub fn report_failures_and_exit(&self) {
70 let failures = self.delayed_failures.lock().unwrap();
71 if failures.is_empty() {
72 return;
73 }
74 eprintln!("\n{} command(s) did not execute successfully:\n", failures.len());
75 for failure in &*failures {
76 eprintln!(" - {failure}");
77 }
78 exit!(1);
79 }
80
81 #[track_caller]
85 pub fn start<'a>(
86 &self,
87 command: &'a mut BootstrapCommand,
88 stdout: OutputMode,
89 stderr: OutputMode,
90 ) -> DeferredCommand<'a> {
91 command.mark_as_executed();
92
93 let created_at = command.get_created_location();
94 let executed_at = std::panic::Location::caller();
95
96 if self.dry_run() && !command.run_always {
97 return DeferredCommand { process: None, stdout, stderr, command, executed_at };
98 }
99
100 #[cfg(feature = "tracing")]
101 let _run_span = trace_cmd!(command);
102
103 self.verbose(|| {
104 println!("running: {command:?} (created at {created_at}, executed at {executed_at})")
105 });
106
107 let cmd = command.as_command_mut();
108 cmd.stdout(stdout.stdio());
109 cmd.stderr(stderr.stdio());
110
111 let child = cmd.spawn();
112
113 DeferredCommand { process: Some(child), stdout, stderr, command, executed_at }
114 }
115
116 #[track_caller]
120 pub fn run(
121 &self,
122 command: &mut BootstrapCommand,
123 stdout: OutputMode,
124 stderr: OutputMode,
125 ) -> CommandOutput {
126 self.start(command, stdout, stderr).wait_for_output(self)
127 }
128
129 fn fail(&self, message: &str, output: CommandOutput) -> ! {
130 if self.is_verbose() {
131 println!("{message}");
132 } else {
133 let (stdout, stderr) = (output.stdout_if_present(), output.stderr_if_present());
134 if stdout.is_some() || stderr.is_some() {
138 if let Some(stdout) = output.stdout_if_present().take_if(|s| !s.trim().is_empty()) {
139 println!("STDOUT:\n{stdout}\n");
140 }
141 if let Some(stderr) = output.stderr_if_present().take_if(|s| !s.trim().is_empty()) {
142 println!("STDERR:\n{stderr}\n");
143 }
144 println!("Command has failed. Rerun with -v to see more details.");
145 } else {
146 println!("Command has failed. Rerun with -v to see more details.");
147 }
148 }
149 exit!(1);
150 }
151}
152
153impl AsRef<ExecutionContext> for ExecutionContext {
154 fn as_ref(&self) -> &ExecutionContext {
155 self
156 }
157}
158
159pub struct DeferredCommand<'a> {
160 process: Option<Result<Child, std::io::Error>>,
161 command: &'a mut BootstrapCommand,
162 stdout: OutputMode,
163 stderr: OutputMode,
164 executed_at: &'a Location<'a>,
165}
166
167impl<'a> DeferredCommand<'a> {
168 pub fn wait_for_output(mut self, exec_ctx: impl AsRef<ExecutionContext>) -> CommandOutput {
169 let exec_ctx = exec_ctx.as_ref();
170
171 let process = match self.process.take() {
172 Some(p) => p,
173 None => return CommandOutput::default(),
174 };
175
176 let created_at = self.command.get_created_location();
177 let executed_at = self.executed_at;
178
179 let mut message = String::new();
180
181 let output = match process {
182 Ok(child) => match child.wait_with_output() {
183 Ok(result) if result.status.success() => {
184 CommandOutput::from_output(result, self.stdout, self.stderr)
186 }
187 Ok(result) => {
188 use std::fmt::Write;
190
191 writeln!(
192 message,
193 r#"
194Command {:?} did not execute successfully.
195Expected success, got {}
196Created at: {created_at}
197Executed at: {executed_at}"#,
198 self.command, result.status,
199 )
200 .unwrap();
201
202 let output = CommandOutput::from_output(result, self.stdout, self.stderr);
203
204 if self.stdout.captures() {
205 writeln!(message, "\nSTDOUT ----\n{}", output.stdout().trim()).unwrap();
206 }
207 if self.stderr.captures() {
208 writeln!(message, "\nSTDERR ----\n{}", output.stderr().trim()).unwrap();
209 }
210
211 output
212 }
213 Err(e) => {
214 use std::fmt::Write;
216
217 writeln!(
218 message,
219 "\n\nCommand {:?} did not execute successfully.\
220 \nIt was not possible to execute the command: {e:?}",
221 self.command
222 )
223 .unwrap();
224
225 CommandOutput::did_not_start(self.stdout, self.stderr)
226 }
227 },
228 Err(e) => {
229 use std::fmt::Write;
231
232 writeln!(
233 message,
234 "\n\nCommand {:?} did not execute successfully.\
235 \nIt was not possible to execute the command: {e:?}",
236 self.command
237 )
238 .unwrap();
239
240 CommandOutput::did_not_start(self.stdout, self.stderr)
241 }
242 };
243
244 if !output.is_success() {
245 match self.command.failure_behavior {
246 BehaviorOnFailure::DelayFail => {
247 if exec_ctx.fail_fast {
248 exec_ctx.fail(&message, output);
249 }
250 exec_ctx.add_to_delay_failure(message);
251 }
252 BehaviorOnFailure::Exit => {
253 exec_ctx.fail(&message, output);
254 }
255 BehaviorOnFailure::Ignore => {
256 }
260 }
261 }
262
263 output
264 }
265}