From ae3d7fc603232a92520c1b6335bd912eb700c1b2 Mon Sep 17 00:00:00 2001 From: Jakob Meier Date: Sun, 13 Aug 2023 17:26:04 +0200 Subject: [PATCH] Updated dependency resolution to show a cycle trace --- Cargo.lock | 7 -- Cargo.toml | 1 - src/cmds/tree.rs | 162 +++++++++++++++++++++++++++++++---------------- src/types.rs | 4 +- 4 files changed, 109 insertions(+), 65 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 61bc08a..54cf412 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -74,7 +74,6 @@ dependencies = [ "regex", "serde", "serde_yaml", - "topological-sort", "walkdir", ] @@ -355,12 +354,6 @@ dependencies = [ "unicode-ident", ] -[[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" diff --git a/Cargo.toml b/Cargo.toml index 4b6d36d..1fb6483 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/cmds/tree.rs b/src/cmds/tree.rs index f306f20..848e8fa 100644 --- a/src/cmds/tree.rs +++ b/src/cmds/tree.rs @@ -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 { /// 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 { +fn list_all_deps(start: PackageName, db: &Database) -> HashSet { 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> { - 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, 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::::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(¤t) { - // already processed vertex + for provider in resolve_package(&node, db) { + if marker.contains(&provider) { + // already processed node continue; } - let res = resolve_package(¤t, 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(¤t) || 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, 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); } diff --git a/src/types.rs b/src/types.rs index e0355ed..b9bec02 100644 --- a/src/types.rs +++ b/src/types.rs @@ -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) { -- 2.38.5