11use std:: borrow:: Cow ;
22use std:: collections:: { BTreeSet , VecDeque } ;
3+ use std:: path:: Path ;
34
45use itertools:: Itertools ;
6+ use petgraph:: graph:: { EdgeIndex , NodeIndex } ;
57use petgraph:: prelude:: EdgeRef ;
6- use petgraph:: visit:: Dfs ;
78use petgraph:: Direction ;
89use rustc_hash:: { FxHashMap , FxHashSet } ;
910
1011use uv_configuration:: DevMode ;
1112use uv_normalize:: { ExtraName , GroupName , PackageName } ;
1213use uv_pypi_types:: ResolverMarkerEnvironment ;
1314
14- use crate :: lock:: { Dependency , PackageId } ;
15+ use crate :: lock:: { Dependency , PackageId , Source } ;
1516use crate :: Lock ;
1617
1718#[ derive( Debug ) ]
@@ -24,6 +25,8 @@ pub struct TreeDisplay<'env> {
2425 depth : usize ,
2526 /// Whether to de-duplicate the displayed dependencies.
2627 no_dedupe : bool ,
28+ /// The packages considered as roots of the dependency tree.
29+ roots : Vec < NodeIndex > ,
2730}
2831
2932impl < ' env > TreeDisplay < ' env > {
@@ -38,6 +41,38 @@ impl<'env> TreeDisplay<'env> {
3841 no_dedupe : bool ,
3942 invert : bool ,
4043 ) -> Self {
44+ // Identify the workspace members.
45+ //
46+ // The members are encoded directly in the lockfile, unless the workspace contains a
47+ // single member at the root, in which case, we identify it by its source.
48+ let members: FxHashSet < & PackageId > = if lock. members ( ) . is_empty ( ) {
49+ lock. packages
50+ . iter ( )
51+ . filter_map ( |package| {
52+ let ( Source :: Editable ( path) | Source :: Virtual ( path) ) = & package. id . source
53+ else {
54+ return None ;
55+ } ;
56+ if path == Path :: new ( "" ) {
57+ Some ( & package. id )
58+ } else {
59+ None
60+ }
61+ } )
62+ . collect ( )
63+ } else {
64+ lock. packages
65+ . iter ( )
66+ . filter_map ( |package| {
67+ if lock. members ( ) . contains ( & package. id . name ) {
68+ Some ( & package. id )
69+ } else {
70+ None
71+ }
72+ } )
73+ . collect ( )
74+ } ;
75+
4176 // Create a graph.
4277 let mut graph = petgraph:: graph:: Graph :: < & PackageId , Edge , petgraph:: Directed > :: new ( ) ;
4378
@@ -138,29 +173,24 @@ impl<'env> TreeDisplay<'env> {
138173
139174 // Step 1: Filter out packages that aren't reachable on this platform.
140175 if let Some ( environment_markers) = markers {
141- let mut reachable = FxHashSet :: default ( ) ;
142-
143176 // Perform a DFS from the root nodes to find the reachable nodes, following only the
144177 // production edges.
145- let mut stack = graph
178+ let mut reachable = graph
146179 . node_indices ( )
147- . filter ( |index| {
148- graph
149- . edges_directed ( * index, Direction :: Incoming )
150- . next ( )
151- . is_none ( )
152- } )
153- . collect :: < VecDeque < _ > > ( ) ;
180+ . filter ( |index| members. contains ( graph[ * index] ) )
181+ . collect :: < FxHashSet < _ > > ( ) ;
182+ let mut stack = reachable. iter ( ) . copied ( ) . collect :: < VecDeque < _ > > ( ) ;
154183 while let Some ( node) = stack. pop_front ( ) {
155- reachable. insert ( node) ;
156184 for edge in graph. edges_directed ( node, Direction :: Outgoing ) {
157185 if edge
158186 . weight ( )
159187 . dependency ( )
160188 . complexified_marker
161189 . evaluate ( environment_markers, & [ ] )
162190 {
163- stack. push_back ( edge. target ( ) ) ;
191+ if reachable. insert ( edge. target ( ) ) {
192+ stack. push_back ( edge. target ( ) ) ;
193+ }
164194 }
165195 }
166196 }
@@ -173,24 +203,19 @@ impl<'env> TreeDisplay<'env> {
173203 // Step 2: Filter the graph to those that are reachable in production or development, if
174204 // `--no-dev` or `--only-dev` were specified, respectively.
175205 if dev != DevMode :: Include {
176- let mut reachable = FxHashSet :: default ( ) ;
177-
178206 // Perform a DFS from the root nodes to find the reachable nodes, following only the
179207 // production edges.
180- let mut stack = graph
208+ let mut reachable = graph
181209 . node_indices ( )
182- . filter ( |index| {
183- graph
184- . edges_directed ( * index, Direction :: Incoming )
185- . next ( )
186- . is_none ( )
187- } )
188- . collect :: < VecDeque < _ > > ( ) ;
210+ . filter ( |index| members. contains ( graph[ * index] ) )
211+ . collect :: < FxHashSet < _ > > ( ) ;
212+ let mut stack = reachable. iter ( ) . copied ( ) . collect :: < VecDeque < _ > > ( ) ;
189213 while let Some ( node) = stack. pop_front ( ) {
190- reachable. insert ( node) ;
191214 for edge in graph. edges_directed ( node, Direction :: Outgoing ) {
192215 if matches ! ( edge. weight( ) , Edge :: Prod ( _) | Edge :: Optional ( _, _) ) {
193- stack. push_back ( edge. target ( ) ) ;
216+ if reachable. insert ( edge. target ( ) ) {
217+ stack. push_back ( edge. target ( ) ) ;
218+ }
194219 }
195220 }
196221 }
@@ -214,25 +239,63 @@ impl<'env> TreeDisplay<'env> {
214239
215240 // Step 4: Filter the graph to those nodes reachable from the target packages.
216241 if !packages. is_empty ( ) {
217- let mut reachable = FxHashSet :: default ( ) ;
218-
219242 // Perform a DFS from the root nodes to find the reachable nodes.
220- let mut dfs = Dfs {
221- stack : graph
222- . node_indices ( )
223- . filter ( |index| packages. contains ( & graph[ * index] . name ) )
224- . collect :: < Vec < _ > > ( ) ,
225- ..Dfs :: empty ( & graph)
226- } ;
227- while let Some ( node) = dfs. next ( & graph) {
228- reachable. insert ( node) ;
243+ let mut reachable = graph
244+ . node_indices ( )
245+ . filter ( |index| packages. contains ( & graph[ * index] . name ) )
246+ . collect :: < FxHashSet < _ > > ( ) ;
247+ let mut stack = reachable. iter ( ) . copied ( ) . collect :: < VecDeque < _ > > ( ) ;
248+ while let Some ( node) = stack. pop_front ( ) {
249+ for edge in graph. edges_directed ( node, Direction :: Outgoing ) {
250+ if reachable. insert ( edge. target ( ) ) {
251+ stack. push_back ( edge. target ( ) ) ;
252+ }
253+ }
229254 }
230255
231256 // Remove the unreachable nodes from the graph.
232257 graph. retain_nodes ( |_, index| reachable. contains ( & index) ) ;
233258 modified = true ;
234259 }
235260
261+ // Compute the list of roots.
262+ let roots = {
263+ let mut edges = vec ! [ ] ;
264+
265+ // Remove any cycles.
266+ let feedback_set: Vec < EdgeIndex > = petgraph:: algo:: greedy_feedback_arc_set ( & graph)
267+ . map ( |e| e. id ( ) )
268+ . collect ( ) ;
269+ for edge_id in feedback_set {
270+ if let Some ( ( source, target) ) = graph. edge_endpoints ( edge_id) {
271+ if let Some ( weight) = graph. remove_edge ( edge_id) {
272+ edges. push ( ( source, target, weight) ) ;
273+ }
274+ }
275+ }
276+
277+ // Find the root nodes.
278+ let mut roots = graph
279+ . node_indices ( )
280+ . filter ( |index| {
281+ graph
282+ . edges_directed ( * index, Direction :: Incoming )
283+ . next ( )
284+ . is_none ( )
285+ } )
286+ . collect :: < Vec < _ > > ( ) ;
287+
288+ // Sort the roots.
289+ roots. sort_by_key ( |index| & graph[ * index] ) ;
290+
291+ // Re-add the removed edges.
292+ for ( source, target, weight) in edges {
293+ graph. add_edge ( source, target, weight) ;
294+ }
295+
296+ roots
297+ } ;
298+
236299 // If the graph was modified, re-create the inverse map.
237300 if modified {
238301 inverse. clear ( ) ;
@@ -246,6 +309,7 @@ impl<'env> TreeDisplay<'env> {
246309 inverse,
247310 depth,
248311 no_dedupe,
312+ roots,
249313 }
250314 }
251315
@@ -362,24 +426,9 @@ impl<'env> TreeDisplay<'env> {
362426 let mut path = Vec :: new ( ) ;
363427 let mut lines = Vec :: new ( ) ;
364428
365- let roots = {
366- let mut roots = self
367- . graph
368- . node_indices ( )
369- . filter ( |index| {
370- self . graph
371- . edges_directed ( * index, petgraph:: Direction :: Incoming )
372- . next ( )
373- . is_none ( )
374- } )
375- . collect :: < Vec < _ > > ( ) ;
376- roots. sort_by_key ( |index| & self . graph [ * index] ) ;
377- roots
378- } ;
379-
380- for node in roots {
429+ for node in & self . roots {
381430 path. clear ( ) ;
382- lines. extend ( self . visit ( Node :: Root ( self . graph [ node] ) , & mut visited, & mut path) ) ;
431+ lines. extend ( self . visit ( Node :: Root ( self . graph [ * node] ) , & mut visited, & mut path) ) ;
383432 }
384433
385434 lines
0 commit comments