~comcloudway/cabin

ae3d7fc603232a92520c1b6335bd912eb700c1b2 — Jakob Meier 1 year, 1 month ago 5e63d45
Updated dependency resolution to show a cycle trace
4 files changed, 109 insertions(+), 65 deletions(-)

M Cargo.lock
M Cargo.toml
M src/cmds/tree.rs
M src/types.rs
M Cargo.lock => Cargo.lock +0 -7
@@ 74,7 74,6 @@ dependencies = [
 "regex",
 "serde",
 "serde_yaml",
 "topological-sort",
 "walkdir",
]



@@ 356,12 355,6 @@ dependencies = [
]

[[package]]
name = "topological-sort"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea68304e134ecd095ac6c3574494fc62b909f416c4fca77e440530221e549d3d"

[[package]]
name = "unicode-ident"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"

M Cargo.toml => Cargo.toml +0 -1
@@ 14,4 14,3 @@ serde_yaml = "0.9"
serde = { version = "1.0", features = [ "derive" ] }
walkdir = "2.3"
regex = { version = "1.9.3", features = [ "std" ] }
topological-sort = "0.2.2"

M src/cmds/tree.rs => src/cmds/tree.rs +106 -56
@@ 1,6 1,4 @@
use std::{path::PathBuf, collections::{LinkedList, HashSet}};

use topological_sort::TopologicalSort;
use std::{path::PathBuf, collections::{LinkedList, HashSet, HashMap}};

use crate::types::{PackageName, Database};



@@ 26,7 24,7 @@ fn resolve_package(name: &str, db: &Database) -> HashSet<PackageName> {

/// creates a list of all dependencies used by the package itself,
/// or by packages required by the start package
fn list_all_deps(start: PackageName, db: &Database, local_only: bool) -> HashSet<PackageName> {
fn list_all_deps(start: PackageName, db: &Database) -> HashSet<PackageName> {
    let mut stack = LinkedList::new();
    let mut marker = HashSet::new();
    stack.push_front(start);


@@ 43,14 41,20 @@ fn list_all_deps(start: PackageName, db: &Database, local_only: bool) -> HashSet
        if res.is_empty() {
            // not a local dependency,
            // so we can't go any deeper
            if !local_only {
                marker.insert(current);
            }
            marker.insert(current);
        } else {
            for loc in res {
                if marker.contains(&loc) {
                    // already processed vertex
                    continue;
                }

                if let Some(deps) = db.depends.get(&loc) {
                    for dep in deps {
                        stack.push_front(dep.to_string());
                        let resolved = resolve_package(dep, db);
                        for res in resolved {
                            stack.push_front(res.to_string());
                        }
                    }
                }



@@ 64,7 68,6 @@ fn list_all_deps(start: PackageName, db: &Database, local_only: bool) -> HashSet
                }
            }
        }

    }

    return marker;


@@ 72,84 75,131 @@ fn list_all_deps(start: PackageName, db: &Database, local_only: bool) -> HashSet

/// topologically sorts all local dependencies
/// filters unknown dependencies
pub fn topological_sort(start: &PackageName, db: &Database) -> Option<Vec<PackageName>> {
    let mut ts = {
        let mut stack = LinkedList::new();
        let mut marker = HashSet::new();
        stack.push_front(start.to_string());
pub fn topological_sort(start: &PackageName, db: &Database) -> Result<Vec<PackageName>, Vec<(PackageName, PackageName)>> {
    let mut stack = LinkedList::new();
    let mut marker = HashSet::new();
    stack.push_front(start.to_string());

    let mut edge_out = HashMap::new();
    let mut edge_in = HashMap::new();

        let mut ts = TopologicalSort::<String>::new();
        ts.insert(start);
    while !stack.is_empty() {
        let node = stack.pop_back().unwrap();

        // setup the topological sort dependency graph
        while ! stack.is_empty() {
            let current = stack.pop_back().unwrap();
        if marker.contains(&node) {
            // already processed node
            continue;
        }

            if marker.contains(&current) {
                // already processed vertex
        for provider in resolve_package(&node, db) {
            if marker.contains(&provider) {
                // already processed node
                continue;
            }

            let res = resolve_package(&current, db);
            if !res.is_empty() {
                for loc in res {
                    if let Some(deps) = db.depends.get(&loc) {
                        for dep in deps {
                            let resolved = resolve_package(dep, db);
                            for res in resolved {
                                stack.push_front(res.to_string());
                                ts.add_dependency(
                                    res.to_string(),
                                    current.to_string()
                                );
                            }
            if let Some(deps) = db.depends.get(&provider) {
                for dep in deps {
                    let resolved = resolve_package(dep, db);
                    for res in resolved {
                        stack.push_front(res.to_string());

                        if res == provider {
                            continue;
                        }
                    }

                    // if the current package is provided by the given package,
                    // we add the package name of the provider-package
                    // to our list
                    if let Some(pkg) = db.packages.get(&loc) {
                        if pkg.provides.contains(&current) || pkg.name == current {
                            marker.insert(pkg.name.to_string());
                        if !edge_out.contains_key(&provider) {
                            edge_out.insert(provider.to_string(), HashSet::new());
                        }
                        edge_out.get_mut(&provider).unwrap()
                                                   .insert(res.to_string());
                        if !edge_in.contains_key(&res) {
                            edge_in.insert(res.to_string(), HashSet::new());
                        }
                        edge_in.get_mut(&res).unwrap()
                                             .insert(provider.to_string());

                    }
                }
            }

            // if the current package is provided by the given package,
            // we add the package name of the provider-package
            // to our list
            if let Some(pkg) = db.packages.get(&provider) {
                if pkg.provides.contains(&node) || pkg.name == node {
                    marker.insert(pkg.name.to_string());
                }
            }

        }
    }

    let mut sources = Vec::new();
    // generate a list of all sinks
    // (packages that do not require other packages)
    for node in &marker {
        if !edge_out.contains_key(node) {
            sources.push(node.to_string());
        }
    }

    let mut order = Vec::new();

        ts
    };
    while !sources.is_empty() {
        let node = sources.pop().unwrap();
        order.push(node.to_string());

    let mut v = Vec::new();
        for out in edge_in.get(&node).unwrap_or(&HashSet::new()).clone() {
            // remove connection from node to out
            if let Some(list) = edge_in.get_mut(&node) {
                list.remove(&out);
                if list.is_empty() {
                    // turned into a sink
                    sources.push(out.to_string());

                    edge_in.remove(&node);
                }
            }
        }
    }

    while !ts.is_empty() {
        let mut next = ts.pop_all();
    if !edge_in.is_empty() {
        let mut v = Vec::new();

        if next.is_empty() {
            // cycle detected
            return None;
        for (node, connections) in edge_in {
            for con in connections {
                v.push((node.to_string(), con));
            }
        }

        v.append(&mut next);
        return Err(v);
    }

    Some(v)
    Ok(order)
}

/// Command used to list packages required to build the given package
/// will print the topological order, when local_only is set
pub fn tree(name: PackageName, db: Option<PathBuf>, local_only: bool) {
    let db = Database::from_file(db.unwrap_or(PathBuf::from("db.yml")))
        .expect("Unable to open database");

    if local_only {
        let sorted = topological_sort(&name, &db)
            .expect("Unable to resolve dependencies, as there is a cycle in the graph");
        for dep in sorted {
            println!("{}", dep);
        match topological_sort(&name, &db) {
            Ok(sorted) => {
                for dep in sorted {
                    println!("{}", dep);
                }
            },
            Err(cycle) => {
                println!("Cycle detected");
                for dep in cycle {
                    println!("{}->{}", dep.1, dep.0);
                }
            }
        }
    } else {
        let deps = list_all_deps(name, &db, false);
        let deps = list_all_deps(name, &db);
        for dep in deps {
            println!("{}", dep);
        }

M src/types.rs => src/types.rs +3 -1
@@ 91,7 91,9 @@ impl Database {
            };

            db.packages.insert(apk.name.to_string(), pkg);
            db.depends.insert(apk.name.to_string(), depends);
            if !depends.is_empty() {
                db.depends.insert(apk.name.to_string(), depends);
            }

            for provider in provides {
                if let Some(table) = db.provides.get_mut(&provider) {