@@ -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| {
0 commit comments