I improved and adjusted @CK1 idea to my needs. My solution generates a DAG with graphviz
and uses helper functions from this article by Matthias Eisen to display dependencies and targets.
The main part of the code looks like this:
import functools
import graphviz as gv
from pathlib import Path
from waflib import Utils
# Make sure that dot.exe is in your system path. I had to do this as
# Graphviz (the program, not the package) is installed with conda. I am
# sure there is a proper way to do this with Waf.
library_bin = Path(sys.executable).parent / 'Library' / 'bin' / 'graphviz'
os.environ['PATH'] += str(library_bin) + ';'
def make_dot_file(ctx):
# Create DAG
dag = digraph()
# Loop over task groups
for group in ctx.groups:
# Loop over tasks
for taskgen in group:
# Get name and add node for task
name = taskgen.get_name()
add_nodes(dag, [name])
# Add nodes for dependencies and edges to task
deps = Utils.to_list(getattr(taskgen, 'deps', []))
for dep in deps:
dep = Path(dep).name
add_nodes(dag, [dep])
add_edges(dag, [(dep, name)])
# Add nodes for targets and edges to task
targets = Utils.to_list(getattr(taskgen, 'target', []))
for target in targets:
target = Path(target).name
add_nodes(dag, [target])
add_edges(dag, [(name, target)])
# Make the DAG pretty
dag = apply_styles(dag, styles)
# Save DAG
dag.render(<output path of graphic>)
def build(bld):
# Build stuff ...
bld.add_post_fun(make_dot_file)
The helper functions used for this example are here:
# -------------------- Start helper functions ----------------------------
graph = functools.partial(gv.Graph, format='png')
digraph = functools.partial(gv.Digraph, format='png')
styles = {
'graph': {
'label': 'Pretty Graph',
'fontsize': '16',
'fontcolor': 'white',
'bgcolor': '#333333',
'rankdir': 'BT',
},
'nodes': {
'fontname': 'Helvetica',
'shape': 'hexagon',
'fontcolor': 'white',
'color': 'white',
'style': 'filled',
'fillcolor': '#006699',
},
'edges': {
'style': 'dashed',
'color': 'white',
'arrowhead': 'open',
'fontname': 'Courier',
'fontsize': '12',
'fontcolor': 'white',
}
}
def apply_styles(graph, styles):
graph.graph_attr.update(
('graph' in styles and styles['graph']) or {}
)
graph.node_attr.update(
('nodes' in styles and styles['nodes']) or {}
)
graph.edge_attr.update(
('edges' in styles and styles['edges']) or {}
)
return graph
def add_nodes(graph, nodes):
for n in nodes:
if isinstance(n, tuple):
graph.node(n[0], **n[1])
else:
graph.node(n)
return graph
def add_edges(graph, edges):
for e in edges:
if isinstance(e[0], tuple):
graph.edge(*e[0], **e[1])
else:
graph.edge(*e)
return graph
# ----------------------- End helper functions -----------------------------