merge three branches into one

This commit is contained in:
Todd Gamblin 2025-05-20 01:00:27 -07:00
parent 485291ef20
commit 07a8f6235b
No known key found for this signature in database
GPG Key ID: C16729F1AACF66C6
2 changed files with 58 additions and 69 deletions

View File

@ -2673,7 +2673,7 @@ def name_and_dependency_types(s: str) -> Tuple[str, dt.DepFlag]:
return name, depflag return name, depflag
def spec_and_dependency_types( def spec_and_dependency_types(
s: Union[Spec, Tuple[Spec, str]] s: Union[Spec, Tuple[Spec, str]],
) -> Tuple[Spec, dt.DepFlag]: ) -> Tuple[Spec, dt.DepFlag]:
"""Given a non-string key in the literal, extracts the spec """Given a non-string key in the literal, extracts the spec
and its dependency types. and its dependency types.
@ -3838,90 +3838,76 @@ def _cmp_iter(self):
# need for the complexity here. It was not clear at the time of writing that how # need for the complexity here. It was not clear at the time of writing that how
# much optimization was possible in `spack.traverse`. # much optimization was possible in `spack.traverse`.
if not self._dependencies: sorted_l1_edges = None
# Spec has no dependencies -- simplest case edge_list = None
def nodes(): node_ids = None
yield self._cmp_node # just yield this node
def edges(): def nodes():
nonlocal sorted_l1_edges
nonlocal edge_list
nonlocal node_ids
yield self._cmp_node # always yield the root (this node)
if not self._dependencies: # done if there are no dependencies
return return
yield
elif not any( # first level: yield sorted direct dependencies (inlines first level of BFS)
dep.spec._dependencies for deplist in self._dependencies.values() for dep in deplist deps_have_deps = False
): sorted_l1_edges = self.edges_to_dependencies(depflag=dt.ALL)
# Spec has dependencies, but none of them have any dependencies. Avoid calling traverse if len(sorted_l1_edges) > 1:
# need to track visited nodes or do any of the other fancy and expensive sorted_l1_edges = spack.traverse.sort_edges(sorted_l1_edges)
# things traverse_edges does.
sorted_edges = None
def nodes(): for edge in sorted_l1_edges:
# We generate sorted_edges once, in nodes, lazily. If the comparison can yield edge.spec._cmp_node
# end with just _cmp_node, we never have to sort. if edge.spec._dependencies:
nonlocal sorted_edges deps_have_deps = True
# yield the root node comparator if not deps_have_deps: # done if level 1 specs have no dependencies
yield self._cmp_node return
# sort only if roots were equal and caller needs to look at deps # second level: now it's general; we need full traverse() to track visited nodes
sorted_edges = spack.traverse.sort_edges( l1_specs = [edge.spec for edge in sorted_l1_edges]
self.edges_to_dependencies(depflag=dt.ALL)
)
# yield dependency node comparators in BFS order
for edge in sorted_edges:
yield edge.spec._cmp_node
def edges():
# we've already sorted to yield dependency nodes in order, now yield
# edges for comparison in the same order, with BFS-consistent ids
for i, edge in enumerate(sorted_edges, start=1):
def yield_edge(i=i, edge=edge):
yield 0 # id of root
yield i # id of dependency in BFS order
yield edge.depflag
yield edge.virtuals
yield yield_edge
else:
# Spec has dependencies, and they have dependencies. Need to do a full traversal here.
# We need a way to compare edges consistently between two arbitrary specs.
# node_ids generates consistent node ids based on BFS traversal order. # node_ids generates consistent node ids based on BFS traversal order.
node_ids = collections.defaultdict(lambda: len(node_ids)) node_ids = collections.defaultdict(lambda: len(node_ids))
node_ids[id(self)] # self is 0
for spec in l1_specs:
node_ids[id(spec)] # l1 starts at 1
# To avoid traversing twice, we put edges in this list as we yield the nodes.
# edges() yields the edge functions later, if necessary.
edge_list = [] edge_list = []
for edge in spack.traverse.traverse_edges(
l1_specs, order="breadth", cover="edges", root=False, visited=set([0])
):
# yield each node only once, and generate a consistent id for it the
# first time it's encountered.
if id(edge.spec) not in node_ids:
yield edge.spec._cmp_node
node_ids[id(edge.spec)]
def nodes(): if edge.parent is None: # skip fake edge to root
# Breadth-first edge traversal, yielding nodes as they're encountered. continue
for edge in self.traverse_edges(order="breadth", cover="edges", deptype=dt.ALL):
# yield each node only once, and generate a consistent id for it the
# first time it's encountered.
if id(edge.spec) not in node_ids:
yield edge.spec._cmp_node
node_ids[id(edge.spec)]
# skip fake edge to root edge_list.append(
if edge.parent is None: (
continue node_ids[id(edge.parent)],
node_ids[id(edge.spec)],
edge.depflag,
edge.virtuals,
)
)
# create "edges" with the ids we generated above def edges():
def yield_edge(edge=edge): # no edges in single-node graph
yield node_ids[id(edge.parent)] if not self._dependencies:
yield node_ids[id(edge.spec)] return
yield edge.depflag
yield edge.virtuals
edge_list.append(yield_edge) # level 1 edges all start with zero
for i, edge in enumerate(sorted_l1_edges, start=1):
yield (0, i, edge.depflag, edge.virtuals)
def edges(): # yield remaining edges in the order they were encountered during traversal
# yield edges in the order they were encountered during traversal if edge_list:
for yield_edge_func in edge_list: yield from edge_list
yield yield_edge_func
yield nodes yield nodes
yield edges yield edges

View File

@ -2140,6 +2140,9 @@ def test_spec_ordering(specs_in_expected_order):
], ],
) )
def test_spec_canonical_comparison_form(spec, expected_tuplified): def test_spec_canonical_comparison_form(spec, expected_tuplified):
print()
print()
print()
assert llnl.util.lang.tuplify(Spec(spec)._cmp_iter) == expected_tuplified assert llnl.util.lang.tuplify(Spec(spec)._cmp_iter) == expected_tuplified