@@ -37,9 +37,14 @@ enum CommandState<'a> {
3737 stdout : OutputMode ,
3838 stderr : OutputMode ,
3939 executed_at : & ' a Location < ' a > ,
40+ cache_key : Option < CommandCacheKey > ,
4041 } ,
4142}
4243
44+ pub struct DeferredCommand < ' a > {
45+ state : CommandState < ' a > ,
46+ }
47+
4348impl CommandCache {
4449 pub fn new ( ) -> Self {
4550 Self { cache : Mutex :: new ( HashMap :: new ( ) ) }
@@ -122,13 +127,33 @@ impl ExecutionContext {
122127 stdout : OutputMode ,
123128 stderr : OutputMode ,
124129 ) -> DeferredCommand < ' a > {
130+ let cache_key = command. cache_key ( ) ;
131+
132+ if let Some ( cached_output) = cache_key. as_ref ( ) . and_then ( |key| self . command_cache . get ( key) )
133+ {
134+ command. mark_as_executed ( ) ;
135+
136+ self . verbose ( || println ! ( "Cache hit: {:?}" , command) ) ;
137+
138+ return DeferredCommand { state : CommandState :: Cached ( cached_output) } ;
139+ }
140+
125141 command. mark_as_executed ( ) ;
126142
127143 let created_at = command. get_created_location ( ) ;
128144 let executed_at = std:: panic:: Location :: caller ( ) ;
129145
130146 if self . dry_run ( ) && !command. run_always {
131- return DeferredCommand { process : None , stdout, stderr, command, executed_at } ;
147+ return DeferredCommand {
148+ state : CommandState :: Deferred {
149+ process : None ,
150+ command,
151+ stdout,
152+ stderr,
153+ executed_at,
154+ cache_key,
155+ } ,
156+ } ;
132157 }
133158
134159 #[ cfg( feature = "tracing" ) ]
@@ -144,7 +169,16 @@ impl ExecutionContext {
144169
145170 let child = cmd. spawn ( ) ;
146171
147- DeferredCommand { process : Some ( child) , stdout, stderr, command, executed_at }
172+ DeferredCommand {
173+ state : CommandState :: Deferred {
174+ process : Some ( child) ,
175+ command,
176+ stdout,
177+ stderr,
178+ executed_at,
179+ cache_key,
180+ } ,
181+ }
148182 }
149183
150184 /// Execute a command and return its output.
@@ -212,33 +246,53 @@ impl AsRef<ExecutionContext> for ExecutionContext {
212246 }
213247}
214248
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-
223249impl < ' a > DeferredCommand < ' a > {
224250 pub fn wait_for_output ( mut self , exec_ctx : impl AsRef < ExecutionContext > ) -> CommandOutput {
251+ match self . state {
252+ CommandState :: Cached ( output) => output,
253+ CommandState :: Deferred { process, command, stdout, stderr, executed_at, cache_key } => {
254+ let exec_ctx = exec_ctx. as_ref ( ) ;
255+
256+ let output =
257+ Self :: execute_process ( process, command, stdout, stderr, executed_at, exec_ctx) ;
258+
259+ if ( !exec_ctx. dry_run ( ) || command. run_always )
260+ && cache_key. is_some ( )
261+ && output. status ( ) . is_some ( )
262+ {
263+ exec_ctx. command_cache . insert ( cache_key. unwrap ( ) , output. clone ( ) ) ;
264+ }
265+
266+ output
267+ }
268+ }
269+ }
270+
271+ pub fn execute_process (
272+ mut process : Option < Result < Child , std:: io:: Error > > ,
273+ command : & mut BootstrapCommand ,
274+ stdout : OutputMode ,
275+ stderr : OutputMode ,
276+ executed_at : & ' a std:: panic:: Location < ' a > ,
277+ exec_ctx : & ExecutionContext ,
278+ ) -> CommandOutput {
225279 let exec_ctx = exec_ctx. as_ref ( ) ;
226280
227- let process = match self . process . take ( ) {
281+ let process = match process. take ( ) {
228282 Some ( p) => p,
229283 None => return CommandOutput :: default ( ) ,
230284 } ;
231285
232- let created_at = self . command . get_created_location ( ) ;
233- let executed_at = self . executed_at ;
286+ let created_at = command. get_created_location ( ) ;
287+ let executed_at = executed_at;
234288
235289 let mut message = String :: new ( ) ;
236290
237291 let output = match process {
238292 Ok ( child) => match child. wait_with_output ( ) {
239293 Ok ( result) if result. status . success ( ) => {
240294 // Successful execution
241- CommandOutput :: from_output ( result, self . stdout , self . stderr )
295+ CommandOutput :: from_output ( result, stdout, stderr)
242296 }
243297 Ok ( result) => {
244298 // Command ran but failed
@@ -251,16 +305,16 @@ Command {:?} did not execute successfully.
251305Expected success, got {}
252306Created at: {created_at}
253307Executed at: {executed_at}"# ,
254- self . command, result. status,
308+ command, result. status,
255309 )
256310 . unwrap ( ) ;
257311
258- let output = CommandOutput :: from_output ( result, self . stdout , self . stderr ) ;
312+ let output = CommandOutput :: from_output ( result, stdout, stderr) ;
259313
260- if self . stdout . captures ( ) {
314+ if stdout. captures ( ) {
261315 writeln ! ( message, "\n STDOUT ----\n {}" , output. stdout( ) . trim( ) ) . unwrap ( ) ;
262316 }
263- if self . stderr . captures ( ) {
317+ if stderr. captures ( ) {
264318 writeln ! ( message, "\n STDERR ----\n {}" , output. stderr( ) . trim( ) ) . unwrap ( ) ;
265319 }
266320
@@ -274,11 +328,11 @@ Executed at: {executed_at}"#,
274328 message,
275329 "\n \n Command {:?} did not execute successfully.\
276330 \n It was not possible to execute the command: {e:?}",
277- self . command
331+ command
278332 )
279333 . unwrap ( ) ;
280334
281- CommandOutput :: did_not_start ( self . stdout , self . stderr )
335+ CommandOutput :: did_not_start ( stdout, stderr)
282336 }
283337 } ,
284338 Err ( e) => {
@@ -289,16 +343,16 @@ Executed at: {executed_at}"#,
289343 message,
290344 "\n \n Command {:?} did not execute successfully.\
291345 \n It was not possible to execute the command: {e:?}",
292- self . command
346+ command
293347 )
294348 . unwrap ( ) ;
295349
296- CommandOutput :: did_not_start ( self . stdout , self . stderr )
350+ CommandOutput :: did_not_start ( stdout, stderr)
297351 }
298352 } ;
299353
300354 if !output. is_success ( ) {
301- match self . command . failure_behavior {
355+ match command. failure_behavior {
302356 BehaviorOnFailure :: DelayFail => {
303357 if exec_ctx. fail_fast {
304358 exec_ctx. fail ( & message, output) ;
0 commit comments