From e9e9d0794c1ad322c68c1dd2a8bee082c26c8911 Mon Sep 17 00:00:00 2001 From: wan9chi Date: Thu, 18 Jun 2026 22:42:25 +0800 Subject: [PATCH] refactor(cache): generalize tracked env query plumbing Co-authored-by: GPT-5 Codex --- crates/vite_task/src/session/cache/display.rs | 8 +- crates/vite_task/src/session/cache/mod.rs | 17 +-- .../src/session/execute/cache_update.rs | 31 +++--- .../src/session/execute/fingerprint.rs | 100 ++++++++++-------- .../vite_task/src/session/reporter/summary.rs | 13 +-- crates/vite_task_client/src/lib.rs | 36 +++++-- crates/vite_task_client_napi/src/lib.rs | 2 +- crates/vite_task_ipc_shared/src/lib.rs | 7 +- crates/vite_task_server/src/lib.rs | 64 +++++++---- crates/vite_task_server/tests/integration.rs | 8 +- 10 files changed, 183 insertions(+), 103 deletions(-) diff --git a/crates/vite_task/src/session/cache/display.rs b/crates/vite_task/src/session/cache/display.rs index 0e9d00254..f0c68fad0 100644 --- a/crates/vite_task/src/session/cache/display.rs +++ b/crates/vite_task/src/session/cache/display.rs @@ -187,7 +187,7 @@ pub fn format_cache_status_inline(cache_status: &CacheStatus) -> Option { format_input_change_str(*kind, path.as_str()) } FingerprintMismatch::TrackedEnvChanged(mismatch) - | FingerprintMismatch::TrackedEnvGlobChanged { mismatch, .. } => { + | FingerprintMismatch::TrackedEnvQueryChanged { mismatch, .. } => { vite_str::format!("{mismatch}") } }; @@ -249,8 +249,10 @@ mod tests { #[test] fn inline_tracked_env_mismatch_preserves_kind() { let added = CacheStatus::Miss(CacheMiss::FingerprintMismatch( - FingerprintMismatch::TrackedEnvGlobChanged { - pattern: Str::from("PROBE_*"), + FingerprintMismatch::TrackedEnvQueryChanged { + query: crate::session::execute::fingerprint::TrackedEnvQuery::Glob(Str::from( + "PROBE_*", + )), mismatch: EnvMismatch::Added { name: Str::from("PROBE_C") }, }, )); diff --git a/crates/vite_task/src/session/cache/mod.rs b/crates/vite_task/src/session/cache/mod.rs index fbafbf8fe..0991f60a6 100644 --- a/crates/vite_task/src/session/cache/mod.rs +++ b/crates/vite_task/src/session/cache/mod.rs @@ -25,7 +25,10 @@ use wincode::{ io::{Reader, Writer}, }; -use super::execute::{fingerprint::PostRunFingerprint, pipe::StdOutput}; +use super::execute::{ + fingerprint::{PostRunFingerprint, TrackedEnvQuery}, + pipe::StdOutput, +}; /// Cache lookup key identifying a task's execution configuration. /// @@ -211,9 +214,9 @@ pub enum FingerprintMismatch { }, /// A runner-aware tool-tracked env var changed between runs. TrackedEnvChanged(EnvMismatch), - /// A runner-aware tool-tracked env glob's match-set changed between runs. - TrackedEnvGlobChanged { - pattern: Str, + /// A runner-aware tool-tracked bulk env query's match-set changed between runs. + TrackedEnvQueryChanged { + query: TrackedEnvQuery, mismatch: EnvMismatch, }, } @@ -224,8 +227,8 @@ impl From for Fingerprint match mismatch { PostRunMismatch::Input { kind, path } => Self::InputChanged { kind, path }, PostRunMismatch::TrackedEnv(mismatch) => Self::TrackedEnvChanged(mismatch), - PostRunMismatch::TrackedEnvGlob { pattern, mismatch } => { - Self::TrackedEnvGlobChanged { pattern, mismatch } + PostRunMismatch::TrackedEnvQuery { query, mismatch } => { + Self::TrackedEnvQueryChanged { query, mismatch } } } } @@ -252,7 +255,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 = 16; +const CACHE_SCHEMA_VERSION: u32 = 17; /// Name of the per-version subdirectory (e.g. `v14`) under the task-cache /// directory that holds the database and output archives for the current diff --git a/crates/vite_task/src/session/execute/cache_update.rs b/crates/vite_task/src/session/execute/cache_update.rs index 85c730d84..3369230a9 100644 --- a/crates/vite_task/src/session/execute/cache_update.rs +++ b/crates/vite_task/src/session/execute/cache_update.rs @@ -11,7 +11,7 @@ use vite_task_server::Reports; use super::{ CacheState, - fingerprint::{PathRead, PostRunFingerprint}, + fingerprint::{PathRead, PostRunFingerprint, TrackedEnvQuery}, glob, spawn::ChildOutcome, }; @@ -37,7 +37,7 @@ struct TrackingOutcome { } type TrackedEnvValues = BTreeMap>; -type TrackedEnvGlobValues = BTreeMap>; +type TrackedEnvQueryValues = BTreeMap>; /// Decide whether the finished run may be cached, and store it if so. /// @@ -122,7 +122,7 @@ pub(super) async fn update_cache( // Collect tool-reported tracked envs for the post-run fingerprint. Env // names that the user already declared are skipped because their values // are already part of the spawn fingerprint. - let (tracked_envs, tracked_env_globs) = match collect_tracked_reports(reports, metadata) { + let (tracked_envs, tracked_env_queries) = match collect_tracked_reports(reports, metadata) { Ok(tracked_reports) => tracked_reports, Err(err) => { return ( @@ -142,7 +142,7 @@ pub(super) async fn update_cache( workspace_root, &globbed_inputs, tracked_envs, - tracked_env_globs, + tracked_env_queries, ) { Ok(fingerprint) => fingerprint, Err(err) => { @@ -262,12 +262,12 @@ fn observe_fspy( fn collect_tracked_reports( reports: Option<&Reports>, metadata: &CacheMetadata, -) -> anyhow::Result<(TrackedEnvValues, TrackedEnvGlobValues)> { +) -> anyhow::Result<(TrackedEnvValues, TrackedEnvQueryValues)> { reports .map(|reports| { let tracked_envs = collect_tracked_envs(reports, metadata)?; - let tracked_env_globs = collect_tracked_env_globs(reports)?; - Ok::<_, anyhow::Error>((tracked_envs, tracked_env_globs)) + let tracked_env_queries = collect_tracked_env_queries(reports)?; + Ok::<_, anyhow::Error>((tracked_envs, tracked_env_queries)) }) .transpose() .map(Option::unwrap_or_default) @@ -332,12 +332,12 @@ fn collect_tracked_envs( Ok(tracked_envs) } -/// Select tool-reported env-glob records to embed in the post-run +/// Select tool-reported bulk env query records to embed in the post-run /// fingerprint. The full match-set is stored as value hashes. -fn collect_tracked_env_globs(reports: &Reports) -> anyhow::Result { - let mut tracked_env_globs = BTreeMap::new(); +fn collect_tracked_env_queries(reports: &Reports) -> anyhow::Result { + let mut tracked_env_queries = BTreeMap::new(); - for (pattern, record) in &reports.tracked_get_envs { + for (query, record) in &reports.tracked_get_envs { let mut matches = BTreeMap::new(); for (name, value) in &record.matches { let name_str = name @@ -348,10 +348,15 @@ fn collect_tracked_env_globs(reports: &Reports) -> anyhow::Result { + TrackedEnvQuery::Glob(Str::from(pattern.as_ref())) + } + }; + tracked_env_queries.insert(query, matches); } - Ok(tracked_env_globs) + Ok(tracked_env_queries) } /// Collect output files and create a tar.zst archive in the cache directory. diff --git a/crates/vite_task/src/session/execute/fingerprint.rs b/crates/vite_task/src/session/execute/fingerprint.rs index 02f29fe19..9d4a425e0 100644 --- a/crates/vite_task/src/session/execute/fingerprint.rs +++ b/crates/vite_task/src/session/execute/fingerprint.rs @@ -24,6 +24,13 @@ use crate::{ session::cache::{EnvMismatch, InputChangeKind}, }; +#[derive( + SchemaWrite, SchemaRead, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, +)] +pub enum TrackedEnvQuery { + Glob(Str), +} + /// Path read access info #[derive(Debug, Clone, Copy)] pub struct PathRead { @@ -44,10 +51,10 @@ pub struct PostRunFingerprint { /// context that served the original request. pub tracked_envs: BTreeMap>, - /// Glob-pattern env queries (`getEnvs`) made with `tracked: true`. - /// Outer key is the glob pattern, inner map is the match-set at execution - /// time (name -> value hash). Validated at cache lookup by re-matching - /// against the current env context and comparing the resulting set. + /// Bulk env queries (`getEnvs`) made with `tracked: true`. + /// Outer key is the query, inner map is the match-set at execution time + /// (name -> value hash). Validated at cache lookup by re-matching against + /// the current env context and comparing the resulting set. /// /// Non-UTF-8 env names are never matched, saved, or treated as errors: /// they are not returned to the client, so their existence cannot affect @@ -55,7 +62,7 @@ pub struct PostRunFingerprint { /// value; the JS client errors when querying a matched non-UTF-8 value, /// and cache-hit validation treats a currently matched non-UTF-8 value as /// a changed mismatch so stale cached output is not replayed. - pub tracked_env_globs: BTreeMap>, + pub tracked_env_queries: BTreeMap>, } /// A mismatch between the stored post-run fingerprint and the current state. @@ -65,9 +72,9 @@ pub enum PostRunMismatch { Input { kind: InputChangeKind, path: RelativePathBuf }, /// A tool-tracked env var changed value, appeared, or disappeared. TrackedEnv(EnvMismatch), - /// A tool-tracked env glob's match-set changed between runs. Carries the - /// first differing entry in env-name order. - TrackedEnvGlob { pattern: Str, mismatch: EnvMismatch }, + /// A tool-tracked bulk env query's match-set changed between runs. Carries + /// the first differing entry in env-name order. + TrackedEnvQuery { query: TrackedEnvQuery, mismatch: EnvMismatch }, } /// Fingerprint for a single path (file or directory) @@ -107,14 +114,14 @@ impl PostRunFingerprint { /// * `base_dir` - Workspace root for resolving relative paths /// * `globbed_inputs` - Prerun glob fingerprint; paths here are skipped /// * `tracked_envs` - Tool-requested env vars (name -> value hash), validated on lookup - /// * `tracked_env_globs` - Tool-requested env globs (pattern -> match-set hashes) + /// * `tracked_env_queries` - Tool-requested bulk env queries (query -> match-set hashes) #[tracing::instrument(level = "debug", skip_all, name = "create_post_run_fingerprint")] pub fn create( inferred_path_reads: &HashMap, base_dir: &AbsolutePath, globbed_inputs: &BTreeMap, tracked_envs: BTreeMap>, - tracked_env_globs: BTreeMap>, + tracked_env_queries: BTreeMap>, ) -> anyhow::Result { let inferred_inputs = inferred_path_reads .par_iter() @@ -126,7 +133,7 @@ impl PostRunFingerprint { }) .collect::>>()?; - Ok(Self { inferred_inputs, tracked_envs, tracked_env_globs }) + Ok(Self { inferred_inputs, tracked_envs, tracked_env_queries }) } /// Validates the fingerprint against current filesystem state and the @@ -194,19 +201,19 @@ impl PostRunFingerprint { } } - for (pattern, stored_matches) in &self.tracked_env_globs { - let current_matches = match match_env_glob(pattern.as_str(), unfiltered_envs)? { - EnvGlobValidation::Matches(matches) => matches, - EnvGlobValidation::NonUtf8Value(mismatch) => { - return Ok(Some(PostRunMismatch::TrackedEnvGlob { - pattern: pattern.clone(), + for (query, stored_matches) in &self.tracked_env_queries { + let current_matches = match match_env_query(query, unfiltered_envs)? { + EnvQueryValidation::Matches(matches) => matches, + EnvQueryValidation::NonUtf8Value(mismatch) => { + return Ok(Some(PostRunMismatch::TrackedEnvQuery { + query: query.clone(), mismatch, })); } }; if let Some(mismatch) = first_env_glob_mismatch(stored_matches, ¤t_matches) { - return Ok(Some(PostRunMismatch::TrackedEnvGlob { - pattern: pattern.clone(), + return Ok(Some(PostRunMismatch::TrackedEnvQuery { + query: query.clone(), mismatch, })); } @@ -216,34 +223,41 @@ impl PostRunFingerprint { } } -/// Build the current match-set for `pattern` by enumerating the given env -/// snapshot and keeping UTF-8 names whose representation matches the glob. If -/// a matching env has a non-UTF-8 value, return a changed mismatch so the stale -/// cache entry is not replayed. -fn match_env_glob( - pattern: &str, +/// Build the current match-set for `query` by enumerating the given env +/// snapshot and keeping matching UTF-8 names. If a matching env has a non-UTF-8 +/// value, return a changed mismatch so the stale cache entry is not replayed. +fn match_env_query( + query: &TrackedEnvQuery, + envs: &FxHashMap, Arc>, +) -> anyhow::Result { + let TrackedEnvQuery::Glob(pattern) = query; + let glob = vite_glob::env::EnvGlob::new(pattern.as_str())?; + Ok(collect_matching_envs(envs, |name| glob.is_match(name))) +} + +fn collect_matching_envs( envs: &FxHashMap, Arc>, -) -> anyhow::Result { - let glob = vite_glob::env::EnvGlob::new(pattern)?; + is_match: impl Fn(&str) -> bool, +) -> EnvQueryValidation { let mut matches = BTreeMap::new(); for (name, value) in envs { let Some(name_str) = name.to_str() else { continue; }; - if !glob.is_match(name_str) { + if !is_match(name_str) { continue; } let Some(value_str) = value.to_str() else { - return Ok(EnvGlobValidation::NonUtf8Value(EnvMismatch::Changed { + return EnvQueryValidation::NonUtf8Value(EnvMismatch::Changed { name: Str::from(name_str), - })); + }); }; matches.insert(Str::from(name_str), EnvValueHash::new(value_str)); } - Ok(EnvGlobValidation::Matches(matches)) + EnvQueryValidation::Matches(matches) } -enum EnvGlobValidation { +enum EnvQueryValidation { Matches(BTreeMap), NonUtf8Value(EnvMismatch), } @@ -516,9 +530,10 @@ mod tests { #[test] fn validate_reports_current_non_utf8_tracked_env_glob_value_as_changed() { - let mut tracked_env_globs = BTreeMap::new(); - tracked_env_globs.insert(Str::from("PROBE_*"), BTreeMap::new()); - let fingerprint = PostRunFingerprint { tracked_env_globs, ..PostRunFingerprint::default() }; + let mut tracked_env_queries = BTreeMap::new(); + tracked_env_queries.insert(TrackedEnvQuery::Glob(Str::from("PROBE_*")), BTreeMap::new()); + let fingerprint = + PostRunFingerprint { tracked_env_queries, ..PostRunFingerprint::default() }; let mut unfiltered_envs = FxHashMap::default(); unfiltered_envs.insert( @@ -531,22 +546,23 @@ mod tests { fingerprint.validate(&workspace_root, &unfiltered_envs).expect("validation succeeds"); match mismatch { - Some(PostRunMismatch::TrackedEnvGlob { - pattern, + Some(PostRunMismatch::TrackedEnvQuery { + query, mismatch: EnvMismatch::Changed { name }, }) => { - assert_eq!(pattern.as_str(), "PROBE_*"); + assert_eq!(query, TrackedEnvQuery::Glob(Str::from("PROBE_*"))); assert_eq!(name.as_str(), "PROBE_BAD"); } - other => panic!("expected changed tracked env glob mismatch, got {other:?}"), + other => panic!("expected changed tracked env query mismatch, got {other:?}"), } } #[test] fn validate_ignores_non_utf8_tracked_env_glob_names() { - let mut tracked_env_globs = BTreeMap::new(); - tracked_env_globs.insert(Str::from("PROBE_*"), BTreeMap::new()); - let fingerprint = PostRunFingerprint { tracked_env_globs, ..PostRunFingerprint::default() }; + let mut tracked_env_queries = BTreeMap::new(); + tracked_env_queries.insert(TrackedEnvQuery::Glob(Str::from("PROBE_*")), BTreeMap::new()); + let fingerprint = + PostRunFingerprint { tracked_env_queries, ..PostRunFingerprint::default() }; let mut unfiltered_envs = FxHashMap::default(); unfiltered_envs.insert( diff --git a/crates/vite_task/src/session/reporter/summary.rs b/crates/vite_task/src/session/reporter/summary.rs index 31d4eb95b..83bd2a937 100644 --- a/crates/vite_task/src/session/reporter/summary.rs +++ b/crates/vite_task/src/session/reporter/summary.rs @@ -24,6 +24,7 @@ use crate::session::{ CacheDisabledReason, CacheErrorKind, CacheNotUpdatedReason, CacheStatus, CacheUpdateStatus, ExecutionError, }, + execute::fingerprint::TrackedEnvQuery, }; // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ @@ -137,9 +138,9 @@ pub enum SavedCacheMissReason { InputChanged { kind: InputChangeKind, path: Str }, /// A runner-aware tool reported a tracked env var that changed between runs. TrackedEnvChanged(EnvMismatch), - /// A runner-aware tool reported a tracked env glob whose match-set changed + /// A runner-aware tool reported a tracked bulk env query whose match-set changed /// between runs. Carries the first differing entry. - TrackedEnvGlobChanged { pattern: Str, mismatch: EnvMismatch }, + TrackedEnvQueryChanged { query: TrackedEnvQuery, mismatch: EnvMismatch }, } /// An execution error, serializable for persistence. @@ -286,9 +287,9 @@ impl SavedCacheMissReason { FingerprintMismatch::TrackedEnvChanged(mismatch) => { Self::TrackedEnvChanged(mismatch.clone()) } - FingerprintMismatch::TrackedEnvGlobChanged { pattern, mismatch } => { - Self::TrackedEnvGlobChanged { - pattern: pattern.clone(), + FingerprintMismatch::TrackedEnvQueryChanged { query, mismatch } => { + Self::TrackedEnvQueryChanged { + query: query.clone(), mismatch: mismatch.clone(), } } @@ -582,7 +583,7 @@ impl TaskResult { vite_str::format!("→ Cache miss: {desc}") } SavedCacheMissReason::TrackedEnvChanged(mismatch) - | SavedCacheMissReason::TrackedEnvGlobChanged { mismatch, .. } => { + | SavedCacheMissReason::TrackedEnvQueryChanged { mismatch, .. } => { vite_str::format!("→ Cache miss: {mismatch}") } }, diff --git a/crates/vite_task_client/src/lib.rs b/crates/vite_task_client/src/lib.rs index 1c30ffe58..5b0ba657d 100644 --- a/crates/vite_task_client/src/lib.rs +++ b/crates/vite_task_client/src/lib.rs @@ -8,7 +8,9 @@ use std::{ use native_str::NativeStr; use rustc_hash::FxHashMap; use vite_path::{self, AbsolutePath}; -use vite_task_ipc_shared::{GetEnvResponse, GetEnvsResponse, IPC_ENV_NAME, Request}; +use vite_task_ipc_shared::{ + EnvQuery as IpcEnvQuery, GetEnvResponse, GetEnvsResponse, IPC_ENV_NAME, Request, +}; use wincode::{SchemaRead, config::DefaultConfig}; #[cfg(unix)] @@ -21,6 +23,24 @@ pub struct Client { scratch: RefCell>, } +#[derive(Debug, Clone, Copy)] +pub enum GetEnvsQuery<'a> { + Glob(&'a str), +} + +impl<'a> GetEnvsQuery<'a> { + #[must_use] + pub const fn glob(pattern: &'a str) -> Self { + Self::Glob(pattern) + } +} + +impl<'a> From<&'a str> for GetEnvsQuery<'a> { + fn from(pattern: &'a str) -> Self { + Self::Glob(pattern) + } +} + impl Client { /// Scans `envs` for the runner's IPC connection info and connects if /// present. Typical callers pass `std::env::vars_os()`. @@ -101,19 +121,23 @@ impl Client { .map(|env_value| Arc::::from(env_value.to_cow_os_str().as_ref()))) } - /// Requests every env whose name matches `pattern` from the runner. The + /// Requests every env whose name matches `query` from the runner. The /// returned map is keyed by env name with its value. /// /// # Errors /// /// Returns an error if the request or response fails, or if the server - /// rejects the pattern as an invalid glob. - pub fn get_envs( + /// rejects a glob query as an invalid glob. + pub fn get_envs<'a>( &self, - pattern: &str, + query: impl Into>, tracked: bool, ) -> io::Result, Arc>> { - self.send(&Request::GetEnvs { pattern, tracked })?; + let query = query.into(); + let query = match query { + GetEnvsQuery::Glob(pattern) => IpcEnvQuery::Glob(pattern), + }; + self.send(&Request::GetEnvs { query, tracked })?; let response: GetEnvsResponse = self.recv()?; Ok(response .entries diff --git a/crates/vite_task_client_napi/src/lib.rs b/crates/vite_task_client_napi/src/lib.rs index 7bb7bc896..a437ee603 100644 --- a/crates/vite_task_client_napi/src/lib.rs +++ b/crates/vite_task_client_napi/src/lib.rs @@ -102,7 +102,7 @@ impl RunnerClient { let tracked = options.and_then(|o| o.tracked).unwrap_or(true); let matches = self .client - .get_envs(&pattern, tracked) + .get_envs(pattern.as_str(), tracked) .map_err(|err| err_string(vite_str::format!("{err}")))?; let mut result = HashMap::with_capacity(matches.len()); for (name, value) in matches { diff --git a/crates/vite_task_ipc_shared/src/lib.rs b/crates/vite_task_ipc_shared/src/lib.rs index d3c2be51e..3b27b6145 100644 --- a/crates/vite_task_ipc_shared/src/lib.rs +++ b/crates/vite_task_ipc_shared/src/lib.rs @@ -31,10 +31,15 @@ pub enum Request<'a> { IgnoreInput(&'a NativeStr), IgnoreOutput(&'a NativeStr), GetEnv { name: &'a NativeStr, tracked: bool }, - GetEnvs { pattern: &'a str, tracked: bool }, + GetEnvs { query: EnvQuery<'a>, tracked: bool }, DisableCache, } +#[derive(Debug, Clone, Copy, SchemaWrite, SchemaRead)] +pub enum EnvQuery<'a> { + Glob(&'a str), +} + #[derive(Debug, SchemaWrite, SchemaRead)] pub struct GetEnvResponse { pub env_value: Option>, diff --git a/crates/vite_task_server/src/lib.rs b/crates/vite_task_server/src/lib.rs index af45b59c2..7069d6fba 100644 --- a/crates/vite_task_server/src/lib.rs +++ b/crates/vite_task_server/src/lib.rs @@ -11,7 +11,9 @@ use rustc_hash::{FxHashMap, FxHashSet}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio_util::sync::CancellationToken; use vite_path::AbsolutePath; -use vite_task_ipc_shared::{GetEnvResponse, GetEnvsResponse, IPC_ENV_NAME, Request}; +use vite_task_ipc_shared::{ + EnvQuery as IpcEnvQuery, GetEnvResponse, GetEnvsResponse, IPC_ENV_NAME, Request, +}; use wincode::{SchemaWrite, config::DefaultConfig}; pub trait Handler { @@ -19,14 +21,14 @@ pub trait Handler { fn ignore_output(&mut self, path: &Arc); fn disable_cache(&mut self); fn get_env(&mut self, name: &OsStr, tracked: bool) -> Option>; - /// Returns the subset of the env map whose names match `pattern` as a glob. + /// Returns the subset of the env map whose names match `query`. /// /// # Errors /// - /// Returns an error if `pattern` fails to parse as a glob. + /// Returns an error if a glob query fails to parse. fn get_envs( &mut self, - pattern: &str, + query: &IpcEnvQuery<'_>, tracked: bool, ) -> Result, Arc>, vite_glob::env::EnvGlobError>; } @@ -70,18 +72,39 @@ pub struct Recorder { ignored_outputs: FxHashSet>, cache_disabled: bool, tracked_get_env: FxHashMap, Option>>, - tracked_get_envs: FxHashMap, EnvGlobRecord>, + tracked_get_envs: FxHashMap, /// The envs `get_env` resolves against. The runner supplies these for the /// spawned task; the server never re-reads the live process env. envs: Arc, Arc>>, } -/// A record of a tracked glob-pattern env query made via `get_envs`. +/// Owned env query key recorded for tracked `get_envs` calls. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum EnvQuery { + Glob(Arc), +} + +impl EnvQuery { + #[must_use] + pub fn glob(pattern: &str) -> Self { + Self::Glob(Arc::from(pattern)) + } +} + +impl From<&IpcEnvQuery<'_>> for EnvQuery { + fn from(query: &IpcEnvQuery<'_>) -> Self { + match query { + IpcEnvQuery::Glob(pattern) => Self::glob(pattern), + } + } +} + +/// A record of a tracked env query made via `get_envs`. /// /// `matches` is captured on the first call and reused on repeat queries; the /// server's env map is immutable for a task's lifetime. #[derive(Debug, Clone, PartialEq, Eq)] -pub struct EnvGlobRecord { +pub struct EnvQueryRecord { pub matches: FxHashMap, Arc>, } @@ -92,7 +115,7 @@ pub struct Reports { pub ignored_outputs: FxHashSet>, pub cache_disabled: bool, pub tracked_get_env: FxHashMap, Option>>, - pub tracked_get_envs: FxHashMap, EnvGlobRecord>, + pub tracked_get_envs: FxHashMap, } impl Recorder { @@ -143,12 +166,14 @@ impl Handler for Recorder { fn get_envs( &mut self, - pattern: &str, + query: &IpcEnvQuery<'_>, tracked: bool, ) -> Result, Arc>, vite_glob::env::EnvGlobError> { - if let Some(existing) = self.tracked_get_envs.get(pattern) { + let key = EnvQuery::from(query); + if let Some(existing) = self.tracked_get_envs.get(&key) { return Ok(existing.matches.clone()); } + let IpcEnvQuery::Glob(pattern) = query; let glob = vite_glob::env::EnvGlob::new(pattern)?; let matches: FxHashMap, Arc> = self .envs @@ -163,8 +188,7 @@ impl Handler for Recorder { }) .collect(); if tracked { - self.tracked_get_envs - .insert(Arc::from(pattern), EnvGlobRecord { matches: matches.clone() }); + self.tracked_get_envs.insert(key, EnvQueryRecord { matches: matches.clone() }); } Ok(matches) } @@ -405,14 +429,14 @@ async fn handle_client(mut stream: Stream, handler: &RefCell) -> let response = GetEnvResponse { env_value: value.as_deref().map(Into::into) }; write_response(&mut stream, &response).await.map_err(Error::WriteResponse)?; } - Request::GetEnvs { pattern, tracked } => { - let matches = - handler.borrow_mut().get_envs(pattern, tracked).map_err(|source| { - Error::InvalidGlob(Box::new(InvalidGlob { - pattern: Box::::from(pattern), - source, - })) - })?; + Request::GetEnvs { query, tracked } => { + let matches = handler.borrow_mut().get_envs(&query, tracked).map_err(|source| { + let IpcEnvQuery::Glob(pattern) = query; + Error::InvalidGlob(Box::new(InvalidGlob { + pattern: Box::::from(pattern), + source, + })) + })?; let response = GetEnvsResponse { entries: matches.iter().map(|(k, v)| ((&**k).into(), (&**v).into())).collect(), }; diff --git a/crates/vite_task_server/tests/integration.rs b/crates/vite_task_server/tests/integration.rs index 09523486e..4b37f0bc5 100644 --- a/crates/vite_task_server/tests/integration.rs +++ b/crates/vite_task_server/tests/integration.rs @@ -15,7 +15,7 @@ use rustc_hash::FxHashMap; use tokio::runtime::Builder; use vite_task_client::Client; use vite_task_ipc_shared::Request; -use vite_task_server::{Error, Recorder, Reports, ServerHandle, serve}; +use vite_task_server::{EnvQuery, Error, Recorder, Reports, ServerHandle, serve}; fn env_map(pairs: &[(&str, &str)]) -> FxHashMap, Arc> { pairs @@ -237,7 +237,7 @@ fn get_envs_returns_matching_entries() { .expect("driver returned error"); assert!(!reports.cache_disabled); - let glob = reports.tracked_get_envs.get("PROBE_*").expect("glob recorded"); + let glob = reports.tracked_get_envs.get(&EnvQuery::glob("PROBE_*")).expect("glob recorded"); assert_eq!(glob.matches.len(), 2); } @@ -252,7 +252,7 @@ fn get_envs_empty_match_set_is_returned() { assert!(!reports.cache_disabled); assert!( - !reports.tracked_get_envs.contains_key("PROBE_*"), + !reports.tracked_get_envs.contains_key(&EnvQuery::glob("PROBE_*")), "untracked getEnvs calls are not recorded" ); } @@ -269,7 +269,7 @@ fn get_envs_untracked_then_tracked_records_once() { }) .expect("driver returned error"); - let glob = reports.tracked_get_envs.get("PROBE_*").expect("glob recorded"); + let glob = reports.tracked_get_envs.get(&EnvQuery::glob("PROBE_*")).expect("glob recorded"); assert_eq!(glob.matches.len(), 1); }