Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new: Implement a new action graph. #1093

Merged
merged 22 commits into from
Oct 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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