Skip to content

Commit 42f5288

Browse files
committed
add execution context in bootstrap utils
1 parent 9b0268a commit 42f5288

File tree

1 file changed

+184
-0
lines changed

1 file changed

+184
-0
lines changed
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
#![allow(warnings)]
2+
use std::sync::{Arc, Mutex};
3+
4+
use crate::core::config::DryRun;
5+
use crate::{BehaviorOnFailure, BootstrapCommand, CommandOutput, OutputMode, RefCell, exit};
6+
7+
#[derive(Clone)]
8+
pub struct ExecutionContext {
9+
dry_run: DryRun,
10+
verbose: u8,
11+
pub fail_fast: bool,
12+
pub delayed_failures: Arc<Mutex<Vec<String>>>,
13+
}
14+
15+
impl ExecutionContext {
16+
pub fn new() -> Self {
17+
ExecutionContext {
18+
dry_run: DryRun::Disabled,
19+
verbose: 0,
20+
fail_fast: false,
21+
delayed_failures: Arc::new(Mutex::new(Vec::new())),
22+
}
23+
}
24+
25+
pub fn dry_run(&self) -> bool {
26+
match self.dry_run {
27+
DryRun::Disabled => false,
28+
DryRun::SelfCheck | DryRun::UserSelected => true,
29+
}
30+
}
31+
32+
pub fn verbose(&self, f: impl Fn()) {
33+
if self.is_verbose() {
34+
f()
35+
}
36+
}
37+
38+
pub fn is_verbose(&self) -> bool {
39+
self.verbose > 0
40+
}
41+
42+
pub fn fail_fast(&self) -> bool {
43+
self.fail_fast
44+
}
45+
46+
pub fn set_dry_run(&mut self, value: DryRun) {
47+
self.dry_run = value;
48+
}
49+
50+
pub fn set_verbose(&mut self, value: u8) {
51+
self.verbose = value;
52+
}
53+
54+
pub fn set_fail_fast(&mut self, value: bool) {
55+
self.fail_fast = value;
56+
}
57+
58+
/// Execute a command and return its output.
59+
/// Note: Ideally, you should use one of the BootstrapCommand::run* functions to
60+
/// execute commands. They internally call this method.
61+
#[track_caller]
62+
pub fn run(
63+
&self,
64+
command: &mut BootstrapCommand,
65+
stdout: OutputMode,
66+
stderr: OutputMode,
67+
) -> CommandOutput {
68+
command.mark_as_executed();
69+
if self.dry_run() && !command.run_always {
70+
return CommandOutput::default();
71+
}
72+
73+
#[cfg(feature = "tracing")]
74+
let _run_span = trace_cmd!(command);
75+
76+
let created_at = command.get_created_location();
77+
let executed_at = std::panic::Location::caller();
78+
79+
self.verbose(|| {
80+
println!("running: {command:?} (created at {created_at}, executed at {executed_at})")
81+
});
82+
83+
let cmd = command.as_command_mut();
84+
cmd.stdout(stdout.stdio());
85+
cmd.stderr(stderr.stdio());
86+
87+
let output = cmd.output();
88+
89+
use std::fmt::Write;
90+
91+
let mut message = String::new();
92+
let output: CommandOutput = match output {
93+
// Command has succeeded
94+
Ok(output) if output.status.success() => {
95+
CommandOutput::from_output(output, stdout, stderr)
96+
}
97+
// Command has started, but then it failed
98+
Ok(output) => {
99+
writeln!(
100+
message,
101+
r#"
102+
Command {command:?} did not execute successfully.
103+
Expected success, got {}
104+
Created at: {created_at}
105+
Executed at: {executed_at}"#,
106+
output.status,
107+
)
108+
.unwrap();
109+
110+
let output: CommandOutput = CommandOutput::from_output(output, stdout, stderr);
111+
112+
// If the output mode is OutputMode::Capture, we can now print the output.
113+
// If it is OutputMode::Print, then the output has already been printed to
114+
// stdout/stderr, and we thus don't have anything captured to print anyway.
115+
if stdout.captures() {
116+
writeln!(message, "\nSTDOUT ----\n{}", output.stdout().trim()).unwrap();
117+
}
118+
if stderr.captures() {
119+
writeln!(message, "\nSTDERR ----\n{}", output.stderr().trim()).unwrap();
120+
}
121+
output
122+
}
123+
// The command did not even start
124+
Err(e) => {
125+
writeln!(
126+
message,
127+
"\n\nCommand {command:?} did not execute successfully.\
128+
\nIt was not possible to execute the command: {e:?}"
129+
)
130+
.unwrap();
131+
CommandOutput::did_not_start(stdout, stderr)
132+
}
133+
};
134+
135+
let fail = |message: &str, output: CommandOutput| -> ! {
136+
if self.is_verbose() {
137+
println!("{message}");
138+
} else {
139+
let (stdout, stderr) = (output.stdout_if_present(), output.stderr_if_present());
140+
// If the command captures output, the user would not see any indication that
141+
// it has failed. In this case, print a more verbose error, since to provide more
142+
// context.
143+
if stdout.is_some() || stderr.is_some() {
144+
if let Some(stdout) =
145+
output.stdout_if_present().take_if(|s| !s.trim().is_empty())
146+
{
147+
println!("STDOUT:\n{stdout}\n");
148+
}
149+
if let Some(stderr) =
150+
output.stderr_if_present().take_if(|s| !s.trim().is_empty())
151+
{
152+
println!("STDERR:\n{stderr}\n");
153+
}
154+
println!("Command {command:?} has failed. Rerun with -v to see more details.");
155+
} else {
156+
println!("Command has failed. Rerun with -v to see more details.");
157+
}
158+
}
159+
exit!(1);
160+
};
161+
162+
if !output.is_success() {
163+
match command.failure_behavior {
164+
BehaviorOnFailure::DelayFail => {
165+
if self.fail_fast {
166+
fail(&message, output);
167+
}
168+
169+
let mut failures = self.delayed_failures.lock().unwrap();
170+
failures.push(message);
171+
}
172+
BehaviorOnFailure::Exit => {
173+
fail(&message, output);
174+
}
175+
BehaviorOnFailure::Ignore => {
176+
// If failures are allowed, either the error has been printed already
177+
// (OutputMode::Print) or the user used a capture output mode and wants to
178+
// handle the error output on their own.
179+
}
180+
}
181+
}
182+
output
183+
}
184+
}

0 commit comments

Comments
 (0)