-
-
Notifications
You must be signed in to change notification settings - Fork 163
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
new: Implement a new action graph. (#1093)
* 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
Showing
79 changed files
with
2,786 additions
and
302 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
Oops, something went wrong.