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:: panic:: Location ;
7+ use std:: process:: Child ;
68use std:: sync:: { Arc , Mutex } ;
79
810use crate :: core:: config:: DryRun ;
@@ -80,23 +82,24 @@ impl ExecutionContext {
8082 /// Note: Ideally, you should use one of the BootstrapCommand::run* functions to
8183 /// execute commands. They internally call this method.
8284 #[ track_caller]
83- pub fn run (
85+ pub fn start < ' a > (
8486 & self ,
85- command : & mut BootstrapCommand ,
87+ command : & ' a mut BootstrapCommand ,
8688 stdout : OutputMode ,
8789 stderr : OutputMode ,
88- ) -> CommandOutput {
90+ ) -> DeferredCommand < ' a > {
8991 command. mark_as_executed ( ) ;
92+
93+ let created_at = command. get_created_location ( ) ;
94+ let executed_at = std:: panic:: Location :: caller ( ) ;
95+
9096 if self . dry_run ( ) && !command. run_always {
91- return CommandOutput :: default ( ) ;
97+ return DeferredCommand { process : None , stdout , stderr , command , executed_at } ;
9298 }
9399
94100 #[ cfg( feature = "tracing" ) ]
95101 let _run_span = trace_cmd ! ( command) ;
96102
97- let created_at = command. get_created_location ( ) ;
98- let executed_at = std:: panic:: Location :: caller ( ) ;
99-
100103 self . verbose ( || {
101104 println ! ( "running: {command:?} (created at {created_at}, executed at {executed_at})" )
102105 } ) ;
@@ -105,92 +108,149 @@ impl ExecutionContext {
105108 cmd. stdout ( stdout. stdio ( ) ) ;
106109 cmd. stderr ( stderr. stdio ( ) ) ;
107110
108- let output = cmd. output ( ) ;
111+ let child = cmd. spawn ( ) ;
109112
110- use std:: fmt:: Write ;
113+ DeferredCommand { process : Some ( child) , stdout, stderr, command, executed_at }
114+ }
111115
112- let mut message = String :: new ( ) ;
113- let output: CommandOutput = match output {
114- // Command has succeeded
115- Ok ( output) if output. status . success ( ) => {
116- CommandOutput :: from_output ( output, stdout, stderr)
116+ /// Execute a command and return its output.
117+ /// Note: Ideally, you should use one of the BootstrapCommand::run* functions to
118+ /// execute commands. They internally call this method.
119+ #[ 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 the command captures output, the user would not see any indication that
135+ // it has failed. In this case, print a more verbose error, since to provide more
136+ // context.
137+ 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." ) ;
117147 }
118- // Command has started, but then it failed
119- Ok ( output) => {
120- writeln ! (
121- message,
122- r#"
123- Command {command:?} did not execute successfully.
148+ }
149+ exit ! ( 1 ) ;
150+ }
151+ }
152+
153+ impl AsRef < ExecutionContext > for ExecutionContext {
154+ fn as_ref ( & self ) -> & ExecutionContext {
155+ self
156+ }
157+ }
158+
159+ pub 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+
167+ impl < ' 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+ // Successful execution
185+ CommandOutput :: from_output ( result, self . stdout , self . stderr )
186+ }
187+ Ok ( result) => {
188+ // Command ran but failed
189+ use std:: fmt:: Write ;
190+
191+ writeln ! (
192+ message,
193+ r#"
194+ Command {:?} did not execute successfully.
124195Expected success, got {}
125196Created at: {created_at}
126197Executed at: {executed_at}"# ,
127- output. status,
128- )
129- . unwrap ( ) ;
198+ self . command, result. status,
199+ )
200+ . unwrap ( ) ;
201+
202+ let output = CommandOutput :: from_output ( result, self . stdout , self . stderr ) ;
130203
131- let output: CommandOutput = CommandOutput :: from_output ( output, stdout, stderr) ;
204+ if self . stdout . captures ( ) {
205+ writeln ! ( message, "\n STDOUT ----\n {}" , output. stdout( ) . trim( ) ) . unwrap ( ) ;
206+ }
207+ if self . stderr . captures ( ) {
208+ writeln ! ( message, "\n STDERR ----\n {}" , output. stderr( ) . trim( ) ) . unwrap ( ) ;
209+ }
132210
133- // If the output mode is OutputMode::Capture, we can now print the output.
134- // If it is OutputMode::Print, then the output has already been printed to
135- // stdout/stderr, and we thus don't have anything captured to print anyway.
136- if stdout. captures ( ) {
137- writeln ! ( message, "\n STDOUT ----\n {}" , output. stdout( ) . trim( ) ) . unwrap ( ) ;
211+ output
138212 }
139- if stderr. captures ( ) {
140- writeln ! ( message, "\n STDERR ----\n {}" , output. stderr( ) . trim( ) ) . unwrap ( ) ;
213+ Err ( e) => {
214+ // Failed to wait for output
215+ use std:: fmt:: Write ;
216+
217+ writeln ! (
218+ message,
219+ "\n \n Command {:?} did not execute successfully.\
220+ \n It was not possible to execute the command: {e:?}",
221+ self . command
222+ )
223+ . unwrap ( ) ;
224+
225+ CommandOutput :: did_not_start ( self . stdout , self . stderr )
141226 }
142- output
143- }
144- // The command did not even start
227+ } ,
145228 Err ( e) => {
229+ // Failed to spawn the command
230+ use std:: fmt:: Write ;
231+
146232 writeln ! (
147233 message,
148- "\n \n Command {command:?} did not execute successfully.\
149- \n It was not possible to execute the command: {e:?}"
234+ "\n \n Command {:?} did not execute successfully.\
235+ \n It was not possible to execute the command: {e:?}",
236+ self . command
150237 )
151238 . unwrap ( ) ;
152- CommandOutput :: did_not_start ( stdout, stderr)
153- }
154- } ;
155239
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- }
240+ CommandOutput :: did_not_start ( self . stdout , self . stderr )
179241 }
180- exit ! ( 1 ) ;
181242 } ;
182243
183244 if !output. is_success ( ) {
184- match command. failure_behavior {
245+ match self . command . failure_behavior {
185246 BehaviorOnFailure :: DelayFail => {
186- if self . fail_fast {
187- fail ( & message, output) ;
247+ if exec_ctx . fail_fast {
248+ exec_ctx . fail ( & message, output) ;
188249 }
189-
190- self . add_to_delay_failure ( message) ;
250+ exec_ctx. add_to_delay_failure ( message) ;
191251 }
192252 BehaviorOnFailure :: Exit => {
193- fail ( & message, output) ;
253+ exec_ctx . fail ( & message, output) ;
194254 }
195255 BehaviorOnFailure :: Ignore => {
196256 // If failures are allowed, either the error has been printed already
@@ -199,6 +259,7 @@ Executed at: {executed_at}"#,
199259 }
200260 }
201261 }
262+
202263 output
203264 }
204265}
0 commit comments