Skip to content

Commit

Permalink
[honk] Add graph resolution stage.
Browse files Browse the repository at this point in the history
  • Loading branch information
anp committed Apr 4, 2021
1 parent b677486 commit 638886e
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 33 deletions.
3 changes: 3 additions & 0 deletions honk/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ pub enum Error {
connected component, found {num_components} components"
)]
GraphIsSplit { num_components: usize },

#[error("couldn't find {target} in the dependency graph")]
GraphResolutionFailure { target: String },
}

impl From<Error> for ValueError {
Expand Down
134 changes: 101 additions & 33 deletions honk/src/revision.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ impl Revision {
self.inner.lock().register_target(name, command);
}

pub fn resolve(&self) -> crate::Result<BuildGraph> {
pub fn resolve(&self) -> crate::Result<ActionGraph> {
self.inner.lock().resolve()
}
}
Expand All @@ -51,8 +51,8 @@ impl RevisionState {
self.targets.insert(name.to_owned(), command.clone());
}

fn resolve(&mut self) -> crate::Result<BuildGraph> {
let mut graph = BuildGraph::new();
fn resolve(&mut self) -> crate::Result<ActionGraph> {
let mut graph = GraphBuilder::new();

for (name, formatter) in &self.formatters {
let idx = graph.command(name, formatter);
Expand All @@ -64,38 +64,50 @@ impl RevisionState {
graph.dep(idx, graph.formatted());
}

let num_components = petgraph::algo::connected_components(&graph.inner);
if num_components != 1 {
Err(Error::GraphIsSplit { num_components })
} else if petgraph::algo::is_cyclic_directed(&graph.inner) {
Err(Error::GraphContainsCycles)
} else {
Ok(graph)
}
graph.build()
}
}

pub struct BuildGraph {
inner: DiGraph<Arc<Node>, ()>,
pub type ActionGraph = DiGraph<Action, i32>;
pub type DepGraph = DiGraph<Arc<Node>, i32>;

pub struct GraphBuilder {
inner: DepGraph,
indices: HashMap<Arc<Node>, NodeIndex>,
formatted: Arc<Node>,
pending_target_name_deps: HashMap<String, Vec<NodeIndex>>,
target_name_to_idx: HashMap<String, NodeIndex>,
}

impl BuildGraph {
impl GraphBuilder {
fn new() -> Self {
let mut inner = DiGraph::default();
let mut inner = DepGraph::default();
let mut indices = HashMap::default();
let formatted = Arc::new(Node::Formatted);
indices.insert(formatted.clone(), inner.add_node(formatted.clone()));
Self { inner, indices, formatted }
Self {
inner,
indices,
formatted,
pending_target_name_deps: Default::default(),
target_name_to_idx: Default::default(),
}
}

fn command(&mut self, name: &str, command: &HonkCommand) -> NodeIndex {
let idx = self.action(name, &command.command, &command.args[..]);

for input in &command.inputs {
let input = self.file(input);
self.dep(idx, input);
todo!()
// match input {
// Input::File(f) => {
// let input = self.file(f);
// self.dep(idx, input);
// }
// Input::Target(t) => {
// self.pending_target_name_deps.entry(t.to_string()).or_default().push(idx)
// }
// }
}

for output in &command.outputs {
Expand All @@ -114,46 +126,102 @@ impl BuildGraph {
}

fn action(&mut self, name: &str, command: &str, args: &[String]) -> NodeIndex {
let Self { inner, indices, .. } = self;
let Self { inner, indices, target_name_to_idx, .. } = self;
// TODO less allocating?
let args = args.iter().map(|a| a.to_string()).collect();
let node =
Arc::new(Node::Action { name: name.to_owned(), command: command.to_owned(), args });
*indices.entry(node.clone()).or_insert_with(|| inner.add_node(node))
let node = Arc::new(Node::Action(Action {
name: name.to_owned(),
command: command.to_owned(),
args,
}));
*indices.entry(node.clone()).or_insert_with(|| {
let idx = inner.add_node(node);
target_name_to_idx.insert(name.to_owned(), idx);
idx
})
}

fn formatted(&self) -> NodeIndex {
self.indices[&self.formatted]
}

fn dep(&mut self, from: NodeIndex, to: NodeIndex) {
self.inner.update_edge(from, to, ());
self.inner.update_edge(from, to, 0);
}
}

impl std::fmt::Debug for BuildGraph {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use petgraph::dot::{Config, Dot};
Dot::with_config(&self.inner, &[Config::EdgeNoLabel]).fmt(f)
fn build(mut self) -> crate::Result<ActionGraph> {
self.drain_pending()?;
let graph = rewrite_file_edges(self.inner);

let num_components = petgraph::algo::connected_components(&graph);
if num_components != 1 {
Err(Error::GraphIsSplit { num_components })
} else if petgraph::algo::is_cyclic_directed(&graph) {
Err(Error::GraphContainsCycles)
} else {
Ok(graph)
}
}

fn drain_pending(&mut self) -> crate::Result<()> {
for (target, deps) in self.pending_target_name_deps.drain().collect::<Vec<_>>() {
let target = if let Some(t) = self.target_name_to_idx.get(&target) {
*t
} else {
return Err(Error::GraphResolutionFailure { target });
};
for dep in deps {
self.dep(dep, target);
}
}

Ok(())
}
}

#[derive(Eq, Hash, PartialEq)]
enum Node {
fn rewrite_file_edges(deps: DepGraph) -> ActionGraph {
let mut actions = ActionGraph::new();

// FIXME this leaves an empty graph lol
// graph.retain_nodes(|this, idx| !this[idx].is_file());
actions
}

#[derive(Debug, Eq, Hash, PartialEq)]
pub enum Node {
/// A special node used to schedule all formatters before anything that relies on their output.
Formatted,
/// A file in the build graph.
File(HonkPath),
/// A command to run in the build graph.
Action { name: String, command: String, args: Vec<String> },
Action(Action),
}

impl std::fmt::Debug for Node {
impl Node {
fn is_file(&self) -> bool {
matches!(self, Self::File(..))
}
}

impl std::fmt::Display for Node {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Formatted => "FORMATTER BARRIER".fmt(f),
Self::File(p) => write!(f, "file:{}", p),
Self::Action { name, .. } => write!(f, "action:{}", name),
Self::Action(a) => write!(f, "{}", a),
}
}
}

#[derive(Debug, Eq, Hash, PartialEq)]
pub struct Action {
name: String,
command: String,
args: Vec<String>,
}

impl std::fmt::Display for Action {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "action:{}", &self.name)
}
}

0 comments on commit 638886e

Please sign in to comment.