Skip to content

Commit 3ca3075

Browse files
committed
feat(cache): add explicit inputs config for cache fingerprinting
Add `inputs` field to task configuration supporting: - Explicit glob patterns: `inputs: ["src/**/*.ts"]` - Auto-inference from fspy: `inputs: [{ auto: true }]` - Negative patterns: `inputs: ["src/**", "!**/*.test.ts"]` - Mixed mode: `inputs: ["package.json", { auto: true }, "!dist/**"]` - Empty array to disable file tracking: `inputs: []` Key changes: - Add `ResolvedInputConfig` to parse and normalize user input config - Add `glob_inputs.rs` for walking glob patterns and hashing files - Update `PreRunFingerprint` to include `input_config` and `glob_base` - Bump cache DB version to 6 for new fingerprint structure - Add comprehensive e2e tests for all input combinations Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> # Conflicts: # crates/vite_task_plan/src/plan.rs
1 parent bf60d66 commit 3ca3075

55 files changed

Lines changed: 2204 additions & 211 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/vite_task/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ vite_str = { workspace = true }
3838
vite_task_graph = { workspace = true }
3939
vite_task_plan = { workspace = true }
4040
vite_workspace = { workspace = true }
41+
wax = { workspace = true }
42+
43+
[dev-dependencies]
44+
tempfile = { workspace = true }
4145

4246
[target.'cfg(unix)'.dependencies]
4347
nix = { workspace = true }

crates/vite_task/src/session/cache/display.rs

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,6 @@ enum SpawnFingerprintChange {
3535
// Working directory change
3636
/// Working directory changed
3737
CwdChanged,
38-
39-
// Fingerprint ignores changes
40-
/// Fingerprint ignore pattern added
41-
FingerprintIgnoreAdded { pattern: String },
42-
/// Fingerprint ignore pattern removed
43-
FingerprintIgnoreRemoved { pattern: String },
4438
}
4539

4640
/// Compare two spawn fingerprints and return all changes
@@ -105,21 +99,6 @@ fn detect_spawn_fingerprint_changes(
10599
changes.push(SpawnFingerprintChange::CwdChanged);
106100
}
107101

108-
// Check fingerprint ignores changes
109-
let old_ignores: HashSet<_> =
110-
old.fingerprint_ignores().map(|v| v.iter().collect()).unwrap_or_default();
111-
let new_ignores: HashSet<_> =
112-
new.fingerprint_ignores().map(|v| v.iter().collect()).unwrap_or_default();
113-
for pattern in old_ignores.difference(&new_ignores) {
114-
changes.push(SpawnFingerprintChange::FingerprintIgnoreRemoved {
115-
pattern: pattern.to_string(),
116-
});
117-
}
118-
for pattern in new_ignores.difference(&old_ignores) {
119-
changes
120-
.push(SpawnFingerprintChange::FingerprintIgnoreAdded { pattern: pattern.to_string() });
121-
}
122-
123102
changes
124103
}
125104

@@ -161,10 +140,6 @@ pub fn format_cache_status_inline(cache_status: &CacheStatus) -> Option<String>
161140
Some(SpawnFingerprintChange::ProgramChanged) => "program changed",
162141
Some(SpawnFingerprintChange::ArgsChanged) => "args changed",
163142
Some(SpawnFingerprintChange::CwdChanged) => "working directory changed",
164-
Some(
165-
SpawnFingerprintChange::FingerprintIgnoreAdded { .. }
166-
| SpawnFingerprintChange::FingerprintIgnoreRemoved { .. },
167-
) => "fingerprint ignores changed",
168143
None => "configuration changed",
169144
}
170145
}
@@ -246,12 +221,6 @@ pub fn format_cache_status_summary(cache_status: &CacheStatus) -> String {
246221
SpawnFingerprintChange::CwdChanged => {
247222
"working directory changed".to_string()
248223
}
249-
SpawnFingerprintChange::FingerprintIgnoreAdded { pattern } => {
250-
format!("fingerprint ignore '{pattern}' added")
251-
}
252-
SpawnFingerprintChange::FingerprintIgnoreRemoved { pattern } => {
253-
format!("fingerprint ignore '{pattern}' removed")
254-
}
255224
})
256225
.collect();
257226

crates/vite_task/src/session/cache/mod.rs

Lines changed: 113 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,40 @@
22
33
pub 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

77
use bincode::{Decode, Encode, decode_from_slice, encode_to_vec};
88
// Re-export display functions for convenience
99
pub use display::{format_cache_status_inline, format_cache_status_summary};
1010
use rusqlite::{Connection, OptionalExtension as _, config::DbConfig};
1111
use serde::{Deserialize, Serialize};
1212
use tokio::sync::Mutex;
13-
use vite_path::{AbsolutePath, AbsolutePathBuf};
13+
use vite_path::{AbsolutePath, AbsolutePathBuf, RelativePathBuf};
14+
use vite_task_graph::config::ResolvedInputConfig;
1415
use vite_task_plan::cache_metadata::{CacheMetadata, ExecutionCacheKey, SpawnFingerprint};
1516

1617
use super::execute::{
1718
fingerprint::{PostRunFingerprint, PostRunFingerprintMismatch},
1819
spawn::StdOutput,
1920
};
2021

22+
/// Pre-run fingerprint computed at execution time.
23+
/// Contains spawn fingerprint, input configuration, and explicit input file hashes.
24+
#[derive(Debug, Encode, Decode, Serialize, PartialEq, Eq, Clone)]
25+
pub struct PreRunFingerprint {
26+
/// The spawn fingerprint (command, args, cwd, envs)
27+
pub spawn_fingerprint: SpawnFingerprint,
28+
/// Resolved input configuration that affects cache behavior.
29+
pub input_config: ResolvedInputConfig,
30+
/// Base directory for glob patterns, relative to workspace root.
31+
/// This is where the task is defined (package path).
32+
pub glob_base: RelativePathBuf,
33+
/// Hashes of explicit input files computed from positive globs.
34+
/// Files matching negative globs are already filtered out.
35+
/// Path is relative to workspace root, value is xxHash3_64 of file content.
36+
pub globbed_inputs: BTreeMap<RelativePathBuf, u64>,
37+
}
38+
2139
/// Command cache value, for validating post-run fingerprint after the spawn fingerprint is matched,
2240
/// and replaying the std outputs if validated.
2341
#[derive(Debug, Encode, Decode, Serialize)]
@@ -85,23 +103,23 @@ impl ExecutionCache {
85103
0 => {
86104
// fresh new db
87105
conn.execute(
88-
"CREATE TABLE spawn_fingerprint_cache (key BLOB PRIMARY KEY, value BLOB);",
106+
"CREATE TABLE pre_run_fingerprint_cache (key BLOB PRIMARY KEY, value BLOB);",
89107
(),
90108
)?;
91109
conn.execute(
92110
"CREATE TABLE execution_key_to_fingerprint (key BLOB PRIMARY KEY, value BLOB);",
93111
(),
94112
)?;
95-
conn.execute("PRAGMA user_version = 4", ())?;
113+
conn.execute("PRAGMA user_version = 6", ())?;
96114
}
97-
1..=3 => {
115+
1..=5 => {
98116
// old internal db version. reset
99117
conn.set_db_config(DbConfig::SQLITE_DBCONFIG_RESET_DATABASE, true)?;
100118
conn.execute("VACUUM", ())?;
101119
conn.set_db_config(DbConfig::SQLITE_DBCONFIG_RESET_DATABASE, false)?;
102120
}
103-
4 => break, // current version
104-
5.. => {
121+
6 => break, // current version
122+
7.. => {
105123
return Err(anyhow::anyhow!("Unrecognized database version: {}", user_version));
106124
}
107125
}
@@ -116,26 +134,58 @@ impl ExecutionCache {
116134
Ok(())
117135
}
118136

119-
/// Try to hit cache with spawn fingerprint.
137+
/// Try to hit cache with pre-run fingerprint (spawn + globbed inputs).
120138
/// Returns `Ok(Ok(cache_value))` on cache hit, `Ok(Err(cache_miss))` on miss.
139+
///
140+
/// # Arguments
141+
/// * `cache_metadata` - Cache metadata from plan stage
142+
/// * `globbed_inputs` - Hashes of explicit input files computed from positive globs
143+
/// * `base_dir` - Workspace root for validating post-run fingerprint
121144
pub async fn try_hit(
122145
&self,
123146
cache_metadata: &CacheMetadata,
147+
globbed_inputs: BTreeMap<RelativePathBuf, u64>,
124148
base_dir: &AbsolutePath,
125149
) -> anyhow::Result<Result<CommandCacheValue, CacheMiss>> {
126150
let spawn_fingerprint = &cache_metadata.spawn_fingerprint;
127151
let execution_cache_key = &cache_metadata.execution_cache_key;
152+
let input_config = &cache_metadata.input_config;
153+
154+
// Convert absolute glob_base to relative for cache key
155+
let glob_base = cache_metadata
156+
.glob_base
157+
.strip_prefix(base_dir)
158+
.map_err(|e| anyhow::anyhow!("failed to strip prefix from glob_base: {e}"))?
159+
.ok_or_else(|| {
160+
anyhow::anyhow!(
161+
"glob_base {:?} is not inside workspace {:?}",
162+
cache_metadata.glob_base,
163+
base_dir
164+
)
165+
})?;
166+
167+
// Build pre-run fingerprint combining spawn fingerprint, input config, and globbed inputs
168+
let pre_run_fingerprint = PreRunFingerprint {
169+
spawn_fingerprint: spawn_fingerprint.clone(),
170+
input_config: input_config.clone(),
171+
glob_base,
172+
globbed_inputs,
173+
};
128174

129-
// Try to directly find the cache by spawn fingerprint first
130-
if let Some(cache_value) = self.get_by_spawn_fingerprint(spawn_fingerprint).await? {
131-
// Validate post-run fingerprint
132-
if let Some(post_run_fingerprint_mismatch) =
133-
cache_value.post_run_fingerprint.validate(base_dir)?
134-
{
135-
// Found the cache with the same spawn fingerprint, but the post-run fingerprint mismatches
136-
return Ok(Err(CacheMiss::FingerprintMismatch(
137-
FingerprintMismatch::PostRunFingerprintMismatch(post_run_fingerprint_mismatch),
138-
)));
175+
// Try to directly find the cache by pre-run fingerprint first
176+
if let Some(cache_value) = self.get_by_pre_run_fingerprint(&pre_run_fingerprint).await? {
177+
// Validate post-run fingerprint (inferred inputs) only if auto inference is enabled
178+
if input_config.includes_auto {
179+
if let Some(post_run_fingerprint_mismatch) =
180+
cache_value.post_run_fingerprint.validate(base_dir)?
181+
{
182+
// Found the cache with the same pre-run fingerprint, but the post-run fingerprint mismatches
183+
return Ok(Err(CacheMiss::FingerprintMismatch(
184+
FingerprintMismatch::PostRunFingerprintMismatch(
185+
post_run_fingerprint_mismatch,
186+
),
187+
)));
188+
}
139189
}
140190
// Associate the execution key to the spawn fingerprint if not already,
141191
// so that next time we can find it and report spawn fingerprint mismatch
@@ -144,8 +194,8 @@ impl ExecutionCache {
144194
return Ok(Ok(cache_value));
145195
}
146196

147-
// No cache found with the current spawn fingerprint,
148-
// check if execution key maps to different fingerprint
197+
// No cache found with the current pre-run fingerprint,
198+
// check if execution key maps to different spawn fingerprint
149199
if let Some(old_spawn_fingerprint) =
150200
self.get_fingerprint_by_execution_key(execution_cache_key).await?
151201
{
@@ -163,15 +213,45 @@ impl ExecutionCache {
163213
}
164214

165215
/// Update cache after successful execution.
216+
///
217+
/// # Arguments
218+
/// * `cache_metadata` - Cache metadata from plan stage
219+
/// * `globbed_inputs` - Hashes of explicit input files computed from positive globs
220+
/// * `base_dir` - Workspace root for converting absolute paths to relative
221+
/// * `cache_value` - The cache value to store (outputs and post-run fingerprint)
166222
pub async fn update(
167223
&self,
168224
cache_metadata: &CacheMetadata,
225+
globbed_inputs: BTreeMap<RelativePathBuf, u64>,
226+
base_dir: &AbsolutePath,
169227
cache_value: CommandCacheValue,
170228
) -> anyhow::Result<()> {
171229
let spawn_fingerprint = &cache_metadata.spawn_fingerprint;
172230
let execution_cache_key = &cache_metadata.execution_cache_key;
231+
let input_config = &cache_metadata.input_config;
232+
233+
// Convert absolute glob_base to relative for cache key
234+
let glob_base = cache_metadata
235+
.glob_base
236+
.strip_prefix(base_dir)
237+
.map_err(|e| anyhow::anyhow!("failed to strip prefix from glob_base: {e}"))?
238+
.ok_or_else(|| {
239+
anyhow::anyhow!(
240+
"glob_base {:?} is not inside workspace {:?}",
241+
cache_metadata.glob_base,
242+
base_dir
243+
)
244+
})?;
173245

174-
self.upsert_spawn_fingerprint_cache(spawn_fingerprint, &cache_value).await?;
246+
// Build pre-run fingerprint combining spawn fingerprint, input config, and globbed inputs
247+
let pre_run_fingerprint = PreRunFingerprint {
248+
spawn_fingerprint: spawn_fingerprint.clone(),
249+
input_config: input_config.clone(),
250+
glob_base,
251+
globbed_inputs,
252+
};
253+
254+
self.upsert_pre_run_fingerprint_cache(&pre_run_fingerprint, &cache_value).await?;
175255
self.upsert_execution_key_to_fingerprint(execution_cache_key, spawn_fingerprint).await?;
176256
Ok(())
177257
}
@@ -197,11 +277,11 @@ impl ExecutionCache {
197277
Ok(Some(value))
198278
}
199279

200-
async fn get_by_spawn_fingerprint(
280+
async fn get_by_pre_run_fingerprint(
201281
&self,
202-
spawn_fingerprint: &SpawnFingerprint,
282+
pre_run_fingerprint: &PreRunFingerprint,
203283
) -> anyhow::Result<Option<CommandCacheValue>> {
204-
self.get_key_by_value("spawn_fingerprint_cache", spawn_fingerprint).await
284+
self.get_key_by_value("pre_run_fingerprint_cache", pre_run_fingerprint).await
205285
}
206286

207287
async fn get_fingerprint_by_execution_key(
@@ -227,12 +307,12 @@ impl ExecutionCache {
227307
Ok(())
228308
}
229309

230-
async fn upsert_spawn_fingerprint_cache(
310+
async fn upsert_pre_run_fingerprint_cache(
231311
&self,
232-
spawn_fingerprint: &SpawnFingerprint,
312+
pre_run_fingerprint: &PreRunFingerprint,
233313
cache_value: &CommandCacheValue,
234314
) -> anyhow::Result<()> {
235-
self.upsert("spawn_fingerprint_cache", spawn_fingerprint, cache_value).await
315+
self.upsert("pre_run_fingerprint_cache", pre_run_fingerprint, cache_value).await
236316
}
237317

238318
async fn upsert_execution_key_to_fingerprint(
@@ -273,9 +353,12 @@ impl ExecutionCache {
273353
&mut out,
274354
)
275355
.await?;
276-
out.write_all(b"------- spawn_fingerprint_cache -------\n")?;
277-
self.list_table::<SpawnFingerprint, CommandCacheValue>("spawn_fingerprint_cache", &mut out)
278-
.await?;
356+
out.write_all(b"------- pre_run_fingerprint_cache -------\n")?;
357+
self.list_table::<PreRunFingerprint, CommandCacheValue>(
358+
"pre_run_fingerprint_cache",
359+
&mut out,
360+
)
361+
.await?;
279362
Ok(())
280363
}
281364
}

0 commit comments

Comments
 (0)