1- use std:: collections:: { HashMap , HashSet } ;
1+ use std:: collections:: HashMap ;
22use std:: fmt:: Debug ;
33use std:: io:: BufWriter ;
4+ use std:: panic:: Location ;
45use std:: path:: Path ;
56
67use crate :: core:: builder:: { AnyDebug , Step , pretty_step_name} ;
78use crate :: t;
9+ use crate :: utils:: tracing:: format_location;
810
911/// Records the executed steps and their dependencies in a directed graph,
1012/// which can then be rendered into a DOT file for visualization.
@@ -20,6 +22,7 @@ pub struct StepGraph {
2022}
2123
2224impl StepGraph {
25+ #[ track_caller]
2326 pub fn register_step_execution < S : Step > (
2427 & mut self ,
2528 step : & S ,
@@ -57,6 +60,7 @@ impl StepGraph {
5760 }
5861 }
5962
63+ #[ track_caller]
6064 pub fn register_cached_step < S : Step > (
6165 & mut self ,
6266 step : & S ,
@@ -97,12 +101,18 @@ struct Node {
97101struct NodeHandle ( usize ) ;
98102
99103/// Represents a dependency between two bootstrap steps.
100- #[ derive( PartialEq , Eq , Hash , PartialOrd , Ord ) ]
101- struct Edge {
102- src : NodeHandle ,
103- dst : NodeHandle ,
104+ #[ derive( Default ) ]
105+ struct EdgeData {
104106 // Was the corresponding execution of a step cached, or was the step actually executed?
105107 cached : bool ,
108+ // Locations from where the step was called
109+ locations : Vec < Location < ' static > > ,
110+ }
111+
112+ #[ derive( PartialEq , Eq , Hash , PartialOrd , Ord , Copy , Clone ) ]
113+ struct EdgeKey {
114+ src : NodeHandle ,
115+ dst : NodeHandle ,
106116}
107117
108118// We could use a library for this, but they either:
@@ -114,8 +124,8 @@ struct Edge {
114124#[ derive( Default ) ]
115125struct DotGraph {
116126 nodes : Vec < Node > ,
127+ edges : HashMap < EdgeKey , EdgeData > ,
117128 /// The `NodeHandle` represents an index within `self.nodes`
118- edges : HashSet < Edge > ,
119129 key_to_index : HashMap < String , NodeHandle > ,
120130}
121131
@@ -127,16 +137,19 @@ impl DotGraph {
127137 handle
128138 }
129139
140+ #[ track_caller]
130141 fn add_edge ( & mut self , src : NodeHandle , dst : NodeHandle ) {
131- self . edges . insert ( Edge { src, dst, cached : false } ) ;
142+ let key = EdgeKey { src, dst } ;
143+ let edge = self . edges . entry ( key) . or_default ( ) ;
144+ edge. locations . push ( * Location :: caller ( ) ) ;
132145 }
133146
147+ #[ track_caller]
134148 fn add_cached_edge ( & mut self , src : NodeHandle , dst : NodeHandle ) {
135- // There's no point in rendering both cached and uncached edge
136- let uncached = Edge { src, dst, cached : false } ;
137- if !self . edges . contains ( & uncached) {
138- self . edges . insert ( Edge { src, dst, cached : true } ) ;
139- }
149+ let key = EdgeKey { src, dst } ;
150+ let edge = self . edges . entry ( key) . or_default ( ) ;
151+ edge. cached = true ;
152+ edge. locations . push ( * Location :: caller ( ) ) ;
140153 }
141154
142155 fn get_handle_by_key ( & self , key : & str ) -> Option < NodeHandle > {
@@ -157,11 +170,23 @@ impl DotGraph {
157170 ) ?;
158171 }
159172
160- let mut edges: Vec < & Edge > = self . edges . iter ( ) . collect ( ) ;
161- edges. sort ( ) ;
162- for edge in edges {
163- let style = if edge. cached { "dashed" } else { "solid" } ;
164- writeln ! ( file, r#"{} -> {} [style="{style}"]"# , edge. src. 0 , edge. dst. 0 ) ?;
173+ let mut edges: Vec < ( & EdgeKey , & EdgeData ) > = self . edges . iter ( ) . collect ( ) ;
174+ edges. sort_by_key ( |( key, _) | * * key) ;
175+ for ( key, data) in edges {
176+ let style = if data. cached { "dashed" } else { "solid" } ;
177+ let mut locations = data
178+ . locations
179+ . iter ( )
180+ . map ( |location| format_location ( * location) )
181+ . collect :: < Vec < _ > > ( ) ;
182+ locations. sort ( ) ;
183+ locations. dedup ( ) ;
184+ let locations = locations. join ( ", " ) ;
185+ writeln ! (
186+ file,
187+ r#"{} -> {} [style="{style}", tooltip="{locations}"]"# ,
188+ key. src. 0 , key. dst. 0 ,
189+ ) ?;
165190 }
166191
167192 writeln ! ( file, "}}" )
0 commit comments