7 Print target/package dependencies of ATLAS releases. For a given target/package 
    8 name, the dependencies are printed as a plain list or DOT graph. The recursion 
   15 from collections 
import deque
 
   16 import PyUtils.acmdlib 
as acmdlib
 
   24    """Read packages.txt as a source for the full package path""" 
   26    with open(package_file) 
as f:
 
   27       packages = [line.rstrip() 
for line 
in f 
if not line.startswith(
'#')]
 
   29    return dict([(p.split(
'/')[-1],p) 
for p 
in packages])
 
   33    """Return a short name for an external library""" 
   36       lcg = 
next(d 
for d 
in dirs 
if d.startswith(
'LCG_'))
 
   37       return '%s::%s' % (dirs[dirs.index(lcg)+1], dirs[-1])
 
   38    elif lib.startswith(
'Gaudi'):
 
   39       return 'Gaudi::%s' % lib
 
   41       return os.path.basename(lib)
 
   45    """Strip `prefix` and `postfix` from string `s`""" 
   46    if s.startswith(prefix): s = s[len(prefix):]
 
   47    if s.endswith(postfix):  s = s[:-len(postfix)]
 
   51 def 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)
 
   85 def 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))
 
  100    """Add legend to graph""" 
  101    graph.add_subgraph(name=
'clusterLegend', label=
'Legend')
 
  102    l = graph.subgraphs()[-1]
 
  104       l.add_node(n, shape=
'point', style=
'invis')
 
  105    l.add_edge(
'a',
'b', label=
'C++', constraint=
False)
 
  106    l.add_edge(
'c',
'd', label=
'Python', style=py_style, constraint=
False)
 
  110    """Copy graph nodes and edges from source to dest including attributes""" 
  111    for e 
in source.edges_iter():
 
  112       dest.add_edge(e, **e.attr)
 
  113    for n 
in source.nodes_iter():
 
  114       dest.add_node(n, **n.attr)
 
  118    """Class to hold dependency information for release""" 
  121       """Read dotfile and and optionally transform package names to full paths""" 
  124       self.
graph = pygraphviz.AGraph(dotfile)
 
  127       legend = self.
graph.get_subgraph(
'clusterLegend')
 
  128       self.
types = { n.attr[
'label'] : n.attr[
'shape'] 
for n 
in legend.nodes_iter() }
 
  131       self.
node = { n.attr[
'label'] : n.get_name() 
for n 
in self.
graph.nodes_iter() }
 
  133       def decorate_package(n0, n1=None):
 
  134          """Assign package name to n0 -> n1 if n0 is a package target""" 
  137          if p.startswith(
'Package_'):
 
  138             n0.attr[
'package'] = 
lrstrip(p, 
'Package_', 
'_tests')
 
  140                n1.attr[
'package'] = n0.attr[
'package']
 
  143       for e 
in self.
graph.edges_iter():
 
  144          decorate_package(e[0], e[1])
 
  147       for n 
in self.
graph.nodes_iter():
 
  151       external_nodes = 
filter(
lambda n : 
'package' not in n.attr.keys(),
 
  152                               self.
graph.nodes_iter())
 
  153       for n 
in external_nodes:
 
  155          n.attr[
'package'] = name.split(
'::')[0]
 
  156          n.attr[
'label'] = name
 
  157          n.attr[
'external'] = 
'yes' 
  160       """Return graph node for label/target""" 
  164       """Check if target should be ignored""" 
  165       label = node.attr[
'label']
 
  166       return True if (label.startswith(
'__') 
or    
  167                       label.startswith(
'-') 
or     
  168                       node.attr[
'shape']==self.
types[
'Custom Target']) 
else False 
  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)]
 
  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))
 
  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)
 
  381    except RuntimeError 
as e: