use petgraph::{stable_graph::StableDiGraph, Direction};
pub trait Node {
type DependencyType;
fn dependencies(&self) -> &[Self::DependencyType];
fn matches(&self, dependency: &Self::DependencyType) -> bool;
}
pub enum Step<'a, N: Node> {
Resolved(&'a N),
Unresolved(&'a N::DependencyType),
}
impl<'a, N: Node> Step<'a, N> {
pub fn is_resolved(&self) -> bool {
match self {
Step::Resolved(_) => true,
Step::Unresolved(_) => false,
}
}
pub fn as_resolved(&self) -> Option<&N> {
match self {
Step::Resolved(node) => Some(node),
Step::Unresolved(_) => None,
}
}
pub fn as_unresolved(&self) -> Option<&N::DependencyType> {
match self {
Step::Resolved(_) => None,
Step::Unresolved(dependency) => Some(dependency),
}
}
}
pub struct DependencyGraph<'a, N: Node> {
graph: StableDiGraph<Step<'a, N>, &'a N::DependencyType>,
}
impl<'a, N> From<&'a [N]> for DependencyGraph<'a, N>
where
N: Node,
{
fn from(nodes: &'a [N]) -> Self {
let mut graph = StableDiGraph::<Step<'a, N>, &'a N::DependencyType>::new();
let nodes: Vec<(_, _)> = nodes
.iter()
.map(|node| (node, graph.add_node(Step::Resolved(node))))
.collect();
for (node, index) in nodes.iter() {
for dependency in node.dependencies() {
if let Some((_, dependent)) = nodes.iter().find(|(dep, _)| dep.matches(dependency))
{
graph.add_edge(*index, *dependent, dependency);
} else {
let unresolved = graph.add_node(Step::Unresolved(dependency));
graph.add_edge(*index, unresolved, dependency);
}
}
}
Self { graph }
}
}
impl<'a, N> DependencyGraph<'a, N>
where
N: Node,
{
pub fn is_internally_resolvable(&self) -> bool {
self.graph.node_weights().all(Step::is_resolved)
}
pub fn unresolved_dependencies(&self) -> impl Iterator<Item = &N::DependencyType> {
self.graph.node_weights().filter_map(Step::as_unresolved)
}
}
impl<'a, N> Iterator for DependencyGraph<'a, N>
where
N: Node,
{
type Item = Step<'a, N>;
fn next(&mut self) -> Option<Self::Item> {
for index in self.graph.node_indices().rev() {
if self
.graph
.neighbors_directed(index, Direction::Outgoing)
.count()
== 0
{
return self.graph.remove_node(index);
}
}
None
}
}
#[cfg(test)]
mod tests {
use crate::{DependencyGraph, Node, Step};
use semver::{BuildMetadata, Prerelease, Version, VersionReq};
#[derive(Debug)]
struct Package {
name: &'static str,
version: Version,
dependencies: Vec<Dependency>,
}
#[derive(Debug)]
struct Dependency {
name: &'static str,
version: VersionReq,
}
impl Node for Package {
type DependencyType = Dependency;
fn dependencies(&self) -> &[Self::DependencyType] {
&self.dependencies[..]
}
fn matches(&self, dependency: &Self::DependencyType) -> bool {
self.name == dependency.name && dependency.version.matches(&self.version)
}
}
#[test]
fn test_dependencies_synchronous() {
let build = build_test_graph();
let graph = DependencyGraph::from(&build[..]);
assert!(!graph.is_internally_resolvable());
for node in graph {
match node {
Step::Resolved(build) => println!("build: {:?}", build.name),
Step::Unresolved(lookup) => println!("lookup: {:?}", lookup.name),
}
}
}
#[test]
fn test_unresolved_dependencies() {
let build = build_test_graph();
let graph = DependencyGraph::from(&build[..]);
assert!(!graph.is_internally_resolvable());
let unresolved_dependencies: Vec<_> = graph
.unresolved_dependencies()
.map(|dep| dep.name)
.collect();
assert_eq!(unresolved_dependencies, vec!["unknown", "remote"]);
}
#[test]
fn test_generate_dependency_graph() {
DependencyGraph::from(&build_test_graph()[..]);
}
fn build_test_graph() -> Vec<Package> {
vec![
Package {
name: "base",
version: semver::Version {
major: 1,
minor: 2,
patch: 3,
pre: Prerelease::new("").unwrap(),
build: BuildMetadata::EMPTY,
},
dependencies: vec![],
},
Package {
name: "derived",
version: semver::Version {
major: 1,
minor: 2,
patch: 3,
pre: Prerelease::new("").unwrap(),
build: BuildMetadata::EMPTY,
},
dependencies: vec![Dependency {
name: "base",
version: ">=1.0.0".parse().unwrap(),
}],
},
Package {
name: "second_order",
version: semver::Version {
major: 1,
minor: 2,
patch: 3,
pre: Prerelease::new("").unwrap(),
build: BuildMetadata::EMPTY,
},
dependencies: vec![Dependency {
name: "derived",
version: ">=1.0.0".parse().unwrap(),
}],
},
Package {
name: "converged",
version: semver::Version {
major: 1,
minor: 2,
patch: 3,
pre: Prerelease::new("").unwrap(),
build: BuildMetadata::EMPTY,
},
dependencies: vec![
Dependency {
name: "base",
version: ">=1.0.0".parse().unwrap(),
},
Dependency {
name: "derived",
version: ">=1.0.0".parse().unwrap(),
},
],
},
Package {
name: "independent",
version: semver::Version {
major: 1,
minor: 2,
patch: 3,
pre: Prerelease::new("").unwrap(),
build: BuildMetadata::EMPTY,
},
dependencies: vec![],
},
Package {
name: "external",
version: semver::Version {
major: 1,
minor: 2,
patch: 3,
pre: Prerelease::new("").unwrap(),
build: BuildMetadata::EMPTY,
},
dependencies: vec![
Dependency {
name: "unknown",
version: ">=1.0.0".parse().unwrap(),
},
Dependency {
name: "remote",
version: "=3.0.0".parse().unwrap(),
},
],
},
]
}
#[test]
fn test_internally_resolved() {
let packages = vec![
Package {
name: "base",
version: semver::Version {
major: 1,
minor: 2,
patch: 3,
pre: Prerelease::new("").unwrap(),
build: BuildMetadata::EMPTY,
},
dependencies: vec![],
},
Package {
name: "derived",
version: semver::Version {
major: 3,
minor: 2,
patch: 0,
pre: Prerelease::new("").unwrap(),
build: BuildMetadata::EMPTY,
},
dependencies: vec![Dependency {
name: "base",
version: "=1.2.3".parse().unwrap(),
}],
},
Package {
name: "second_order",
version: semver::Version {
major: 11,
minor: 2,
patch: 4,
pre: Prerelease::new("").unwrap(),
build: BuildMetadata::EMPTY,
},
dependencies: vec![Dependency {
name: "derived",
version: ">=3.0.0".parse().unwrap(),
}],
},
];
let graph = DependencyGraph::from(&packages[..]);
for package in graph {
match package {
Step::Resolved(package) => println!("Building {}!", package.name),
Step::Unresolved(_) => unreachable!(),
}
}
}
}