Skip to main content

dotm/
resolver.rs

1use crate::config::RootConfig;
2use anyhow::{Result, bail};
3use std::collections::HashSet;
4
5/// Resolve a list of requested packages into a fully-expanded, dependency-ordered list.
6/// Dependencies come before the packages that depend on them.
7/// Circular dependencies produce an error.
8pub fn resolve_packages(root: &RootConfig, requested: &[&str]) -> Result<Vec<String>> {
9    let mut resolved: Vec<String> = Vec::new();
10    let mut seen: HashSet<String> = HashSet::new();
11
12    for pkg in requested {
13        resolve_one(root, pkg, &mut resolved, &mut seen, &mut Vec::new())?;
14    }
15
16    Ok(resolved)
17}
18
19fn resolve_one(
20    root: &RootConfig,
21    pkg: &str,
22    resolved: &mut Vec<String>,
23    seen: &mut HashSet<String>,
24    stack: &mut Vec<String>,
25) -> Result<()> {
26    if seen.contains(pkg) {
27        return Ok(());
28    }
29
30    if stack.contains(&pkg.to_string()) {
31        stack.push(pkg.to_string());
32        bail!("circular dependency detected: {}", stack.join(" -> "));
33    }
34
35    let pkg_config = root.packages.get(pkg);
36    if let Some(config) = pkg_config {
37        stack.push(pkg.to_string());
38        for dep in &config.depends {
39            if !root.packages.contains_key(dep.as_str()) {
40                bail!("package '{pkg}' depends on unknown package '{dep}'");
41            }
42            resolve_one(root, dep, resolved, seen, stack)?;
43        }
44        stack.pop();
45    } else {
46        bail!("unknown package: '{pkg}'");
47    }
48
49    seen.insert(pkg.to_string());
50    resolved.push(pkg.to_string());
51    Ok(())
52}