22
33pub mod display;
44
5- use std:: { fmt:: Display , fs:: File , io:: Write , sync:: Arc , time:: Duration } ;
5+ use std:: { collections :: BTreeMap , fmt:: Display , fs:: File , io:: Write , sync:: Arc , time:: Duration } ;
66
77use bincode:: { Decode , Encode , decode_from_slice, encode_to_vec} ;
88// Re-export display functions for convenience
@@ -11,14 +11,32 @@ pub use display::{SpawnFingerprintChange, detect_spawn_fingerprint_changes, form
1111use rusqlite:: { Connection , OptionalExtension as _, config:: DbConfig } ;
1212use serde:: { Deserialize , Serialize } ;
1313use tokio:: sync:: Mutex ;
14- use vite_path:: AbsolutePath ;
14+ use vite_path:: { AbsolutePath , RelativePathBuf } ;
15+ use vite_task_graph:: config:: ResolvedInputConfig ;
1516use vite_task_plan:: cache_metadata:: { CacheMetadata , ExecutionCacheKey , SpawnFingerprint } ;
1617
1718use super :: execute:: {
1819 fingerprint:: { PostRunFingerprint , PostRunFingerprintMismatch } ,
1920 spawn:: StdOutput ,
2021} ;
2122
23+ /// Pre-run fingerprint computed at execution time.
24+ /// Contains spawn fingerprint, input configuration, and explicit input file hashes.
25+ #[ derive( Debug , Encode , Decode , Serialize , PartialEq , Eq , Clone ) ]
26+ pub struct PreRunFingerprint {
27+ /// The spawn fingerprint (command, args, cwd, envs)
28+ pub spawn_fingerprint : SpawnFingerprint ,
29+ /// Resolved input configuration that affects cache behavior.
30+ pub input_config : ResolvedInputConfig ,
31+ /// Base directory for glob patterns, relative to workspace root.
32+ /// This is where the task is defined (package path).
33+ pub glob_base : RelativePathBuf ,
34+ /// Hashes of explicit input files computed from positive globs.
35+ /// Files matching negative globs are already filtered out.
36+ /// Path is relative to workspace root, value is xxHash3_64 of file content.
37+ pub globbed_inputs : BTreeMap < RelativePathBuf , u64 > ,
38+ }
39+
2240/// Command cache value, for validating post-run fingerprint after the spawn fingerprint is matched,
2341/// and replaying the std outputs if validated.
2442#[ derive( Debug , Encode , Decode , Serialize ) ]
@@ -98,7 +116,7 @@ impl ExecutionCache {
98116 0 => {
99117 // fresh new db
100118 conn. execute (
101- "CREATE TABLE spawn_fingerprint_cache (key BLOB PRIMARY KEY, value BLOB);" ,
119+ "CREATE TABLE pre_run_fingerprint_cache (key BLOB PRIMARY KEY, value BLOB);" ,
102120 ( ) ,
103121 ) ?;
104122 conn. execute (
@@ -129,27 +147,59 @@ impl ExecutionCache {
129147 Ok ( ( ) )
130148 }
131149
132- /// Try to hit cache with spawn fingerprint.
150+ /// Try to hit cache with pre-run fingerprint (spawn + globbed inputs) .
133151 /// Returns `Ok(Ok(cache_value))` on cache hit, `Ok(Err(cache_miss))` on miss.
152+ ///
153+ /// # Arguments
154+ /// * `cache_metadata` - Cache metadata from plan stage
155+ /// * `globbed_inputs` - Hashes of explicit input files computed from positive globs
156+ /// * `base_dir` - Workspace root for validating post-run fingerprint
134157 #[ tracing:: instrument( level = "debug" , skip_all) ]
135158 pub async fn try_hit (
136159 & self ,
137160 cache_metadata : & CacheMetadata ,
161+ globbed_inputs : BTreeMap < RelativePathBuf , u64 > ,
138162 base_dir : & AbsolutePath ,
139163 ) -> anyhow:: Result < Result < CommandCacheValue , CacheMiss > > {
140164 let spawn_fingerprint = & cache_metadata. spawn_fingerprint ;
141165 let execution_cache_key = & cache_metadata. execution_cache_key ;
166+ let input_config = & cache_metadata. input_config ;
167+
168+ // Convert absolute glob_base to relative for cache key
169+ let glob_base = cache_metadata
170+ . glob_base
171+ . strip_prefix ( base_dir)
172+ . map_err ( |e| anyhow:: anyhow!( "failed to strip prefix from glob_base: {e}" ) ) ?
173+ . ok_or_else ( || {
174+ anyhow:: anyhow!(
175+ "glob_base {:?} is not inside workspace {:?}" ,
176+ cache_metadata. glob_base,
177+ base_dir
178+ )
179+ } ) ?;
142180
143- // Try to directly find the cache by spawn fingerprint first
144- if let Some ( cache_value) = self . get_by_spawn_fingerprint ( spawn_fingerprint) . await ? {
145- // Validate post-run fingerprint
146- if let Some ( post_run_fingerprint_mismatch) =
147- cache_value. post_run_fingerprint . validate ( base_dir) ?
148- {
149- // Found the cache with the same spawn fingerprint, but the post-run fingerprint mismatches
150- return Ok ( Err ( CacheMiss :: FingerprintMismatch (
151- FingerprintMismatch :: PostRunFingerprintMismatch ( post_run_fingerprint_mismatch) ,
152- ) ) ) ;
181+ // Build pre-run fingerprint combining spawn fingerprint, input config, and globbed inputs
182+ let pre_run_fingerprint = PreRunFingerprint {
183+ spawn_fingerprint : spawn_fingerprint. clone ( ) ,
184+ input_config : input_config. clone ( ) ,
185+ glob_base,
186+ globbed_inputs,
187+ } ;
188+
189+ // Try to directly find the cache by pre-run fingerprint first
190+ if let Some ( cache_value) = self . get_by_pre_run_fingerprint ( & pre_run_fingerprint) . await ? {
191+ // Validate post-run fingerprint (inferred inputs) only if auto inference is enabled
192+ if input_config. includes_auto {
193+ if let Some ( post_run_fingerprint_mismatch) =
194+ cache_value. post_run_fingerprint . validate ( base_dir) ?
195+ {
196+ // Found the cache with the same pre-run fingerprint, but the post-run fingerprint mismatches
197+ return Ok ( Err ( CacheMiss :: FingerprintMismatch (
198+ FingerprintMismatch :: PostRunFingerprintMismatch (
199+ post_run_fingerprint_mismatch,
200+ ) ,
201+ ) ) ) ;
202+ }
153203 }
154204 // Associate the execution key to the spawn fingerprint if not already,
155205 // so that next time we can find it and report spawn fingerprint mismatch
@@ -158,8 +208,8 @@ impl ExecutionCache {
158208 return Ok ( Ok ( cache_value) ) ;
159209 }
160210
161- // No cache found with the current spawn fingerprint,
162- // check if execution key maps to different fingerprint
211+ // No cache found with the current pre-run fingerprint,
212+ // check if execution key maps to different spawn fingerprint
163213 if let Some ( old_spawn_fingerprint) =
164214 self . get_fingerprint_by_execution_key ( execution_cache_key) . await ?
165215 {
@@ -177,16 +227,46 @@ impl ExecutionCache {
177227 }
178228
179229 /// Update cache after successful execution.
230+ ///
231+ /// # Arguments
232+ /// * `cache_metadata` - Cache metadata from plan stage
233+ /// * `globbed_inputs` - Hashes of explicit input files computed from positive globs
234+ /// * `base_dir` - Workspace root for converting absolute paths to relative
235+ /// * `cache_value` - The cache value to store (outputs and post-run fingerprint)
180236 #[ tracing:: instrument( level = "debug" , skip_all) ]
181237 pub async fn update (
182238 & self ,
183239 cache_metadata : & CacheMetadata ,
240+ globbed_inputs : BTreeMap < RelativePathBuf , u64 > ,
241+ base_dir : & AbsolutePath ,
184242 cache_value : CommandCacheValue ,
185243 ) -> anyhow:: Result < ( ) > {
186244 let spawn_fingerprint = & cache_metadata. spawn_fingerprint ;
187245 let execution_cache_key = & cache_metadata. execution_cache_key ;
246+ let input_config = & cache_metadata. input_config ;
247+
248+ // Convert absolute glob_base to relative for cache key
249+ let glob_base = cache_metadata
250+ . glob_base
251+ . strip_prefix ( base_dir)
252+ . map_err ( |e| anyhow:: anyhow!( "failed to strip prefix from glob_base: {e}" ) ) ?
253+ . ok_or_else ( || {
254+ anyhow:: anyhow!(
255+ "glob_base {:?} is not inside workspace {:?}" ,
256+ cache_metadata. glob_base,
257+ base_dir
258+ )
259+ } ) ?;
260+
261+ // Build pre-run fingerprint combining spawn fingerprint, input config, and globbed inputs
262+ let pre_run_fingerprint = PreRunFingerprint {
263+ spawn_fingerprint : spawn_fingerprint. clone ( ) ,
264+ input_config : input_config. clone ( ) ,
265+ glob_base,
266+ globbed_inputs,
267+ } ;
188268
189- self . upsert_spawn_fingerprint_cache ( spawn_fingerprint , & cache_value) . await ?;
269+ self . upsert_pre_run_fingerprint_cache ( & pre_run_fingerprint , & cache_value) . await ?;
190270 self . upsert_execution_key_to_fingerprint ( execution_cache_key, spawn_fingerprint) . await ?;
191271 Ok ( ( ) )
192272 }
@@ -227,11 +307,11 @@ impl ExecutionCache {
227307 Ok ( Some ( value) )
228308 }
229309
230- async fn get_by_spawn_fingerprint (
310+ async fn get_by_pre_run_fingerprint (
231311 & self ,
232- spawn_fingerprint : & SpawnFingerprint ,
312+ pre_run_fingerprint : & PreRunFingerprint ,
233313 ) -> anyhow:: Result < Option < CommandCacheValue > > {
234- self . get_key_by_value ( "spawn_fingerprint_cache " , spawn_fingerprint ) . await
314+ self . get_key_by_value ( "pre_run_fingerprint_cache " , pre_run_fingerprint ) . await
235315 }
236316
237317 async fn get_fingerprint_by_execution_key (
@@ -266,12 +346,12 @@ impl ExecutionCache {
266346 Ok ( ( ) )
267347 }
268348
269- async fn upsert_spawn_fingerprint_cache (
349+ async fn upsert_pre_run_fingerprint_cache (
270350 & self ,
271- spawn_fingerprint : & SpawnFingerprint ,
351+ pre_run_fingerprint : & PreRunFingerprint ,
272352 cache_value : & CommandCacheValue ,
273353 ) -> anyhow:: Result < ( ) > {
274- self . upsert ( "spawn_fingerprint_cache " , spawn_fingerprint , cache_value) . await
354+ self . upsert ( "pre_run_fingerprint_cache " , pre_run_fingerprint , cache_value) . await
275355 }
276356
277357 async fn upsert_execution_key_to_fingerprint (
@@ -320,8 +400,8 @@ impl ExecutionCache {
320400 & mut out,
321401 )
322402 . await ?;
323- out. write_all ( b"------- spawn_fingerprint_cache -------\n " ) ?;
324- self . list_table :: < SpawnFingerprint , CommandCacheValue > ( "spawn_fingerprint_cache " , & mut out)
403+ out. write_all ( b"------- pre_run_fingerprint_cache -------\n " ) ?;
404+ self . list_table :: < PreRunFingerprint , CommandCacheValue > ( "pre_run_fingerprint_cache " , & mut out)
325405 . await ?;
326406 Ok ( ( ) )
327407 }
0 commit comments