4242can take a number of specs as input.
4343
4444"""
45+ import heapq
46+ import itertools
4547import sys
46- from heapq import heapify , heappop , heappush
4748
48- from llnl .util .tty .color import ColorStream
49+ import llnl .util .tty .color
4950
50- from spack .dependency import all_deptypes , canonical_deptype
51+ import spack .dependency
5152
52- __all__ = ['topological_sort' , ' graph_ascii' , 'AsciiGraph' , 'graph_dot' ]
53+ __all__ = ['graph_ascii' , 'AsciiGraph' , 'graph_dot' ]
5354
5455
55- def topological_sort (spec , reverse = False , deptype = 'all' ):
56- """Topological sort for specs.
56+ def node_label (spec ):
57+ return spec . format ( '{name}{@version}{/hash:7}' )
5758
58- Return a list of dependency specs sorted topologically. The spec
59- argument is not modified in the process.
6059
61- """
62- deptype = canonical_deptype (deptype )
63-
64- # Work on a copy so this is nondestructive.
65- spec = spec .copy (deps = deptype )
66- nodes = spec .index (deptype = deptype )
60+ def topological_sort (spec , deptype = 'all' ):
61+ """Return a list of dependency specs in topological sorting order.
6762
68- parents = lambda s : [p for p in s .dependents () if p .name in nodes ]
69- children = lambda s : s .dependencies ()
63+ The spec argument is not modified in by the function.
7064
71- if reverse :
72- parents , children = children , parents
65+ This function assumes specs don't have cycles, i.e. that we are really
66+ operating with a DAG.
7367
74- topo_order = []
75- par = dict ((name , parents (nodes [name ])) for name in nodes .keys ())
76- remaining = [name for name in nodes .keys () if not parents (nodes [name ])]
77- heapify (remaining )
68+ Args:
69+ spec (spack.spec.Spec): the spec to be analyzed
70+ deptype (str or tuple): dependency types to account for when
71+ constructing the list
72+ """
73+ deptype = spack .dependency .canonical_deptype (deptype )
7874
79- while remaining :
80- name = heappop ( remaining )
81- topo_order . append ( name )
75+ # Work on a copy so this is nondestructive
76+ spec = spec . copy ( deps = True )
77+ nodes = spec . index ( deptype = deptype )
8278
83- node = nodes [ name ]
84- for dep in children ( node ):
85- par [ dep . name ]. remove ( node )
86- if not par [ dep . name ]:
87- heappush ( remaining , dep . name )
79+ def dependencies ( specs ):
80+ """Return all the dependencies (including transitive) for a spec."""
81+ return list ( set ( itertools . chain . from_iterable (
82+ s . dependencies ( deptype = deptype ) for s in specs
83+ )) )
8884
89- if any (par .get (s .name , []) for s in spec .traverse ()):
90- raise ValueError ("Spec has cycles!" )
91- else :
92- return topo_order
85+ def dependents (specs ):
86+ """Return all the dependents (including those of transitive dependencies)
87+ for a spec.
88+ """
89+ candidates = list (set (itertools .chain .from_iterable (
90+ s .dependents (deptype = deptype ) for s in specs
91+ )))
92+ return [x for x in candidates if x .name in nodes ]
93+
94+ topological_order , children = [], {}
95+
96+ # Map a spec encoded as (id, name) to a list of its transitive dependencies
97+ for spec in itertools .chain .from_iterable (nodes .values ()):
98+ children [(id (spec ), spec .name )] = [
99+ x for x in dependencies ([spec ]) if x .name in nodes
100+ ]
101+
102+ # To return a result that is topologically ordered we need to add nodes
103+ # only after their dependencies. The first nodes we can add are leaf nodes,
104+ # i.e. nodes that have no dependencies.
105+ ready = [
106+ spec for spec in itertools .chain .from_iterable (nodes .values ())
107+ if not dependencies ([spec ])
108+ ]
109+ heapq .heapify (ready )
110+
111+ while ready :
112+ # Pop a "ready" node and add it to the topologically ordered list
113+ s = heapq .heappop (ready )
114+ topological_order .append (s )
115+
116+ # Check if adding the last node made other nodes "ready"
117+ for dep in dependents ([s ]):
118+ children [(id (dep ), dep .name )].remove (s )
119+ if not children [(id (dep ), dep .name )]:
120+ heapq .heappush (ready , dep )
121+
122+ return topological_order
93123
94124
95125def find (seq , predicate ):
@@ -120,7 +150,7 @@ def __init__(self):
120150 self .node_character = 'o'
121151 self .debug = False
122152 self .indent = 0
123- self .deptype = all_deptypes
153+ self .deptype = spack . dependency . all_deptypes
124154
125155 # These are colors in the order they'll be used for edges.
126156 # See llnl.util.tty.color for details on color characters.
@@ -131,7 +161,6 @@ def __init__(self):
131161 self ._name_to_color = None # Node name to color
132162 self ._out = None # Output stream
133163 self ._frontier = None # frontier
134- self ._nodes = None # dict from name -> node
135164 self ._prev_state = None # State of previous line
136165 self ._prev_index = None # Index of expansion point of prev line
137166
@@ -290,7 +319,10 @@ def advance(to_pos, edges):
290319 self ._set_state (BACK_EDGE , end , label )
291320 self ._out .write ("\n " )
292321
293- def _node_line (self , index , name ):
322+ def _node_label (self , node ):
323+ return node .format ('{name}@@{version}{/hash:7}' )
324+
325+ def _node_line (self , index , node ):
294326 """Writes a line with a node at index."""
295327 self ._indent ()
296328 for c in range (index ):
@@ -301,7 +333,7 @@ def _node_line(self, index, name):
301333 for c in range (index + 1 , len (self ._frontier )):
302334 self ._write_edge ("| " , c )
303335
304- self ._out .write (" %s" % name )
336+ self ._out .write (self . _node_label ( node ) )
305337 self ._set_state (NODE , index )
306338 self ._out .write ("\n " )
307339
@@ -363,29 +395,29 @@ def write(self, spec, color=None, out=None):
363395 if color is None :
364396 color = out .isatty ()
365397
366- self ._out = ColorStream (out , color = color )
398+ self ._out = llnl . util . tty . color . ColorStream (out , color = color )
367399
368- # We'll traverse the spec in topo order as we graph it.
369- topo_order = topological_sort (spec , reverse = True , deptype = self .deptype )
400+ # We'll traverse the spec in topological order as we graph it.
401+ nodes_in_topological_order = topological_sort (spec , deptype = self .deptype )
370402
371403 # Work on a copy to be nondestructive
372404 spec = spec .copy ()
373- self ._nodes = spec .index ()
374405
375406 # Colors associated with each node in the DAG.
376407 # Edges are colored by the node they point to.
377- self ._name_to_color = dict ((name , self .colors [i % len (self .colors )])
378- for i , name in enumerate (topo_order ))
408+ self ._name_to_color = {
409+ spec .full_hash (): self .colors [i % len (self .colors )]
410+ for i , spec in enumerate (nodes_in_topological_order )
411+ }
379412
380413 # Frontier tracks open edges of the graph as it's written out.
381- self ._frontier = [[spec .name ]]
414+ self ._frontier = [[spec .full_hash () ]]
382415 while self ._frontier :
383416 # Find an unexpanded part of frontier
384417 i = find (self ._frontier , lambda f : len (f ) > 1 )
385418
386419 if i >= 0 :
387- # Expand frontier until there are enough columns for all
388- # children.
420+ # Expand frontier until there are enough columns for all children.
389421
390422 # Figure out how many back connections there are and
391423 # sort them so we do them in order
@@ -436,8 +468,8 @@ def write(self, spec, color=None, out=None):
436468
437469 else :
438470 # Just allow the expansion here.
439- name = self ._frontier [i ].pop (0 )
440- deps = [name ]
471+ dep_hash = self ._frontier [i ].pop (0 )
472+ deps = [dep_hash ]
441473 self ._frontier .insert (i , deps )
442474 self ._expand_right_line (i )
443475
@@ -453,18 +485,17 @@ def write(self, spec, color=None, out=None):
453485
454486 else :
455487 # Nothing to expand; add dependencies for a node.
456- name = topo_order .pop ()
457- node = self ._nodes [name ]
488+ node = nodes_in_topological_order .pop ()
458489
459490 # Find the named node in the frontier and draw it.
460- i = find (self ._frontier , lambda f : name in f )
461- self ._node_line (i , name )
491+ i = find (self ._frontier , lambda f : node . full_hash () in f )
492+ self ._node_line (i , node )
462493
463494 # Replace node with its dependencies
464495 self ._frontier .pop (i )
465- deps = node .dependencies (self .deptype )
496+ deps = node .dependencies (deptype = self .deptype )
466497 if deps :
467- deps = sorted ((d .name for d in deps ), reverse = True )
498+ deps = sorted ((d .full_hash () for d in deps ), reverse = True )
468499 self ._connect_deps (i , deps , "new-deps" ) # anywhere.
469500
470501 elif self ._frontier :
@@ -478,7 +509,7 @@ def graph_ascii(spec, node='o', out=None, debug=False,
478509 graph .indent = indent
479510 graph .node_character = node
480511 if deptype :
481- graph .deptype = canonical_deptype (deptype )
512+ graph .deptype = spack . dependency . canonical_deptype (deptype )
482513
483514 graph .write (spec , color = color , out = out )
484515
@@ -499,7 +530,7 @@ def graph_dot(specs, deptype='all', static=False, out=None):
499530
500531 if out is None :
501532 out = sys .stdout
502- deptype = canonical_deptype (deptype )
533+ deptype = spack . dependency . canonical_deptype (deptype )
503534
504535 def static_graph (spec , deptype ):
505536 pkg = spec .package
@@ -517,7 +548,7 @@ def dynamic_graph(spec, deptypes):
517548 nodes = set () # elements are (node key, node label)
518549 edges = set () # elements are (src key, dest key)
519550 for s in spec .traverse (deptype = deptype ):
520- nodes .add ((s .dag_hash (), s . name ))
551+ nodes .add ((s .dag_hash (), node_label ( s ) ))
521552 for d in s .dependencies (deptype = deptype ):
522553 edge = (s .dag_hash (), d .dag_hash ())
523554 edges .add (edge )
0 commit comments