1use std::collections::{HashSet, HashMap, BinaryHeap};
4
5use anyhow::{Result, bail};
6use pyo3::prelude::*;
7
8use super::{Package, Id, manifest::{Dependency, Version}};
9
10#[derive(Debug, Default, Clone)]
13#[pyclass(module = "merlon.package.registry")]
14pub struct Registry {
15 packages: HashMap<Id, Package>,
16}
17
18#[pymethods]
19impl Registry {
20 #[new]
22 pub fn new() -> Self {
23 Self {
24 packages: Default::default(),
25 }
26 }
27
28 pub fn register(&mut self, package: Package) -> Result<Id> {
32 let id = package.id()?;
33 if self.packages.contains_key(&id) {
34 bail!("package {} already in registry", package);
35 }
36 self.packages.insert(id, package);
37 Ok(id)
38 }
39
40 pub fn take(&mut self, id: Id) -> Result<Package> {
43 match self.packages.remove(&id) {
44 Some(package) => Ok(package),
45 None => bail!("package {} not in registry", id),
46 }
47 }
48
49 pub fn has(&self, id: Id) -> bool {
51 self.packages.contains_key(&id)
52 }
53}
54
55impl Registry {
56 pub fn get(&self, id: Id) -> Option<&Package> {
58 self.packages.get(&id)
59 }
60
61 pub fn get_or_error(&self, id: Id) -> Result<&Package> {
63 match self.get(id) {
64 Some(package) => Ok(package),
65 None => bail!("package {id} not found in registry"),
66 }
67 }
68
69 pub fn edit<F, T>(&mut self, id: Id, f: F) -> Result<T>
71 where
72 F: FnOnce(&mut Package) -> Result<T>
73 {
74 let mut package = self.take(id)?;
75 let ret = f(&mut package)?;
76 self.register(package)?;
77 Ok(ret)
78 }
79
80 pub fn package_ids(&self) -> impl Iterator<Item = Id> + '_ {
82 self.packages.keys().copied()
83 }
84}
85
86#[pymethods]
88impl Registry {
89 pub fn get_direct_dependencies(&self, id: Id) -> Result<HashSet<Dependency>> {
91 let package = self.get_or_error(id)?;
92 let manifest = package.manifest()?;
93 Ok(manifest.iter_direct_dependencies()
94 .map(|dep| dep.clone()) .collect())
96 }
97
98 pub fn get_dependencies(&self, id: Id) -> Result<HashSet<Dependency>> {
100 let mut dependencies = HashSet::new();
102 let mut stack: Vec<Dependency> = self.get_direct_dependencies(id)?
103 .into_iter()
104 .collect();
105 while let Some(popped_dep) = stack.pop() {
106 if dependencies.contains(&popped_dep) {
107 continue;
108 }
109 if let Dependency::Package { id: dep_id, .. } = &popped_dep {
110 if *dep_id == id {
111 bail!("found circular dependency");
112 }
113 for dependency in self.get_direct_dependencies(*dep_id)? {
114 stack.push(dependency);
115 }
116 }
117 dependencies.insert(popped_dep);
118 }
119 Ok(dependencies)
120 }
121
122 pub fn all_dependencies(&self) -> Result<HashSet<Dependency>> {
124 let mut dependencies = HashSet::new();
125 for (id, _) in self.packages.iter() {
126 for dependency in self.get_dependencies(*id)? {
127 dependencies.insert(dependency);
128 }
129 }
130 Ok(dependencies)
131 }
132
133 pub fn has_dependency(&self, id: Id, dependency_id: Id) -> Result<bool> {
135 let dependencies = self.get_dependencies(id)?;
136 Ok(dependencies.iter().any(|dep| {
137 if let Dependency::Package { id: dep_id, .. } = dep {
138 *dep_id == dependency_id
139 } else {
140 false
141 }
142 }))
143 }
144
145 pub fn add_direct_dependency(&mut self, id: Id, dependency_id: Id) -> Result<()> {
148 let package = self.get_or_error(id)?;
149 let dependency_package = self.get_or_error(dependency_id)?;
150 let dependency_manifest = dependency_package.manifest()?;
151 let dependency_metadata = dependency_manifest.metadata();
152 let dependency = dependency_metadata.into();
153 package.edit_manifest(|manifest| {
154 manifest.declare_direct_dependency(dependency)
155 })
156 }
157
158 pub fn check_version_compatibility(&self) -> Result<()> {
161 let map = self.package_version_map()?;
162 for dependency in self.all_dependencies()? {
163 if let Dependency::Package { id, version } = dependency {
164 match map.get(&id) {
165 None => bail!("dependency exists for {id} {version}, but it is not in registry"),
166 Some(actual_version) => {
167 if !version.matches(actual_version) {
168 bail!(
169 "a package depends on {} {} which is incompatible with its actual version {}",
170 self.get(id).unwrap(), version,
172 actual_version,
173 );
174 }
175 }
176 }
177
178 }
179 }
180 Ok(())
181 }
182
183 pub fn calc_dependency_patch_order(&self, root: Id) -> Result<Vec<Id>> {
185 if !self.get_orphans(root)?.is_empty() {
189 bail!("there are orphaned packages");
190 }
191
192 let ordering = self.topological_ordering()?;
194
195 if ordering.last() != Some(&root) {
197 bail!("root package is not the final package in the topological ordering");
198 }
199
200 Ok(ordering)
201 }
202
203 pub fn topological_ordering(&self) -> Result<Vec<Id>> {
206 let mut topological_ordering = Vec::new();
208 let mut not_visited: BinaryHeap<Id> = self.packages.keys().map(|&k| k).collect();
209 let mut visit_in_progress = HashSet::new(); let mut visited = HashSet::new(); while let Some(id) = not_visited.pop() {
212 self.topological_ordering_visit(id, &mut topological_ordering, &mut visit_in_progress, &mut visited)?;
213 }
214 Ok(topological_ordering)
215 }
216
217 pub fn get_orphans(&self, root: Id) -> Result<HashSet<Id>> {
219 let dependency_ids: HashSet<Id> = self.get_dependencies(root)?
220 .into_iter()
221 .filter_map(|dep| {
222 if let Dependency::Package { id, .. } = dep {
223 Some(id)
224 } else {
225 None
226 }
227 })
228 .collect();
229 Ok(self.packages.keys()
230 .map(|&id| id)
231 .filter(|&id| id != root && !dependency_ids.contains(&id))
232 .collect())
233 }
234
235 pub fn delete_orphans(&mut self, root: Id) -> Result<()> {
237 for id in self.get_orphans(root)? {
238 let package = self.take(id)?;
239 std::fs::remove_dir_all(package.path())?;
240 }
241 Ok(())
242 }
243}
244
245impl Registry {
246 pub fn package_version_map(&self) -> Result<HashMap<Id, Version>> {
248 let mut map = HashMap::new();
249 for (id, package) in self.packages.iter() {
250 let id = *id;
251 let manifest = package.manifest()?;
252 let metadata = manifest.metadata();
253 if metadata.id() != id {
254 bail!("package id mismatch: {id}");
255 }
256 let version = metadata.version();
257 if let Some(other_version) = map.insert(id, version.clone()) {
258 if other_version != *version {
259 bail!("package {id} has multiple versions: {version} and {other_version}");
260 }
261 }
262 }
263 Ok(map)
264 }
265
266 fn topological_ordering_visit(
267 &self,
268 id: Id,
269 topological_ordering: &mut Vec<Id>,
270 visit_in_progress: &mut HashSet<Id>,
271 visited: &mut HashSet<Id>,
272 ) -> Result<()> {
273 if !visited.contains(&id) {
274 if visit_in_progress.contains(&id) {
275 bail!("found circular dependency {}", id);
276 }
277 visit_in_progress.insert(id);
278 let package = self.get_or_error(id)?;
279 let manifest = package.manifest()?;
280 for dependency in manifest.iter_direct_dependencies() {
281 if let Dependency::Package { id, .. } = dependency {
282 self.topological_ordering_visit(*id, topological_ordering, visit_in_progress, visited)?;
283 }
284 }
285 visit_in_progress.remove(&id);
286 visited.insert(id);
287 topological_ordering.push(id);
288 }
289 Ok(())
290 }
291}
292
293#[cfg(test)]
294mod test {
295 use std::collections::HashSet;
296 use temp_dir::TempDir;
297 use anyhow::Result;
298
299 use super::{Registry, Package, Dependency, Version};
300
301 #[test]
302 fn dependency_graph() -> Result<()> {
303 let dir = TempDir::new()?;
304 let mut registry = Registry::new();
305
306 let base = Package::new("Base", dir.path().join("base.merlon"))?;
308 let a = Package::new("A", dir.path().join("a.merlon"))?;
309 let b = Package::new("B", dir.path().join("b.merlon"))?;
310 let c = Package::new("C", dir.path().join("c.merlon"))?;
311 let base = registry.register(base)?;
312 let a = registry.register(a)?;
313 let b = registry.register(b)?;
314 let c = registry.register(c)?;
315
316 dbg!(&base, &a, &b, &c);
318
319 registry.add_direct_dependency(a, b)?;
321 registry.add_direct_dependency(b, c)?;
322
323 for parent in [a, b, c] {
325 registry.add_direct_dependency(parent, base)?;
326 }
327
328 let base_as_dep: Dependency = registry.get_or_error(base)?.try_into()?;
329 let b_as_dep: Dependency = registry.get_or_error(b)?.try_into()?;
330 let c_as_dep: Dependency = registry.get_or_error(c)?.try_into()?;
331
332 let deps = registry.get_dependencies(a)?;
334 let expected: HashSet<Dependency> = vec![b_as_dep.clone(), c_as_dep.clone(), base_as_dep.clone()].into_iter().collect();
335 assert_eq!(deps, expected);
336
337 let deps = registry.get_dependencies(b)?;
339 let expected: HashSet<Dependency> = vec![c_as_dep.clone(), base_as_dep.clone()].into_iter().collect();
340 assert_eq!(deps, expected);
341
342 let deps = registry.get_dependencies(c)?;
344 let expected: HashSet<Dependency> = vec![base_as_dep.clone()].into_iter().collect();
345 assert_eq!(deps, expected);
346
347 let base_deps = registry.get_dependencies(base)?;
349 assert!(base_deps.is_empty());
350
351 registry.check_version_compatibility()?;
353 registry.edit(base, |package| {
354 package.edit_manifest(|manifest| {
355 manifest.metadata_mut().set_version(Version::new(2, 0, 0));
356 Ok(())
357 })
358 })?;
359 assert!(registry.check_version_compatibility().is_err());
360
361 let sorted = registry.topological_ordering()?;
363 assert_eq!(sorted, vec![base, c, b, a]); assert!(registry.get_orphans(a)?.is_empty());
367 let orphan_path = dir.path().join("orphan.merlon");
368 let orphan = Package::new("Orphan", orphan_path.clone())?;
369 let orphan = registry.register(orphan)?;
370 let sorted = registry.topological_ordering()?;
371 assert!(sorted[sorted.len() - 2] == orphan || sorted[sorted.len() - 1] == orphan);
373 assert_eq!(registry.get_orphans(a)?, vec![orphan].into_iter().collect());
374 registry.delete_orphans(a)?;
375 assert!(!registry.has(orphan));
376 assert!(!orphan_path.exists());
377
378 registry.add_direct_dependency(c, a)?;
380 assert!(registry.topological_ordering().is_err());
381
382 Ok(())
383 }
384}