use std::collections::{BTreeMap, BTreeSet};
use camino::{Utf8Path, Utf8PathBuf};
use hashbrown::HashMap;
use crate::ir::{BuildEdge, BuildGraph};
pub mod render;
pub mod render_dot;
pub mod render_html;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GraphView {
pub default_targets: Vec<Utf8PathBuf>,
pub nodes: Vec<NodeView>,
pub edges: Vec<EdgeView>,
pub limit: Option<usize>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NodeView {
pub path: Utf8PathBuf,
pub kind: NodeKind,
pub action_id: Option<String>,
pub description: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NodeKind {
Source,
Target {
phony: bool,
always: bool,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EdgeView {
pub from: Utf8PathBuf,
pub to: Utf8PathBuf,
pub class: EdgeClass,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum EdgeClass {
Explicit,
ImplicitDep,
ImplicitOutput,
OrderOnly,
}
impl GraphView {
#[must_use]
pub fn from_build_graph(graph: &BuildGraph) -> Self {
let edges_seen = collect_unique_edges(graph);
let mut registry = NodePathRegistry::default();
let mut node_metadata: BTreeMap<Utf8PathBuf, NodeMetadata> = BTreeMap::new();
let mut edges: BTreeSet<EdgeView> = BTreeSet::new();
for edge in &edges_seen {
register_outputs(graph, edge, &mut registry, &mut node_metadata);
EdgeRegistrar {
edge,
registry: &mut registry,
edges: &mut edges,
}
.register();
}
let nodes = registry
.into_inner()
.into_iter()
.map(|(path, kind)| {
let meta = node_metadata.remove(&path).unwrap_or_default();
NodeView {
path,
kind,
action_id: meta.action_id,
description: meta.description,
}
})
.collect();
let mut default_targets = graph.default_targets.clone();
default_targets.sort();
default_targets.dedup();
Self {
default_targets,
nodes,
edges: edges.into_iter().collect(),
limit: None,
}
}
}
#[derive(Debug, Default, Clone)]
struct NodeMetadata {
action_id: Option<String>,
description: Option<String>,
}
#[derive(Debug, Default)]
struct NodePathRegistry {
paths: HashMap<Utf8PathBuf, NodeKind>,
}
impl NodePathRegistry {
fn ensure_node_mut(&mut self, path: &Utf8Path) -> &mut NodeKind {
self.paths.entry_ref(path).or_insert(NodeKind::Source)
}
fn insert_target(&mut self, path: &Utf8Path, kind: NodeKind) {
self.paths.insert(path.to_owned(), kind);
}
fn into_inner(self) -> BTreeMap<Utf8PathBuf, NodeKind> {
self.paths.into_iter().collect()
}
}
fn collect_unique_edges(graph: &BuildGraph) -> Vec<BuildEdge> {
let mut by_key: BTreeMap<Vec<Utf8PathBuf>, BuildEdge> = BTreeMap::new();
for edge in graph.targets.values() {
let mut key = edge.explicit_outputs.clone();
key.sort();
by_key.entry(key).or_insert_with(|| edge.clone());
}
by_key.into_values().collect()
}
fn register_outputs(
graph: &BuildGraph,
edge: &BuildEdge,
registry: &mut NodePathRegistry,
node_metadata: &mut BTreeMap<Utf8PathBuf, NodeMetadata>,
) {
let description = graph
.actions
.get(&edge.action_id)
.and_then(|action| action.description.clone());
for out in all_outputs(edge) {
registry.insert_target(
out,
NodeKind::Target {
phony: edge.phony,
always: edge.always,
},
);
node_metadata.insert(
out.clone(),
NodeMetadata {
action_id: Some(edge.action_id.clone()),
description: description.clone(),
},
);
}
}
fn all_outputs(edge: &BuildEdge) -> impl Iterator<Item = &Utf8PathBuf> {
edge.explicit_outputs
.iter()
.chain(edge.implicit_outputs.iter())
}
struct EdgeRegistrar<'a> {
edge: &'a BuildEdge,
registry: &'a mut NodePathRegistry,
edges: &'a mut BTreeSet<EdgeView>,
}
impl EdgeRegistrar<'_> {
fn register(&mut self) {
self.register_inputs();
let edge = self.edge;
self.register_dependencies(&edge.implicit_deps, EdgeClass::ImplicitDep);
self.register_dependencies(&edge.order_only_deps, EdgeClass::OrderOnly);
}
fn register_inputs(&mut self) {
let edge = self.edge;
let implicit: BTreeSet<&Utf8PathBuf> = edge.implicit_outputs.iter().collect();
for input in &edge.inputs {
self.registry.ensure_node_mut(input);
self.register_input_edges(input, &implicit);
}
}
fn register_input_edges(&mut self, input: &Utf8PathBuf, implicit: &BTreeSet<&Utf8PathBuf>) {
self.insert_edges(input, |out| {
if implicit.contains(out) {
EdgeClass::ImplicitOutput
} else {
EdgeClass::Explicit
}
});
}
fn register_dependencies(&mut self, deps: &[Utf8PathBuf], class: EdgeClass) {
for dep in deps {
self.registry.ensure_node_mut(dep);
self.insert_edges(dep, |_| class);
}
}
fn insert_edges(&mut self, from: &Utf8PathBuf, class_for: impl Fn(&Utf8PathBuf) -> EdgeClass) {
for out in all_outputs(self.edge) {
self.edges.insert(EdgeView {
from: from.clone(),
to: out.clone(),
class: class_for(out),
});
}
}
}
impl Ord for EdgeView {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
(&self.from, &self.to, self.class).cmp(&(&other.from, &other.to, other.class))
}
}
impl PartialOrd for EdgeView {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
#[cfg(test)]
mod tests;