Skip to content
Draft
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
- **Added** `output` field for cached tasks: archives output files matching the configured globs after a successful run and restores them on cache hit. Patterns are relative to the package directory; supports negative patterns (e.g. `"!dist/cache/**"`) and `{pattern, base}` form for explicit base. ([#321](https://github.com/voidzero-dev/vite-task/pull/321))
- **Fixed** Windows cached tasks can now run package shims rewritten through PowerShell; default env passthrough now preserves `PATHEXT` ([#366](https://github.com/voidzero-dev/vite-task/pull/366))
- **Added** Platform support for targets without `input` auto-inference (e.g. Android). Tasks still run; those relying on auto-inference run uncached, with the summary noting that `input` must be configured manually to enable caching ([#352](https://github.com/voidzero-dev/vite-task/pull/352))
- **Added** `output` field for cached tasks: archives output files after a successful run and restores them on cache hit. Defaults to automatically tracking files the task writes; accepts globs (e.g. `"dist/**"`), `{ "auto": true }`, and negative patterns (`"!dist/cache/**"`) ([#321](https://github.com/voidzero-dev/vite-task/pull/321))
- **Fixed** `vp run` no longer aborts with `failed to prepare the command for injection: Invalid argument` when the user environment already has `LD_PRELOAD` (Linux) or `DYLD_INSERT_LIBRARIES` (macOS) set. The tracer shim is now appended to any existing value and placed last, so user preloads keep their symbol-interposition precedence ([#340](https://github.com/voidzero-dev/vite-task/issues/340))
- **Changed** Arguments passed after a task name (e.g. `vp run test some-filter`) are now forwarded only to that task. Tasks pulled in via `dependsOn` no longer receive them ([#324](https://github.com/voidzero-dev/vite-task/issues/324))
- **Fixed** Windows file access tracking no longer panics when a task touches malformed paths that cannot be represented as workspace-relative inputs ([#330](https://github.com/voidzero-dev/vite-task/pull/330))
Expand Down
4 changes: 2 additions & 2 deletions crates/vite_task/docs/task-cache.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ The cache entry key uniquely identifies a command execution context:
```rust
pub struct CacheEntryKey {
pub spawn_fingerprint: SpawnFingerprint,
pub input_config: ResolvedInputConfig,
pub input_config: ResolvedGlobConfig,
}
```

Expand Down Expand Up @@ -303,7 +303,7 @@ Cache entries are serialized using `bincode` for efficient storage.
│ ────────────────────── │
│ CacheEntryKey { │
│ spawn_fingerprint: SpawnFingerprint { ... }, │
│ input_config: ResolvedInputConfig { ... }, │
│ input_config: ResolvedGlobConfig { ... }, │
│ } │
│ ExecutionCacheKey::UserTask { │
│ task_name: "build", │
Expand Down
334 changes: 201 additions & 133 deletions crates/vite_task/src/session/execute/mod.rs

Large diffs are not rendered by default.

33 changes: 12 additions & 21 deletions crates/vite_task/src/session/execute/tracked_accesses.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
//! Normalize raw fspy path accesses into workspace-relative, filtered form.
//!
//! User-configured negative globs are NOT applied here. They are applied later,
//! separately for reads (input config) and writes (output config), since those
//! two configs are independent.
#![cfg(fspy)]

use std::collections::hash_map::Entry;
Expand All @@ -21,22 +25,19 @@ pub struct TrackedPathAccesses {
}

impl TrackedPathAccesses {
/// Build from fspy's raw iterable by stripping the workspace prefix,
/// normalizing `..` components, and filtering against the negative globs.
pub fn from_raw(
raw: &PathAccessIterable,
workspace_root: &AbsolutePath,
resolved_negatives: &[wax::Glob<'static>],
) -> Self {
/// Build from fspy's raw iterable by stripping the workspace prefix and
/// normalizing `..` components. `.git/*` paths are skipped. User-configured
/// negatives are applied by the caller (see module docs).
pub fn from_raw(raw: &PathAccessIterable, workspace_root: &AbsolutePath) -> Self {
let mut accesses = Self::default();
for access in raw.iter() {
// Strip workspace root, clean `..` components, and filter in one pass.
// Strip workspace root and clean `..` components in one pass.
// fspy may report paths like `packages/sub-pkg/../shared/dist/output.js`.
let relative_path = access.path.strip_path_prefix(workspace_root, |strip_result| {
let Ok(stripped_path) = strip_result else {
return None;
};
normalize_tracked_workspace_path(stripped_path, resolved_negatives)
normalize_tracked_workspace_path(stripped_path)
});

let Some(relative_path) = relative_path else {
Expand Down Expand Up @@ -71,10 +72,7 @@ impl TrackedPathAccesses {
clippy::disallowed_types,
reason = "fspy strip_path_prefix exposes std::path::Path; convert to RelativePathBuf immediately"
)]
fn normalize_tracked_workspace_path(
stripped_path: &std::path::Path,
resolved_negatives: &[wax::Glob<'static>],
) -> Option<RelativePathBuf> {
fn normalize_tracked_workspace_path(stripped_path: &std::path::Path) -> Option<RelativePathBuf> {
// On Windows, paths are possible to be still absolute after stripping the workspace root.
// For example: c:\workspace\subdir\c:\workspace\subdir
// Just ignore those accesses.
Expand All @@ -90,12 +88,6 @@ fn normalize_tracked_workspace_path(
return None;
}

if !resolved_negatives.is_empty()
&& resolved_negatives.iter().any(|neg| wax::Program::is_match(neg, relative.as_str()))
{
return None;
}

Some(relative)
}

Expand All @@ -111,8 +103,7 @@ mod tests {
clippy::disallowed_types,
reason = "normalize_tracked_workspace_path requires std::path::Path for fspy strip_path_prefix output"
)]
let relative_path =
normalize_tracked_workspace_path(std::path::Path::new(r"foo\C:\bar"), &[]);
let relative_path = normalize_tracked_workspace_path(std::path::Path::new(r"foo\C:\bar"));
assert!(relative_path.is_none());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ should not see `FSPY` set.

```
$ vtt print-env FSPY
(undefined)
1
```
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v1
Original file line number Diff line number Diff line change
@@ -1,38 +1,226 @@
[[e2e]]
name = "output_globs___files_restored_on_cache_hit"
name = "auto_output___files_restored_on_cache_hit"
comment = """
With explicit output globs (`dist/**`), the first run writes a file to
`dist/`. After deleting `dist/`, a second run with no input changes is a
cache hit and the archived output file is restored.
Auto output detection (default): output files written by the task are
restored on a cache hit.
"""
steps = [
# First run - cache miss, writes dist/output.txt
["vt", "run", "build"],
# Verify file was written
["vtt", "print-file", "dist/output.txt"],
# Delete dist/ to prove restoration is real
["vtt", "rm", "-rf", "dist"],
# Second run - cache hit, restores dist/output.txt from archive
["vt", "run", "build"],
# File should be restored
["vtt", "print-file", "dist/output.txt"],
{ argv = [
"vt",
"run",
"auto-output",
], comment = "first run writes dist/out.txt" },
{ argv = [
"vtt",
"print-file",
"dist/out.txt",
], comment = "output exists after the build" },
{ argv = [
"vtt",
"rm",
"dist/out.txt",
], comment = "delete only the file (keep dir to avoid an fspy inferred-input miss on Windows)" },
{ argv = [
"vt",
"run",
"auto-output",
], comment = "cache hit, should restore dist/out.txt" },
{ argv = [
"vtt",
"print-file",
"dist/out.txt",
], comment = "output was restored from the archive" },
]

[[e2e]]
name = "output_globs___negative_excludes_files_from_archive"
name = "glob_output___only_matched_files_restored"
comment = """
A file matched by a negative output glob is not archived, so it is not
restored on cache hit.
Glob output: only files matching the configured output globs are restored
on a cache hit; files produced outside those globs are left alone.
"""
steps = [
# First run - writes both dist/keep.txt and dist/skip.txt
["vt", "run", "build-with-negative"],
# Both files exist after the run
["vtt", "print-file", "dist/keep.txt"],
["vtt", "print-file", "dist/skip.txt"],
# Delete dist/ to prove restoration is real
["vtt", "rm", "-rf", "dist"],
# Second run - cache hit, only dist/keep.txt is restored
["vt", "run", "build-with-negative"],
["vtt", "print-file", "dist/keep.txt"],
{ argv = [
"vt",
"run",
"glob-output",
], comment = "first run writes dist/out.txt and tmp/temp.txt" },
{ argv = [
"vtt",
"rm",
"dist/out.txt",
], comment = "delete the glob-matched output" },
{ argv = [
"vtt",
"rm",
"tmp/temp.txt",
], comment = "delete the non-matched output" },
{ argv = [
"vt",
"run",
"glob-output",
], comment = "cache hit: should restore dist/out.txt but not tmp/temp.txt" },
{ argv = [
"vtt",
"print-file",
"dist/out.txt",
], comment = "dist/out.txt was restored" },
{ argv = [
"vtt",
"print-file",
"tmp/temp.txt",
], comment = "should fail - tmp not in output globs" },
]

[[e2e]]
name = "auto_output_with_non_auto_input"
comment = """
Auto output works even when input tracking is explicit
(`input: [\"src/**\"]`). Output auto-detection and input auto-detection
are independent.
"""
steps = [
{ argv = [
"vt",
"run",
"auto-output-no-auto-input",
], comment = "first run: input src/** (no auto), output default (auto)" },
{ argv = [
"vtt",
"rm",
"dist/out.txt",
], comment = "delete the output" },
{ argv = [
"vt",
"run",
"auto-output-no-auto-input",
], comment = "cache hit - output files should still be restored" },
{ argv = [
"vtt",
"print-file",
"dist/out.txt",
], comment = "output was restored" },
]

[[e2e]]
name = "negative_output___excluded_files_not_restored"
comment = """
Negative output globs exclude matching files from the cache archive, so
they are not restored on a cache hit even though the task produced them.
"""
steps = [
{ argv = [
"vt",
"run",
"negative-output",
], comment = "first run writes dist/out.txt and dist/cache/tmp.txt" },
{ argv = [
"vtt",
"rm",
"dist/out.txt",
], comment = "delete the archived output" },
{ argv = [
"vtt",
"rm",
"dist/cache/tmp.txt",
], comment = "delete the excluded output" },
{ argv = [
"vt",
"run",
"negative-output",
], comment = "cache hit: should restore dist/out.txt but NOT dist/cache/tmp.txt" },
{ argv = [
"vtt",
"print-file",
"dist/out.txt",
], comment = "dist/out.txt was restored" },
{ argv = [
"vtt",
"print-file",
"dist/cache/tmp.txt",
], comment = "should fail - excluded by !dist/cache/**" },
]

[[e2e]]
name = "output_config_change_invalidates_cache"
comment = """
Changing a task's output configuration invalidates the cache entry —
the next run is a miss, not a hit of the stale archive.
"""
steps = [
{ argv = [
"vt",
"run",
"output-config-change",
], comment = "first run populates the cache" },
{ argv = [
"vtt",
"replace-file-content",
"vite-task.json",
"REPLACE_ME",
"dist/**",
], comment = "change the output globs in the task config" },
{ argv = [
"vt",
"run",
"output-config-change",
], comment = "cache miss: output config changed" },
]

[[e2e]]
name = "input_negative_does_not_drop_output_writes"
comment = """
Input negative globs must not drop matching writes from the output
archive. Here the user excludes `dist/**` from inferred inputs (so
rewriting dist/ won't mark inputs as modified), but default auto output
should still capture dist writes and restore them on a cache hit.
"""
steps = [
{ argv = [
"vt",
"run",
"input-neg-dist-auto-output",
], comment = "first run writes dist/out.txt" },
{ argv = [
"vtt",
"rm",
"dist/out.txt",
], comment = "delete the output" },
{ argv = [
"vt",
"run",
"input-neg-dist-auto-output",
], comment = "cache hit should restore dist/out.txt" },
{ argv = [
"vtt",
"print-file",
"dist/out.txt",
], comment = "output was restored despite dist/** being a negative input glob" },
]

[[e2e]]
name = "explicit_input_ignores_fspy_reads"
comment = """
When input auto is disabled (explicit globs only), unrelated reads
tracked by fspy must NOT become inferred inputs. Default auto output
still needs fspy for write tracking, but reads outside `input: [\"src/**\"]`
should be ignored.
"""
steps = [
{ argv = [
"vt",
"run",
"explicit-input-auto-output",
], comment = "first run reads README.md (not in input globs) and captures the output" },
{ argv = [
"vtt",
"replace-file-content",
"README.md",
"v1",
"v2",
], comment = "modify README.md — not an input, so it must not invalidate the cache" },
{ argv = [
"vt",
"run",
"explicit-input-auto-output",
], comment = "cache hit: README.md not in input globs" },
]
Loading
Loading