Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/develop' into fnal-develop
Browse files Browse the repository at this point in the history
  • Loading branch information
greenc-FNAL committed Nov 25, 2024
2 parents 0ab2c67 + 8e91430 commit c16d7fb
Show file tree
Hide file tree
Showing 86 changed files with 866 additions and 485 deletions.
7 changes: 7 additions & 0 deletions .github/workflows/build-containers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ jobs:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683

- name: Determine latest release tag
id: latest
run: |
git fetch --quiet --tags
echo "tag=$(git tag --list --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1)" | tee -a $GITHUB_OUTPUT
- uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96
id: docker_meta
with:
Expand All @@ -71,6 +77,7 @@ jobs:
type=semver,pattern={{major}}
type=ref,event=branch
type=ref,event=pr
type=raw,value=latest,enable=${{ github.ref == format('refs/tags/{0}', steps.latest.outputs.tag) }}
- name: Generate the Dockerfile
env:
Expand Down
13 changes: 8 additions & 5 deletions lib/spack/spack/build_environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,9 @@ def __init__(self, *roots: spack.spec.Spec, context: Context):
elif context == Context.RUN:
self.root_depflag = dt.RUN | dt.LINK

def accept(self, item):
return True

def neighbors(self, item):
spec = item.edge.spec
if spec.dag_hash() in self.root_hashes:
Expand Down Expand Up @@ -919,19 +922,19 @@ def effective_deptypes(
a flag specifying in what way they do so. The list is ordered topologically
from root to leaf, meaning that environment modifications should be applied
in reverse so that dependents override dependencies, not the other way around."""
visitor = traverse.TopoVisitor(
EnvironmentVisitor(*specs, context=context),
key=lambda x: x.dag_hash(),
topo_sorted_edges = traverse.traverse_topo_edges_generator(
traverse.with_artificial_edges(specs),
visitor=EnvironmentVisitor(*specs, context=context),
key=traverse.by_dag_hash,
root=True,
all_edges=True,
)
traverse.traverse_depth_first_with_visitor(traverse.with_artificial_edges(specs), visitor)

# Dictionary with "no mode" as default value, so it's easy to write modes[x] |= flag.
use_modes = defaultdict(lambda: UseMode(0))
nodes_with_type = []

for edge in visitor.edges:
for edge in topo_sorted_edges:
parent, child, depflag = edge.parent, edge.spec, edge.depflag

# Mark the starting point
Expand Down
7 changes: 1 addition & 6 deletions lib/spack/spack/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,12 +325,7 @@ def write(self, spec, color=None, out=None):
self._out = llnl.util.tty.color.ColorStream(out, color=color)

# We'll traverse the spec in topological order as we graph it.
nodes_in_topological_order = [
edge.spec
for edge in spack.traverse.traverse_edges_topo(
[spec], direction="children", deptype=self.depflag
)
]
nodes_in_topological_order = list(spec.traverse(order="topo", deptype=self.depflag))
nodes_in_topological_order.reverse()

# Work on a copy to be nondestructive
Expand Down
21 changes: 7 additions & 14 deletions lib/spack/spack/install_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,23 +375,16 @@ def phase_tests(self, builder, phase_name: str, method_names: List[str]):

for name in method_names:
try:
# Prefer the method in the package over the builder's.
# We need this primarily to pick up arbitrarily named test
# methods but also some build-time checks.
fn = getattr(builder.pkg, name, getattr(builder, name))

msg = f"RUN-TESTS: {phase_name}-time tests [{name}]"
print_message(logger, msg, verbose)

fn()

fn = getattr(builder, name, None) or getattr(builder.pkg, name)
except AttributeError as e:
msg = f"RUN-TESTS: method not implemented [{name}]"
print_message(logger, msg, verbose)

self.add_failure(e, msg)
print_message(logger, f"RUN-TESTS: method not implemented [{name}]", verbose)
self.add_failure(e, f"RUN-TESTS: method not implemented [{name}]")
if fail_fast:
break
continue

print_message(logger, f"RUN-TESTS: {phase_name}-time tests [{name}]", verbose)
fn()

if have_tests:
print_message(logger, "Completed testing", verbose)
Expand Down
13 changes: 13 additions & 0 deletions lib/spack/spack/test/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,18 @@ def test_ascii_graph_mpileaks(config, mock_packages, monkeypatch):
o | libdwarf
|/
o libelf
"""
or graph_str
== r"""o mpileaks
|\
| o callpath
|/|
| o dyninst
| |\
o | | mpich
/ /
| o libdwarf
|/
o libelf
"""
)
23 changes: 23 additions & 0 deletions lib/spack/spack/test/traverse.py
Original file line number Diff line number Diff line change
Expand Up @@ -431,3 +431,26 @@ def test_traverse_nodes_no_deps(abstract_specs_dtuse):
]
outputs = [x for x in traverse.traverse_nodes(inputs, deptype=dt.NONE)]
assert outputs == [abstract_specs_dtuse["dtuse"], abstract_specs_dtuse["dtlink5"]]


@pytest.mark.parametrize("cover", ["nodes", "edges"])
def test_topo_is_bfs_for_trees(cover):
"""For trees, both DFS and BFS produce a topological order, but BFS is the most sensible for
our applications, where we typically want to avoid that transitive dependencies shadow direct
depenencies in global search paths, etc. This test ensures that for trees, the default topo
order coincides with BFS."""
binary_tree = create_dag(
nodes=["A", "B", "C", "D", "E", "F", "G"],
edges=(
("A", "B", "all"),
("A", "C", "all"),
("B", "D", "all"),
("B", "E", "all"),
("C", "F", "all"),
("C", "G", "all"),
),
)

assert list(traverse.traverse_nodes([binary_tree["A"]], order="topo", cover=cover)) == list(
traverse.traverse_nodes([binary_tree["A"]], order="breadth", cover=cover)
)
149 changes: 51 additions & 98 deletions lib/spack/spack/traverse.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,70 +115,6 @@ def neighbors(self, item):
return self.visitor.neighbors(item)


class TopoVisitor:
"""Visitor that can be used in :py:func:`depth-first traversal
<spack.traverse.traverse_depth_first_with_visitor>` to generate
a topologically ordered list of specs.
Algorithm based on "Section 22.4: Topological sort", Introduction to Algorithms
(2001, 2nd edition) by Cormen, Thomas H.; Leiserson, Charles E.; Rivest, Ronald L.;
Stein, Clifford.
Summary of the algorithm: prepend each vertex to a list in depth-first post-order,
not following edges to nodes already seen. This ensures all descendants occur after
their parent, yielding a topological order.
Note: in this particular implementation we collect the *edges* through which the
vertices are discovered, meaning that a topological order of *vertices* is obtained
by taking the specs pointed to: ``map(lambda edge: edge.spec, visitor.edges)``.
Lastly, ``all_edges=True`` can be used to retrieve a list of all reachable
edges, with the property that for each vertex all in-edges precede all out-edges.
"""

def __init__(self, visitor, key=id, root=True, all_edges=False):
"""
Arguments:
visitor: visitor that implements accept(), pre(), post() and neighbors()
key: uniqueness key for nodes
root (bool): Whether to include the root node.
all_edges (bool): when ``False`` (default): Each node is reached once,
and ``map(lambda edge: edge.spec, visitor.edges)`` is topologically
ordered. When ``True``, every edge is listed, ordered such that for
each node all in-edges precede all out-edges.
"""
self.visited = set()
self.visitor = visitor
self.key = key
self.root = root
self.reverse_order = []
self.all_edges = all_edges

def accept(self, item):
if self.key(item.edge.spec) not in self.visited:
return True
if self.all_edges and (self.root or item.depth > 0):
self.reverse_order.append(item.edge)
return False

def pre(self, item):
# You could add a temporary marker for cycle detection
# that's cleared in `post`, but we assume no cycles.
pass

def post(self, item):
self.visited.add(self.key(item.edge.spec))
if self.root or item.depth > 0:
self.reverse_order.append(item.edge)

def neighbors(self, item):
return self.visitor.neighbors(item)

@property
def edges(self):
"""Return edges in topological order (in-edges precede out-edges)."""
return list(reversed(self.reverse_order))


def get_visitor_from_args(
cover, direction, depflag: Union[dt.DepFlag, dt.DepTypes], key=id, visited=None, visitor=None
):
Expand Down Expand Up @@ -381,39 +317,54 @@ def traverse_breadth_first_tree_nodes(parent_id, edges, key=id, depth=0):
yield item


# Topologic order
def traverse_edges_topo(
specs,
direction="children",
deptype: Union[dt.DepFlag, dt.DepTypes] = "all",
key=id,
root=True,
all_edges=False,
):
def traverse_topo_edges_generator(edges, visitor, key=id, root=True, all_edges=False):
"""
Returns a list of edges in topological order, in the sense that all in-edges of a
vertex appear before all out-edges. By default with direction=children edges are
directed from dependent to dependency. With directions=parents, the edges are
directed from dependency to dependent.
Returns a list of edges in topological order, in the sense that all in-edges of a vertex appear
before all out-edges.
Arguments:
specs (list): List of root specs (considered to be depth 0)
direction (str): ``children`` (edges are directed from dependent to dependency)
or ``parents`` (edges are flipped / directed from dependency to dependent)
deptype: allowed dependency types
edges (list): List of EdgeAndDepth instances
visitor: visitor instance that defines the sub-DAG to traverse
key: function that takes a spec and outputs a key for uniqueness test.
root (bool): Yield the root nodes themselves
all_edges (bool): When ``False`` only one in-edge per node is returned, when
``True`` all reachable edges are returned.
"""
if not isinstance(deptype, dt.DepFlag):
deptype = dt.canonicalize(deptype)
visitor: Union[BaseVisitor, ReverseVisitor, TopoVisitor] = BaseVisitor(deptype)
if direction == "parents":
visitor = ReverseVisitor(visitor, deptype)
visitor = TopoVisitor(visitor, key=key, root=root, all_edges=all_edges)
traverse_depth_first_with_visitor(with_artificial_edges(specs), visitor)
return visitor.edges
# Topo order used to be implemented using a DFS visitor, which was relatively efficient in that
# it would visit nodes only once, and it was composable. In practice however it would yield a
# DFS order on DAGs that are trees, which is undesirable in many cases. For example, a list of
# search paths for trees is better in BFS order, so that direct dependencies are listed first.
# That way a transitive dependency cannot shadow a direct one. So, here we collect the sub-DAG
# of interest and then compute a topological order that is the most breadth-first possible.

# maps node identifier to the number of remaining in-edges
in_edge_count = defaultdict(int)
# maps parent identifier to a list of edges, where None is a special identifier
# for the artificial root/source.
node_to_edges = defaultdict(list)
for edge in traverse_breadth_first_edges_generator(
edges, CoverEdgesVisitor(visitor, key=key), root=True, depth=False
):
in_edge_count[key(edge.spec)] += 1
parent_id = key(edge.parent) if edge.parent is not None else None
node_to_edges[parent_id].append(edge)

queue = [None]

while queue:
for edge in node_to_edges[queue.pop(0)]:
child_id = key(edge.spec)
in_edge_count[child_id] -= 1

should_yield = root or edge.parent is not None

if all_edges and should_yield:
yield edge

if in_edge_count[child_id] == 0:
if not all_edges and should_yield:
yield edge
queue.append(key(edge.spec))


# High-level API: traverse_edges, traverse_nodes, traverse_tree.
Expand Down Expand Up @@ -462,20 +413,20 @@ def traverse_edges(
A generator that yields ``DependencySpec`` if depth is ``False``
or a tuple of ``(depth, DependencySpec)`` if depth is ``True``.
"""

# validate input
if order == "topo":
if cover == "paths":
raise ValueError("cover=paths not supported for order=topo")
# TODO: There is no known need for topological ordering of traversals (edge or node)
# with an initialized "visited" set. Revisit if needed.
if visited is not None:
raise ValueError("visited set not implemented for order=topo")
return traverse_edges_topo(
specs, direction, deptype, key, root, all_edges=cover == "edges"
)
elif order not in ("post", "pre", "breadth"):
raise ValueError(f"Unknown order {order}")

# In topo traversal we need to construct a sub-DAG including all edges even if we are yielding
# a subset of them, hence "paths".
_cover = "paths" if order == "topo" else cover
visitor = get_visitor_from_args(_cover, direction, deptype, key, visited)
root_edges = with_artificial_edges(specs)
visitor = get_visitor_from_args(cover, direction, deptype, key, visited)

# Depth-first
if order in ("pre", "post"):
Expand All @@ -484,8 +435,10 @@ def traverse_edges(
)
elif order == "breadth":
return traverse_breadth_first_edges_generator(root_edges, visitor, root, depth)

raise ValueError("Unknown order {}".format(order))
elif order == "topo":
return traverse_topo_edges_generator(
root_edges, visitor, key, root, all_edges=cover == "edges"
)


def traverse_nodes(
Expand Down
1 change: 1 addition & 0 deletions var/spack/repos/builtin/packages/apex/package.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class Apex(CMakePackage):

version("develop", branch="develop")
version("master", branch="master")
version("2.7.0", sha256="81cd7e8dbea35cec2360d6404e20f7527f66410614f06a73c8c782ac2cfdb0b0")
version("2.6.5", sha256="2ba29a1198c904ac209fc6bc02962304a1416443b249f34ef96889aff39644ce")
version("2.6.4", sha256="281a673f447762a488577beaa60e48d88cb6354f220457cf8f05c1de2e1fce70")
version("2.6.3", sha256="7fef12937d3bd1271a01abe44cb931b1d63823fb5c74287a332f3012ed7297d5")
Expand Down
23 changes: 15 additions & 8 deletions var/spack/repos/builtin/packages/awscli-v2/package.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,31 @@ class AwscliV2(PythonPackage):

maintainers("climbfuji", "teaguesterling")

version("2.22.4", sha256="56c6170f3be830afef2dea60fc3fd7ed14cf2ca2efba055c085fe6a7c4de358e")
version("2.15.53", sha256="a4f5fd4e09b8f2fb3d2049d0610c7b0993f9aafaf427f299439f05643b25eb4b")
version("2.13.22", sha256="dd731a2ba5973f3219f24c8b332a223a29d959493c8a8e93746d65877d02afc1")

with default_args(type="build"):
depends_on("[email protected]:3.9.0", when="@2.22:")
depends_on("[email protected]:3.8.0", when="@:2.15")

with default_args(type=("build", "run")):
depends_on("[email protected]:")
depends_on("[email protected]:3.8.0")
depends_on("[email protected]:0.4.6")
depends_on("[email protected]:0.19")
# Remove upper bound to enable Python 3.12 support
# depends_on("[email protected]:40.0.1")
depends_on("[email protected]:")
depends_on("py-cryptography@40:43.0.1", when="@2.22:")
depends_on("[email protected]:40.0.1", when="@:2.15")
depends_on("[email protected]:0.17.21")
depends_on("[email protected]:0.2.7", when="^python@:3.9")
depends_on("[email protected]:", when="@2.22:")
# Upper bound relaxed for Python 3.13 support
# depends_on("[email protected]:0.2.8", when="@2.22:")
depends_on("[email protected]:0.2.7", when="@:2.15")
depends_on("[email protected]:3.0.38")
depends_on("[email protected]:1.8")
depends_on("py-awscrt@0.16.4:0.16.16", when="@2.13")
depends_on("py-awscrt@0.19.18:0.22.0", when="@2.22:")
depends_on("[email protected]:0.19.19", when="@2.15")
depends_on("[email protected]:2.8.2")
depends_on("[email protected]:0.16.16", when="@2.13")
depends_on("[email protected]:2.9.0", when="@2.22:")
depends_on("[email protected]:2.8.2", when="@:2.15")
depends_on("[email protected]:1.0")
depends_on("[email protected]:1.26")

Expand Down
Loading

0 comments on commit c16d7fb

Please sign in to comment.