@@ -69,8 +69,10 @@ impl fmt::Display for TaskCycleError {
6969impl std:: error:: Error for TaskCycleError { }
7070
7171#[ derive( Debug , Clone , Default , PartialEq , Eq ) ]
72- /// State contributed by a task's completed direct dependencies.
72+ /// State contributed by a task's completed dependencies.
7373pub ( crate ) struct TaskDependencyState {
74+ /// Prerequisite tasks whose declared outputs may be read by this task.
75+ pub ( crate ) dependencies : Vec < Task > ,
7476 /// Stable artifact identities to include in the task's cache key.
7577 pub cache_keys : Vec < String > ,
7678 /// Whether any dependency executed or restored outputs.
@@ -104,6 +106,7 @@ pub(crate) struct Deps {
104106 executed : HashSet < TaskKey > , // tasks that actually began executing (not just scheduled)
105107 did_work : HashSet < TaskKey > , // tasks that executed or restored outputs (not freshness-skipped)
106108 cache_keys : HashMap < TaskKey , String > , // stable artifact identities published by completed tasks
109+ tasks : HashMap < TaskKey , Task > , // resolved definitions retained after graph nodes are removed
107110 dep_edges : HashMap < TaskKey , HashSet < TaskKey > > , // maps each task to its direct dependency task keys
108111 post_dep_parents : HashMap < TaskKey , HashSet < TaskKey > > , // maps each post-subtree task to its triggering parents
109112 tx : mpsc:: UnboundedSender < Option < Task > > ,
@@ -306,6 +309,10 @@ impl Deps {
306309 let executed = HashSet :: new ( ) ;
307310 let did_work = HashSet :: new ( ) ;
308311 let cache_keys = HashMap :: new ( ) ;
312+ let tasks = graph
313+ . node_indices ( )
314+ . map ( |idx| ( task_key ( & graph[ idx] ) , graph[ idx] . clone ( ) ) )
315+ . collect ( ) ;
309316 Ok ( Self {
310317 graph,
311318 tx,
@@ -314,6 +321,7 @@ impl Deps {
314321 executed,
315322 did_work,
316323 cache_keys,
324+ tasks,
317325 dep_edges,
318326 post_dep_parents,
319327 } )
@@ -450,7 +458,23 @@ impl Deps {
450458 . collect :: < Vec < _ > > ( ) ;
451459 cache_keys. sort ( ) ;
452460 cache_keys. dedup ( ) ;
461+ let mut prerequisite_keys = deps. clone ( ) ;
462+ let mut pending = deps. iter ( ) . copied ( ) . collect_vec ( ) ;
463+ while let Some ( key) = pending. pop ( ) {
464+ for dependency in self . dep_edges . get ( key) . into_iter ( ) . flatten ( ) {
465+ if prerequisite_keys. insert ( dependency) {
466+ pending. push ( dependency) ;
467+ }
468+ }
469+ }
470+ let mut dependencies = prerequisite_keys
471+ . iter ( )
472+ . filter_map ( |key| self . tasks . get ( * key) . cloned ( ) )
473+ . collect_vec ( ) ;
474+ dependencies. sort ( ) ;
475+ dependencies. dedup ( ) ;
453476 TaskDependencyState {
477+ dependencies,
454478 cache_keys,
455479 any_did_work : deps. iter ( ) . any ( |dep_key| self . did_work . contains ( dep_key) ) ,
456480 any_unkeyed_did_work : deps. iter ( ) . any ( |dep_key| {
@@ -669,6 +693,7 @@ mod tests {
669693 executed : HashSet :: new ( ) ,
670694 did_work : HashSet :: new ( ) ,
671695 cache_keys : HashMap :: new ( ) ,
696+ tasks : HashMap :: new ( ) ,
672697 dep_edges,
673698 post_dep_parents,
674699 tx,
@@ -706,6 +731,7 @@ mod tests {
706731 assert_eq ! (
707732 deps. dependency_state( & c) ,
708733 TaskDependencyState {
734+ dependencies: vec![ ] ,
709735 cache_keys: vec![ ] ,
710736 any_did_work: true ,
711737 any_unkeyed_did_work: true ,
@@ -716,13 +742,56 @@ mod tests {
716742 assert_eq ! (
717743 deps. dependency_state( & c) ,
718744 TaskDependencyState {
745+ dependencies: vec![ ] ,
719746 cache_keys: vec![ "b-key" . to_string( ) ] ,
720747 any_did_work: true ,
721748 any_unkeyed_did_work: false ,
722749 }
723750 ) ;
724751 }
725752
753+ // https://github.com/jdx/mise/discussions/12264
754+ #[ test]
755+ fn dependency_state_retains_direct_dependency_tasks ( ) {
756+ let dependency = Task {
757+ outputs : crate :: task:: TaskOutputs :: Files ( vec ! [ "dist" . to_string( ) ] ) ,
758+ ..task ( "dependency" )
759+ } ;
760+ let parent = task ( "parent" ) ;
761+ let mut deps = deps_with_relationships (
762+ HashMap :: from ( [ ( task_key ( & parent) , HashSet :: from ( [ task_key ( & dependency) ] ) ) ] ) ,
763+ HashMap :: new ( ) ,
764+ ) ;
765+ deps. tasks . insert ( task_key ( & dependency) , dependency. clone ( ) ) ;
766+
767+ assert_eq ! ( deps. dependency_state( & parent) . dependencies, [ dependency] ) ;
768+ }
769+
770+ // https://github.com/jdx/mise/discussions/12264
771+ #[ test]
772+ fn dependency_state_retains_transitive_prerequisite_tasks ( ) {
773+ let first = Task {
774+ outputs : crate :: task:: TaskOutputs :: Files ( vec ! [ "first-output" . to_string( ) ] ) ,
775+ ..task ( "first" )
776+ } ;
777+ let second = Task {
778+ outputs : crate :: task:: TaskOutputs :: Files ( vec ! [ "second-output" . to_string( ) ] ) ,
779+ ..task ( "second" )
780+ } ;
781+ let parent = task ( "parent" ) ;
782+ let mut deps = deps_with_relationships (
783+ HashMap :: from ( [
784+ ( task_key ( & parent) , HashSet :: from ( [ task_key ( & second) ] ) ) ,
785+ ( task_key ( & second) , HashSet :: from ( [ task_key ( & first) ] ) ) ,
786+ ] ) ,
787+ HashMap :: new ( ) ,
788+ ) ;
789+ deps. tasks . insert ( task_key ( & first) , first. clone ( ) ) ;
790+ deps. tasks . insert ( task_key ( & second) , second. clone ( ) ) ;
791+
792+ assert_eq ! ( deps. dependency_state( & parent) . dependencies, [ first, second] ) ;
793+ }
794+
726795 #[ test]
727796 fn dependency_state_includes_post_dependency_parents ( ) {
728797 let parent = task ( "parent" ) ;
@@ -737,6 +806,7 @@ mod tests {
737806 assert_eq ! (
738807 deps. dependency_state( & post) ,
739808 TaskDependencyState {
809+ dependencies: vec![ ] ,
740810 cache_keys: vec![ "parent-key" . to_string( ) ] ,
741811 any_did_work: true ,
742812 any_unkeyed_did_work: false ,
0 commit comments