Skip to content

Commit

Permalink
new: Implement a new action graph. (#1093)
Browse files Browse the repository at this point in the history
* Start on new graph.

* More graph work.

* Add caching to lookups.

* Add node labels.

* Add graph.

* Start on topo and tests.

* Add basic tests.

* Add tests.

* Add run task.

* Use digraph.

* Update docs.

* Add back link.

* Add cycle stuff.

* Add test utils crate.

* Start on new test utils crate.

* Add target tests.

* Test deps.

* Rework peer dep.

* Add dependents.

* Polish.

* Bump visualizer.

* Fix test.
  • Loading branch information
milesj committed Oct 9, 2023
1 parent e19a6d2 commit 216ccc7
Show file tree
Hide file tree
Showing 79 changed files with 2,786 additions and 302 deletions.
17 changes: 9 additions & 8 deletions .yarn/versions/22a0caa2.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
releases:
"@moonrepo/cli": minor
"@moonrepo/core-linux-arm64-gnu": minor
"@moonrepo/core-linux-arm64-musl": minor
"@moonrepo/core-linux-x64-gnu": minor
"@moonrepo/core-linux-x64-musl": minor
"@moonrepo/core-macos-arm64": minor
"@moonrepo/core-macos-x64": minor
"@moonrepo/core-windows-x64-msvc": minor
'@moonrepo/cli': minor
'@moonrepo/core-linux-arm64-gnu': minor
'@moonrepo/core-linux-arm64-musl': minor
'@moonrepo/core-linux-x64-gnu': minor
'@moonrepo/core-linux-x64-musl': minor
'@moonrepo/core-macos-arm64': minor
'@moonrepo/core-macos-x64': minor
'@moonrepo/core-windows-x64-msvc': minor
'@moonrepo/visualizer': minor
40 changes: 40 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions crates/core/dep-graph/tests/dep_graph_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,11 +262,11 @@ mod run_target {
vec![NodeIndex::new(1)],
vec![
NodeIndex::new(2),
NodeIndex::new(5),
NodeIndex::new(4),
NodeIndex::new(6),
NodeIndex::new(7)
],
vec![NodeIndex::new(4), NodeIndex::new(12), NodeIndex::new(13)],
vec![NodeIndex::new(5), NodeIndex::new(12), NodeIndex::new(13)],
vec![NodeIndex::new(3), NodeIndex::new(11)],
vec![NodeIndex::new(0)],
vec![
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ digraph {
1 [ label="SetupNodeTool(16.0.0)" style=filled, shape=oval, fillcolor=gray, fontcolor=black]
2 [ label="InstallNodeDeps(16.0.0)" style=filled, shape=oval, fillcolor=gray, fontcolor=black]
3 [ label="SyncNodeProject(persistent)" style=filled, shape=oval, fillcolor=gray, fontcolor=black]
4 [ label="SyncNodeProject(buildA)" style=filled, shape=oval, fillcolor=gray, fontcolor=black]
5 [ label="SyncNodeProject(buildC)" style=filled, shape=oval, fillcolor=gray, fontcolor=black]
4 [ label="SyncNodeProject(buildC)" style=filled, shape=oval, fillcolor=gray, fontcolor=black]
5 [ label="SyncNodeProject(buildA)" style=filled, shape=oval, fillcolor=gray, fontcolor=black]
6 [ label="SyncNodeProject(basic)" style=filled, shape=oval, fillcolor=gray, fontcolor=black]
7 [ label="SyncNodeProject(noTasks)" style=filled, shape=oval, fillcolor=gray, fontcolor=black]
8 [ label="RunPersistentTarget(persistent:dev)" style=filled, shape=oval, fillcolor=gray, fontcolor=black]
Expand All @@ -21,13 +21,13 @@ digraph {
2 -> 1 [ arrowhead=box, arrowtail=box]
3 -> 1 [ arrowhead=box, arrowtail=box]
4 -> 1 [ arrowhead=box, arrowtail=box]
3 -> 4 [ arrowhead=box, arrowtail=box]
5 -> 1 [ arrowhead=box, arrowtail=box]
4 -> 5 [ arrowhead=box, arrowtail=box]
5 -> 4 [ arrowhead=box, arrowtail=box]
6 -> 1 [ arrowhead=box, arrowtail=box]
4 -> 6 [ arrowhead=box, arrowtail=box]
5 -> 6 [ arrowhead=box, arrowtail=box]
7 -> 1 [ arrowhead=box, arrowtail=box]
4 -> 7 [ arrowhead=box, arrowtail=box]
3 -> 4 [ arrowhead=box, arrowtail=box]
5 -> 7 [ arrowhead=box, arrowtail=box]
3 -> 5 [ arrowhead=box, arrowtail=box]
8 -> 2 [ arrowhead=box, arrowtail=box]
8 -> 3 [ arrowhead=box, arrowtail=box]
Expand All @@ -36,11 +36,11 @@ digraph {
10 -> 2 [ arrowhead=box, arrowtail=box]
10 -> 3 [ arrowhead=box, arrowtail=box]
11 -> 2 [ arrowhead=box, arrowtail=box]
11 -> 4 [ arrowhead=box, arrowtail=box]
11 -> 5 [ arrowhead=box, arrowtail=box]
12 -> 2 [ arrowhead=box, arrowtail=box]
12 -> 6 [ arrowhead=box, arrowtail=box]
13 -> 2 [ arrowhead=box, arrowtail=box]
13 -> 5 [ arrowhead=box, arrowtail=box]
13 -> 4 [ arrowhead=box, arrowtail=box]
11 -> 12 [ arrowhead=box, arrowtail=box]
11 -> 13 [ arrowhead=box, arrowtail=box]
10 -> 11 [ arrowhead=box, arrowtail=box]
Expand Down
29 changes: 29 additions & 0 deletions nextgen/action-graph/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[package]
name = "moon_action_graph"
version = "0.1.0"
edition = "2021"
license = "MIT"
description = "Dependency graph for actions (tasks)."
homepage = "https://moonrepo.dev/moon"
repository = "https://github.com/moonrepo/moon"

[dependencies]
moon_common = { path = "../common" }
# TODO remove
moon_platform = { path = "../../crates/core/platform" }
moon_platform_runtime = { path = "../platform-runtime" }
moon_project = { path = "../project" }
moon_project_graph = { path = "../project-graph" }
moon_task = { path = "../task" }
moon_query = { path = "../query" }
miette = { workspace = true }
petgraph = { workspace = true }
rustc-hash = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }

[dev-dependencies]
moon_config = { path = "../config" }
moon_test_utils2 = { path = "../test-utils" }
starbase_sandbox = { workspace = true }
tokio = { workspace = true }
151 changes: 151 additions & 0 deletions nextgen/action-graph/src/action_graph.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
use crate::action_graph_error::ActionGraphError;
use crate::action_node::ActionNode;
use moon_common::is_test_env;
use petgraph::dot::{Config, Dot};
use petgraph::prelude::*;
use petgraph::visit::{IntoEdgeReferences, IntoNodeReferences};
use rustc_hash::{FxHashMap, FxHashSet};
use std::collections::VecDeque;

pub type GraphType = DiGraph<ActionNode, ()>;
pub type IndicesMap = FxHashMap<ActionNode, NodeIndex>;

pub struct ActionGraph {
graph: GraphType,
indices: IndicesMap,

// States when iterating
queue: VecDeque<NodeIndex>,
visited: FxHashSet<NodeIndex>,
}

impl ActionGraph {
pub fn new(graph: GraphType, indices: IndicesMap) -> Self {
ActionGraph {
graph,
indices,
queue: VecDeque::default(),
visited: FxHashSet::default(),
}
}

pub fn reset_iterator(&mut self) -> miette::Result<()> {
self.detect_cycle()?;

self.queue.clear();
self.visited.clear();

// Extract root/initial nodes (those without edges)
self.queue.extend(self.graph.node_indices().filter(|&idx| {
self.graph
.neighbors_directed(idx, Outgoing)
.next()
.is_none()
}));

Ok(())
}

pub fn is_empty(&self) -> bool {
self.get_node_count() == 0
}

pub fn get_index_from_node(&self, node: &ActionNode) -> Option<&NodeIndex> {
self.indices.get(node)
}

pub fn get_node_count(&self) -> usize {
self.graph.node_count()
}

pub fn get_node_from_index(&self, index: &NodeIndex) -> Option<&ActionNode> {
self.graph.node_weight(*index)
}

pub fn to_dot(&self) -> String {
type DotGraph = DiGraph<String, ()>;

let is_test = is_test_env() || cfg!(debug_assertions);
let graph = self.graph.map(|_, n| n.label(), |_, _| ());

let edge = |_: &DotGraph, e: <&DotGraph as IntoEdgeReferences>::EdgeRef| {
if is_test {
String::new()
} else if e.source().index() == 0 {
String::from("arrowhead=none")
} else {
String::from("arrowhead=box, arrowtail=box")
}
};

let node = |_: &DotGraph, n: <&DotGraph as IntoNodeReferences>::NodeRef| {
if is_test {
format!("label=\"{}\" ", n.1)
} else {
format!(
"label=\"{}\" style=filled, shape=oval, fillcolor=gray, fontcolor=black ",
n.1
)
}
};

let dot = Dot::with_attr_getters(
&graph,
&[Config::EdgeNoLabel, Config::NodeNoLabel],
&edge,
&node,
);

format!("{dot:?}")
}

pub fn to_labeled_graph(&self) -> DiGraph<String, String> {
self.graph.map(|_, n| n.label(), |_, _| String::new())
}

fn detect_cycle(&self) -> miette::Result<()> {
if self.get_node_count() > 1 {
if let Err(cycle) = petgraph::algo::toposort(&self.graph, None) {
return Err(ActionGraphError::CycleDetected(
self.get_node_from_index(&cycle.node_id())
.map(|n| n.label())
.unwrap_or_else(|| "(unknown)".into()),
)
.into());
}
}

Ok(())
}
}

// This is based on the `Topo` struct from petgraph!
impl Iterator for ActionGraph {
type Item = ActionNode;

fn next(&mut self) -> Option<Self::Item> {
while let Some(idx) = self.queue.pop_front() {
if self.visited.contains(&idx) {
continue;
}

self.visited.insert(idx);

for neighbor in self.graph.neighbors_directed(idx, Direction::Incoming) {
// Look at each neighbor, and those that only have incoming edges
// from the already ordered list, they are the next to visit.
if self
.graph
.neighbors_directed(neighbor, Direction::Outgoing)
.all(|b| self.visited.contains(&b))
{
self.queue.push_back(neighbor);
}
}

return self.graph.node_weight(idx).map(|n| n.to_owned());
}

None
}
}
Loading

0 comments on commit 216ccc7

Please sign in to comment.