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 ;
67use std:: sync:: { Arc , Mutex } ;
78
89use 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.
124194Expected success, got {}
125195Created at: {created_at}
126196Executed 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, "\n STDOUT ----\n {}" , output. stdout( ) . trim( ) ) . unwrap ( ) ;
138209 }
139- if stderr. captures ( ) {
210+ if self . stderr . captures ( ) {
140211 writeln ! ( message, "\n STDERR ----\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 \n Command {command:?} did not execute successfully.\
149- \n It was not possible to execute the command: {e:?}"
219+ "\n \n Command {:?} did not execute successfully.\
220+ \n It 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