51def traverse(graph, root, reverse=False, maxdepth=None, nodegetter=lambda n:n):
52 """Depth-limited BFS edge traversal of graph starting at root.
55 @param root start node for traversal
56 @param reverse traverse graph in reverse
57 @param maxdepth maximum traversal depth (1 = only direct neighbors)
58 @param nodegetter functor returning node names
59 @return edge tuple (parent,node)
61 Inspired by https://github.com/networkx/networkx/tree/master/networkx/algorithms/traversal
65 queue = deque([(root,root,0)])
66 neighbors = graph.iterpred
if reverse
else graph.itersucc
68 parent,node,level = queue.popleft()
70 if node
not in visited_nodes:
71 visited_nodes.add(node)
74 if maxdepth
is None or level < maxdepth:
75 queue.extend((node,n,level+1)
for n
in neighbors(node))
78 queue.extend((node,n,level+1)
for n
in neighbors(node)
if n
in visited_nodes)
80 if (parent,node)
not in visited_edges:
81 visited_edges.add((parent,node))
82 yield nodegetter(parent), nodegetter(node)
85def subgraph(graph, sources, reverse=False, maxdepth=None, nodegetter=lambda n : n.attr.get(
'label')):
86 """Extract subgraph created by traversing from one or more sources.
87 Parameters are the same as in `traverse`. Return list of edge tuples.
91 for a,b
in traverse(graph, root, reverse=reverse, maxdepth=maxdepth, nodegetter=nodegetter):
93 if reverse: edges.add((b,a))
94 else: edges.add((a,b))
172 """Create and return dependency graph.
174 @param target name of target
175 @param deps AthGraph cmake dependencies
176 @param pydeps python dependencies
177 @param args command line arguments
181 if not args.all
and deps.ignore_target(node):
return None
182 if args.externals
or not node.attr[
'external']:
183 a =
'label' if args.target
else 'package'
186 target = target.split(
'/')[-1]
189 depth = args.recursive
190 if not args.target
and not args.clients
and args.recursive
is not None:
195 r = re.compile(target)
196 targets = [getnode(n)
for n
in deps.graph.nodes_iter()
if r.match(n.attr[
'label'])]
198 targets += [n
for n
in pydeps.nodes_iter()
if r.match(n)]
199 targets = sorted(
set(filter(
lambda t : t
is not None, targets)))
209 if deps.get_node(l).attr[
'external']
and not args.externals:
210 raise RuntimeError(f
"{l} is an external target. Run with -e/--externals.")
214 if args.clients
and not args.target:
215 sources.extend([b
for a,b
in traverse(deps.graph, deps.get_node(l), maxdepth=1)])
217 sources.extend([deps.get_node(l)])
219 raise RuntimeError(f
"Target with name {l} does not exist.")
222 g =
subgraph(deps.graph, sources, reverse=args.clients,
223 maxdepth=depth, nodegetter=getnode)
225 graph = pygraphviz.AGraph(name=target, directed=
True, strict=
False)
226 graph.add_edges_from(g)
231 pysources = [pydeps.get_node(t)
for t
in targets
if pydeps.has_node(t)]
232 g =
subgraph(pydeps, pysources, reverse=args.clients,
233 maxdepth=args.recursive, nodegetter=
lambda n : n.name)
235 graph.add_edges_from(g, style=py_style)
238 for n
in graph.nodes_iter():
239 if all(e.attr[
'style']==py_style
for e
in graph.edges_iter(n)):
240 n.attr[
'style'] = py_style
246 """Output final graph"""
249 if args.batch
or not args.dot:
250 f = open(graph.name+
'.txt',
'w')
if args.batch
else sys.stdout
251 nodes = [e[0]
for e
in graph.in_edges_iter()]
if args.clients \
252 else [e[1]
for e
in graph.out_edges_iter()]
256 suffix =
':py' if p.attr[
'style']==py_style
else ''
257 output.append(
'%s%s' % (package_paths.get(p,p), suffix))
258 print(
'\n'.join(sorted(output)), file=f)
261 if args.batch
or args.dot:
262 f = open(graph.name+
'.dot',
'w')
if args.batch
else sys.stdout
271@acmdlib.command(name=
'cmake.depends',
274@acmdlib.argument(
'names', nargs=
'+', metavar=
'NAME',
275 help=
'package/target name or regular expression')
277@acmdlib.argument(
'-t',
'--target', action=
'store_true',
278 help=
'treat NAME as target instead of package name')
280@acmdlib.argument(
'-c',
'--clients', action=
'store_true',
281 help=
'show clients (instead of dependencies)')
283@acmdlib.argument(
'-e',
'--externals', action=
'store_true',
284 help=
'include external dependencies')
286@acmdlib.argument(
'-l',
'--long', action=
'store_true',
287 help=
'show full package names (only for txt output)')
289@acmdlib.argument(
'-r',
'--recursive', nargs=
'?', metavar=
'DEPTH',
290 type=int, default=1, const=
None,
291 help=
'recursively resolve dependencies up to DEPTH (default: unlimited)')
293@acmdlib.argument(
'--py', action=
'store_true',
294 help=f
'add Python dependencies (marked with ":py" in printout, {py_style} in graph)')
296@acmdlib.argument(
'--regex', action=
'store_true',
297 help=
'treat NAME as regular expression')
299@acmdlib.argument(
'--all', action=
'store_true',
300 help=
'do not apply any target filter (e.g. custom targets)')
302@acmdlib.argument(
'-d',
'--dot', action=
'store_true',
303 help=
'print DOT graph')
305@acmdlib.argument(
'--legend', action=
'store_true',
306 help=
'add legend to graph')
308@acmdlib.argument(
'--batch', nargs=
'?', metavar=
'N', type=int, const=1,
309 help=
'Batch mode using N jobs (default: 1). Create dot and txt dependencies '
310 'for all NAMEs and store them in separate files.')
314@acmdlib.argument('--cmakedot', help=argparse.SUPPRESS)
315@acmdlib.argument('--pydot', help=argparse.SUPPRESS)
319 """Inspect cmake build dependencies"""
322 if not args.cmakedot:
324 args.cmakedot = os.path.join(os.environ[
'AtlasArea'],
'InstallArea',
325 os.environ[
'BINARY_TAG'],
'packages.dot')
327 main.parser.error(
"Cannot find 'packages.dot'. Setup a release or use --cmakedot.")
333 main.parser.error(
"Python dependencies not possible in target mode.")
335 args.pydot = args.pydot
or args.cmakedot.replace(
'.dot',
'.py.dot')
337 pydeps = pygraphviz.AGraph(args.pydot)
339 main.parser.error(f
"Cannot read '{args.pydot}'. Setup a release or use --pydot.")
345 package_paths =
read_package_list(os.path.join(os.environ[
'AtlasArea'],
'InstallArea',
346 os.environ[
'BINARY_TAG'],
'packages.txt'))
348 main.parser.error(
"Cannot read 'packages.txt'. Setup a release or run without -l/--long.")
355 subgraphs = [
create_dep_graph(target, deps, pydeps, args)
for target
in args.names]
357 graph = pygraphviz.AGraph(name=
'AthGraph', directed=
True, strict=
False)
359 graph.add_subgraph(name=g.get_name())
368 import multiprocessing
374 pool = multiprocessing.Pool(args.batch)
375 pool.map(doit, args.names)