-
Notifications
You must be signed in to change notification settings - Fork 1.4k
/
Copy pathhydrate.rs
109 lines (98 loc) · 3.59 KB
/
hydrate.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
use crate::types::PackageReq;
use libsemverator::range::Range as VersionReq;
use std::collections::{HashMap, HashSet};
use std::error::Error;
#[derive(Clone)]
struct Node {
parent: Option<Box<Node>>,
pkg: PackageReq,
children: HashSet<String>,
}
impl Node {
fn new(pkg: PackageReq, parent: Option<Box<Node>>) -> Self {
Self {
parent,
pkg,
children: HashSet::new(),
}
}
fn count(&self) -> usize {
let mut count = 0;
let mut node = self.parent.as_ref();
while let Some(parent_node) = node {
count += 1;
node = parent_node.parent.as_ref();
}
count
}
}
/// Hydrates dependencies and returns a topologically sorted list of packages.
pub async fn hydrate<F>(
input: &Vec<PackageReq>,
get_deps: F,
) -> Result<Vec<PackageReq>, Box<dyn Error>>
where
F: Fn(String) -> Result<Vec<PackageReq>, Box<dyn Error>>,
{
let dry = condense(input);
let mut graph: HashMap<String, Box<Node>> = HashMap::new();
let mut stack: Vec<Box<Node>> = vec![];
let mut additional_unicodes: Vec<VersionReq> = vec![];
for pkg in dry.iter() {
let node = graph
.entry(pkg.project.clone())
.or_insert_with(|| Box::new(Node::new(pkg.clone(), None)));
node.pkg.constraint = intersect_constraints(&node.pkg.constraint, &pkg.constraint)?;
stack.push(node.clone());
}
while let Some(mut current) = stack.pop() {
for child_pkg in get_deps(current.pkg.project.clone())? {
let child_node = graph
.entry(child_pkg.project.clone())
.or_insert_with(|| Box::new(Node::new(child_pkg.clone(), Some(current.clone()))));
let intersection =
intersect_constraints(&child_node.pkg.constraint, &child_pkg.constraint);
if let Ok(constraint) = intersection {
child_node.pkg.constraint = constraint;
current.children.insert(child_node.pkg.project.clone());
stack.push(child_node.clone());
} else if child_pkg.project == "unicode.org" {
// we handle unicode.org for now to allow situations like:
// https://github.com/pkgxdev/pantry/issues/4104
// https://github.com/pkgxdev/pkgx/issues/899
additional_unicodes.push(child_pkg.constraint);
} else {
return Err(intersection.unwrap_err());
}
}
}
let mut pkgs: Vec<&Box<Node>> = graph.values().collect();
pkgs.sort_by_key(|node| node.count());
let mut pkgs: Vec<PackageReq> = pkgs.into_iter().map(|node| node.pkg.clone()).collect();
// see above explanation
for constraint in additional_unicodes {
let pkg = PackageReq {
project: "unicode.org".to_string(),
constraint,
};
pkgs.push(pkg);
}
Ok(pkgs)
}
/// Condenses a list of `PackageRequirement` by intersecting constraints for duplicates.
fn condense(pkgs: &Vec<PackageReq>) -> Vec<PackageReq> {
let mut out: Vec<PackageReq> = vec![];
for pkg in pkgs {
if let Some(existing) = out.iter_mut().find(|p| p.project == pkg.project) {
existing.constraint = intersect_constraints(&existing.constraint, &pkg.constraint)
.expect("Failed to intersect constraints");
} else {
out.push(pkg.clone());
}
}
out
}
/// Intersects two version constraints.
fn intersect_constraints(a: &VersionReq, b: &VersionReq) -> Result<VersionReq, Box<dyn Error>> {
a.intersect(b).map_err(|e| e.into())
}