Skip to content

Commit

Permalink
Merge pull request #55 from serpent-os/feat/transaction-remove
Browse files Browse the repository at this point in the history
Feat/transaction remove
  • Loading branch information
ikeycode authored Oct 14, 2023
2 parents a9dfe29 + 7ec5df8 commit 1bff5cc
Show file tree
Hide file tree
Showing 8 changed files with 394 additions and 97 deletions.
102 changes: 96 additions & 6 deletions dag/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,102 @@
//
// SPDX-License-Identifier: MPL-2.0

pub mod subgraph;
use petgraph::{
prelude::DiGraph,
visit::{Dfs, Topo, Walker},
};

pub use self::reexport::*;
use self::subgraph::subgraph;

pub mod reexport {
pub use petgraph::algo::{toposort, Cycle};
pub use petgraph::graph::DiGraph;
pub use petgraph::visit::Dfs;
mod subgraph;

pub type NodeIndex = petgraph::prelude::NodeIndex<u32>;

#[derive(Debug, Clone)]
pub struct Dag<N>(DiGraph<N, (), u32>);

impl<N> Default for Dag<N> {
fn default() -> Self {
Self(DiGraph::default())
}
}

impl<N> Dag<N>
where
N: Clone + PartialEq,
{
pub fn new() -> Self {
Self::default()
}

/// Adds node N to the graph and retusn it's index.
/// If N already exists, it'll return the index of that node.
pub fn add_node_or_get_index(&mut self, node: N) -> NodeIndex {
if let Some(index) = self.get_index(&node) {
index
} else {
self.0.add_node(node)
}
}

pub fn node_exists(&self, node: &N) -> bool {
self.get_index(node).is_some()
}

pub fn remove_node(&mut self, node: &N) -> Option<N> {
if let Some(index) = self.get_index(node) {
self.0.remove_node(index)
} else {
None
}
}

pub fn add_edge(&mut self, a: NodeIndex, b: NodeIndex) -> bool {
let a_node = &self.0[a];

// prevent cycle (b connects to a)
if self.dfs(b).any(|n| n == a_node) {
return false;
}

// don't add edge if it alread exists
if self.0.find_edge(a, b).is_some() {
return false;
}

// We're good, add it
self.0.add_edge(a, b, ());

true
}

pub fn iter_nodes(&self) -> impl Iterator<Item = &'_ N> {
self.0.node_indices().map(|i| &self.0[i])
}

pub fn dfs(&self, start: NodeIndex) -> impl Iterator<Item = &'_ N> {
let dfs = Dfs::new(&self.0, start);

dfs.iter(&self.0).map(|i| &self.0[i])
}

pub fn topo(&self) -> impl Iterator<Item = &'_ N> {
let topo = Topo::new(&self.0);

topo.iter(&self.0).map(|i| &self.0[i])
}

pub fn transpose(&self) -> Self {
let mut transposed = self.0.clone();
transposed.reverse();
Self(transposed)
}

pub fn subgraph(&self, starting_nodes: &[N]) -> Self {
Self(subgraph(&self.0, starting_nodes))
}

pub fn get_index(&self, node: &N) -> Option<NodeIndex> {
self.0.node_indices().find(|i| self.0[*i] == *node)
}
}
158 changes: 131 additions & 27 deletions dag/src/subgraph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,47 @@
//
// SPDX-License-Identifier: MPL-2.0

use petgraph::{prelude::GraphMap, visit::Dfs, EdgeType};
use petgraph::{prelude::Graph, stable_graph::IndexType, visit::Dfs, EdgeType};

/// Given an input [GraphMap] and the start nodes, construct a subgraph
/// Given an input [`Graph`] and the start nodes, construct a subgraph
/// Used largely in transposed form for reverse dependency calculation
pub fn subgraph<V, E, Ty>(graph: &GraphMap<V, E, Ty>, starting_nodes: Vec<V>) -> GraphMap<V, E, Ty>
pub fn subgraph<N, E, Ty, Ix>(
graph: &Graph<N, E, Ty, Ix>,
starting_nodes: &[N],
) -> Graph<N, E, Ty, Ix>
where
V: Eq + std::hash::Hash + Ord + Copy,
E: Default,
N: PartialEq + Clone,
E: Clone,
Ix: IndexType,
Ty: EdgeType,
{
let mut res = GraphMap::default();
let add_node = |graph: &mut Graph<N, E, Ty, Ix>, node| {
if let Some(index) = graph.node_indices().find(|i| graph[*i] == node) {
index
} else {
graph.add_node(node)
}
};

let mut res = Graph::default();
let mut dfs = Dfs::empty(&graph);

for starting_node in starting_nodes {
dfs.move_to(starting_node);
let Some(starting_node_index) = graph.node_indices().find(|n| graph[*n] == *starting_node)
else {
continue;
};

dfs.move_to(starting_node_index);

while let Some(node) = dfs.next(&graph) {
res.extend(
graph
.neighbors_directed(node, petgraph::Direction::Outgoing)
.map(|adj| (node, adj)),
);
let node_index = add_node(&mut res, graph[node].clone());
for neighbor in graph.neighbors_directed(node, petgraph::Direction::Outgoing) {
if let Some(edge) = graph.find_edge(node, neighbor) {
let neighbor_index = add_node(&mut res, graph[neighbor].clone());
res.add_edge(node_index, neighbor_index, graph[edge].clone());
}
}
}
}

Expand All @@ -32,42 +52,126 @@ where
#[cfg(test)]
mod test {
use petgraph::{
prelude::DiGraphMap,
data::{Element, FromElements},
prelude::DiGraph,
visit::{Reversed, Topo, Walker},
};

use super::*;

#[test]
fn basic_topo() {
let graph: DiGraphMap<i32, ()> = DiGraphMap::from_edges(&[(1, 2), (1, 3), (2, 3)]);
let subg = subgraph(&graph, vec![1]);
let graph: DiGraph<i32, ()> = DiGraph::from_elements([
Element::Node { weight: 1 },
Element::Node { weight: 2 },
Element::Node { weight: 3 },
Element::Node { weight: 4 },
Element::Edge {
source: 0,
target: 1,
weight: (),
},
Element::Edge {
source: 0,
target: 2,
weight: (),
},
Element::Edge {
source: 1,
target: 2,
weight: (),
},
Element::Edge {
source: 2,
target: 3,
weight: (),
},
]);
let subg = subgraph(&graph, &[2]);
let topo = Topo::new(&subg);
let order: Vec<i32> = topo.iter(&subg).collect();
let order: Vec<i32> = topo.iter(&subg).map(|n| subg[n]).collect();

assert_eq!(order, vec![1, 2, 3]);
assert_eq!(order, vec![2, 3, 4]);
}

#[test]
fn reverse_topo() {
let graph: DiGraphMap<i32, ()> = DiGraphMap::from_edges(&[(1, 2), (1, 3), (2, 3)]);
let items = vec![1];
let subg = subgraph(&graph, items);
let graph: DiGraph<i32, ()> = DiGraph::from_elements([
Element::Node { weight: 1 },
Element::Node { weight: 2 },
Element::Node { weight: 3 },
Element::Node { weight: 4 },
Element::Edge {
source: 0,
target: 1,
weight: (),
},
Element::Edge {
source: 0,
target: 2,
weight: (),
},
Element::Edge {
source: 1,
target: 2,
weight: (),
},
Element::Edge {
source: 2,
target: 3,
weight: (),
},
]);
let subg = subgraph(&graph, &[2]);
let revg = Reversed(&subg);
let removal: Vec<i32> = Topo::new(revg).iter(revg).collect();
assert_eq!(removal, vec![3, 2, 1]);
let removal: Vec<i32> = Topo::new(revg).iter(revg).map(|n| subg[n]).collect();
assert_eq!(removal, vec![4, 3, 2]);
}

// TODO: break cycles!
#[ignore = "cycles breaking needs to be implemented"]
#[test]
fn cyclic_topo() {
let graph: DiGraphMap<i32, ()> =
DiGraphMap::from_edges(&[(1, 2), (1, 3), (2, 4), (2, 5), (3, 5), (4, 1)]);
let items = vec![1, 4];
let subg = subgraph(&graph, items);
let graph: DiGraph<i32, ()> = DiGraph::from_elements([
Element::Node { weight: 1 },
Element::Node { weight: 2 },
Element::Node { weight: 3 },
Element::Node { weight: 4 },
Element::Node { weight: 5 },
Element::Edge {
source: 0,
target: 1,
weight: (),
},
Element::Edge {
source: 0,
target: 2,
weight: (),
},
Element::Edge {
source: 1,
target: 3,
weight: (),
},
Element::Edge {
source: 1,
target: 4,
weight: (),
},
Element::Edge {
source: 2,
target: 4,
weight: (),
},
Element::Edge {
source: 3,
target: 0,
weight: (),
},
]);
let subg = subgraph(&graph, &[1, 4]);
let revg = Reversed(&subg);
let removal: Vec<i32> = Topo::new(revg).iter(revg).collect();
let removal: Vec<i32> = Topo::new(revg).iter(revg).map(|n| subg[n]).collect();
assert_eq!(removal, vec![5, 3, 4, 2, 1]);
}
}
7 changes: 2 additions & 5 deletions moss/src/cli/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: MPL-2.0

use std::{path::PathBuf, time::Duration};
use std::{path::Path, time::Duration};

use clap::{arg, ArgMatches, Command};
use futures::{future::join_all, stream, StreamExt, TryStreamExt};
Expand Down Expand Up @@ -45,9 +45,7 @@ async fn find_packages(id: &str, client: &Client) -> Result<Vec<Package>, Error>
}

/// Handle execution of `moss install`
pub async fn handle(args: &ArgMatches) -> Result<(), Error> {
let root = args.get_one::<PathBuf>("root").unwrap().clone();

pub async fn handle(args: &ArgMatches, root: &Path) -> Result<(), Error> {
let pkgs = args
.get_many::<String>("NAME")
.into_iter()
Expand Down Expand Up @@ -82,7 +80,6 @@ pub async fn handle(args: &ArgMatches) -> Result<(), Error> {
// Resolve and map it. Remove any installed items. OK to unwrap here because they're resolved already
let results = join_all(
tx.finalize()
.iter()
.map(|p| async { client.registry.by_id(p).boxed().next().await.unwrap() }),
)
.await;
Expand Down
10 changes: 7 additions & 3 deletions moss/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,14 @@ pub async fn process() -> Result<(), Error> {
Some(("extract", args)) => extract::handle(args).await.map_err(Error::Extract),
Some(("info", args)) => info::handle(args).await.map_err(Error::Info),
Some(("inspect", args)) => inspect::handle(args).await.map_err(Error::Inspect),
Some(("install", args)) => install::handle(args).await.map_err(Error::Install),
Some(("install", args)) => install::handle(args, root).await.map_err(Error::Install),
Some(("version", _)) => {
version::print();
Ok(())
}
Some(("list", a)) => list::handle(a).await.map_err(Error::List),
Some(("repo", a)) => repo::handle(a, root).await.map_err(Error::Repo),
Some(("list", args)) => list::handle(args).await.map_err(Error::List),
Some(("remove", args)) => remove::handle(args, root).await.map_err(Error::Remove),
Some(("repo", args)) => repo::handle(args, root).await.map_err(Error::Repo),
_ => unreachable!(),
}
}
Expand All @@ -110,6 +111,9 @@ pub enum Error {
#[error("error in extraction: {0}")]
Extract(#[from] extract::Error),

#[error("error handling remove: {0}")]
Remove(#[from] remove::Error),

#[error("error handling repo: {0}")]
Repo(#[from] repo::Error),
}
Loading

0 comments on commit 1bff5cc

Please sign in to comment.