1+ use std:: collections:: { HashMap , HashSet } ;
2+
13use petgraph:: graph:: { DiGraph , NodeIndex } ;
24use petgraph:: visit:: { Dfs , EdgeRef } ;
35use salsa:: Setter ;
4- use smol_str:: SmolStr ;
5- use std:: collections:: HashMap ;
66use url:: Url ;
77
8- use super :: DependencyArguments ;
8+ use super :: { DependencyAlias , DependencyArguments , RemoteFiles } ;
99use crate :: InputDb ;
1010
11+ type EdgeWeight = ( DependencyAlias , DependencyArguments ) ;
12+
1113#[ salsa:: input]
1214#[ derive( Debug ) ]
1315pub struct DependencyGraph {
14- graph : DiGraph < Url , ( SmolStr , DependencyArguments ) > ,
15- node_map : HashMap < Url , NodeIndex > ,
16+ local_graph : DiGraph < Url , EdgeWeight > ,
17+ local_node_map : HashMap < Url , NodeIndex > ,
18+ remote_graph : DiGraph < Url , EdgeWeight > ,
19+ remote_node_map : HashMap < Url , NodeIndex > ,
20+ remote_sets : HashMap < Url , Vec < Url > > ,
21+ git_locations : HashMap < Url , RemoteFiles > ,
22+ reverse_git_map : HashMap < RemoteFiles , Url > ,
1623}
1724
1825#[ salsa:: tracked]
1926impl DependencyGraph {
2027 pub fn default ( db : & dyn InputDb ) -> Self {
21- DependencyGraph :: new ( db, DiGraph :: new ( ) , HashMap :: new ( ) )
28+ DependencyGraph :: new (
29+ db,
30+ DiGraph :: new ( ) ,
31+ HashMap :: new ( ) ,
32+ DiGraph :: new ( ) ,
33+ HashMap :: new ( ) ,
34+ HashMap :: new ( ) ,
35+ HashMap :: new ( ) ,
36+ HashMap :: new ( ) ,
37+ )
38+ }
39+
40+ fn allocate_node (
41+ graph : & mut DiGraph < Url , EdgeWeight > ,
42+ node_map : & mut HashMap < Url , NodeIndex > ,
43+ url : & Url ,
44+ ) -> NodeIndex {
45+ if let Some ( & idx) = node_map. get ( url) {
46+ idx
47+ } else {
48+ let idx = graph. add_node ( url. clone ( ) ) ;
49+ node_map. insert ( url. clone ( ) , idx) ;
50+ idx
51+ }
2252 }
2353
24- /// Returns true if the given URL exists as a node in the dependency graph.
25- pub fn contains_url ( & self , db : & dyn InputDb , url : & Url ) -> bool {
26- self . node_map ( db) . contains_key ( url)
54+ pub fn ensure_local_node ( & self , db : & mut dyn InputDb , url : & Url ) {
55+ if self . local_node_map ( db) . contains_key ( url) {
56+ return ;
57+ }
58+ let mut graph = self . local_graph ( db) ;
59+ let mut node_map = self . local_node_map ( db) ;
60+ Self :: allocate_node ( & mut graph, & mut node_map, url) ;
61+ self . set_local_graph ( db) . to ( graph) ;
62+ self . set_local_node_map ( db) . to ( node_map) ;
63+ }
64+
65+ pub fn contains_local_url ( & self , db : & dyn InputDb , url : & Url ) -> bool {
66+ self . local_node_map ( db) . contains_key ( url)
67+ }
68+
69+ pub fn add_local_dependency (
70+ & self ,
71+ db : & mut dyn InputDb ,
72+ source : & Url ,
73+ target : & Url ,
74+ alias : DependencyAlias ,
75+ arguments : DependencyArguments ,
76+ ) {
77+ let mut graph = self . local_graph ( db) ;
78+ let mut node_map = self . local_node_map ( db) ;
79+ let source_idx = Self :: allocate_node ( & mut graph, & mut node_map, source) ;
80+ let target_idx = Self :: allocate_node ( & mut graph, & mut node_map, target) ;
81+ graph. add_edge ( source_idx, target_idx, ( alias, arguments) ) ;
82+ self . set_local_graph ( db) . to ( graph) ;
83+ self . set_local_node_map ( db) . to ( node_map) ;
2784 }
2885
29- /// Returns a subgraph containing all cyclic nodes and all nodes that lead to cycles.
30- ///
31- /// This method identifies strongly connected components (SCCs) in the graph and returns
32- /// a subgraph that includes:
33- /// - All nodes that are part of multi-node cycles
34- /// - All nodes that have paths leading to cyclic nodes
35- ///
36- /// Returns an empty graph if no cycles are detected.
37- pub fn cyclic_subgraph (
86+ pub fn add_remote_dependency (
3887 & self ,
39- db : & dyn InputDb ,
40- ) -> DiGraph < Url , ( SmolStr , DependencyArguments ) > {
88+ db : & mut dyn InputDb ,
89+ source : & Url ,
90+ target : & Url ,
91+ alias : DependencyAlias ,
92+ arguments : DependencyArguments ,
93+ ) {
94+ let mut graph = self . remote_graph ( db) ;
95+ let mut node_map = self . remote_node_map ( db) ;
96+ let source_idx = Self :: allocate_node ( & mut graph, & mut node_map, source) ;
97+ let target_idx = Self :: allocate_node ( & mut graph, & mut node_map, target) ;
98+ graph. add_edge ( source_idx, target_idx, ( alias, arguments) ) ;
99+ self . set_remote_graph ( db) . to ( graph) ;
100+ self . set_remote_node_map ( db) . to ( node_map) ;
101+ }
102+
103+ pub fn ensure_remote_node ( & self , db : & mut dyn InputDb , url : & Url ) {
104+ if self . remote_node_map ( db) . contains_key ( url) {
105+ return ;
106+ }
107+ let mut graph = self . remote_graph ( db) ;
108+ let mut node_map = self . remote_node_map ( db) ;
109+ Self :: allocate_node ( & mut graph, & mut node_map, url) ;
110+ self . set_remote_graph ( db) . to ( graph) ;
111+ self . set_remote_node_map ( db) . to ( node_map) ;
112+ }
113+
114+ pub fn cyclic_subgraph ( & self , db : & dyn InputDb ) -> DiGraph < Url , EdgeWeight > {
41115 use petgraph:: algo:: tarjan_scc;
42- use std:: collections:: VecDeque ;
43116
44- let graph = self . graph ( db) ;
117+ let graph = self . local_graph ( db) ;
45118 let sccs = tarjan_scc ( & graph) ;
46119
47- // Find actual cyclic nodes (multi-node SCCs only)
48- let mut cyclic_nodes = std:: collections:: HashSet :: new ( ) ;
120+ let mut cyclic_nodes = HashSet :: new ( ) ;
49121 for scc in sccs {
50122 if scc. len ( ) > 1 {
51- // Multi-node SCC = actual cycle
52123 for node_idx in scc {
53124 cyclic_nodes. insert ( node_idx) ;
54125 }
55126 }
56127 }
57128
58- // If no cycles, return empty graph
59129 if cyclic_nodes. is_empty ( ) {
60130 return DiGraph :: new ( ) ;
61131 }
62132
63- // Use reverse DFS from cyclic nodes to find all predecessors
64133 let mut nodes_to_include = cyclic_nodes. clone ( ) ;
65- let mut visited = std :: collections :: HashSet :: new ( ) ;
66- let mut queue = VecDeque :: new ( ) ;
134+ let mut visited = HashSet :: new ( ) ;
135+ let mut queue: Vec < NodeIndex > = cyclic_nodes . iter ( ) . copied ( ) . collect ( ) ;
67136
68- // Start from all cyclic nodes and work backwards
69- for & cyclic_node in & cyclic_nodes {
70- queue. push_back ( cyclic_node) ;
71- }
72-
73- while let Some ( current) = queue. pop_front ( ) {
74- if visited. contains ( & current) {
137+ while let Some ( current) = queue. pop ( ) {
138+ if !visited. insert ( current) {
75139 continue ;
76140 }
77- visited. insert ( current) ;
78141 nodes_to_include. insert ( current) ;
79142
80- // Add all predecessors (nodes that point to current)
81143 for pred in graph. node_indices ( ) {
82144 if graph. find_edge ( pred, current) . is_some ( ) && !visited. contains ( & pred) {
83- queue. push_back ( pred) ;
145+ queue. push ( pred) ;
84146 }
85147 }
86148 }
87149
88- // Build subgraph with all nodes that lead to cycles
89150 let mut subgraph = DiGraph :: new ( ) ;
90151 let mut node_map = HashMap :: new ( ) ;
91152
92- // Add nodes
93153 for & node_idx in & nodes_to_include {
94154 let url = & graph[ node_idx] ;
95155 let new_idx = subgraph. add_node ( url. clone ( ) ) ;
96156 node_map. insert ( node_idx, new_idx) ;
97157 }
98158
99- // Add edges between included nodes
100159 for edge in graph. edge_references ( ) {
101160 if let ( Some ( & from_new) , Some ( & to_new) ) =
102161 ( node_map. get ( & edge. source ( ) ) , node_map. get ( & edge. target ( ) ) )
@@ -108,68 +167,58 @@ impl DependencyGraph {
108167 subgraph
109168 }
110169
111- /// Adds a dependency edge from one URL to another.
112- ///
113- /// Creates nodes for both URLs if they don't already exist in the graph,
114- /// then adds a directed edge from `source` to `target` with the given
115- /// alias and arguments.
116- pub fn add_dependency (
117- & self ,
118- db : & mut dyn InputDb ,
119- source : & Url ,
120- target : & Url ,
121- alias : SmolStr ,
122- arguments : DependencyArguments ,
123- ) {
124- let mut graph = self . graph ( db) ;
125- let mut node_map = self . node_map ( db) ;
126-
127- // Add nodes if they don't exist
128- let source_node = if let Some ( & node) = node_map. get ( source) {
129- node
130- } else {
131- let node = graph. add_node ( source. clone ( ) ) ;
132- node_map. insert ( source. clone ( ) , node) ;
133- node
134- } ;
135-
136- let target_node = if let Some ( & node) = node_map. get ( target) {
137- node
138- } else {
139- let node = graph. add_node ( target. clone ( ) ) ;
140- node_map. insert ( target. clone ( ) , node) ;
141- node
142- } ;
143-
144- // Add the edge
145- graph. add_edge ( source_node, target_node, ( alias, arguments) ) ;
146-
147- // Update the graph state
148- self . set_graph ( db) . to ( graph) ;
149- self . set_node_map ( db) . to ( node_map) ;
150- }
151-
152- /// Returns all transitive dependencies of the given URL.
153- ///
154- /// This performs a depth-first search to find all URLs that are reachable
155- /// from the given URL through the dependency graph, excluding the URL itself.
156170 pub fn dependency_urls ( & self , db : & dyn InputDb , url : & Url ) -> Vec < Url > {
157- let node_map = self . node_map ( db) ;
158- let graph = self . graph ( db) ;
171+ let node_map = self . local_node_map ( db) ;
172+ let graph = self . local_graph ( db) ;
159173
160174 if let Some ( & root) = node_map. get ( url) {
161175 let mut dfs = Dfs :: new ( & graph, root) ;
162176 let mut visited = Vec :: new ( ) ;
163-
164177 while let Some ( node) = dfs. next ( & graph) {
165178 if node != root {
166179 visited. push ( graph[ node] . clone ( ) ) ;
167180 }
168181 }
169-
170182 visited
171183 } else {
172184 Vec :: new ( )
173185 }
174186 }
187+
188+ pub fn add_remote_set ( & self , db : & mut dyn InputDb , local_url : & Url , remote_root : Url ) {
189+ self . ensure_remote_node ( db, & remote_root) ;
190+ let mut sets = self . remote_sets ( db) ;
191+ sets. entry ( local_url. clone ( ) ) . or_default ( ) . push ( remote_root) ;
192+ self . set_remote_sets ( db) . to ( sets) ;
193+ }
194+
195+ pub fn remote_sets_for ( & self , db : & dyn InputDb , local_url : & Url ) -> Vec < Url > {
196+ self . remote_sets ( db)
197+ . get ( local_url)
198+ . cloned ( )
199+ . unwrap_or_default ( )
200+ }
201+
202+ pub fn register_remote_checkout (
203+ & self ,
204+ db : & mut dyn InputDb ,
205+ local_url : Url ,
206+ remote : RemoteFiles ,
207+ ) {
208+ let mut git_map = self . git_locations ( db) ;
209+ git_map. insert ( local_url. clone ( ) , remote. clone ( ) ) ;
210+ self . set_git_locations ( db) . to ( git_map) ;
211+
212+ let mut reverse = self . reverse_git_map ( db) ;
213+ reverse. insert ( remote, local_url) ;
214+ self . set_reverse_git_map ( db) . to ( reverse) ;
215+ }
216+
217+ pub fn remote_git_for_local ( & self , db : & dyn InputDb , local_url : & Url ) -> Option < RemoteFiles > {
218+ self . git_locations ( db) . get ( local_url) . cloned ( )
219+ }
220+
221+ pub fn local_for_remote_git ( & self , db : & dyn InputDb , remote : & RemoteFiles ) -> Option < Url > {
222+ self . reverse_git_map ( db) . get ( remote) . cloned ( )
223+ }
175224}
0 commit comments