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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Changelog

- **Changed** Exclusions in a task's `input`, like `"!build/**"`, now also keep matching entries out of the folders the task lists, and so do the paths a tool reports with `ignoreInput`. For example, a lint task with `"!build/**"` in its `input` that lists the package root is now a cache hit when another task's `build` folder appears there. Previously an exclusion only left out the files the task read, so the new folder was a cache miss. A folder entry is left out when the pattern matches the folder itself, as `build/**` does, but not `build/*` ([#941](https://github.com/voidzero-dev/vite-task/pull/941)).
- **Fixed** A task's automatically tracked outputs now include the symbolic links it creates, like the links Nitro's dependency tracer makes in `.output/server/node_modules`, so they no longer need an `output` glob that matches them. The next run is a cache hit if they are missing, as on a fresh clone, and the hit restores them as links if they're among the task's outputs. Previously a hit left them out, and removing one that the task had checked for was a cache miss ([#918](https://github.com/voidzero-dev/vite-task/pull/918)).
- **Fixed** A cache hit now restores the empty folders a task made if they're among its outputs, like the `.astro/collections` folder that `astro build` leaves empty: with automatic output tracking that's every such folder, and with `output` globs, the ones they match. A folder holding only such folders and files the task wrote, like `.astro`, may now be missing, as on a fresh clone, and the next run is still a cache hit, which restores it if what it holds is among the task's outputs. Previously a hit left empty folders missing, and removing a folder holding one was a cache miss ([#917](https://github.com/voidzero-dev/vite-task/pull/917)).
- **Fixed** A task's automatically tracked outputs now include the files it renames into place, like Vitest's `fsModuleCache` entries written under a temporary name, every file in a folder it renames into place, like Vite's dependency optimizer renaming `deps_temp_*` to `deps`, and the hard links it creates. The next run is a cache hit if they are missing, as on a fresh clone, and the hit restores them if they're among the task's outputs. Previously they weren't restored, and removing one that the task read back was a cache miss ([#916](https://github.com/voidzero-dev/vite-task/pull/916)).
Expand Down
2 changes: 1 addition & 1 deletion crates/vt/docs/task-cache.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ The `input` field under a task's `cache` controls which files are tracked for ca
- **`[]`** (empty array): disables file tracking entirely
- **Glob patterns** (e.g. `"src/**"`): select specific files
- **`{auto: true}`**: enables automatic file tracking
- **Negative patterns** (e.g. `"!dist/**"`): exclude matched files
- **Negative patterns** (e.g. `"!dist/**"`): exclude matched files, and matching entries of the folders the task lists

See [inputs.md](../../../docs/inputs.md) for full details.

Expand Down
24 changes: 21 additions & 3 deletions crates/vt/src/session/cache/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use rusqlite::{Connection, OptionalExtension as _};
use serde::{Deserialize, Serialize};
use tokio::sync::Mutex;
use tokio_util::sync::CancellationToken;
use vt_glob::path::PathGlobSet;
use vt_graph::config::ResolvedGlobConfig;
use vt_path::{AbsolutePath, RelativePathBuf};
use vt_plan::{
Expand Down Expand Up @@ -276,7 +277,7 @@ pub fn split_path(path: &str) -> (Option<&str>, &str) {
/// its own cache warm across branch switches, and a cache from a different
/// version is simply ignored (it lives in a directory this build never looks
/// at) rather than aborting the run. Bumping the version starts a fresh cache.
const CACHE_SCHEMA_VERSION: u32 = 21;
const CACHE_SCHEMA_VERSION: u32 = 22;

/// Name of the per-version subdirectory (e.g. `v14`) under the task-cache
/// directory that holds the database and output archives for the current
Expand Down Expand Up @@ -363,9 +364,17 @@ impl ExecutionCache {
cancel_token: &CancellationToken,
) -> anyhow::Result<Result<CacheHit, CacheMiss>> {
let cache_key = CacheEntryKey::from_metadata(cache_metadata);
// Part of the key, so the same as when the entry was created.
let input_negative_globs = PathGlobSet::new(&cache_metadata.input_config.negative_globs)?;

let local_miss = match self
.try_hit_local(cache_metadata, &cache_key, globbed_inputs, workspace_root)
.try_hit_local(
cache_metadata,
&cache_key,
globbed_inputs,
&input_negative_globs,
workspace_root,
)
.await?
{
Ok(value) => return Ok(Ok(CacheHit { value, source: CacheHitSource::Local })),
Expand All @@ -390,6 +399,7 @@ impl ExecutionCache {
cache_metadata,
&cache_key,
globbed_inputs,
&input_negative_globs,
workspace_root,
cache_dir,
cancel_token,
Expand All @@ -410,6 +420,7 @@ impl ExecutionCache {
cache_metadata: &CacheMetadata,
cache_key: &CacheEntryKey,
globbed_inputs: &BTreeMap<RelativePathBuf, u64>,
input_negative_globs: &PathGlobSet<'_>,
workspace_root: &AbsolutePath,
) -> anyhow::Result<Result<CacheEntryValue, CacheMiss>> {
let execution_cache_key = &cache_metadata.execution_cache_key;
Expand All @@ -419,6 +430,7 @@ impl ExecutionCache {
if let Some(mismatch) = cache_value.validate(
&cache_metadata.unfiltered_envs,
globbed_inputs,
input_negative_globs,
workspace_root,
)? {
return Ok(Err(CacheMiss::FingerprintMismatch(mismatch)));
Expand Down Expand Up @@ -456,6 +468,7 @@ impl ExecutionCache {
cache_metadata: &CacheMetadata,
cache_key: &CacheEntryKey,
globbed_inputs: &BTreeMap<RelativePathBuf, u64>,
input_negative_globs: &PathGlobSet<'_>,
workspace_root: &AbsolutePath,
cache_dir: &AbsolutePath,
cancel_token: &CancellationToken,
Expand All @@ -465,7 +478,12 @@ impl ExecutionCache {
.fetch(remote_config, cache_key, &cache_metadata.execution_cache_key, cancel_token)
.await;
let validate = |cache_value: &CacheEntryValue| {
cache_value.validate(&cache_metadata.unfiltered_envs, globbed_inputs, workspace_root)
cache_value.validate(
&cache_metadata.unfiltered_envs,
globbed_inputs,
input_negative_globs,
workspace_root,
)
};
let Restore { value: cache_value, blob_id } =
match remote::resolve(fetched, cache_key, validate) {
Expand Down
8 changes: 7 additions & 1 deletion crates/vt/src/session/cache/remote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,7 @@ mod tests {
use std::{collections::BTreeMap, io::Read as _, net::TcpListener, time::Duration};

use tokio::sync::oneshot;
use vt_glob::path::PathGlobSet;
use vt_graph::config::ResolvedGlobConfig;
use vt_path::{AbsolutePathBuf, RelativePathBuf};
use vt_plan::{
Expand Down Expand Up @@ -502,7 +503,12 @@ mod tests {
) -> impl FnOnce(&CacheEntryValue) -> anyhow::Result<Option<FingerprintMismatch>> {
move |value| {
let workspace_root = vt_path::current_dir().unwrap();
value.validate(&FxHashMap::default(), &globbed_inputs, &workspace_root)
value.validate(
&FxHashMap::default(),
&globbed_inputs,
&PathGlobSet::default(),
&workspace_root,
)
}
}

Expand Down
9 changes: 6 additions & 3 deletions crates/vt/src/session/cache/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@ use std::{collections::BTreeMap, ffi::OsStr, sync::Arc};

use rustc_hash::FxHashMap;
use vt_casefold::EnvName;
use vt_glob::path::PathGlobSet;
use vt_path::{AbsolutePath, RelativePathBuf};

use super::{CacheEntryKey, CacheEntryValue, FingerprintMismatch, InputChangeKind};

impl CacheEntryValue {
/// Validate explicit inputs, then inferred inputs and tracked environment
/// values against `unfiltered_envs`, the execution's
/// `CacheMetadata::unfiltered_envs`. Returns the first mismatch, or `None`
/// when the entry is valid.
/// `CacheMetadata::unfiltered_envs`. `input_negative_globs`, compiled from
/// the entry key's input config, leave entries out of listings. Returns
/// the first mismatch, or `None` when the entry is valid.
///
/// # Errors
///
Expand All @@ -21,14 +23,15 @@ impl CacheEntryValue {
&self,
unfiltered_envs: &FxHashMap<EnvName<Arc<OsStr>>, Arc<OsStr>>,
globbed_inputs: &BTreeMap<RelativePathBuf, u64>,
input_negative_globs: &PathGlobSet<'_>,
workspace_root: &AbsolutePath,
) -> anyhow::Result<Option<FingerprintMismatch>> {
if let Some(mismatch) = detect_globbed_input_change(&self.globbed_inputs, globbed_inputs) {
return Ok(Some(mismatch));
}

self.post_run_fingerprint
.validate(workspace_root, unfiltered_envs)
.validate(workspace_root, input_negative_globs, unfiltered_envs)
.map(|mismatch| mismatch.map(FingerprintMismatch::from))
}
}
Expand Down
5 changes: 5 additions & 0 deletions crates/vt/src/session/execute/cache_update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,14 @@ pub(super) async fn update_cache(
let path_reads = fspy_outcome.as_ref().map_or(&empty_path_reads, |o| &o.path_reads);
let no_writes = Rebuildable::default();
let rebuildable = fspy_outcome.as_ref().map_or(&no_writes, |o| &o.rebuildable);
// Listings leave out the entries input exclusions match, as reads do.
let no_globs = PathGlobSet::default();
let input_negative_globs = fspy.map_or(&no_globs, |fspy| &fspy.input_negative_globs);
let post_run_fingerprint = match PostRunFingerprint::create(
path_reads,
rebuildable,
input_negative_globs,
&ignored_input_rels,
workspace_root,
&globbed_inputs,
tracked_envs,
Expand Down
Loading
Loading