meta: initial rewrite (final)
This commit is contained in:
+217
@@ -0,0 +1,217 @@
|
||||
use crate::recipe::{PackageRecipe, RecipeSet, SourceRecipe, ToolRecipe};
|
||||
|
||||
use petgraph::{
|
||||
Direction,
|
||||
graph::{DiGraph, NodeIndex},
|
||||
};
|
||||
use smallvec::{SmallVec, smallvec};
|
||||
use std::{
|
||||
cmp::Reverse,
|
||||
collections::{BinaryHeap, HashMap, HashSet},
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum PlanError {
|
||||
#[error("missing source recipe '{0}'")]
|
||||
MissingSource(String),
|
||||
#[error("missing tool recipe '{0}'")]
|
||||
MissingTool(String),
|
||||
#[error("missing package recipe '{0}'")]
|
||||
MissingPackage(String),
|
||||
#[error("plan cycle detected")]
|
||||
CycleDetected,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum PlanKey<'a> {
|
||||
SourceFetch(&'a SourceRecipe),
|
||||
SourcePatch(&'a SourceRecipe),
|
||||
SourcePrepare(&'a SourceRecipe),
|
||||
|
||||
ToolConfigure(&'a ToolRecipe),
|
||||
ToolBuild(&'a ToolRecipe),
|
||||
ToolInstall(&'a ToolRecipe),
|
||||
|
||||
PkgConfigure(&'a PackageRecipe),
|
||||
PkgBuild(&'a PackageRecipe),
|
||||
PkgInstall(&'a PackageRecipe),
|
||||
PkgPackage(&'a PackageRecipe),
|
||||
}
|
||||
|
||||
impl<'a> PlanKey<'a> {
|
||||
fn weight(&self) -> i8 {
|
||||
match self {
|
||||
PlanKey::SourceFetch(_) => 0,
|
||||
PlanKey::SourcePatch(_) => 1,
|
||||
PlanKey::SourcePrepare(_) => 2,
|
||||
PlanKey::ToolConfigure(_) => 3,
|
||||
PlanKey::ToolBuild(_) => 4,
|
||||
PlanKey::ToolInstall(_) => 5,
|
||||
PlanKey::PkgConfigure(_) => 6,
|
||||
PlanKey::PkgBuild(_) => 7,
|
||||
PlanKey::PkgInstall(_) => 8,
|
||||
PlanKey::PkgPackage(_) => 9,
|
||||
}
|
||||
}
|
||||
|
||||
fn dependencies(
|
||||
&self,
|
||||
recipes: &'a RecipeSet,
|
||||
) -> Result<SmallVec<[PlanKey<'a>; 8]>, PlanError> {
|
||||
match self {
|
||||
PlanKey::SourceFetch(_) => Ok(smallvec![]),
|
||||
PlanKey::SourcePatch(recipe) => Ok(smallvec![PlanKey::SourceFetch(recipe)]),
|
||||
PlanKey::SourcePrepare(recipe) => Ok(smallvec![PlanKey::SourcePatch(recipe)]),
|
||||
PlanKey::ToolConfigure(recipe) => {
|
||||
let source_deps = recipe.sources.iter().map(|name| {
|
||||
recipes
|
||||
.source(name)
|
||||
.map(PlanKey::SourcePrepare)
|
||||
.ok_or(PlanError::MissingSource(name.clone()))
|
||||
});
|
||||
|
||||
let tool_deps = recipe.tools_wanted.iter().map(|name| {
|
||||
recipes
|
||||
.tool(name)
|
||||
.map(PlanKey::ToolInstall)
|
||||
.ok_or(PlanError::MissingTool(name.clone()))
|
||||
});
|
||||
|
||||
let pkg_deps = recipe.pkgs_wanted.iter().map(|name| {
|
||||
recipes
|
||||
.package(name)
|
||||
.map(PlanKey::PkgPackage)
|
||||
.ok_or(PlanError::MissingPackage(name.clone()))
|
||||
});
|
||||
|
||||
source_deps.chain(tool_deps).chain(pkg_deps).collect()
|
||||
}
|
||||
PlanKey::ToolBuild(recipe) => Ok(smallvec![PlanKey::ToolConfigure(recipe)]),
|
||||
PlanKey::ToolInstall(recipe) => Ok(smallvec![PlanKey::ToolBuild(recipe)]),
|
||||
PlanKey::PkgConfigure(recipe) => {
|
||||
let source_deps = recipe.sources.iter().map(|name| {
|
||||
recipes
|
||||
.source(name)
|
||||
.map(PlanKey::SourcePrepare)
|
||||
.ok_or(PlanError::MissingSource(name.clone()))
|
||||
});
|
||||
|
||||
let tool_deps = recipe.tools_wanted.iter().map(|name| {
|
||||
recipes
|
||||
.tool(name)
|
||||
.map(PlanKey::ToolInstall)
|
||||
.ok_or(PlanError::MissingTool(name.clone()))
|
||||
});
|
||||
|
||||
let pkg_deps = recipe.pkgs_wanted.iter().map(|name| {
|
||||
recipes
|
||||
.package(name)
|
||||
.map(PlanKey::PkgPackage)
|
||||
.ok_or(PlanError::MissingPackage(name.clone()))
|
||||
});
|
||||
|
||||
source_deps.chain(tool_deps).chain(pkg_deps).collect()
|
||||
}
|
||||
PlanKey::PkgBuild(recipe) => Ok(smallvec![PlanKey::PkgConfigure(recipe)]),
|
||||
PlanKey::PkgInstall(recipe) => Ok(smallvec![PlanKey::PkgBuild(recipe)]),
|
||||
PlanKey::PkgPackage(recipe) => Ok(smallvec![PlanKey::PkgInstall(recipe)]),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_active(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Plan<'a> {
|
||||
recipes: &'a RecipeSet,
|
||||
wanted: HashSet<PlanKey<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> Plan<'a> {
|
||||
pub fn new(recipes: &'a RecipeSet) -> Self {
|
||||
Self {
|
||||
recipes,
|
||||
wanted: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_wanted(&mut self, key: PlanKey<'a>) {
|
||||
self.wanted.insert(key);
|
||||
}
|
||||
|
||||
pub fn steps(&self) -> Result<Vec<PlanKey<'a>>, PlanError> {
|
||||
let mut stack: Vec<_> = self.wanted.iter().copied().collect();
|
||||
let mut graph: DiGraph<_, ()> = DiGraph::new();
|
||||
let mut nodes = HashMap::new();
|
||||
|
||||
while let Some(node) = stack.pop() {
|
||||
let node_idx = match nodes.get(&node) {
|
||||
Some(&idx) => idx,
|
||||
None => {
|
||||
let idx = graph.add_node(node);
|
||||
nodes.insert(node, idx);
|
||||
idx
|
||||
}
|
||||
};
|
||||
|
||||
for dep in node.dependencies(self.recipes)?.iter().copied() {
|
||||
let dep_idx = match nodes.get(&dep) {
|
||||
Some(&idx) => idx,
|
||||
None => {
|
||||
let idx = graph.add_node(dep);
|
||||
nodes.insert(dep, idx);
|
||||
stack.push(dep);
|
||||
idx
|
||||
}
|
||||
};
|
||||
|
||||
graph.update_edge(dep_idx, node_idx, ());
|
||||
}
|
||||
}
|
||||
|
||||
// petgraph::algo::toposort(&graph, None)
|
||||
// .and_then(|nodes| {
|
||||
// Ok(nodes
|
||||
// .iter()
|
||||
// .map(|&k| graph[k])
|
||||
// .filter(|node| node.is_active())
|
||||
// .collect())
|
||||
// })
|
||||
// .map_err(|_| PlanError::CycleDetected)
|
||||
|
||||
let mut in_degree: HashMap<NodeIndex, usize> = graph
|
||||
.node_indices()
|
||||
.map(|i| (i, graph.neighbors_directed(i, Direction::Incoming).count()))
|
||||
.collect();
|
||||
|
||||
let mut heap: BinaryHeap<(Reverse<i8>, NodeIndex)> = in_degree
|
||||
.iter()
|
||||
.filter(|&(_, d)| *d == 0)
|
||||
.map(|(&i, _)| (Reverse(graph[i].weight()), i))
|
||||
.collect();
|
||||
|
||||
let mut result = Vec::with_capacity(graph.node_count());
|
||||
|
||||
while let Some((_, idx)) = heap.pop() {
|
||||
result.push(graph[idx]);
|
||||
|
||||
for neighbor in graph.neighbors_directed(idx, Direction::Outgoing) {
|
||||
let d = in_degree.get_mut(&neighbor).unwrap();
|
||||
*d -= 1;
|
||||
if *d == 0 {
|
||||
heap.push((Reverse(graph[neighbor].weight()), neighbor));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if result.len() != graph.node_count() {
|
||||
Err(PlanError::CycleDetected)
|
||||
} else {
|
||||
result.retain(|node| node.is_active());
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user