Merge branch 'features/dep-graph' into develop
This commit is contained in:
		
							
								
								
									
										10
									
								
								bin/spack
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								bin/spack
									
									
									
									
									
								
							| @@ -103,7 +103,7 @@ if args.insecure: | |||||||
| # Try to load the particular command asked for and run it | # Try to load the particular command asked for and run it | ||||||
| command = spack.cmd.get_command(args.command) | command = spack.cmd.get_command(args.command) | ||||||
| try: | try: | ||||||
|     command(parser, args) |     return_val = command(parser, args) | ||||||
| except SpackError, e: | except SpackError, e: | ||||||
|     if spack.debug: |     if spack.debug: | ||||||
|         # In debug mode, raise with a full stack trace. |         # In debug mode, raise with a full stack trace. | ||||||
| @@ -116,3 +116,11 @@ except SpackError, e: | |||||||
| except KeyboardInterrupt: | except KeyboardInterrupt: | ||||||
|     sys.stderr.write('\n') |     sys.stderr.write('\n') | ||||||
|     tty.die("Keyboard interrupt.") |     tty.die("Keyboard interrupt.") | ||||||
|  |  | ||||||
|  | # Allow commands to return values if they want to exit with some ohter code. | ||||||
|  | if return_val is None: | ||||||
|  |     sys.exit(0) | ||||||
|  | elif isinstance(return_val, int): | ||||||
|  |     sys.exit(return_val) | ||||||
|  | else: | ||||||
|  |     tty.die("Bad return value from command %s: %s" % (args.command, return_val)) | ||||||
|   | |||||||
| @@ -269,6 +269,28 @@ def in_function(function_name): | |||||||
|         del stack |         del stack | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def check_kwargs(kwargs, fun): | ||||||
|  |     """Helper for making functions with kwargs.  Checks whether the kwargs | ||||||
|  |        are empty after all of them have been popped off.  If they're | ||||||
|  |        not, raises an error describing which kwargs are invalid. | ||||||
|  |  | ||||||
|  |        Example:: | ||||||
|  |  | ||||||
|  |           def foo(self, **kwargs): | ||||||
|  |               x = kwargs.pop('x', None) | ||||||
|  |               y = kwargs.pop('y', None) | ||||||
|  |               z = kwargs.pop('z', None) | ||||||
|  |               check_kwargs(kwargs, self.foo) | ||||||
|  |  | ||||||
|  |           # This raises a TypeError: | ||||||
|  |           foo(w='bad kwarg') | ||||||
|  |     """ | ||||||
|  |     if kwargs: | ||||||
|  |         raise TypeError( | ||||||
|  |             "'%s' is an invalid keyword argument for function %s()." | ||||||
|  |             % (next(kwargs.iterkeys()), fun.__name__)) | ||||||
|  |  | ||||||
|  |  | ||||||
| class RequiredAttributeError(ValueError): | class RequiredAttributeError(ValueError): | ||||||
|     def __init__(self, message): |     def __init__(self, message): | ||||||
|         super(RequiredAttributeError, self).__init__(message) |         super(RequiredAttributeError, self).__init__(message) | ||||||
|   | |||||||
| @@ -177,17 +177,20 @@ def cescape(string): | |||||||
|  |  | ||||||
| class ColorStream(object): | class ColorStream(object): | ||||||
|     def __init__(self, stream, color=None): |     def __init__(self, stream, color=None): | ||||||
|         self.__class__ = type(stream.__class__.__name__, |         self._stream = stream | ||||||
|                               (self.__class__, stream.__class__), {}) |         self._color = color | ||||||
|         self.__dict__ = stream.__dict__ |  | ||||||
|         self.color = color |  | ||||||
|         self.stream = stream |  | ||||||
|  |  | ||||||
|     def write(self, string, **kwargs): |     def write(self, string, **kwargs): | ||||||
|         if kwargs.get('raw', False): |         raw = kwargs.get('raw', False) | ||||||
|             super(ColorStream, self).write(string) |         raw_write = getattr(self._stream, 'write') | ||||||
|         else: |  | ||||||
|             cwrite(string, self.stream, self.color) |         color = self._color | ||||||
|  |         if self._color is None: | ||||||
|  |             if raw: | ||||||
|  |                 color=True | ||||||
|  |             else: | ||||||
|  |                 color = self._stream.isatty() | ||||||
|  |         raw_write(colorize(string, color=color)) | ||||||
|  |  | ||||||
|     def writelines(self, sequence, **kwargs): |     def writelines(self, sequence, **kwargs): | ||||||
|         raw = kwargs.get('raw', False) |         raw = kwargs.get('raw', False) | ||||||
|   | |||||||
| @@ -22,9 +22,45 @@ | |||||||
| # along with this program; if not, write to the Free Software Foundation, | # along with this program; if not, write to the Free Software Foundation, | ||||||
| # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||||||
| ############################################################################## | ############################################################################## | ||||||
| import spack | from external import argparse | ||||||
|  |  | ||||||
|  | import spack | ||||||
|  | import spack.cmd | ||||||
|  | from spack.graph import * | ||||||
|  |  | ||||||
|  | description = "Generate graphs of package dependency relationships." | ||||||
|  |  | ||||||
|  | def setup_parser(subparser): | ||||||
|  |     setup_parser.parser = subparser | ||||||
|  |  | ||||||
|  |     method = subparser.add_mutually_exclusive_group() | ||||||
|  |     method.add_argument( | ||||||
|  |         '--ascii', action='store_true', | ||||||
|  |         help="Draw graph as ascii to stdout (default).") | ||||||
|  |     method.add_argument( | ||||||
|  |         '--dot', action='store_true', | ||||||
|  |         help="Generate graph in dot format and print to stdout.") | ||||||
|  |  | ||||||
|  |     subparser.add_argument( | ||||||
|  |         '--concretize', action='store_true', help="Concretize specs before graphing.") | ||||||
|  |  | ||||||
|  |     subparser.add_argument( | ||||||
|  |         'specs', nargs=argparse.REMAINDER, help="specs of packages to graph.") | ||||||
|  |  | ||||||
| description = "Write out inter-package dependencies in dot graph format" |  | ||||||
|  |  | ||||||
| def graph(parser, args): | def graph(parser, args): | ||||||
|     spack.db.graph_dependencies() |     specs = spack.cmd.parse_specs( | ||||||
|  |         args.specs, normalize=True, concretize=args.concretize) | ||||||
|  |  | ||||||
|  |     if not specs: | ||||||
|  |         setup_parser.parser.print_help() | ||||||
|  |         return 1 | ||||||
|  |  | ||||||
|  |     if args.dot:    # Dot graph only if asked for. | ||||||
|  |         graph_dot(*specs) | ||||||
|  |  | ||||||
|  |     elif specs:     # ascii is default: user doesn't need to provide it explicitly | ||||||
|  |         graph_ascii(specs[0], debug=spack.debug) | ||||||
|  |         for spec in specs[1:]: | ||||||
|  |             print # extra line bt/w independent graphs | ||||||
|  |             graph_ascii(spec, debug=spack.debug) | ||||||
|   | |||||||
| @@ -111,4 +111,3 @@ def location(parser, args): | |||||||
|                     tty.die("Build directory does not exist yet. Run this to create it:", |                     tty.die("Build directory does not exist yet. Run this to create it:", | ||||||
|                             "spack stage " + " ".join(args.spec)) |                             "spack stage " + " ".join(args.spec)) | ||||||
|                 print pkg.stage.source_path |                 print pkg.stage.source_path | ||||||
|  |  | ||||||
|   | |||||||
| @@ -41,6 +41,7 @@ def setup_parser(subparser): | |||||||
| def md5(parser, args): | def md5(parser, args): | ||||||
|     if not args.files: |     if not args.files: | ||||||
|         setup_parser.parser.print_help() |         setup_parser.parser.print_help() | ||||||
|  |         return 1 | ||||||
|  |  | ||||||
|     for f in args.files: |     for f in args.files: | ||||||
|         if not os.path.isfile(f): |         if not os.path.isfile(f): | ||||||
|   | |||||||
| @@ -27,8 +27,8 @@ | |||||||
|  |  | ||||||
| import llnl.util.tty as tty | import llnl.util.tty as tty | ||||||
|  |  | ||||||
| import spack.url as url |  | ||||||
| import spack | import spack | ||||||
|  | import spack.url as url | ||||||
|  |  | ||||||
| description = "print out abstract and concrete versions of a spec." | description = "print out abstract and concrete versions of a spec." | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										553
									
								
								lib/spack/spack/graph.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										553
									
								
								lib/spack/spack/graph.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,553 @@ | |||||||
|  | ############################################################################## | ||||||
|  | # Copyright (c) 2013, Lawrence Livermore National Security, LLC. | ||||||
|  | # Produced at the Lawrence Livermore National Laboratory. | ||||||
|  | # | ||||||
|  | # This file is part of Spack. | ||||||
|  | # Written by Todd Gamblin, tgamblin@llnl.gov, All rights reserved. | ||||||
|  | # LLNL-CODE-647188 | ||||||
|  | # | ||||||
|  | # For details, see https://scalability-llnl.github.io/spack | ||||||
|  | # Please also see the LICENSE file for our notice and the LGPL. | ||||||
|  | # | ||||||
|  | # This program is free software; you can redistribute it and/or modify | ||||||
|  | # it under the terms of the GNU General Public License (as published by | ||||||
|  | # the Free Software Foundation) version 2.1 dated February 1999. | ||||||
|  | # | ||||||
|  | # This program is distributed in the hope that it will be useful, but | ||||||
|  | # WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF | ||||||
|  | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and | ||||||
|  | # conditions of the GNU General Public License for more details. | ||||||
|  | # | ||||||
|  | # You should have received a copy of the GNU Lesser General Public License | ||||||
|  | # along with this program; if not, write to the Free Software Foundation, | ||||||
|  | # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||||||
|  | ############################################################################## | ||||||
|  | """Functions for graphing DAGs of dependencies. | ||||||
|  |  | ||||||
|  | This file contains code for graphing DAGs of software packages | ||||||
|  | (i.e. Spack specs).  There are two main functions you probably care | ||||||
|  | about: | ||||||
|  |  | ||||||
|  | graph_ascii() will output a colored graph of a spec in ascii format, | ||||||
|  | kind of like the graph git shows with "git log --graph", e.g.:: | ||||||
|  |  | ||||||
|  |     o  mpileaks | ||||||
|  |     |\ | ||||||
|  |     | |\ | ||||||
|  |     | o |  callpath | ||||||
|  |     |/| | | ||||||
|  |     | |\| | ||||||
|  |     | |\ \ | ||||||
|  |     | | |\ \ | ||||||
|  |     | | | | o  adept-utils | ||||||
|  |     | |_|_|/| | ||||||
|  |     |/| | | | | ||||||
|  |     o | | | |  mpi | ||||||
|  |      / / / / | ||||||
|  |     | | o |  dyninst | ||||||
|  |     | |/| | | ||||||
|  |     |/|/| | | ||||||
|  |     | | |/ | ||||||
|  |     | o |  libdwarf | ||||||
|  |     |/ / | ||||||
|  |     o |  libelf | ||||||
|  |      / | ||||||
|  |     o  boost | ||||||
|  |  | ||||||
|  | graph_dot() will output a graph of a spec (or multiple specs) in dot | ||||||
|  | format. | ||||||
|  |  | ||||||
|  | Note that ``graph_ascii`` assumes a single spec while ``graph_dot`` | ||||||
|  | can take a number of specs as input. | ||||||
|  |  | ||||||
|  | """ | ||||||
|  | __all__ = ['topological_sort', 'graph_ascii', 'AsciiGraph', 'graph_dot'] | ||||||
|  |  | ||||||
|  | from heapq import * | ||||||
|  |  | ||||||
|  | from llnl.util.lang import * | ||||||
|  | from llnl.util.tty.color import * | ||||||
|  |  | ||||||
|  | import spack | ||||||
|  | from spack.spec import Spec | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def topological_sort(spec, **kwargs): | ||||||
|  |     """Topological sort for specs. | ||||||
|  |  | ||||||
|  |     Return a list of dependency specs sorted topologically.  The spec | ||||||
|  |     argument is not modified in the process. | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |     reverse = kwargs.get('reverse', False) | ||||||
|  |     if not reverse: | ||||||
|  |         parents  = lambda s: s.dependents | ||||||
|  |         children = lambda s: s.dependencies | ||||||
|  |     else: | ||||||
|  |         parents  = lambda s: s.dependencies | ||||||
|  |         children = lambda s: s.dependents | ||||||
|  |  | ||||||
|  |     # Work on a copy so this is nondestructive. | ||||||
|  |     spec = spec.copy() | ||||||
|  |     nodes = spec.index() | ||||||
|  |  | ||||||
|  |     topo_order = [] | ||||||
|  |     remaining = [name for name in nodes.keys() if not parents(nodes[name])] | ||||||
|  |     heapify(remaining) | ||||||
|  |  | ||||||
|  |     while remaining: | ||||||
|  |         name = heappop(remaining) | ||||||
|  |         topo_order.append(name) | ||||||
|  |  | ||||||
|  |         node = nodes[name] | ||||||
|  |         for dep in children(node).values(): | ||||||
|  |             del parents(dep)[node.name] | ||||||
|  |             if not parents(dep): | ||||||
|  |                 heappush(remaining, dep.name) | ||||||
|  |  | ||||||
|  |     if any(parents(s) for s in spec.traverse()): | ||||||
|  |         raise ValueError("Spec has cycles!") | ||||||
|  |     else: | ||||||
|  |         return topo_order | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def find(seq, predicate): | ||||||
|  |     """Find index in seq for which predicate is True. | ||||||
|  |  | ||||||
|  |     Searches the sequence and returns the index of the element for | ||||||
|  |     which the predicate evaluates to True.  Returns -1 if the | ||||||
|  |     predicate does not evaluate to True for any element in seq. | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |     for i, elt in enumerate(seq): | ||||||
|  |         if predicate(elt): | ||||||
|  |             return i | ||||||
|  |     return -1 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # Names of different graph line states.  We Record previous line | ||||||
|  | # states so that we can easily determine what to do when connecting. | ||||||
|  | states = ('node', 'collapse', 'merge-right', 'expand-right', 'back-edge') | ||||||
|  | NODE, COLLAPSE, MERGE_RIGHT, EXPAND_RIGHT, BACK_EDGE = states | ||||||
|  |  | ||||||
|  | class AsciiGraph(object): | ||||||
|  |     def __init__(self): | ||||||
|  |         # These can be set after initialization or after a call to | ||||||
|  |         # graph() to change behavior. | ||||||
|  |         self.node_character = '*' | ||||||
|  |         self.debug = False | ||||||
|  |         self.indent = 0 | ||||||
|  |  | ||||||
|  |         # These are colors in the order they'll be used for edges. | ||||||
|  |         # See llnl.util.tty.color for details on color characters. | ||||||
|  |         self.colors = 'rgbmcyRGBMCY' | ||||||
|  |  | ||||||
|  |         # Internal vars are used in the graph() function and are | ||||||
|  |         # properly initialized there. | ||||||
|  |         self._name_to_color = None    # Node name to color | ||||||
|  |         self._out = None              # Output stream | ||||||
|  |         self._frontier = None         # frontier | ||||||
|  |         self._nodes = None            # dict from name -> node | ||||||
|  |         self._prev_state = None       # State of previous line | ||||||
|  |         self._prev_index = None       # Index of expansion point of prev line | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def _indent(self): | ||||||
|  |         self._out.write(self.indent * ' ') | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def _write_edge(self, string, index, sub=0): | ||||||
|  |         """Write a colored edge to the output stream.""" | ||||||
|  |         name = self._frontier[index][sub] | ||||||
|  |         edge = "@%s{%s}" % (self._name_to_color[name], string) | ||||||
|  |         self._out.write(edge) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def _connect_deps(self, i, deps, label=None): | ||||||
|  |         """Connect dependencies to existing edges in the frontier. | ||||||
|  |  | ||||||
|  |         ``deps`` are to be inserted at position i in the | ||||||
|  |         frontier. This routine determines whether other open edges | ||||||
|  |         should be merged with <deps> (if there are other open edges | ||||||
|  |         pointing to the same place) or whether they should just be | ||||||
|  |         inserted as a completely new open edge. | ||||||
|  |  | ||||||
|  |         Open edges that are not fully expanded (i.e. those that point | ||||||
|  |         at multiple places) are left intact. | ||||||
|  |  | ||||||
|  |         Parameters: | ||||||
|  |  | ||||||
|  |         label    -- optional debug label for the connection. | ||||||
|  |  | ||||||
|  |         Returns: True if the deps were connected to another edge | ||||||
|  |         (i.e. the frontier did not grow) and False if the deps were | ||||||
|  |         NOT already in the frontier (i.e. they were inserted and the | ||||||
|  |         frontier grew). | ||||||
|  |  | ||||||
|  |         """ | ||||||
|  |         if len(deps) == 1 and deps in self._frontier: | ||||||
|  |             j = self._frontier.index(deps) | ||||||
|  |  | ||||||
|  |             # convert a right connection into a left connection | ||||||
|  |             if i < j: | ||||||
|  |                 self._frontier.pop(j) | ||||||
|  |                 self._frontier.insert(i, deps) | ||||||
|  |                 return self._connect_deps(j, deps, label) | ||||||
|  |  | ||||||
|  |             collapse = True | ||||||
|  |             if self._prev_state == EXPAND_RIGHT: | ||||||
|  |                 # Special case where previous line expanded and i is off by 1. | ||||||
|  |                 self._back_edge_line([], j, i+1, True, label + "-1.5 " + str((i+1,j))) | ||||||
|  |                 collapse = False | ||||||
|  |  | ||||||
|  |             else: | ||||||
|  |                 # Previous node also expanded here, so i is off by one. | ||||||
|  |                 if self._prev_state == NODE and self._prev_index < i: | ||||||
|  |                     i += 1 | ||||||
|  |  | ||||||
|  |                 if i-j > 1: | ||||||
|  |                     # We need two lines to connect if distance > 1 | ||||||
|  |                     self._back_edge_line([], j,  i, True, label + "-1 " + str((i,j))) | ||||||
|  |                     collapse = False | ||||||
|  |  | ||||||
|  |             self._back_edge_line([j], -1, -1, collapse, label + "-2 " + str((i,j))) | ||||||
|  |             return True | ||||||
|  |  | ||||||
|  |         elif deps: | ||||||
|  |             self._frontier.insert(i, deps) | ||||||
|  |             return False | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def _set_state(self, state, index, label=None): | ||||||
|  |         if state not in states: | ||||||
|  |             raise ValueError("Invalid graph state!") | ||||||
|  |         self._prev_state = state | ||||||
|  |         self._prev_index = index | ||||||
|  |  | ||||||
|  |         if self.debug: | ||||||
|  |             self._out.write(" " * 20) | ||||||
|  |             self._out.write("%-20s" % ( | ||||||
|  |                 str(self._prev_state) if self._prev_state else '')) | ||||||
|  |             self._out.write("%-20s" % (str(label) if label else '')) | ||||||
|  |             self._out.write("%s" % self._frontier) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def _back_edge_line(self, prev_ends, end, start, collapse, label=None): | ||||||
|  |         """Write part of a backwards edge in the graph. | ||||||
|  |  | ||||||
|  |         Writes single- or multi-line backward edges in an ascii graph. | ||||||
|  |         For example, a single line edge:: | ||||||
|  |  | ||||||
|  |             | | | | o | | ||||||
|  |             | | | |/ /  <-- single-line edge connects two nodes. | ||||||
|  |             | | | o | | ||||||
|  |  | ||||||
|  |         Or a multi-line edge (requires two calls to back_edge):: | ||||||
|  |  | ||||||
|  |             | | | | o | | ||||||
|  |             | |_|_|/ /   <-- multi-line edge crosses vertical edges. | ||||||
|  |             |/| | | | | ||||||
|  |             o | | | | | ||||||
|  |  | ||||||
|  |         Also handles "pipelined" edges, where the same line contains | ||||||
|  |         parts of multiple edges:: | ||||||
|  |  | ||||||
|  |                       o start | ||||||
|  |             | |_|_|_|/| | ||||||
|  |             |/| | |_|/| <-- this line has parts of 2 edges. | ||||||
|  |             | | |/| | | | ||||||
|  |             o   o | ||||||
|  |  | ||||||
|  |         Arguments: | ||||||
|  |  | ||||||
|  |         prev_ends -- indices in frontier of previous edges that need | ||||||
|  |                      to be finished on this line. | ||||||
|  |  | ||||||
|  |         end -- end of the current edge on this line. | ||||||
|  |  | ||||||
|  |         start -- start index of the current edge. | ||||||
|  |  | ||||||
|  |         collapse -- whether the graph will be collapsing (i.e. whether | ||||||
|  |                     to slant the end of the line or keep it straight) | ||||||
|  |  | ||||||
|  |         label -- optional debug label to print after the line. | ||||||
|  |  | ||||||
|  |         """ | ||||||
|  |         def advance(to_pos, edges): | ||||||
|  |             """Write edges up to <to_pos>.""" | ||||||
|  |             for i in range(self._pos, to_pos): | ||||||
|  |                 for e in edges(): | ||||||
|  |                     self._write_edge(*e) | ||||||
|  |                 self._pos += 1 | ||||||
|  |  | ||||||
|  |         flen = len(self._frontier) | ||||||
|  |         self._pos = 0 | ||||||
|  |         self._indent() | ||||||
|  |  | ||||||
|  |         for p in prev_ends: | ||||||
|  |             advance(p,         lambda: [("| ", self._pos)] ) | ||||||
|  |             advance(p+1,       lambda: [("|/", self._pos)] ) | ||||||
|  |  | ||||||
|  |         if end >= 0: | ||||||
|  |             advance(end + 1,   lambda: [("| ", self._pos)] ) | ||||||
|  |             advance(start - 1, lambda: [("|",  self._pos), ("_", end)] ) | ||||||
|  |         else: | ||||||
|  |             advance(start - 1, lambda: [("| ", self._pos)] ) | ||||||
|  |  | ||||||
|  |         if start >= 0: | ||||||
|  |             advance(start,     lambda: [("|",  self._pos), ("/", end)] ) | ||||||
|  |  | ||||||
|  |         if collapse: | ||||||
|  |             advance(flen,      lambda: [(" /", self._pos)] ) | ||||||
|  |         else: | ||||||
|  |             advance(flen,      lambda: [("| ", self._pos)] ) | ||||||
|  |  | ||||||
|  |         self._set_state(BACK_EDGE, end, label) | ||||||
|  |         self._out.write("\n") | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def _node_line(self, index, name): | ||||||
|  |         """Writes a line with a node at index.""" | ||||||
|  |         self._indent() | ||||||
|  |         for c in range(index): | ||||||
|  |             self._write_edge("| ", c) | ||||||
|  |  | ||||||
|  |         self._out.write("%s " % self.node_character) | ||||||
|  |  | ||||||
|  |         for c in range(index+1, len(self._frontier)): | ||||||
|  |             self._write_edge("| ", c) | ||||||
|  |  | ||||||
|  |         self._out.write(" %s" % name) | ||||||
|  |         self._set_state(NODE, index) | ||||||
|  |         self._out.write("\n") | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def _collapse_line(self, index): | ||||||
|  |         """Write a collapsing line after a node was added at index.""" | ||||||
|  |         self._indent() | ||||||
|  |         for c in range(index): | ||||||
|  |             self._write_edge("| ", c) | ||||||
|  |         for c in range(index, len(self._frontier)): | ||||||
|  |             self._write_edge(" /", c) | ||||||
|  |  | ||||||
|  |         self._set_state(COLLAPSE, index) | ||||||
|  |         self._out.write("\n") | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def _merge_right_line(self, index): | ||||||
|  |         """Edge at index is same as edge to right.  Merge directly with '\'""" | ||||||
|  |         self._indent() | ||||||
|  |         for c in range(index): | ||||||
|  |             self._write_edge("| ", c) | ||||||
|  |         self._write_edge("|", index) | ||||||
|  |         self._write_edge("\\", index+1) | ||||||
|  |         for c in range(index+1, len(self._frontier)): | ||||||
|  |             self._write_edge("| ", c ) | ||||||
|  |  | ||||||
|  |         self._set_state(MERGE_RIGHT, index) | ||||||
|  |         self._out.write("\n") | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def _expand_right_line(self, index): | ||||||
|  |         self._indent() | ||||||
|  |         for c in range(index): | ||||||
|  |             self._write_edge("| ", c) | ||||||
|  |  | ||||||
|  |         self._write_edge("|", index) | ||||||
|  |         self._write_edge("\\", index+1) | ||||||
|  |  | ||||||
|  |         for c in range(index+2, len(self._frontier)): | ||||||
|  |             self._write_edge(" \\", c) | ||||||
|  |  | ||||||
|  |         self._set_state(EXPAND_RIGHT, index) | ||||||
|  |         self._out.write("\n") | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def write(self, spec, **kwargs): | ||||||
|  |         """Write out an ascii graph of the provided spec. | ||||||
|  |  | ||||||
|  |         Arguments: | ||||||
|  |         spec -- spec to graph.  This only handles one spec at a time. | ||||||
|  |  | ||||||
|  |         Optional arguments: | ||||||
|  |  | ||||||
|  |         out -- file object to write out to (default is sys.stdout) | ||||||
|  |  | ||||||
|  |         color -- whether to write in color.  Default is to autodetect | ||||||
|  |                  based on output file. | ||||||
|  |  | ||||||
|  |         """ | ||||||
|  |         out = kwargs.get('out', None) | ||||||
|  |         if not out: | ||||||
|  |             out = sys.stdout | ||||||
|  |  | ||||||
|  |         color = kwargs.get('color', None) | ||||||
|  |         if not color: | ||||||
|  |             color = out.isatty() | ||||||
|  |         self._out = ColorStream(sys.stdout, color=color) | ||||||
|  |  | ||||||
|  |         # We'll traverse the spec in topo order as we graph it. | ||||||
|  |         topo_order = topological_sort(spec, reverse=True) | ||||||
|  |  | ||||||
|  |         # Work on a copy to be nondestructive | ||||||
|  |         spec = spec.copy() | ||||||
|  |         self._nodes = spec.index() | ||||||
|  |  | ||||||
|  |         # Colors associated with each node in the DAG. | ||||||
|  |         # Edges are colored by the node they point to. | ||||||
|  |         self._name_to_color = dict((name, self.colors[i % len(self.colors)]) | ||||||
|  |                                   for i, name in enumerate(topo_order)) | ||||||
|  |  | ||||||
|  |         # Frontier tracks open edges of the graph as it's written out. | ||||||
|  |         self._frontier = [[spec.name]] | ||||||
|  |         while self._frontier: | ||||||
|  |             # Find an unexpanded part of frontier | ||||||
|  |             i = find(self._frontier, lambda f: len(f) > 1) | ||||||
|  |  | ||||||
|  |             if i >= 0: | ||||||
|  |                 # Expand frontier until there are enough columns for all children. | ||||||
|  |  | ||||||
|  |                 # Figure out how many back connections there are and | ||||||
|  |                 # sort them so we do them in order | ||||||
|  |                 back = [] | ||||||
|  |                 for d in self._frontier[i]: | ||||||
|  |                     b = find(self._frontier[:i], lambda f: f == [d]) | ||||||
|  |                     if b != -1: | ||||||
|  |                         back.append((b, d)) | ||||||
|  |  | ||||||
|  |                 # Do all back connections in sorted order so we can | ||||||
|  |                 # pipeline them and save space. | ||||||
|  |                 if back: | ||||||
|  |                     back.sort() | ||||||
|  |                     prev_ends = [] | ||||||
|  |                     for j, (b, d) in enumerate(back): | ||||||
|  |                         self._frontier[i].remove(d) | ||||||
|  |                         if i-b > 1: | ||||||
|  |                             self._back_edge_line(prev_ends, b, i, False, 'left-1') | ||||||
|  |                             del prev_ends[:] | ||||||
|  |                         prev_ends.append(b) | ||||||
|  |  | ||||||
|  |                     # Check whether we did ALL the deps as back edges, | ||||||
|  |                     # in which case we're done. | ||||||
|  |                     collapse = not self._frontier[i] | ||||||
|  |                     if collapse: | ||||||
|  |                         self._frontier.pop(i) | ||||||
|  |                     self._back_edge_line(prev_ends, -1, -1, collapse, 'left-2') | ||||||
|  |  | ||||||
|  |                 elif len(self._frontier[i]) > 1: | ||||||
|  |                     # Expand forward after doing all back connections | ||||||
|  |  | ||||||
|  |                     if (i+1 < len(self._frontier) and len(self._frontier[i+1]) == 1 | ||||||
|  |                         and self._frontier[i+1][0] in self._frontier[i]): | ||||||
|  |                         # We need to connect to the element to the right. | ||||||
|  |                         # Keep lines straight by connecting directly and | ||||||
|  |                         # avoiding unnecessary expand/contract. | ||||||
|  |                         name = self._frontier[i+1][0] | ||||||
|  |                         self._frontier[i].remove(name) | ||||||
|  |                         self._merge_right_line(i) | ||||||
|  |  | ||||||
|  |                     else: | ||||||
|  |                         # Just allow the expansion here. | ||||||
|  |                         name = self._frontier[i].pop(0) | ||||||
|  |                         deps = [name] | ||||||
|  |                         self._frontier.insert(i, deps) | ||||||
|  |                         self._expand_right_line(i) | ||||||
|  |  | ||||||
|  |                         self._frontier.pop(i) | ||||||
|  |                         self._connect_deps(i, deps, "post-expand") | ||||||
|  |  | ||||||
|  |  | ||||||
|  |                 # Handle any remaining back edges to the right | ||||||
|  |                 j = i+1 | ||||||
|  |                 while j < len(self._frontier): | ||||||
|  |                     deps = self._frontier.pop(j) | ||||||
|  |                     if not self._connect_deps(j, deps, "back-from-right"): | ||||||
|  |                         j += 1 | ||||||
|  |  | ||||||
|  |             else: | ||||||
|  |                 # Nothing to expand; add dependencies for a node. | ||||||
|  |                 name = topo_order.pop() | ||||||
|  |                 node = self._nodes[name] | ||||||
|  |  | ||||||
|  |                 # Find the named node in the frontier and draw it. | ||||||
|  |                 i = find(self._frontier, lambda f: name in f) | ||||||
|  |                 self._node_line(i, name) | ||||||
|  |  | ||||||
|  |                 # Replace node with its dependencies | ||||||
|  |                 self._frontier.pop(i) | ||||||
|  |                 if node.dependencies: | ||||||
|  |                     deps = sorted((d for d in node.dependencies), reverse=True) | ||||||
|  |                     self._connect_deps(i, deps, "new-deps") # anywhere. | ||||||
|  |  | ||||||
|  |                 elif self._frontier: | ||||||
|  |                     self._collapse_line(i) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def graph_ascii(spec, **kwargs): | ||||||
|  |     node_character = kwargs.get('node', 'o') | ||||||
|  |     out            = kwargs.pop('out', None) | ||||||
|  |     debug          = kwargs.pop('debug', False) | ||||||
|  |     indent         = kwargs.pop('indent', 0) | ||||||
|  |     color          = kwargs.pop('color', None) | ||||||
|  |     check_kwargs(kwargs, graph_ascii) | ||||||
|  |  | ||||||
|  |     graph = AsciiGraph() | ||||||
|  |     graph.debug = debug | ||||||
|  |     graph.indent = indent | ||||||
|  |     graph.node_character = node_character | ||||||
|  |  | ||||||
|  |     graph.write(spec, color=color, out=out) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def graph_dot(*specs, **kwargs): | ||||||
|  |     """Generate a graph in dot format of all provided specs. | ||||||
|  |  | ||||||
|  |     Print out a dot formatted graph of all the dependencies between | ||||||
|  |     package.  Output can be passed to graphviz, e.g.: | ||||||
|  |  | ||||||
|  |         spack graph --dot qt | dot -Tpdf > spack-graph.pdf | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |     out = kwargs.pop('out', sys.stdout) | ||||||
|  |     check_kwargs(kwargs, graph_dot) | ||||||
|  |  | ||||||
|  |     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('\n') | ||||||
|  |  | ||||||
|  |     def quote(string): | ||||||
|  |         return '"%s"' % string | ||||||
|  |  | ||||||
|  |     if not specs: | ||||||
|  |         specs = [p.name for p in spack.db.all_packages()] | ||||||
|  |     else: | ||||||
|  |         roots = specs | ||||||
|  |         specs = set() | ||||||
|  |         for spec in roots: | ||||||
|  |             specs.update(Spec(s.name) for s in spec.normalized().traverse()) | ||||||
|  |  | ||||||
|  |     deps = [] | ||||||
|  |     for spec in specs: | ||||||
|  |         out.write('  %-30s [label="%s"]\n' % (quote(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.append((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)) | ||||||
|  |  | ||||||
|  |     out.write('\n') | ||||||
|  |  | ||||||
|  |     for pair in deps: | ||||||
|  |         out.write('  "%s" -> "%s"\n' % pair) | ||||||
|  |     out.write('}\n') | ||||||
| @@ -30,7 +30,7 @@ | |||||||
|  |  | ||||||
| import llnl.util.tty as tty | import llnl.util.tty as tty | ||||||
| from llnl.util.filesystem import join_path | from llnl.util.filesystem import join_path | ||||||
| from llnl.util.lang import memoized | from llnl.util.lang import * | ||||||
|  |  | ||||||
| import spack.error | import spack.error | ||||||
| import spack.spec | import spack.spec | ||||||
| @@ -214,38 +214,6 @@ def get_class_for_package_name(self, pkg_name): | |||||||
|         return cls |         return cls | ||||||
|  |  | ||||||
|  |  | ||||||
|     def graph_dependencies(self, out=sys.stdout): |  | ||||||
|         """Print out a graph of all the dependencies between package. |  | ||||||
|            Graph is in dot format.""" |  | ||||||
|         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('\n') |  | ||||||
|  |  | ||||||
|         def quote(string): |  | ||||||
|             return '"%s"' % string |  | ||||||
|  |  | ||||||
|         deps = [] |  | ||||||
|         for pkg in self.all_packages(): |  | ||||||
|             out.write('  %-30s [label="%s"]\n' % (quote(pkg.name), pkg.name)) |  | ||||||
|  |  | ||||||
|             # Add edges for each depends_on in the package. |  | ||||||
|             for dep_name, dep in pkg.dependencies.iteritems(): |  | ||||||
|                 deps.append((pkg.name, dep_name)) |  | ||||||
|  |  | ||||||
|             # If the package provides something, add an edge for that. |  | ||||||
|             for provider in set(p.name for p in pkg.provided): |  | ||||||
|                 deps.append((provider, pkg.name)) |  | ||||||
|  |  | ||||||
|         out.write('\n') |  | ||||||
|  |  | ||||||
|         for pair in deps: |  | ||||||
|             out.write('  "%s" -> "%s"\n' % pair) |  | ||||||
|         out.write('}\n') |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class UnknownPackageError(spack.error.SpackError): | class UnknownPackageError(spack.error.SpackError): | ||||||
|     """Raised when we encounter a package spack doesn't have.""" |     """Raised when we encounter a package spack doesn't have.""" | ||||||
|     def __init__(self, name): |     def __init__(self, name): | ||||||
|   | |||||||
| @@ -712,6 +712,15 @@ def flat_dependencies(self, **kwargs): | |||||||
|             raise InconsistentSpecError("Invalid Spec DAG: %s" % e.message) |             raise InconsistentSpecError("Invalid Spec DAG: %s" % e.message) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def index(self): | ||||||
|  |         """Return DependencyMap that points to all the dependencies in this | ||||||
|  |            spec.""" | ||||||
|  |         dm = DependencyMap() | ||||||
|  |         for spec in self.traverse(): | ||||||
|  |             dm[spec.name] = spec | ||||||
|  |         return dm | ||||||
|  |  | ||||||
|  |  | ||||||
|     def flatten(self): |     def flatten(self): | ||||||
|         """Pull all dependencies up to the root (this spec). |         """Pull all dependencies up to the root (this spec). | ||||||
|            Merge constraints for dependencies with the same name, and if they |            Merge constraints for dependencies with the same name, and if they | ||||||
| @@ -858,7 +867,7 @@ def normalize(self, **kwargs): | |||||||
|     def normalized(self): |     def normalized(self): | ||||||
|         """Return a normalized copy of this spec without modifying this spec.""" |         """Return a normalized copy of this spec without modifying this spec.""" | ||||||
|         clone = self.copy() |         clone = self.copy() | ||||||
|         clone.normalized() |         clone.normalize() | ||||||
|         return clone |         return clone | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -1289,12 +1298,13 @@ def __str__(self): | |||||||
|     def tree(self, **kwargs): |     def tree(self, **kwargs): | ||||||
|         """Prints out this spec and its dependencies, tree-formatted |         """Prints out this spec and its dependencies, tree-formatted | ||||||
|            with indentation.""" |            with indentation.""" | ||||||
|         color  = kwargs.get('color', False) |         color  = kwargs.pop('color', False) | ||||||
|         depth  = kwargs.get('depth', False) |         depth  = kwargs.pop('depth', False) | ||||||
|         showid = kwargs.get('ids',   False) |         showid = kwargs.pop('ids',   False) | ||||||
|         cover  = kwargs.get('cover', 'nodes') |         cover  = kwargs.pop('cover', 'nodes') | ||||||
|         indent = kwargs.get('indent', 0) |         indent = kwargs.pop('indent', 0) | ||||||
|         format = kwargs.get('format', '$_$@$%@$+$=') |         fmt    = kwargs.pop('format', '$_$@$%@$+$=') | ||||||
|  |         check_kwargs(kwargs, self.tree) | ||||||
|  |  | ||||||
|         out = "" |         out = "" | ||||||
|         cur_id = 0 |         cur_id = 0 | ||||||
| @@ -1311,7 +1321,7 @@ def tree(self, **kwargs): | |||||||
|             out += ("    " * d) |             out += ("    " * d) | ||||||
|             if d > 0: |             if d > 0: | ||||||
|                 out += "^" |                 out += "^" | ||||||
|             out += node.format(format, color=color) + "\n" |             out += node.format(fmt, color=color) + "\n" | ||||||
|         return out |         return out | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Todd Gamblin
					Todd Gamblin