Skip to content

Commit deff46d

Browse files
author
Grant Wuerker
committed
misc
1 parent c9aa8af commit deff46d

File tree

12 files changed

+260
-193
lines changed

12 files changed

+260
-193
lines changed
Lines changed: 140 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,161 @@
1+
use std::collections::{HashMap, HashSet};
2+
13
use petgraph::graph::{DiGraph, NodeIndex};
24
use petgraph::visit::{Dfs, EdgeRef};
35
use salsa::Setter;
4-
use smol_str::SmolStr;
5-
use std::collections::HashMap;
66
use url::Url;
77

8-
use super::DependencyArguments;
8+
use super::{DependencyAlias, DependencyArguments, RemoteFiles};
99
use crate::InputDb;
1010

11+
type EdgeWeight = (DependencyAlias, DependencyArguments);
12+
1113
#[salsa::input]
1214
#[derive(Debug)]
1315
pub 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]
1926
impl 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
}

crates/common/src/dependencies/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ pub struct DependencyArguments {
1212
pub version: Option<Version>,
1313
}
1414

15+
pub type DependencyAlias = SmolStr;
16+
1517
/// Metadata describing a git checkout on disk. This can later become an enum if
1618
/// we support multiple remote transport mechanisms.
1719
#[derive(Clone, Debug, PartialEq, Eq, Hash)]

crates/common/src/file/workspace.rs

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use radix_immutable::{StringPrefixView, StringTrie, Trie};
33
use salsa::Setter;
44
use url::Url;
55

6-
use crate::{InputDb, dependencies::RemoteFiles, file::File, indexmap::IndexMap};
6+
use crate::{InputDb, file::File, indexmap::IndexMap};
77

88
#[derive(Debug)]
99
pub enum InputIndexError {
@@ -15,13 +15,12 @@ pub enum InputIndexError {
1515
pub struct Workspace {
1616
files: StringTrie<Url, File>,
1717
paths: IndexMap<File, Url>,
18-
remote_git: IndexMap<Url, RemoteFiles>,
1918
}
2019

2120
#[salsa::tracked]
2221
impl Workspace {
2322
pub fn default(db: &dyn InputDb) -> Self {
24-
Workspace::new(db, Trie::new(), IndexMap::default(), IndexMap::default())
23+
Workspace::new(db, Trie::new(), IndexMap::default())
2524
}
2625
pub(crate) fn set(
2726
&self,
@@ -107,26 +106,6 @@ impl Workspace {
107106
pub fn all_files(&self, db: &dyn InputDb) -> StringTrie<Url, File> {
108107
self.files(db)
109108
}
110-
111-
pub fn register_remote_git(&self, db: &mut dyn InputDb, local_url: Url, remote: RemoteFiles) {
112-
let mut map = self.remote_git(db);
113-
map.insert(local_url, remote);
114-
self.set_remote_git(db).to(map);
115-
}
116-
117-
pub fn remote_git_for_local(&self, db: &dyn InputDb, local_url: &Url) -> Option<RemoteFiles> {
118-
self.remote_git(db).get(local_url).cloned()
119-
}
120-
121-
pub fn local_for_remote_git(&self, db: &dyn InputDb, remote: &RemoteFiles) -> Option<Url> {
122-
self.remote_git(db).iter().find_map(|(url, stored)| {
123-
if stored == remote {
124-
Some(url.clone())
125-
} else {
126-
None
127-
}
128-
})
129-
}
130109
}
131110

132111
#[cfg(test)]

crates/common/src/ingot.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,14 +139,14 @@ impl<'db> Ingot<'db> {
139139
#[salsa::tracked]
140140
pub fn dependencies(self, db: &'db dyn InputDb) -> Vec<(SmolStr, Url)> {
141141
let base_url = self.base(db);
142-
let workspace = db.workspace();
143142
let mut deps = match self.config(db) {
144143
Some(config) => config
145144
.dependencies(&base_url)
146145
.into_iter()
147146
.map(|dependency| {
148147
let url = match &dependency.location {
149-
DependencyLocation::Remote(remote) => workspace
148+
DependencyLocation::Remote(remote) => db
149+
.dependency_graph()
150150
.local_for_remote_git(db, remote)
151151
.unwrap_or_else(|| remote.source.clone()),
152152
DependencyLocation::Local(local) => local.url.clone(),

0 commit comments

Comments
 (0)