Skip to content

Commit 26715e8

Browse files
branchseerclaude
andcommitted
feat(cache): disable fspy when auto inference is off
- Add FSPY=1 env var to tracked processes in fspy crate (unix + windows) to indicate when file tracking is active - Split SpawnTrackResult into separate Vec<StdOutput> and TrackedPathAccesses to decouple output capturing from file access tracking - Only enable fspy when input_config.includes_auto is true - When inference is disabled (explicit globs only), spawn without fspy overhead while still capturing outputs for cache replay - Add e2e tests verifying FSPY env is set/unset based on config Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 97b3c8d commit 26715e8

8 files changed

Lines changed: 133 additions & 54 deletions

File tree

crates/fspy/src/unix/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ impl SpyImpl {
100100
)
101101
.map_err(|err| SpawnError::InjectionError(err.into()))?;
102102
command.set_exec(exec);
103+
command.env("FSPY", "1");
103104

104105
let mut tokio_command = command.into_tokio_command();
105106

crates/fspy/src/windows/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,9 @@ impl SpyImpl {
7171
Ok(Self { ansi_dll_path_with_nul: ansi_dll_path_with_nul.into() })
7272
}
7373

74-
pub(crate) async fn spawn(&self, command: Command) -> Result<TrackedChild, SpawnError> {
74+
pub(crate) async fn spawn(&self, mut command: Command) -> Result<TrackedChild, SpawnError> {
7575
let ansi_dll_path_with_nul = Arc::clone(&self.ansi_dll_path_with_nul);
76+
command.env("FSPY", "1");
7677
let mut command = command.into_tokio_command();
7778

7879
command.creation_flags(CREATE_SUSPENDED);

crates/vite_task/src/session/execute/mod.rs

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use super::{
2525
},
2626
reporter::{ExitStatus, Reporter},
2727
};
28-
use crate::{Session, session::execute::spawn::SpawnTrackResult};
28+
use crate::{Session, session::execute::spawn::TrackedPathAccesses};
2929

3030
/// Internal error type used to abort execution when errors occur.
3131
/// This error is swallowed in Session::execute and never exposed externally.
@@ -261,11 +261,18 @@ impl ExecutionContext<'_> {
261261

262262
// 4. Execute spawn (cache miss or disabled)
263263
// Track file system access if caching is enabled (for future cache updates)
264-
let mut track_result_with_cache_metadata_and_inputs =
264+
// - std_outputs: always captured when caching is enabled (for cache replay)
265+
// - path_accesses: only tracked when includes_auto is true (fspy inference)
266+
let (mut std_outputs, mut path_accesses, cache_metadata_and_inputs) =
265267
if let Some(cache_metadata) = cache_metadata {
266-
Some((SpawnTrackResult::default(), cache_metadata, globbed_inputs))
268+
let path_accesses = if cache_metadata.input_config.includes_auto {
269+
Some(TrackedPathAccesses::default())
270+
} else {
271+
None // Skip fspy when inference is disabled
272+
};
273+
(Some(Vec::new()), path_accesses, Some((cache_metadata, globbed_inputs)))
267274
} else {
268-
None
275+
(None, None, None)
269276
};
270277

271278
// Execute command with tracking, emitting output events in real-time
@@ -284,9 +291,8 @@ impl ExecutionContext<'_> {
284291
},
285292
});
286293
},
287-
track_result_with_cache_metadata_and_inputs
288-
.as_mut()
289-
.map(|(track_result, _, _)| track_result),
294+
std_outputs.as_mut(),
295+
path_accesses.as_mut(),
290296
)
291297
.await
292298
{
@@ -303,20 +309,24 @@ impl ExecutionContext<'_> {
303309
};
304310

305311
// 5. Update cache if successful
306-
// Only update cache if: (a) tracking was enabled, and (b) execution succeeded
307-
if let Some((track_result, cache_metadata, globbed_inputs)) =
308-
track_result_with_cache_metadata_and_inputs
312+
// Only update cache if: (a) caching was enabled, and (b) execution succeeded
313+
if let Some((cache_metadata, globbed_inputs)) = cache_metadata_and_inputs
309314
&& result.exit_status.success()
310315
{
316+
// path_reads is empty when inference is disabled (path_accesses is None)
317+
let empty_path_reads = Default::default();
318+
let path_reads =
319+
path_accesses.as_ref().map(|pa| &pa.path_reads).unwrap_or(&empty_path_reads);
320+
311321
match PostRunFingerprint::create(
312-
&track_result.path_reads,
322+
path_reads,
313323
&*self.cache_base_path,
314324
&cache_metadata.input_config,
315325
) {
316326
Ok(post_run_fingerprint) => {
317327
let cache_value = CommandCacheValue {
318328
post_run_fingerprint,
319-
std_outputs: track_result.std_outputs.clone().into(),
329+
std_outputs: std_outputs.unwrap_or_default().into(),
320330
duration: result.duration,
321331
};
322332
if let Err(err) = self

crates/vite_task/src/session/execute/spawn.rs

Lines changed: 37 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -47,31 +47,30 @@ pub struct SpawnResult {
4747
pub duration: Duration,
4848
}
4949

50-
/// Tracking result from a spawned process for caching
50+
/// Tracked file accesses from fspy.
51+
/// Only populated when fspy tracking is enabled (includes_auto is true).
5152
#[derive(Default, Debug)]
52-
pub struct SpawnTrackResult {
53-
/// captured stdout/stderr
54-
pub std_outputs: Vec<StdOutput>,
55-
53+
pub struct TrackedPathAccesses {
5654
/// Tracked path reads
5755
pub path_reads: HashMap<RelativePathBuf, PathRead>,
5856

5957
/// Tracked path writes
6058
pub path_writes: HashMap<RelativePathBuf, PathWrite>,
6159
}
6260

63-
/// Spawn a command with file system tracking via fspy.
61+
/// Spawn a command with optional file system tracking via fspy.
6462
///
65-
/// Returns the execution result including captured outputs, exit status,
66-
/// and tracked file accesses.
63+
/// Returns the execution result including exit status and duration.
6764
///
6865
/// - `on_output` is called in real-time as stdout/stderr data arrives.
69-
/// - `track_result` if provided, will be populated with captured outputs and path accesses for caching. If `None`, tracking is disabled.
66+
/// - `std_outputs` if provided, will be populated with captured outputs for cache replay.
67+
/// - `path_accesses` if provided, fspy will be used to track file accesses. If `None`, fspy is disabled.
7068
pub async fn spawn_with_tracking<F>(
7169
spawn_command: &SpawnCommand,
7270
workspace_root: &AbsolutePath,
7371
mut on_output: F,
74-
track_result: Option<&mut SpawnTrackResult>,
72+
std_outputs: Option<&mut Vec<StdOutput>>,
73+
path_accesses: Option<&mut TrackedPathAccesses>,
7574
) -> anyhow::Result<SpawnResult>
7675
where
7776
F: FnMut(OutputKind, BString),
@@ -82,36 +81,35 @@ where
8281
cmd.current_dir(&*spawn_command.cwd);
8382
cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
8483

85-
/// The tracking state of the spawned process
86-
enum TrackingState<'a> {
87-
/// Tacking is enabled, with the tracked child and result reference
88-
Enabled(fspy::TrackedChild, &'a mut SpawnTrackResult),
84+
/// The tracking state of the spawned process.
85+
/// Determined by whether path_accesses is Some (fspy enabled) or None (fspy disabled).
86+
enum TrackingState {
87+
/// fspy tracking is enabled
88+
FspyEnabled(fspy::TrackedChild),
8989

90-
/// Tracking is disabled, with the tokio child process
91-
Disabled(tokio::process::Child),
90+
/// fspy tracking is disabled, using plain tokio process
91+
FspyDisabled(tokio::process::Child),
9292
}
9393

94-
let mut tracking_state = if let Some(track_result) = track_result {
95-
// track_result is Some. Spawn with tracking enabled
96-
TrackingState::Enabled(cmd.spawn().await?, track_result)
94+
let mut tracking_state = if path_accesses.is_some() {
95+
// path_accesses is Some, spawn with fspy tracking enabled
96+
TrackingState::FspyEnabled(cmd.spawn().await?)
9797
} else {
98-
// Spawn without tracking
99-
TrackingState::Disabled(cmd.into_tokio_command().spawn()?)
98+
// path_accesses is None, spawn without fspy
99+
TrackingState::FspyDisabled(cmd.into_tokio_command().spawn()?)
100100
};
101101

102102
let mut child_stdout = match &mut tracking_state {
103-
TrackingState::Enabled(tracked_child, _) => tracked_child.stdout.take().unwrap(),
104-
TrackingState::Disabled(tokio_child) => tokio_child.stdout.take().unwrap(),
103+
TrackingState::FspyEnabled(tracked_child) => tracked_child.stdout.take().unwrap(),
104+
TrackingState::FspyDisabled(tokio_child) => tokio_child.stdout.take().unwrap(),
105105
};
106106
let mut child_stderr = match &mut tracking_state {
107-
TrackingState::Enabled(tracked_child, _) => tracked_child.stderr.take().unwrap(),
108-
TrackingState::Disabled(tokio_child) => tokio_child.stderr.take().unwrap(),
107+
TrackingState::FspyEnabled(tracked_child) => tracked_child.stderr.take().unwrap(),
108+
TrackingState::FspyDisabled(tokio_child) => tokio_child.stderr.take().unwrap(),
109109
};
110110

111-
let mut outputs = match &mut tracking_state {
112-
TrackingState::Enabled(_, track_result) => Some(&mut track_result.std_outputs),
113-
TrackingState::Disabled(_) => None,
114-
};
111+
// Output capturing is independent of fspy tracking
112+
let mut outputs = std_outputs;
115113
let mut stdout_buf = [0u8; 8192];
116114
let mut stderr_buf = [0u8; 8192];
117115
let mut stdout_done = false;
@@ -156,22 +154,20 @@ where
156154
}
157155
}
158156

159-
let (termination, track_result) = match tracking_state {
160-
TrackingState::Enabled(tracked_child, track_result) => {
161-
(tracked_child.wait_handle.await?, track_result)
162-
}
163-
TrackingState::Disabled(mut tokio_child) => {
164-
return Ok(SpawnResult {
165-
exit_status: tokio_child.wait().await?,
166-
duration: start.elapsed(),
167-
});
157+
let termination = match tracking_state {
158+
TrackingState::FspyEnabled(tracked_child) => Some(tracked_child.wait_handle.await?),
159+
TrackingState::FspyDisabled(mut tokio_child) => {
160+
let exit_status = tokio_child.wait().await?;
161+
return Ok(SpawnResult { exit_status, duration: start.elapsed() });
168162
}
169163
};
170164
let duration = start.elapsed();
171165

172-
// Process path accesses
173-
let path_reads = &mut track_result.path_reads;
174-
let path_writes = &mut track_result.path_writes;
166+
// Process path accesses from fspy (only when fspy was enabled)
167+
let termination = termination.expect("fspy enabled but no termination");
168+
let path_accesses = path_accesses.expect("fspy enabled but no path_accesses");
169+
let path_reads = &mut path_accesses.path_reads;
170+
let path_writes = &mut path_accesses.path_writes;
175171

176172
for access in termination.path_accesses.iter() {
177173
let relative_path = access.path.strip_path_prefix(workspace_root, |strip_result| {

crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/snapshots.toml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,3 +183,20 @@ steps = [
183183
# Cache miss: command changed
184184
"vite run empty-inputs",
185185
]
186+
187+
# 7. FSPY environment variable
188+
# - FSPY=1 is set when fspy is enabled (includes_auto is true)
189+
# - FSPY is NOT set when fspy is disabled (explicit globs only)
190+
[[e2e]]
191+
name = "fspy env - set when auto inference enabled"
192+
steps = [
193+
# Run task with auto inference - should see FSPY=1
194+
"vite run check-fspy-env-with-auto",
195+
]
196+
197+
[[e2e]]
198+
name = "fspy env - not set when auto inference disabled"
199+
steps = [
200+
# Run task without auto inference - should see (undefined)
201+
"vite run check-fspy-env-without-auto",
202+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
source: crates/vite_task_bin/tests/e2e_snapshots/main.rs
3+
expression: e2e_outputs
4+
input_file: crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test
5+
---
6+
> vite run check-fspy-env-without-auto
7+
$ print-env FSPY
8+
(undefined)
9+
10+
11+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
12+
Vite+ Task RunnerExecution Summary
13+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
14+
15+
Statistics: 1 tasks0 cache hits1 cache misses
16+
Performance: 0% cache hit rate
17+
18+
Task Details:
19+
────────────────────────────────────────────────
20+
[1] inputs-cache-test#check-fspy-env-without-auto: $ print-env FSPY
21+
Cache miss: no previous cache entry found
22+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
source: crates/vite_task_bin/tests/e2e_snapshots/main.rs
3+
expression: e2e_outputs
4+
input_file: crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test
5+
---
6+
> vite run check-fspy-env-with-auto
7+
$ print-env FSPY
8+
1
9+
10+
11+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
12+
Vite+ Task RunnerExecution Summary
13+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
14+
15+
Statistics: 1 tasks0 cache hits1 cache misses
16+
Performance: 0% cache hit rate
17+
18+
Task Details:
19+
────────────────────────────────────────────────
20+
[1] inputs-cache-test#check-fspy-env-with-auto: $ print-env FSPY
21+
Cache miss: no previous cache entry found
22+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

crates/vite_task_bin/tests/e2e_snapshots/fixtures/inputs-cache-test/vite.config.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,16 @@
3434
"command": "print-file src/main.ts",
3535
"inputs": [],
3636
"cache": true
37+
},
38+
"check-fspy-env-with-auto": {
39+
"command": "print-env FSPY",
40+
"inputs": [{ "auto": true }],
41+
"cache": true
42+
},
43+
"check-fspy-env-without-auto": {
44+
"command": "print-env FSPY",
45+
"inputs": ["src/**/*.ts"],
46+
"cache": true
3747
}
3848
}
3949
}

0 commit comments

Comments
 (0)