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- #![ allow( warnings) ]
76use std:: collections:: HashMap ;
87use std:: panic:: Location ;
98use std:: process:: Child ;
@@ -37,14 +36,15 @@ enum CommandState<'a> {
3736 stdout : OutputMode ,
3837 stderr : OutputMode ,
3938 executed_at : & ' a Location < ' a > ,
39+ cache_key : Option < CommandCacheKey > ,
4040 } ,
4141}
4242
43- impl CommandCache {
44- pub fn new ( ) -> Self {
45- Self { cache : Mutex :: new ( HashMap :: new ( ) ) }
46- }
43+ pub struct DeferredCommand < ' a > {
44+ state : CommandState < ' a > ,
45+ }
4746
47+ impl CommandCache {
4848 pub fn get ( & self , key : & CommandCacheKey ) -> Option < CommandOutput > {
4949 self . cache . lock ( ) . unwrap ( ) . get ( key) . cloned ( )
5050 }
@@ -122,13 +122,33 @@ impl ExecutionContext {
122122 stdout : OutputMode ,
123123 stderr : OutputMode ,
124124 ) -> DeferredCommand < ' a > {
125+ let cache_key = command. cache_key ( ) ;
126+
127+ if let Some ( cached_output) = cache_key. as_ref ( ) . and_then ( |key| self . command_cache . get ( key) )
128+ {
129+ command. mark_as_executed ( ) ;
130+
131+ self . verbose ( || println ! ( "Cache hit: {command:?}" ) ) ;
132+
133+ return DeferredCommand { state : CommandState :: Cached ( cached_output) } ;
134+ }
135+
125136 command. mark_as_executed ( ) ;
126137
127138 let created_at = command. get_created_location ( ) ;
128139 let executed_at = std:: panic:: Location :: caller ( ) ;
129140
130141 if self . dry_run ( ) && !command. run_in_dry_run {
131- return DeferredCommand { process : None , stdout, stderr, command, executed_at } ;
142+ return DeferredCommand {
143+ state : CommandState :: Deferred {
144+ process : None ,
145+ command,
146+ stdout,
147+ stderr,
148+ executed_at,
149+ cache_key,
150+ } ,
151+ } ;
132152 }
133153
134154 #[ cfg( feature = "tracing" ) ]
@@ -144,7 +164,16 @@ impl ExecutionContext {
144164
145165 let child = cmd. spawn ( ) ;
146166
147- DeferredCommand { process : Some ( child) , stdout, stderr, command, executed_at }
167+ DeferredCommand {
168+ state : CommandState :: Deferred {
169+ process : Some ( child) ,
170+ command,
171+ stdout,
172+ stderr,
173+ executed_at,
174+ cache_key,
175+ } ,
176+ }
148177 }
149178
150179 /// Execute a command and return its output.
@@ -157,29 +186,7 @@ impl ExecutionContext {
157186 stdout : OutputMode ,
158187 stderr : OutputMode ,
159188 ) -> CommandOutput {
160- let cache_key = command. cache_key ( ) ;
161-
162- if let Some ( cached_output) = cache_key. as_ref ( ) . and_then ( |key| self . command_cache . get ( key) )
163- {
164- command. mark_as_executed ( ) ;
165-
166- if self . dry_run ( ) && !command. run_always {
167- return CommandOutput :: default ( ) ;
168- }
169-
170- self . verbose ( || println ! ( "Cache hit: {:?}" , command) ) ;
171- return cached_output;
172- }
173-
174- let output = self . start ( command, stdout, stderr) . wait_for_output ( self ) ;
175-
176- if !self . dry_run ( ) || command. run_always {
177- if let Some ( cache_key) = cache_key {
178- self . command_cache . insert ( cache_key, output. clone ( ) ) ;
179- }
180- }
181-
182- output
189+ self . start ( command, stdout, stderr) . wait_for_output ( self )
183190 }
184191
185192 fn fail ( & self , message : & str , output : CommandOutput ) -> ! {
@@ -212,33 +219,50 @@ impl AsRef<ExecutionContext> for ExecutionContext {
212219 }
213220}
214221
215- pub struct DeferredCommand < ' a > {
216- process : Option < Result < Child , std:: io:: Error > > ,
217- command : & ' a mut BootstrapCommand ,
218- stdout : OutputMode ,
219- stderr : OutputMode ,
220- executed_at : & ' a Location < ' a > ,
221- }
222-
223222impl < ' a > DeferredCommand < ' a > {
224- pub fn wait_for_output ( mut self , exec_ctx : impl AsRef < ExecutionContext > ) -> CommandOutput {
225- let exec_ctx = exec_ctx. as_ref ( ) ;
223+ pub fn wait_for_output ( self , exec_ctx : impl AsRef < ExecutionContext > ) -> CommandOutput {
224+ match self . state {
225+ CommandState :: Cached ( output) => output,
226+ CommandState :: Deferred { process, command, stdout, stderr, executed_at, cache_key } => {
227+ let exec_ctx = exec_ctx. as_ref ( ) ;
228+
229+ let output =
230+ Self :: finish_process ( process, command, stdout, stderr, executed_at, exec_ctx) ;
231+
232+ if ( !exec_ctx. dry_run ( ) || command. run_always )
233+ && let ( Some ( cache_key) , Some ( _) ) = ( & cache_key, output. status ( ) )
234+ {
235+ exec_ctx. command_cache . insert ( cache_key. clone ( ) , output. clone ( ) ) ;
236+ }
237+
238+ output
239+ }
240+ }
241+ }
226242
227- let process = match self . process . take ( ) {
243+ pub fn finish_process (
244+ mut process : Option < Result < Child , std:: io:: Error > > ,
245+ command : & mut BootstrapCommand ,
246+ stdout : OutputMode ,
247+ stderr : OutputMode ,
248+ executed_at : & ' a std:: panic:: Location < ' a > ,
249+ exec_ctx : & ExecutionContext ,
250+ ) -> CommandOutput {
251+ let process = match process. take ( ) {
228252 Some ( p) => p,
229253 None => return CommandOutput :: default ( ) ,
230254 } ;
231255
232- let created_at = self . command . get_created_location ( ) ;
233- let executed_at = self . executed_at ;
256+ let created_at = command. get_created_location ( ) ;
257+ let executed_at = executed_at;
234258
235259 let mut message = String :: new ( ) ;
236260
237261 let output = match process {
238262 Ok ( child) => match child. wait_with_output ( ) {
239263 Ok ( result) if result. status . success ( ) => {
240264 // Successful execution
241- CommandOutput :: from_output ( result, self . stdout , self . stderr )
265+ CommandOutput :: from_output ( result, stdout, stderr)
242266 }
243267 Ok ( result) => {
244268 // Command ran but failed
@@ -247,20 +271,20 @@ impl<'a> DeferredCommand<'a> {
247271 writeln ! (
248272 message,
249273 r#"
250- Command {:?} did not execute successfully.
274+ Command {command :?} did not execute successfully.
251275Expected success, got {}
252276Created at: {created_at}
253277Executed at: {executed_at}"# ,
254- self . command , result. status,
278+ result. status,
255279 )
256280 . unwrap ( ) ;
257281
258- let output = CommandOutput :: from_output ( result, self . stdout , self . stderr ) ;
282+ let output = CommandOutput :: from_output ( result, stdout, stderr) ;
259283
260- if self . stdout . captures ( ) {
284+ if stdout. captures ( ) {
261285 writeln ! ( message, "\n STDOUT ----\n {}" , output. stdout( ) . trim( ) ) . unwrap ( ) ;
262286 }
263- if self . stderr . captures ( ) {
287+ if stderr. captures ( ) {
264288 writeln ! ( message, "\n STDERR ----\n {}" , output. stderr( ) . trim( ) ) . unwrap ( ) ;
265289 }
266290
@@ -272,13 +296,12 @@ Executed at: {executed_at}"#,
272296
273297 writeln ! (
274298 message,
275- "\n \n Command {:?} did not execute successfully.\
276- \n It was not possible to execute the command: {e:?}",
277- self . command
299+ "\n \n Command {command:?} did not execute successfully.\
300+ \n It was not possible to execute the command: {e:?}"
278301 )
279302 . unwrap ( ) ;
280303
281- CommandOutput :: did_not_start ( self . stdout , self . stderr )
304+ CommandOutput :: did_not_start ( stdout, stderr)
282305 }
283306 } ,
284307 Err ( e) => {
@@ -287,18 +310,17 @@ Executed at: {executed_at}"#,
287310
288311 writeln ! (
289312 message,
290- "\n \n Command {:?} did not execute successfully.\
291- \n It was not possible to execute the command: {e:?}",
292- self . command
313+ "\n \n Command {command:?} did not execute successfully.\
314+ \n It was not possible to execute the command: {e:?}"
293315 )
294316 . unwrap ( ) ;
295317
296- CommandOutput :: did_not_start ( self . stdout , self . stderr )
318+ CommandOutput :: did_not_start ( stdout, stderr)
297319 }
298320 } ;
299321
300322 if !output. is_success ( ) {
301- match self . command . failure_behavior {
323+ match command. failure_behavior {
302324 BehaviorOnFailure :: DelayFail => {
303325 if exec_ctx. fail_fast {
304326 exec_ctx. fail ( & message, output) ;
0 commit comments