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
4 changes: 4 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@ Enforced by `.clippy.toml`:
- Only convert to std paths when interfacing with std library functions
- Add necessary methods in `vite_path` instead of falling back to std path types

### Environment Variables

`std::env::vars_os` is read exactly once — in `Session::init` — to bootstrap the session env snapshot (`Session.envs`). Everything downstream (planning, spawn env resolution, IPC `getEnv`/`getEnvs`, cache fingerprint validation) must use that snapshot or the plan's resolved env maps, never re-read the live process env. This keeps a run's behavior consistent with its plan and lets tests inject envs via `Session::init_with`.

### Cross-Platform Requirements

All code must work on both Unix and Windows without platform skipping:
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion crates/fspy/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,6 @@ fn register_preload_cdylib() -> anyhow::Result<()> {
}

fn main() -> anyhow::Result<()> {
println!("cargo:rerun-if-changed=build.rs");
let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
fetch_macos_binaries(&out_dir).context("Failed to fetch macOS binaries")?;
register_preload_cdylib().context("Failed to register preload cdylib")?;
Expand Down
1 change: 1 addition & 0 deletions crates/vite_task/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ tracing = { workspace = true }
twox-hash = { workspace = true }
materialized_artifact = { workspace = true }
uuid = { workspace = true, features = ["v4"] }
vite_glob = { workspace = true }
vite_path = { workspace = true }
vite_select = { workspace = true }
vite_str = { workspace = true }
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
11 changes: 9 additions & 2 deletions crates/vite_task/src/session/cache/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@ pub fn detect_spawn_fingerprint_changes(
}

/// Names of the env vars involved in a set of spawn-fingerprint changes, in the
/// order detected. Only env changes are collected; untracked-env and non-env
/// changes are skipped.
/// order detected. Only the tracked-env kinds are collected; untracked-env and
/// non-env changes are skipped.
fn env_change_names(changes: &[SpawnFingerprintChange]) -> Vec<&Str> {
changes
.iter()
Expand Down Expand Up @@ -195,6 +195,13 @@ pub fn format_cache_status_inline(cache_status: &CacheStatus) -> Option<Str> {
FingerprintMismatch::InputChanged { kind, path } => {
format_input_change_str(*kind, path.as_str())
}
// Env changes reported by a runner-aware tool render the same as
// changes detected from a manual `env` config — both name the
// env var that changed.
FingerprintMismatch::TrackedEnvChanged(mismatch)
| FingerprintMismatch::TrackedEnvGlobChanged { mismatch, .. } => {
format_env_changed_inline(&[mismatch.name()])
}
};
Some(vite_str::format!("○ cache miss: {reason}, executing"))
}
Expand Down
68 changes: 59 additions & 9 deletions crates/vite_task/src/session/cache/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
pub mod archive;
pub mod display;

use std::{collections::BTreeMap, fmt::Display, fs::File, io::Write, sync::Arc, time::Duration};
use std::{
collections::BTreeMap, ffi::OsStr, fmt::Display, fs::File, io::Write, sync::Arc, time::Duration,
};

// Re-export display functions for convenience
pub use display::format_cache_status_inline;
Expand All @@ -12,6 +14,7 @@ pub use display::{
format_spawn_change,
};
use rusqlite::{Connection, OptionalExtension as _};
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
use tokio::sync::Mutex;
use vite_path::{AbsolutePath, RelativePathBuf};
Expand Down Expand Up @@ -142,9 +145,10 @@ pub enum InputChangeKind {
/// A single env var difference between a stored fingerprint and the current
/// environment.
///
/// The canonical shape for an env change wherever one is detected and
/// reported. The [`Display`] impl is the single source of the user-facing
/// wording.
/// Shared by the prerun (spawn fingerprint) and post-run (tool-tracked env /
/// env glob) mismatch paths, so every env change is reported and rendered the
/// same way regardless of where it was detected. The [`Display`] impl is the
/// single source of the user-facing wording.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum EnvMismatch {
/// Set now, but absent from the stored fingerprint.
Expand All @@ -165,6 +169,23 @@ impl EnvMismatch {
}
}
}

/// Compare a stored env value against the current one, returning the
/// mismatch if they differ. `None` on either side means the env is unset
/// there; two unset (or two equal) values are not a mismatch.
#[must_use]
pub fn compare(name: &Str, stored: Option<&Str>, current: Option<&Str>) -> Option<Self> {
match (stored, current) {
(None, Some(value)) => Some(Self::Added { name: name.clone(), value: value.clone() }),
(Some(value), None) => Some(Self::Removed { name: name.clone(), value: value.clone() }),
(Some(old_value), Some(new_value)) if old_value != new_value => Some(Self::Changed {
name: name.clone(),
old_value: old_value.clone(),
new_value: new_value.clone(),
}),
_ => None,
}
}
}

impl Display for EnvMismatch {
Expand Down Expand Up @@ -198,6 +219,27 @@ pub enum FingerprintMismatch {
kind: InputChangeKind,
path: RelativePathBuf,
},
/// A tool-tracked env var changed between runs.
TrackedEnvChanged(EnvMismatch),
/// A tool-tracked env glob's match-set changed between runs. Carries the
/// first differing entry.
TrackedEnvGlobChanged {
pattern: Str,
mismatch: EnvMismatch,
},
}

impl From<crate::session::execute::fingerprint::PostRunMismatch> for FingerprintMismatch {
fn from(mismatch: crate::session::execute::fingerprint::PostRunMismatch) -> Self {
use crate::session::execute::fingerprint::PostRunMismatch;
match mismatch {
PostRunMismatch::InputChanged { kind, path } => Self::InputChanged { kind, path },
PostRunMismatch::TrackedEnvChanged(mismatch) => Self::TrackedEnvChanged(mismatch),
PostRunMismatch::TrackedEnvGlobChanged { pattern, mismatch } => {
Self::TrackedEnvGlobChanged { pattern, mismatch }
}
}
}
}

impl Display for FingerprintMismatch {
Expand All @@ -215,6 +257,10 @@ impl Display for FingerprintMismatch {
Self::InputChanged { kind, path } => {
write!(f, "{}", display::format_input_change_str(*kind, path.as_str()))
}
Self::TrackedEnvChanged(mismatch) => write!(f, "tracked {mismatch}"),
Self::TrackedEnvGlobChanged { pattern, mismatch } => {
write!(f, "tracked env glob {:?}: {mismatch}", pattern.as_str())
}
}
}
}
Expand Down Expand Up @@ -286,12 +332,16 @@ impl ExecutionCache {

/// Try to hit cache by looking up the cache entry key and validating inputs.
/// Returns `Ok(Ok(cache_value))` on cache hit, `Ok(Err(cache_miss))` on miss.
///
/// `envs` is the session env snapshot the run's plan was bootstrapped from;
/// tracked-env validation resolves against it, never the live process env.
#[tracing::instrument(level = "debug", skip_all)]
pub async fn try_hit(
&self,
cache_metadata: &CacheMetadata,
globbed_inputs: &BTreeMap<RelativePathBuf, u64>,
workspace_root: &AbsolutePath,
envs: &FxHashMap<Arc<OsStr>, Arc<OsStr>>,
) -> anyhow::Result<Result<CacheEntryValue, CacheMiss>> {
let spawn_fingerprint = &cache_metadata.spawn_fingerprint;
let execution_cache_key = &cache_metadata.execution_cache_key;
Expand All @@ -307,11 +357,11 @@ impl ExecutionCache {
return Ok(Err(CacheMiss::FingerprintMismatch(mismatch)));
}

// Validate post-run fingerprint (inferred inputs from fspy)
if let Some((kind, path)) = cache_value.post_run_fingerprint.validate(workspace_root)? {
return Ok(Err(CacheMiss::FingerprintMismatch(
FingerprintMismatch::InputChanged { kind, path },
)));
// Validate post-run fingerprint (inferred inputs + tracked envs)
if let Some(mismatch) =
cache_value.post_run_fingerprint.validate(workspace_root, envs)?
{
return Ok(Err(CacheMiss::FingerprintMismatch(mismatch.into())));
}
// Associate the execution key to the cache entry key if not already,
// so that next time we can find it and report what changed
Expand Down
Loading
Loading