Skip to content

Commit 5eb6631

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 e66bcdb commit 5eb6631

8 files changed

Lines changed: 140 additions & 53 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::Injection(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
@@ -73,8 +73,9 @@ impl SpyImpl {
7373
}
7474

7575
#[expect(clippy::unused_async, reason = "async signature required by SpyImpl trait")]
76-
pub(crate) async fn spawn(&self, command: Command) -> Result<TrackedChild, SpawnError> {
76+
pub(crate) async fn spawn(&self, mut command: Command) -> Result<TrackedChild, SpawnError> {
7777
let ansi_dll_path_with_nul = Arc::clone(&self.ansi_dll_path_with_nul);
78+
command.env("FSPY", "1");
7879
let mut command = command.into_tokio_command();
7980

8081
command.creation_flags(CREATE_SUSPENDED);

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

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ use super::{
2929
StdioSuggestion,
3030
},
3131
};
32-
use crate::{Session, session::execute::spawn::SpawnTrackResult};
32+
use crate::Session;
33+
use self::spawn::TrackedPathAccesses;
3334

3435
/// Outcome of a spawned execution.
3536
///
@@ -309,8 +310,19 @@ pub async fn execute_spawn(
309310
}
310311

311312
// 5. Piped mode: execute spawn with tracking, streaming output to writers.
312-
let mut track_result_with_cache_metadata_and_inputs =
313-
cache_metadata.map(|cache_metadata| (SpawnTrackResult::default(), cache_metadata, globbed_inputs));
313+
// - std_outputs: always captured when caching is enabled (for cache replay)
314+
// - path_accesses: only tracked when includes_auto is true (fspy inference)
315+
let (mut std_outputs, mut path_accesses, cache_metadata_and_inputs) =
316+
if let Some(cache_metadata) = cache_metadata {
317+
let path_accesses = if cache_metadata.input_config.includes_auto {
318+
Some(TrackedPathAccesses::default())
319+
} else {
320+
None // Skip fspy when inference is disabled
321+
};
322+
(Some(Vec::new()), path_accesses, Some((cache_metadata, globbed_inputs)))
323+
} else {
324+
(None, None, None)
325+
};
314326

315327
#[expect(
316328
clippy::large_futures,
@@ -321,7 +333,8 @@ pub async fn execute_spawn(
321333
cache_base_path,
322334
&mut stdio_config.stdout_writer,
323335
&mut stdio_config.stderr_writer,
324-
track_result_with_cache_metadata_and_inputs.as_mut().map(|(track_result, _, _)| track_result),
336+
std_outputs.as_mut(),
337+
path_accesses.as_mut(),
325338
)
326339
.await
327340
{
@@ -341,20 +354,25 @@ pub async fn execute_spawn(
341354
// 6. Update cache if successful and determine cache update status.
342355
// Errors during cache update are terminal (reported through finish).
343356
let (cache_update_status, cache_error) =
344-
if let Some((track_result, cache_metadata, globbed_inputs)) =
345-
track_result_with_cache_metadata_and_inputs
346-
{
357+
if let Some((cache_metadata, globbed_inputs)) = cache_metadata_and_inputs {
347358
if result.exit_status.success() {
359+
// path_reads is empty when inference is disabled (path_accesses is None)
360+
let empty_path_reads = Default::default();
361+
let path_reads = path_accesses
362+
.as_ref()
363+
.map(|pa| &pa.path_reads)
364+
.unwrap_or(&empty_path_reads);
365+
348366
// Execution succeeded — attempt to create fingerprint and update cache
349367
match PostRunFingerprint::create(
350-
&track_result.path_reads,
368+
path_reads,
351369
cache_base_path,
352370
&cache_metadata.input_config,
353371
) {
354372
Ok(post_run_fingerprint) => {
355373
let new_cache_value = CommandCacheValue {
356374
post_run_fingerprint,
357-
std_outputs: track_result.std_outputs.clone().into(),
375+
std_outputs: std_outputs.unwrap_or_default().into(),
358376
duration: result.duration,
359377
};
360378
match cache

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

Lines changed: 38 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -43,27 +43,25 @@ pub struct SpawnResult {
4343
pub duration: Duration,
4444
}
4545

46-
/// Tracking result from a spawned process for caching
46+
/// Tracked file accesses from fspy.
47+
/// Only populated when fspy tracking is enabled (includes_auto is true).
4748
#[derive(Default, Debug)]
48-
pub struct SpawnTrackResult {
49-
/// captured stdout/stderr
50-
pub std_outputs: Vec<StdOutput>,
51-
49+
pub struct TrackedPathAccesses {
5250
/// Tracked path reads
5351
pub path_reads: HashMap<RelativePathBuf, PathRead>,
5452

5553
/// Tracked path writes
5654
pub path_writes: FxHashSet<RelativePathBuf>,
5755
}
5856

59-
/// Spawn a command with file system tracking via fspy, using piped stdio.
57+
/// Spawn a command with optional file system tracking via fspy, using piped stdio.
6058
///
61-
/// Returns the execution result including captured outputs, exit status,
62-
/// and tracked file accesses.
59+
/// Returns the execution result including exit status and duration.
6360
///
6461
/// - stdin is always `/dev/null` (piped mode is for non-interactive execution).
6562
/// - `stdout_writer`/`stderr_writer` receive the child's stdout/stderr output in real-time.
66-
/// - `track_result` if provided, will be populated with captured outputs and path accesses for caching. If `None`, tracking is disabled.
63+
/// - `std_outputs` if provided, will be populated with captured outputs for cache replay.
64+
/// - `path_accesses` if provided, fspy will be used to track file accesses. If `None`, fspy is disabled.
6765
#[tracing::instrument(level = "debug", skip_all)]
6866
#[expect(clippy::future_not_send, reason = "uses !Send dyn AsyncWrite writers internally")]
6967
#[expect(
@@ -75,15 +73,17 @@ pub async fn spawn_with_tracking(
7573
workspace_root: &AbsolutePath,
7674
stdout_writer: &mut (dyn AsyncWrite + Unpin),
7775
stderr_writer: &mut (dyn AsyncWrite + Unpin),
78-
track_result: Option<&mut SpawnTrackResult>,
76+
std_outputs: Option<&mut Vec<StdOutput>>,
77+
path_accesses: Option<&mut TrackedPathAccesses>,
7978
) -> anyhow::Result<SpawnResult> {
80-
/// The tracking state of the spawned process
81-
enum TrackingState<'a> {
82-
/// Tacking is enabled, with the tracked child and result reference
83-
Enabled(fspy::TrackedChild, &'a mut SpawnTrackResult),
84-
85-
/// Tracking is disabled, with the tokio child process
86-
Disabled(tokio::process::Child),
79+
/// The tracking state of the spawned process.
80+
/// Determined by whether path_accesses is Some (fspy enabled) or None (fspy disabled).
81+
enum TrackingState {
82+
/// fspy tracking is enabled
83+
FspyEnabled(fspy::TrackedChild),
84+
85+
/// fspy tracking is disabled, using plain tokio process
86+
FspyDisabled(tokio::process::Child),
8787
}
8888

8989
let mut cmd = fspy::Command::new(spawn_command.program_path.as_path());
@@ -92,27 +92,25 @@ pub async fn spawn_with_tracking(
9292
cmd.current_dir(&*spawn_command.cwd);
9393
cmd.stdin(Stdio::null()).stdout(Stdio::piped()).stderr(Stdio::piped());
9494

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

103103
let mut child_stdout = match &mut tracking_state {
104-
TrackingState::Enabled(tracked_child, _) => tracked_child.stdout.take().unwrap(),
105-
TrackingState::Disabled(tokio_child) => tokio_child.stdout.take().unwrap(),
104+
TrackingState::FspyEnabled(tracked_child) => tracked_child.stdout.take().unwrap(),
105+
TrackingState::FspyDisabled(tokio_child) => tokio_child.stdout.take().unwrap(),
106106
};
107107
let mut child_stderr = match &mut tracking_state {
108-
TrackingState::Enabled(tracked_child, _) => tracked_child.stderr.take().unwrap(),
109-
TrackingState::Disabled(tokio_child) => tokio_child.stderr.take().unwrap(),
108+
TrackingState::FspyEnabled(tracked_child) => tracked_child.stderr.take().unwrap(),
109+
TrackingState::FspyDisabled(tokio_child) => tokio_child.stderr.take().unwrap(),
110110
};
111111

112-
let mut outputs = match &mut tracking_state {
113-
TrackingState::Enabled(_, track_result) => Some(&mut track_result.std_outputs),
114-
TrackingState::Disabled(_) => None,
115-
};
112+
// Output capturing is independent of fspy tracking
113+
let mut outputs = std_outputs;
116114
let mut stdout_buf = [0u8; 8192];
117115
let mut stderr_buf = [0u8; 8192];
118116
let mut stdout_done = false;
@@ -169,22 +167,20 @@ pub async fn spawn_with_tracking(
169167
}
170168
}
171169

172-
let (termination, track_result) = match tracking_state {
173-
TrackingState::Enabled(tracked_child, track_result) => {
174-
(tracked_child.wait_handle.await?, track_result)
175-
}
176-
TrackingState::Disabled(mut tokio_child) => {
177-
return Ok(SpawnResult {
178-
exit_status: tokio_child.wait().await?,
179-
duration: start.elapsed(),
180-
});
170+
let termination = match tracking_state {
171+
TrackingState::FspyEnabled(tracked_child) => Some(tracked_child.wait_handle.await?),
172+
TrackingState::FspyDisabled(mut tokio_child) => {
173+
let exit_status = tokio_child.wait().await?;
174+
return Ok(SpawnResult { exit_status, duration: start.elapsed() });
181175
}
182176
};
183177
let duration = start.elapsed();
184178

185-
// Process path accesses
186-
let path_reads = &mut track_result.path_reads;
187-
let path_writes = &mut track_result.path_writes;
179+
// Process path accesses from fspy (only when fspy was enabled)
180+
let termination = termination.expect("fspy enabled but no termination");
181+
let path_accesses = path_accesses.expect("fspy enabled but no path_accesses");
182+
let path_reads = &mut path_accesses.path_reads;
183+
let path_writes = &mut path_accesses.path_writes;
188184

189185
for access in termination.path_accesses.iter() {
190186
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: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,24 @@ steps = [
179179
# Initial run
180180
"vite run empty-inputs",
181181
# Change the command
182-
"json-edit vite.config.json \"_.tasks['empty-inputs'].command = 'print-file src/utils.ts'\"",
182+
"json-edit vite-task.json \"_.tasks['empty-inputs'].command = 'print-file src/utils.ts'\"",
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-task.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)