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:: process:: Child ;
6
7
use std:: sync:: { Arc , Mutex } ;
7
8
8
9
use crate :: core:: config:: DryRun ;
@@ -80,15 +81,16 @@ impl ExecutionContext {
80
81
/// Note: Ideally, you should use one of the BootstrapCommand::run* functions to
81
82
/// execute commands. They internally call this method.
82
83
#[ track_caller]
83
- pub fn run (
84
+ pub fn start < ' a > (
84
85
& self ,
85
- command : & mut BootstrapCommand ,
86
+ command : & ' a mut BootstrapCommand ,
86
87
stdout : OutputMode ,
87
88
stderr : OutputMode ,
88
- ) -> CommandOutput {
89
+ ) -> DeferredCommand < ' a > {
89
90
command. mark_as_executed ( ) ;
91
+
90
92
if self . dry_run ( ) && !command. run_always {
91
- return CommandOutput :: default ( ) ;
93
+ return DeferredCommand :: new ( None , stdout , stderr , command , Arc :: new ( self . clone ( ) ) ) ;
92
94
}
93
95
94
96
#[ cfg( feature = "tracing" ) ]
@@ -105,38 +107,107 @@ impl ExecutionContext {
105
107
cmd. stdout ( stdout. stdio ( ) ) ;
106
108
cmd. stderr ( stderr. stdio ( ) ) ;
107
109
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 ( ) ;
109
179
110
180
use std:: fmt:: Write ;
111
181
112
182
let mut message = String :: new ( ) ;
113
183
let output: CommandOutput = match output {
114
184
// Command has succeeded
115
185
Ok ( output) if output. status . success ( ) => {
116
- CommandOutput :: from_output ( output, stdout, stderr)
186
+ CommandOutput :: from_output ( output, self . stdout , self . stderr )
117
187
}
118
188
// Command has started, but then it failed
119
189
Ok ( output) => {
120
190
writeln ! (
121
191
message,
122
192
r#"
123
- Command {command :?} did not execute successfully.
193
+ Command {:?} did not execute successfully.
124
194
Expected success, got {}
125
195
Created at: {created_at}
126
196
Executed at: {executed_at}"# ,
127
- output. status,
197
+ self . command , output. status,
128
198
)
129
199
. unwrap ( ) ;
130
200
131
- let output: CommandOutput = CommandOutput :: from_output ( output, stdout, stderr) ;
201
+ let output: CommandOutput =
202
+ CommandOutput :: from_output ( output, self . stdout , self . stderr ) ;
132
203
133
204
// If the output mode is OutputMode::Capture, we can now print the output.
134
205
// If it is OutputMode::Print, then the output has already been printed to
135
206
// stdout/stderr, and we thus don't have anything captured to print anyway.
136
- if stdout. captures ( ) {
207
+ if self . stdout . captures ( ) {
137
208
writeln ! ( message, "\n STDOUT ----\n {}" , output. stdout( ) . trim( ) ) . unwrap ( ) ;
138
209
}
139
- if stderr. captures ( ) {
210
+ if self . stderr . captures ( ) {
140
211
writeln ! ( message, "\n STDERR ----\n {}" , output. stderr( ) . trim( ) ) . unwrap ( ) ;
141
212
}
142
213
output
@@ -145,52 +216,26 @@ Executed at: {executed_at}"#,
145
216
Err ( e) => {
146
217
writeln ! (
147
218
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
150
222
)
151
223
. 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 )
179
225
}
180
- exit ! ( 1 ) ;
181
226
} ;
182
227
183
228
if !output. is_success ( ) {
184
- match command. failure_behavior {
229
+ match self . command . failure_behavior {
185
230
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) ;
188
233
}
189
234
190
- self . add_to_delay_failure ( message) ;
235
+ self . exec_ctx . add_to_delay_failure ( message) ;
191
236
}
192
237
BehaviorOnFailure :: Exit => {
193
- fail ( & message, output) ;
238
+ self . exec_ctx . fail ( & message, output) ;
194
239
}
195
240
BehaviorOnFailure :: Ignore => {
196
241
// If failures are allowed, either the error has been printed already
0 commit comments