summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTodd Gamblin <tgamblin@llnl.gov>2016-09-27 10:22:46 -0400
committerTodd Gamblin <tgamblin@llnl.gov>2016-09-27 11:00:50 -0400
commit0d3d74e5c2f0ad76c818449f21f91d6c9ed4e74a (patch)
treef4d1680a3acd47835dfba0f4c85dfe50fe620e2e
parent43ca805248deb8fe2f87c0009ab44cc30458e39a (diff)
downloadspack-0d3d74e5c2f0ad76c818449f21f91d6c9ed4e74a.tar.gz
spack-0d3d74e5c2f0ad76c818449f21f91d6c9ed4e74a.tar.bz2
spack-0d3d74e5c2f0ad76c818449f21f91d6c9ed4e74a.tar.xz
spack-0d3d74e5c2f0ad76c818449f21f91d6c9ed4e74a.zip
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.
-rw-r--r--lib/spack/spack/cmd/graph.py40
-rw-r--r--lib/spack/spack/graph.py78
-rw-r--r--lib/spack/spack/package.py14
3 files changed, 103 insertions, 29 deletions
diff --git a/lib/spack/spack/cmd/graph.py b/lib/spack/spack/cmd/graph.py
index 8faabfbb7b..6de1ed974b 100644
--- a/lib/spack/spack/cmd/graph.py
+++ b/lib/spack/spack/cmd/graph.py
@@ -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)
diff --git a/lib/spack/spack/graph.py b/lib/spack/spack/graph.py
index b875e9da99..330e08cc71 100644
--- a/lib/spack/spack/graph.py
+++ b/lib/spack/spack/graph.py
@@ -67,8 +67,7 @@ from heapq import *
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")
+
+ # 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()
- deps = []
+ 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
+
+ # Add edges for each depends_on in the package.
+ for dep_name, dep in spec.package.dependencies.iteritems():
+ deps.add((spec.name, dep_name))
- # Skip virtual specs (we'll find out about them from concrete ones.
- if spec.virtual:
- continue
+ # 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))
- # Add edges for each depends_on in the package.
- for dep_name, dep in spec.package.dependencies.iteritems():
- deps.append((spec.name, dep_name))
+ for s in spec.traverse(deptype=deptype):
+ skey, slabel = key_label(s)
+ out.write(' "%s" [label="%s"]\n' % (skey, slabel))
- # 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))
+ for d in s.dependencies(deptype=deptype):
+ dkey, _ = key_label(d)
+ deps.add((skey, dkey))
out.write('\n')
diff --git a/lib/spack/spack/package.py b/lib/spack/spack/package.py
index fe19e5d400..cffc795586 100644
--- a/lib/spack/spack/package.py
+++ b/lib/spack/spack/package.py
@@ -411,6 +411,20 @@ class Package(object):
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."""