3
3
//! This module provides the [`ExecutionContext`] type, which holds global configuration
4
4
//! relevant during the execution of commands in bootstrap. This includes dry-run
5
5
//! mode, verbosity level, and behavior on failure.
6
+ use std:: panic:: Location ;
7
+ use std:: process:: Child ;
6
8
use std:: sync:: { Arc , Mutex } ;
7
9
8
10
use crate :: core:: config:: DryRun ;
@@ -80,23 +82,24 @@ impl ExecutionContext {
80
82
/// Note: Ideally, you should use one of the BootstrapCommand::run* functions to
81
83
/// execute commands. They internally call this method.
82
84
#[ track_caller]
83
- pub fn run (
85
+ pub fn start < ' a > (
84
86
& self ,
85
- command : & mut BootstrapCommand ,
87
+ command : & ' a mut BootstrapCommand ,
86
88
stdout : OutputMode ,
87
89
stderr : OutputMode ,
88
- ) -> CommandOutput {
90
+ ) -> DeferredCommand < ' a > {
89
91
command. mark_as_executed ( ) ;
92
+
93
+ let created_at = command. get_created_location ( ) ;
94
+ let executed_at = std:: panic:: Location :: caller ( ) ;
95
+
90
96
if self . dry_run ( ) && !command. run_always {
91
- return CommandOutput :: default ( ) ;
97
+ return DeferredCommand { process : None , stdout , stderr , command , executed_at } ;
92
98
}
93
99
94
100
#[ cfg( feature = "tracing" ) ]
95
101
let _run_span = trace_cmd ! ( command) ;
96
102
97
- let created_at = command. get_created_location ( ) ;
98
- let executed_at = std:: panic:: Location :: caller ( ) ;
99
-
100
103
self . verbose ( || {
101
104
println ! ( "running: {command:?} (created at {created_at}, executed at {executed_at})" )
102
105
} ) ;
@@ -105,92 +108,149 @@ impl ExecutionContext {
105
108
cmd. stdout ( stdout. stdio ( ) ) ;
106
109
cmd. stderr ( stderr. stdio ( ) ) ;
107
110
108
- let output = cmd. output ( ) ;
111
+ let child = cmd. spawn ( ) ;
109
112
110
- use std:: fmt:: Write ;
113
+ DeferredCommand { process : Some ( child) , stdout, stderr, command, executed_at }
114
+ }
111
115
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." ) ;
117
147
}
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.
124
195
Expected success, got {}
125
196
Created at: {created_at}
126
197
Executed 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 ) ;
130
203
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
+ }
132
210
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
138
212
}
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 )
141
226
}
142
- output
143
- }
144
- // The command did not even start
227
+ } ,
145
228
Err ( e) => {
229
+ // Failed to spawn the command
230
+ use std:: fmt:: Write ;
231
+
146
232
writeln ! (
147
233
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
150
237
)
151
238
. unwrap ( ) ;
152
- CommandOutput :: did_not_start ( stdout, stderr)
153
- }
154
- } ;
155
239
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 )
179
241
}
180
- exit ! ( 1 ) ;
181
242
} ;
182
243
183
244
if !output. is_success ( ) {
184
- match command. failure_behavior {
245
+ match self . command . failure_behavior {
185
246
BehaviorOnFailure :: DelayFail => {
186
- if self . fail_fast {
187
- fail ( & message, output) ;
247
+ if exec_ctx . fail_fast {
248
+ exec_ctx . fail ( & message, output) ;
188
249
}
189
-
190
- self . add_to_delay_failure ( message) ;
250
+ exec_ctx. add_to_delay_failure ( message) ;
191
251
}
192
252
BehaviorOnFailure :: Exit => {
193
- fail ( & message, output) ;
253
+ exec_ctx . fail ( & message, output) ;
194
254
}
195
255
BehaviorOnFailure :: Ignore => {
196
256
// If failures are allowed, either the error has been printed already
@@ -199,6 +259,7 @@ Executed at: {executed_at}"#,
199
259
}
200
260
}
201
261
}
262
+
202
263
output
203
264
}
204
265
}
0 commit comments