Skip to content

Commit 7889332

Browse files
committed
add deferred command in execution context and update run method
1 parent 55d4364 commit 7889332

File tree

1 file changed

+91
-46
lines changed

1 file changed

+91
-46
lines changed

src/bootstrap/src/utils/execution_context.rs

Lines changed: 91 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
//! This module provides the [`ExecutionContext`] type, which holds global configuration
44
//! relevant during the execution of commands in bootstrap. This includes dry-run
55
//! mode, verbosity level, and behavior on failure.
6+
use std::process::Child;
67
use std::sync::{Arc, Mutex};
78

89
use crate::core::config::DryRun;
@@ -80,15 +81,16 @@ impl ExecutionContext {
8081
/// Note: Ideally, you should use one of the BootstrapCommand::run* functions to
8182
/// execute commands. They internally call this method.
8283
#[track_caller]
83-
pub fn run(
84+
pub fn start<'a>(
8485
&self,
85-
command: &mut BootstrapCommand,
86+
command: &'a mut BootstrapCommand,
8687
stdout: OutputMode,
8788
stderr: OutputMode,
88-
) -> CommandOutput {
89+
) -> DeferredCommand<'a> {
8990
command.mark_as_executed();
91+
9092
if self.dry_run() && !command.run_always {
91-
return CommandOutput::default();
93+
return DeferredCommand::new(None, stdout, stderr, command, Arc::new(self.clone()));
9294
}
9395

9496
#[cfg(feature = "tracing")]
@@ -105,38 +107,107 @@ impl ExecutionContext {
105107
cmd.stdout(stdout.stdio());
106108
cmd.stderr(stderr.stdio());
107109

108-
let output = cmd.output();
110+
let child = cmd.spawn().unwrap();
111+
112+
DeferredCommand::new(Some(child), stdout, stderr, command, Arc::new(self.clone()))
113+
}
114+
115+
/// Execute a command and return its output.
116+
/// Note: Ideally, you should use one of the BootstrapCommand::run* functions to
117+
/// execute commands. They internally call this method.
118+
#[track_caller]
119+
pub fn run(
120+
&self,
121+
command: &mut BootstrapCommand,
122+
stdout: OutputMode,
123+
stderr: OutputMode,
124+
) -> CommandOutput {
125+
self.start(command, stdout, stderr).wait_for_output()
126+
}
127+
128+
fn fail(&self, message: &str, output: CommandOutput) -> ! {
129+
if self.is_verbose() {
130+
println!("{message}");
131+
} else {
132+
let (stdout, stderr) = (output.stdout_if_present(), output.stderr_if_present());
133+
// If the command captures output, the user would not see any indication that
134+
// it has failed. In this case, print a more verbose error, since to provide more
135+
// context.
136+
if stdout.is_some() || stderr.is_some() {
137+
if let Some(stdout) = output.stdout_if_present().take_if(|s| !s.trim().is_empty()) {
138+
println!("STDOUT:\n{stdout}\n");
139+
}
140+
if let Some(stderr) = output.stderr_if_present().take_if(|s| !s.trim().is_empty()) {
141+
println!("STDERR:\n{stderr}\n");
142+
}
143+
println!("Command has failed. Rerun with -v to see more details.");
144+
} else {
145+
println!("Command has failed. Rerun with -v to see more details.");
146+
}
147+
}
148+
exit!(1);
149+
}
150+
}
151+
152+
pub struct DeferredCommand<'a> {
153+
process: Option<Child>,
154+
command: &'a mut BootstrapCommand,
155+
stdout: OutputMode,
156+
stderr: OutputMode,
157+
exec_ctx: Arc<ExecutionContext>,
158+
}
159+
160+
impl<'a> DeferredCommand<'a> {
161+
pub fn new(
162+
child: Option<Child>,
163+
stdout: OutputMode,
164+
stderr: OutputMode,
165+
command: &'a mut BootstrapCommand,
166+
exec_ctx: Arc<ExecutionContext>,
167+
) -> Self {
168+
DeferredCommand { process: child, stdout, stderr, command, exec_ctx }
169+
}
170+
171+
pub fn wait_for_output(mut self) -> CommandOutput {
172+
if self.process.is_none() {
173+
return CommandOutput::default();
174+
}
175+
let output = self.process.take().unwrap().wait_with_output();
176+
177+
let created_at = self.command.get_created_location();
178+
let executed_at = std::panic::Location::caller();
109179

110180
use std::fmt::Write;
111181

112182
let mut message = String::new();
113183
let output: CommandOutput = match output {
114184
// Command has succeeded
115185
Ok(output) if output.status.success() => {
116-
CommandOutput::from_output(output, stdout, stderr)
186+
CommandOutput::from_output(output, self.stdout, self.stderr)
117187
}
118188
// Command has started, but then it failed
119189
Ok(output) => {
120190
writeln!(
121191
message,
122192
r#"
123-
Command {command:?} did not execute successfully.
193+
Command {:?} did not execute successfully.
124194
Expected success, got {}
125195
Created at: {created_at}
126196
Executed at: {executed_at}"#,
127-
output.status,
197+
self.command, output.status,
128198
)
129199
.unwrap();
130200

131-
let output: CommandOutput = CommandOutput::from_output(output, stdout, stderr);
201+
let output: CommandOutput =
202+
CommandOutput::from_output(output, self.stdout, self.stderr);
132203

133204
// If the output mode is OutputMode::Capture, we can now print the output.
134205
// If it is OutputMode::Print, then the output has already been printed to
135206
// stdout/stderr, and we thus don't have anything captured to print anyway.
136-
if stdout.captures() {
207+
if self.stdout.captures() {
137208
writeln!(message, "\nSTDOUT ----\n{}", output.stdout().trim()).unwrap();
138209
}
139-
if stderr.captures() {
210+
if self.stderr.captures() {
140211
writeln!(message, "\nSTDERR ----\n{}", output.stderr().trim()).unwrap();
141212
}
142213
output
@@ -145,52 +216,26 @@ Executed at: {executed_at}"#,
145216
Err(e) => {
146217
writeln!(
147218
message,
148-
"\n\nCommand {command:?} did not execute successfully.\
149-
\nIt was not possible to execute the command: {e:?}"
219+
"\n\nCommand {:?} did not execute successfully.\
220+
\nIt was not possible to execute the command: {e:?}",
221+
self.command
150222
)
151223
.unwrap();
152-
CommandOutput::did_not_start(stdout, stderr)
153-
}
154-
};
155-
156-
let fail = |message: &str, output: CommandOutput| -> ! {
157-
if self.is_verbose() {
158-
println!("{message}");
159-
} else {
160-
let (stdout, stderr) = (output.stdout_if_present(), output.stderr_if_present());
161-
// If the command captures output, the user would not see any indication that
162-
// it has failed. In this case, print a more verbose error, since to provide more
163-
// context.
164-
if stdout.is_some() || stderr.is_some() {
165-
if let Some(stdout) =
166-
output.stdout_if_present().take_if(|s| !s.trim().is_empty())
167-
{
168-
println!("STDOUT:\n{stdout}\n");
169-
}
170-
if let Some(stderr) =
171-
output.stderr_if_present().take_if(|s| !s.trim().is_empty())
172-
{
173-
println!("STDERR:\n{stderr}\n");
174-
}
175-
println!("Command {command:?} has failed. Rerun with -v to see more details.");
176-
} else {
177-
println!("Command has failed. Rerun with -v to see more details.");
178-
}
224+
CommandOutput::did_not_start(self.stdout, self.stderr)
179225
}
180-
exit!(1);
181226
};
182227

183228
if !output.is_success() {
184-
match command.failure_behavior {
229+
match self.command.failure_behavior {
185230
BehaviorOnFailure::DelayFail => {
186-
if self.fail_fast {
187-
fail(&message, output);
231+
if self.exec_ctx.fail_fast {
232+
self.exec_ctx.fail(&message, output);
188233
}
189234

190-
self.add_to_delay_failure(message);
235+
self.exec_ctx.add_to_delay_failure(message);
191236
}
192237
BehaviorOnFailure::Exit => {
193-
fail(&message, output);
238+
self.exec_ctx.fail(&message, output);
194239
}
195240
BehaviorOnFailure::Ignore => {
196241
// If failures are allowed, either the error has been printed already

0 commit comments

Comments
 (0)