1use petgraph::{stable_graph::StableDiGraph, Direction};
2
3pub trait Node {
6 type DependencyType;
9
10 fn dependencies(&self) -> &[Self::DependencyType];
12
13 fn matches(&self, dependency: &Self::DependencyType) -> bool;
15}
16
17pub enum Step<'a, N: Node> {
24 Resolved(&'a N),
25 Unresolved(&'a N::DependencyType),
26}
27
28impl<'a, N: Node> Step<'a, N> {
29 pub fn is_resolved(&self) -> bool {
30 match self {
31 Step::Resolved(_) => true,
32 Step::Unresolved(_) => false,
33 }
34 }
35
36 pub fn as_resolved(&self) -> Option<&N> {
37 match self {
38 Step::Resolved(node) => Some(node),
39 Step::Unresolved(_) => None,
40 }
41 }
42
43 pub fn as_unresolved(&self) -> Option<&N::DependencyType> {
44 match self {
45 Step::Resolved(_) => None,
46 Step::Unresolved(dependency) => Some(dependency),
47 }
48 }
49}
50
51pub struct DependencyGraph<'a, N: Node> {
54 graph: StableDiGraph<Step<'a, N>, &'a N::DependencyType>,
55}
56
57impl<'a, N> From<&'a [N]> for DependencyGraph<'a, N>
61where
62 N: Node,
63{
64 fn from(nodes: &'a [N]) -> Self {
65 let mut graph = StableDiGraph::<Step<'a, N>, &'a N::DependencyType>::new();
66
67 let nodes: Vec<(_, _)> = nodes
71 .iter()
72 .map(|node| (node, graph.add_node(Step::Resolved(node))))
73 .collect();
74
75 for (node, index) in nodes.iter() {
76 for dependency in node.dependencies() {
77 if let Some((_, dependent)) = nodes.iter().find(|(dep, _)| dep.matches(dependency))
79 {
80 graph.add_edge(*index, *dependent, dependency);
82 } else {
83 let unresolved = graph.add_node(Step::Unresolved(dependency));
85 graph.add_edge(*index, unresolved, dependency);
86 }
87 }
88 }
89
90 Self { graph }
91 }
92}
93
94impl<'a, N> DependencyGraph<'a, N>
95where
96 N: Node,
97{
98 pub fn is_internally_resolvable(&self) -> bool {
101 self.graph.node_weights().all(Step::is_resolved)
102 }
103
104 pub fn unresolved_dependencies(&self) -> impl Iterator<Item = &N::DependencyType> {
108 self.graph.node_weights().filter_map(Step::as_unresolved)
109 }
110}
111
112impl<'a, N> Iterator for DependencyGraph<'a, N>
116where
117 N: Node,
118{
119 type Item = Step<'a, N>;
120
121 fn next(&mut self) -> Option<Self::Item> {
122 for index in self.graph.node_indices().rev() {
125 if self
126 .graph
127 .neighbors_directed(index, Direction::Outgoing)
128 .count()
129 == 0
130 {
131 return self.graph.remove_node(index);
132 }
133 }
134
135 None
136 }
137}
138
139#[cfg(test)]
140mod tests {
141
142 use super::{DependencyGraph, Node, Step};
143 use semver::{BuildMetadata, Prerelease, Version, VersionReq};
144
145 #[derive(Debug)]
146 struct Package {
147 name: &'static str,
148 version: Version,
149 dependencies: Vec<Dependency>,
150 }
151
152 #[derive(Debug)]
153 struct Dependency {
154 name: &'static str,
155 version: VersionReq,
156 }
157
158 impl Node for Package {
159 type DependencyType = Dependency;
160
161 fn dependencies(&self) -> &[Self::DependencyType] {
162 &self.dependencies[..]
163 }
164
165 fn matches(&self, dependency: &Self::DependencyType) -> bool {
166 self.name == dependency.name && dependency.version.matches(&self.version)
167 }
168 }
169
170 #[test]
171 fn test_dependencies_synchronous() {
172 let build = build_test_graph();
173 let graph = DependencyGraph::from(&build[..]);
174
175 assert!(!graph.is_internally_resolvable());
176
177 for node in graph {
178 match node {
179 Step::Resolved(build) => println!("build: {:?}", build.name),
180 Step::Unresolved(lookup) => println!("lookup: {:?}", lookup.name),
181 }
182 }
183 }
184
185 #[test]
186 fn test_unresolved_dependencies() {
187 let build = build_test_graph();
188 let graph = DependencyGraph::from(&build[..]);
189
190 assert!(!graph.is_internally_resolvable());
191
192 let unresolved_dependencies: Vec<_> = graph
193 .unresolved_dependencies()
194 .map(|dep| dep.name)
195 .collect();
196
197 assert_eq!(
198 unresolved_dependencies,
199 vec!["@scope/unknown", "@scope/remote"]
200 );
201 }
202
203 #[test]
204 fn test_generate_dependency_graph() {
205 let _ = DependencyGraph::from(&build_test_graph()[..]);
206 }
207
208 fn build_test_graph() -> Vec<Package> {
209 vec![
210 Package {
211 name: "@scope/package-a",
212 version: semver::Version {
213 major: 1,
214 minor: 2,
215 patch: 3,
216 pre: Prerelease::new("").unwrap(),
217 build: BuildMetadata::EMPTY,
218 },
219 dependencies: vec![],
220 },
221 Package {
222 name: "@scope/package-b",
223 version: semver::Version {
224 major: 1,
225 minor: 2,
226 patch: 3,
227 pre: Prerelease::new("").unwrap(),
228 build: BuildMetadata::EMPTY,
229 },
230 dependencies: vec![Dependency {
231 name: "@scope/package-a",
232 version: ">=1.0.0".parse().unwrap(),
233 }],
234 },
235 Package {
236 name: "@scope/package-c",
237 version: semver::Version {
238 major: 1,
239 minor: 2,
240 patch: 3,
241 pre: Prerelease::new("").unwrap(),
242 build: BuildMetadata::EMPTY,
243 },
244 dependencies: vec![Dependency {
245 name: "@scope/package-b",
246 version: ">=1.0.0".parse().unwrap(),
247 }],
248 },
249 Package {
250 name: "@scope/package-d",
251 version: semver::Version {
252 major: 1,
253 minor: 2,
254 patch: 3,
255 pre: Prerelease::new("").unwrap(),
256 build: BuildMetadata::EMPTY,
257 },
258 dependencies: vec![
259 Dependency {
260 name: "@scope/package-a",
261 version: ">=1.0.0".parse().unwrap(),
262 },
263 Dependency {
264 name: "@scope/package-b",
265 version: ">=1.0.0".parse().unwrap(),
266 },
267 ],
268 },
269 Package {
270 name: "@scope/package-e",
271 version: semver::Version {
272 major: 1,
273 minor: 2,
274 patch: 3,
275 pre: Prerelease::new("").unwrap(),
276 build: BuildMetadata::EMPTY,
277 },
278 dependencies: vec![],
279 },
280 Package {
281 name: "@scope/package-f",
282 version: semver::Version {
283 major: 1,
284 minor: 2,
285 patch: 3,
286 pre: Prerelease::new("").unwrap(),
287 build: BuildMetadata::EMPTY,
288 },
289 dependencies: vec![
290 Dependency {
291 name: "@scope/unknown",
292 version: ">=1.0.0".parse().unwrap(),
293 },
294 Dependency {
295 name: "@scope/remote",
296 version: "=3.0.0".parse().unwrap(),
297 },
298 ],
299 },
300 ]
301 }
302
303 #[test]
304 fn test_internally_resolved() {
305 let packages = vec![
306 Package {
307 name: "@scope/package-a",
308 version: semver::Version {
309 major: 1,
310 minor: 2,
311 patch: 3,
312 pre: Prerelease::new("").unwrap(),
313 build: BuildMetadata::EMPTY,
314 },
315 dependencies: vec![],
316 },
317 Package {
318 name: "@scope/package-b",
319 version: semver::Version {
320 major: 3,
321 minor: 2,
322 patch: 0,
323 pre: Prerelease::new("").unwrap(),
324 build: BuildMetadata::EMPTY,
325 },
326 dependencies: vec![Dependency {
327 name: "@scope/package-a",
328 version: "=1.2.3".parse().unwrap(),
329 }],
330 },
331 Package {
332 name: "@scope/package-c",
333 version: semver::Version {
334 major: 11,
335 minor: 2,
336 patch: 4,
337 pre: Prerelease::new("").unwrap(),
338 build: BuildMetadata::EMPTY,
339 },
340 dependencies: vec![Dependency {
341 name: "@scope/package-b",
342 version: ">=3.0.0".parse().unwrap(),
343 }],
344 },
345 ];
346
347 let graph = DependencyGraph::from(&packages[..]);
348
349 for package in graph {
350 match package {
351 Step::Resolved(package) => println!("Building {}!", package.name),
353
354 Step::Unresolved(_) => unreachable!(),
361 }
362 }
363 }
364}