Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
491 changes: 491 additions & 0 deletions crates/vite_task/docs/rfc-cache-fingerprint-ignore-patterns.md

Large diffs are not rendered by default.

55 changes: 55 additions & 0 deletions crates/vite_task/fixtures/fingerprint-ignore-test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Fingerprint Ignore Test Fixture

This fixture demonstrates the `fingerprintIgnores` feature for cache fingerprint calculation.

## Task Configuration

The `create-files` task in `vite-task.json` uses the following ignore patterns:

```json
{
"fingerprintIgnores": [
"node_modules/**/*",
"!node_modules/**/package.json",
"dist/**/*"
]
}
```

## Behavior

With these ignore patterns:

1. **`node_modules/**/*`** - Ignores all files under `node_modules/`
2. **`!node_modules/**/package.json`** - BUT keeps `package.json` files (negation pattern)
3. **`dist/**/*`** - Ignores all files under `dist/`

### Cache Behavior

- ✅ Cache **WILL BE INVALIDATED** when `node_modules/pkg-a/package.json` changes
- ❌ Cache **WILL NOT BE INVALIDATED** when `node_modules/pkg-a/index.js` changes
- ❌ Cache **WILL NOT BE INVALIDATED** when `dist/bundle.js` changes

This allows caching package installation tasks where only dependency manifests (package.json) matter for cache validation, not the actual implementation files.

## Example Usage

```bash
# First run - task executes
vite run create-files

# Second run - cache hit (all files tracked in fingerprint remain the same)
vite run create-files

# Modify node_modules/pkg-a/index.js
echo 'modified' > node_modules/pkg-a/index.js

# Third run - still cache hit (index.js is ignored)
vite run create-files

# Modify node_modules/pkg-a/package.json
echo '{"name":"pkg-a","version":"2.0.0"}' > node_modules/pkg-a/package.json

# Fourth run - cache miss (package.json is NOT ignored due to negation pattern)
vite run create-files
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "@test/fingerprint-ignore",
"version": "1.0.0"
}
13 changes: 13 additions & 0 deletions crates/vite_task/fixtures/fingerprint-ignore-test/vite-task.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"tasks": {
"create-files": {
"command": "mkdir -p node_modules/pkg-a && echo '{\"name\":\"pkg-a\"}' > node_modules/pkg-a/package.json && echo 'content' > node_modules/pkg-a/index.js && mkdir -p dist && echo 'output' > dist/bundle.js",
"cacheable": true,
"fingerprintIgnores": [
"node_modules/**/*",
"!node_modules/**/package.json",
"dist/**/*"
]
}
}
}
14 changes: 9 additions & 5 deletions crates/vite_task/src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ impl CommandCacheValue {
executed_task: ExecutedTask,
fs: &impl FileSystem,
base_dir: &AbsolutePath,
fingerprint_ignores: Option<&[Str]>,
) -> Result<Self, Error> {
let post_run_fingerprint = PostRunFingerprint::create(&executed_task, fs, base_dir)?;
let post_run_fingerprint =
PostRunFingerprint::create(&executed_task, fs, base_dir, fingerprint_ignores)?;
Ok(Self {
post_run_fingerprint,
std_outputs: executed_task.std_outputs,
Expand Down Expand Up @@ -105,16 +107,18 @@ impl TaskCache {
"CREATE TABLE taskrun_to_command (key BLOB PRIMARY KEY, value BLOB);",
(),
)?;
Comment thread
fengmk2 marked this conversation as resolved.
conn.execute("PRAGMA user_version = 2", ())?;
// Bump to version 3 to invalidate cache entries due to a change in the serialized cache key content
// (addition of the `fingerprint_ignores` field). No schema change was made.
conn.execute("PRAGMA user_version = 3", ())?;
}
1 => {
1..=2 => {
// old internal db version. reset
conn.set_db_config(DbConfig::SQLITE_DBCONFIG_RESET_DATABASE, true)?;
conn.execute("VACUUM", ())?;
conn.set_db_config(DbConfig::SQLITE_DBCONFIG_RESET_DATABASE, false)?;
}
2 => break, // current version
3.. => return Err(Error::UnrecognizedDbVersion(user_version)),
3 => break, // current version
4.. => return Err(Error::UnrecognizedDbVersion(user_version)),
}
}
Ok(Self { conn: Mutex::new(conn), path: cache_path })
Expand Down
26 changes: 25 additions & 1 deletion crates/vite_task/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@ pub struct TaskConfig {

#[serde(default)]
pub(crate) pass_through_envs: HashSet<Str>,

#[serde(default)]
pub(crate) fingerprint_ignores: Option<Vec<Str>>,
}

impl TaskConfig {
pub fn set_fingerprint_ignores(&mut self, fingerprint_ignores: Option<Vec<Str>>) {
self.fingerprint_ignores = fingerprint_ignores;
}
}

#[derive(Serialize, Deserialize, Debug, Clone)]
Expand Down Expand Up @@ -148,6 +157,7 @@ impl ResolvedTask {
args,
ResolveCommandResult { bin_path, envs },
false,
None,
)
}

Expand All @@ -157,14 +167,16 @@ impl ResolvedTask {
args: impl Iterator<Item = impl AsRef<str>> + Clone,
command_result: ResolveCommandResult,
ignore_replay: bool,
fingerprint_ignores: Option<Vec<Str>>,
) -> Result<Self, Error> {
let ResolveCommandResult { bin_path, envs } = command_result;
let builtin_task = TaskCommand::Parsed(TaskParsedCommand {
args: args.clone().map(|arg| arg.as_ref().into()).collect(),
envs: envs.into_iter().map(|(k, v)| (k.into(), v.into())).collect(),
program: bin_path.into(),
});
let task_config: TaskConfig = builtin_task.clone().into();
let mut task_config: TaskConfig = builtin_task.clone().into();
task_config.set_fingerprint_ignores(fingerprint_ignores.clone());
let pass_through_envs = task_config.pass_through_envs.iter().cloned().collect();
let cwd = &workspace.cwd;
let resolved_task_config =
Expand All @@ -179,6 +191,7 @@ impl ResolvedTask {
.into_iter()
.collect(),
pass_through_envs,
fingerprint_ignores,
},
all_envs: resolved_envs.all_envs,
};
Expand Down Expand Up @@ -245,6 +258,13 @@ impl std::fmt::Debug for ResolvedTaskCommand {
/// - The resolver provides envs which become part of the fingerprint
/// - If resolver provides different envs between runs, cache breaks
/// - Each built-in task type must have unique task name to avoid cache collision
///
/// # Fingerprint Ignores Impact on Cache
///
/// The `fingerprint_ignores` field controls which files are tracked in PostRunFingerprint:
/// - Changes to this config must invalidate the cache
/// - Vec maintains insertion order (pattern order matters for last-match-wins semantics)
/// - Even though ignore patterns only affect PostRunFingerprint, the config itself is part of the cache key
#[derive(Encode, Decode, Debug, Serialize, Deserialize, PartialEq, Eq, Diff, Clone)]
#[diff(attr(#[derive(Debug)]))]
pub struct CommandFingerprint {
Expand All @@ -256,6 +276,10 @@ pub struct CommandFingerprint {
/// even though value changes to `pass_through_envs` shouldn't invalidate the cache,
/// The names should still be fingerprinted so that the cache can be invalidated if the `pass_through_envs` config changes
pub pass_through_envs: BTreeSet<Str>, // using BTreeSet to have a stable order in cache db

/// Glob patterns for fingerprint filtering. Order matters (last match wins).
/// Changes to this config invalidate the cache to ensure correct fingerprint tracking.
pub fingerprint_ignores: Option<Vec<Str>>,
}

#[cfg(test)]
Expand Down
2 changes: 2 additions & 0 deletions crates/vite_task/src/config/task_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ impl From<TaskCommand> for TaskConfig {
inputs: Default::default(),
envs: Default::default(),
pass_through_envs: Default::default(),
fingerprint_ignores: Default::default(),
}
}
}
Expand Down Expand Up @@ -105,6 +106,7 @@ impl ResolvedTaskConfig {
.into_iter()
.collect(),
pass_through_envs: self.config.pass_through_envs.iter().cloned().collect(),
fingerprint_ignores: self.config.fingerprint_ignores.clone(),
},
all_envs: task_envs.all_envs,
})
Expand Down
4 changes: 4 additions & 0 deletions crates/vite_task/src/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,7 @@ mod tests {
inputs: HashSet::new(),
envs: HashSet::new(),
pass_through_envs: HashSet::new(),
fingerprint_ignores: None,
};

let resolved_task_config =
Expand Down Expand Up @@ -615,6 +616,7 @@ mod tests {
inputs: HashSet::new(),
envs,
pass_through_envs: HashSet::new(),
fingerprint_ignores: None,
};

let resolved_task_config =
Expand Down Expand Up @@ -737,6 +739,7 @@ mod tests {
inputs: HashSet::new(),
envs,
pass_through_envs: HashSet::new(),
fingerprint_ignores: None,
};

let resolved_task_config =
Expand Down Expand Up @@ -803,6 +806,7 @@ mod tests {
inputs: HashSet::new(),
envs,
pass_through_envs: HashSet::new(),
fingerprint_ignores: None,
};

let resolved_task_config =
Expand Down
Loading
Loading