merlon/package/
registry.rs

1//! Package registry
2
3use std::collections::{HashSet, HashMap, BinaryHeap};
4
5use anyhow::{Result, bail};
6use pyo3::prelude::*;
7
8use super::{Package, Id, manifest::{Dependency, Version}};
9
10/// A package registry. This is an arena of packages.
11/// Allows for querying packages by name, uuid, etc., and dependency queries.
12#[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    /// Create a new, empty registry.
21    #[new]
22    pub fn new() -> Self {
23        Self {
24            packages: Default::default(),
25        }
26    }
27
28    /// Add a package to the registry.
29    /// Returns an error if the package is already in the registry.
30    /// Returns the package's ID so it can be used to refer to the package.
31    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    /// Remove a package from the registry.
41    /// Returns an error if the package is not in the registry.
42    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    /// Returns true if the registry contains a package with the given ID.
50    pub fn has(&self, id: Id) -> bool {
51        self.packages.contains_key(&id)
52    }
53}
54
55impl Registry {
56    /// Get a package by ID.
57    pub fn get(&self, id: Id) -> Option<&Package> {
58        self.packages.get(&id)
59    }
60
61    /// Get a package by ID, or return an error if it is not in the registry.
62    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    /// Edits a package given its ID. The callback is given a mutable reference to the package.
70    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    /// Returns an iterator over IDs of the packages in the registry.
81    pub fn package_ids(&self) -> impl Iterator<Item = Id> + '_ {
82        self.packages.keys().copied()
83    }
84}
85
86// Queries. Note they talk in IDs, not a &Package, to satisfy the borrow checker.
87#[pymethods]
88impl Registry {
89    /// Iterates over the direct dependency packages of a package.
90    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()) // Clone so we can drop manifest
95            .collect())
96    }
97
98    /// Iterates over all dependencies of a package, including both direct and transitive dependencies.
99    pub fn get_dependencies(&self, id: Id) -> Result<HashSet<Dependency>> {
100        // Breadth first search
101        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    /// Returns the set of all dependencies across all packages in the registry.
123    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    /// Returns true if a package has a dependency - transitive or direct - on another package.
134    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    /// Adds a direct dependency to a package.
146    /// Both the package and the dependency must be registered.
147    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    /// Returns an error if packages exist with incompatible versions.
159    /// For example, if package A depends on package B ^1.0.0, but package B is registered as 2.0.0, its an error.
160    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(), // unwrap: if its in map, its in registry
171                                version,
172                                actual_version,
173                            );
174                        }
175                    }
176                }
177                
178            }
179        }
180        Ok(())
181    }
182
183    /// Calculates the patch order in order to build a given root package.
184    pub fn calc_dependency_patch_order(&self, root: Id) -> Result<Vec<Id>> {
185        // https://en.wikipedia.org/wiki/Longest_path_problem#Acyclic_graphs
186
187        // Topological ordering will be wrong if there are orphaned packages
188        if !self.get_orphans(root)?.is_empty() {
189            bail!("there are orphaned packages");
190        }
191
192        // Because all graph weights are the same, we can just use the topological ordering
193        let ordering = self.topological_ordering()?;
194
195        // The root package should be the final package in the ordering
196        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    /// Returns a topological ordering of the packages in the registry.
204    /// That is, a list of packages such that for every dependency, the dependency appears before the dependent.
205    pub fn topological_ordering(&self) -> Result<Vec<Id>> {
206        // https://en.wikipedia.org/wiki/Topological_sorting#Depth-first_search
207        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(); // "temporary mark"
210        let mut visited = HashSet::new(); // "permanent mark"
211        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    /// Returns packages that don't appear in the dependency tree for the given root package.
218    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    /// Unregisters and deletes the directories for all orphaned packages.
236    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    /// Generates a map of package IDs to their versions.
247    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        // Create a graph of four packages: Base, A, B, and C.
307        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        // Print IDs for debugging.
317        dbg!(&base, &a, &b, &c);
318
319        // Both A and B directly depend on C.
320        registry.add_direct_dependency(a, b)?;
321        registry.add_direct_dependency(b, c)?;
322
323        // All depend directly on base.
324        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        // Assert A depends on B, C, and base only.
333        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        // Assert B depends on C and base only.
338        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        // Assert C depends on base only.
343        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        // Assert base has no dependencies.
348        let base_deps = registry.get_dependencies(base)?;
349        assert!(base_deps.is_empty());
350
351        // If we update base's major version, it should become incompatible
352        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        // Topological ordering
362        let sorted = registry.topological_ordering()?;
363        assert_eq!(sorted, vec![base, c, b, a]); // c, b can be in either order
364
365        // Add an orphan package that nothing depends on
366        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        // orphan must be in last 2 places (it can trade with a)
372        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        // Add a circular dependency
379        registry.add_direct_dependency(c, a)?;
380        assert!(registry.topological_ordering().is_err());
381
382        Ok(())
383    }
384}