I'd like to generate a simple DOT file when building a C++ project with waf. Ideally I'd like to just use the use
and target
attributes of the bld
command to generate the file. Is this easily injectable into the system?
e.g. This wscript file (just mentioning the parts I'd like to use)
def build(bld):
bld( use = [ 'lib1',
'lib2', ] ,
target = 'lib3' )
Would produce output of
lib3 -> lib1
lib3 -> lib2
Where would be the best place to inject this behavior?
Thanks!
You can add add a tool like this easily via the add_post_fun
in the build step, something along like this:
from waflib.Errors import WafError
from waflib import Utils
def filter_uses(ctx, uses):
filtered = []
for use in uses:
try:
ctx.get_tgen_by_name(use)
filtered.append(use)
except WafError:
pass
return filtered
@Utils.run_once # print only once, even if used in multiple script
def make_dot_file(ctx):
for group in ctx.groups:
for taskgen in group:
uses = Utils.to_list(getattr(taskgen, 'use', []))
uses = filter_uses(ctx, uses) # Optional, only print TaskGens
try:
name = taskgen.name # Sometimes this fails, don't know why
print "{} -> {}".format(name, ", ".join(uses))
except AttributeError:
pass
def build(bld):
# Build stuff ...
bld.add_post_fun(make_dot_file)
Note: To get real nice output some more filtering might be useful
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 -----------------------------
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With