Improvements to the Spack graph command.
- Distinguish between static (package) and dynamic (spec) graphs. - static graphs ignore conditions and multiple instances (hashes) and plot raw dependencies among packages. - dynamic graphs include information from particular specs (instances of packages) and can have multiple instances with hashes. - Allow graphing all packages in the install DB. - useful for debugging.
This commit is contained in:
parent
43ca805248
commit
0d3d74e5c2
3 changed files with 103 additions and 29 deletions
|
@ -24,8 +24,11 @@
|
|||
##############################################################################
|
||||
import argparse
|
||||
|
||||
import llnl.util.tty as tty
|
||||
|
||||
import spack
|
||||
import spack.cmd
|
||||
from spack.spec import *
|
||||
from spack.graph import *
|
||||
|
||||
description = "Generate graphs of package dependency relationships."
|
||||
|
@ -43,8 +46,21 @@ def setup_parser(subparser):
|
|||
help="Generate graph in dot format and print to stdout.")
|
||||
|
||||
subparser.add_argument(
|
||||
'--concretize', action='store_true',
|
||||
help="Concretize specs before graphing.")
|
||||
'--normalize', action='store_true',
|
||||
help="Skip concretization; only print normalized spec.")
|
||||
|
||||
subparser.add_argument(
|
||||
'-s', '--static', action='store_true',
|
||||
help="Use static information from packages, not dynamic spec info.")
|
||||
|
||||
subparser.add_argument(
|
||||
'-i', '--installed', action='store_true',
|
||||
help="Graph all installed specs in dot format (implies --dot).")
|
||||
|
||||
subparser.add_argument(
|
||||
'-t', '--deptype', action='store',
|
||||
help="Comma-separated list of deptypes to traverse. default=%s."
|
||||
% ','.join(alldeps))
|
||||
|
||||
subparser.add_argument(
|
||||
'specs', nargs=argparse.REMAINDER,
|
||||
|
@ -52,15 +68,29 @@ def setup_parser(subparser):
|
|||
|
||||
|
||||
def graph(parser, args):
|
||||
specs = spack.cmd.parse_specs(
|
||||
args.specs, normalize=True, concretize=args.concretize)
|
||||
concretize = not args.normalize
|
||||
if args.installed:
|
||||
if args.specs:
|
||||
tty.die("Can't specify specs with --installed")
|
||||
args.dot = True
|
||||
specs = spack.installed_db.query()
|
||||
|
||||
else:
|
||||
specs = spack.cmd.parse_specs(
|
||||
args.specs, normalize=True, concretize=concretize)
|
||||
|
||||
if not specs:
|
||||
setup_parser.parser.print_help()
|
||||
return 1
|
||||
|
||||
deptype = alldeps
|
||||
if args.deptype:
|
||||
deptype = tuple(args.deptype.split(','))
|
||||
validate_deptype(deptype)
|
||||
deptype = canonical_deptype(deptype)
|
||||
|
||||
if args.dot: # Dot graph only if asked for.
|
||||
graph_dot(*specs)
|
||||
graph_dot(specs, static=args.static, deptype=deptype)
|
||||
|
||||
elif specs: # ascii is default: user doesn't need to provide it explicitly
|
||||
graph_ascii(specs[0], debug=spack.debug)
|
||||
|
|
|
@ -67,8 +67,7 @@
|
|||
from llnl.util.lang import *
|
||||
from llnl.util.tty.color import *
|
||||
|
||||
import spack
|
||||
from spack.spec import Spec
|
||||
from spack.spec import *
|
||||
|
||||
__all__ = ['topological_sort', 'graph_ascii', 'AsciiGraph', 'graph_dot']
|
||||
|
||||
|
@ -501,7 +500,7 @@ def graph_ascii(spec, **kwargs):
|
|||
graph.write(spec, color=color, out=out)
|
||||
|
||||
|
||||
def graph_dot(*specs, **kwargs):
|
||||
def graph_dot(specs, deptype=None, static=False, out=None):
|
||||
"""Generate a graph in dot format of all provided specs.
|
||||
|
||||
Print out a dot formatted graph of all the dependencies between
|
||||
|
@ -510,42 +509,73 @@ def graph_dot(*specs, **kwargs):
|
|||
spack graph --dot qt | dot -Tpdf > spack-graph.pdf
|
||||
|
||||
"""
|
||||
out = kwargs.pop('out', sys.stdout)
|
||||
check_kwargs(kwargs, graph_dot)
|
||||
if out is None:
|
||||
out = sys.stdout
|
||||
|
||||
if deptype is None:
|
||||
deptype = alldeps
|
||||
|
||||
out.write('digraph G {\n')
|
||||
out.write(' label = "Spack Dependencies"\n')
|
||||
out.write(' labelloc = "b"\n')
|
||||
out.write(' rankdir = "LR"\n')
|
||||
out.write(' ranksep = "5"\n')
|
||||
out.write('node[\n')
|
||||
out.write(' fontname=Monaco,\n')
|
||||
out.write(' penwidth=2,\n')
|
||||
out.write(' fontsize=12,\n')
|
||||
out.write(' margin=.1,\n')
|
||||
out.write(' shape=box,\n')
|
||||
out.write(' fillcolor=lightblue,\n')
|
||||
out.write(' style="rounded,filled"]\n')
|
||||
|
||||
out.write('\n')
|
||||
|
||||
def quote(string):
|
||||
def q(string):
|
||||
return '"%s"' % string
|
||||
|
||||
if not specs:
|
||||
specs = [p.name for p in spack.repo.all_packages()]
|
||||
else:
|
||||
roots = specs
|
||||
specs = set()
|
||||
for spec in roots:
|
||||
specs.update(Spec(s.name) for s in spec.normalized().traverse())
|
||||
raise ValueError("Must provide specs ot graph_dot")
|
||||
|
||||
deps = []
|
||||
# Static graph includes anything a package COULD depend on.
|
||||
if static:
|
||||
names = set.union(*[s.package.possible_dependencies() for s in specs])
|
||||
specs = [Spec(name) for name in names]
|
||||
|
||||
labeled = set()
|
||||
|
||||
def label(key, label):
|
||||
if key not in labeled:
|
||||
out.write(' "%s" [label="%s"]\n' % (key, label))
|
||||
labeled.add(key)
|
||||
|
||||
deps = set()
|
||||
for spec in specs:
|
||||
out.write(' %-30s [label="%s"]\n' % (quote(spec.name), spec.name))
|
||||
if static:
|
||||
out.write(' "%s" [label="%s"]\n' % (spec.name, spec.name))
|
||||
|
||||
# Skip virtual specs (we'll find out about them from concrete ones.
|
||||
if spec.virtual:
|
||||
continue
|
||||
# Skip virtual specs (we'll find out about them from concrete ones.
|
||||
if spec.virtual:
|
||||
continue
|
||||
|
||||
# Add edges for each depends_on in the package.
|
||||
for dep_name, dep in spec.package.dependencies.iteritems():
|
||||
deps.append((spec.name, dep_name))
|
||||
# Add edges for each depends_on in the package.
|
||||
for dep_name, dep in spec.package.dependencies.iteritems():
|
||||
deps.add((spec.name, dep_name))
|
||||
|
||||
# If the package provides something, add an edge for that.
|
||||
for provider in set(s.name for s in spec.package.provided):
|
||||
deps.append((provider, spec.name))
|
||||
# If the package provides something, add an edge for that.
|
||||
for provider in set(s.name for s in spec.package.provided):
|
||||
deps.add((provider, spec.name))
|
||||
|
||||
else:
|
||||
def key_label(s):
|
||||
return s.dag_hash(), "%s-%s" % (s.name, s.dag_hash(7))
|
||||
|
||||
for s in spec.traverse(deptype=deptype):
|
||||
skey, slabel = key_label(s)
|
||||
out.write(' "%s" [label="%s"]\n' % (skey, slabel))
|
||||
|
||||
for d in s.dependencies(deptype=deptype):
|
||||
dkey, _ = key_label(d)
|
||||
deps.add((skey, dkey))
|
||||
|
||||
out.write('\n')
|
||||
|
||||
|
|
|
@ -411,6 +411,20 @@ def __init__(self, spec):
|
|||
if self.is_extension:
|
||||
spack.repo.get(self.extendee_spec)._check_extendable()
|
||||
|
||||
def possible_dependencies(self, visited=None):
|
||||
"""Return set of possible transitive dependencies of this package."""
|
||||
if visited is None:
|
||||
visited = set()
|
||||
|
||||
visited.add(self.name)
|
||||
for name in self.dependencies:
|
||||
if name not in visited and not spack.spec.Spec(name).virtual:
|
||||
pkg = spack.repo.get(name)
|
||||
for name in pkg.possible_dependencies(visited):
|
||||
visited.add(name)
|
||||
|
||||
return visited
|
||||
|
||||
@property
|
||||
def package_dir(self):
|
||||
"""Return the directory where the package.py file lives."""
|
||||
|
|
Loading…
Reference in a new issue