summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorHarmen Stoppels <me@harmenstoppels.nl>2024-07-08 14:53:51 +0200
committerGitHub <noreply@github.com>2024-07-08 14:53:51 +0200
commit297874bfed27caedf32d93bba550206729ba353e (patch)
tree450e58aac00f460a1e71216284f9da2eb59bb4e4 /lib
parent74398d74ace4b09ec9aabc9ce243b98ea4d7fada (diff)
downloadspack-297874bfed27caedf32d93bba550206729ba353e.tar.gz
spack-297874bfed27caedf32d93bba550206729ba353e.tar.bz2
spack-297874bfed27caedf32d93bba550206729ba353e.tar.xz
spack-297874bfed27caedf32d93bba550206729ba353e.zip
spec.py: fix __getitem__ looking outside of dag (#45090)
`Spec.__getitem__` queries dependent edges, which almost always point to nodes outside the sub-dag considered. It should only ever look at edges being traversed.
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/spack/spec.py28
-rw-r--r--lib/spack/spack/test/spec_dag.py20
2 files changed, 30 insertions, 18 deletions
diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py
index 5bdb00173d..0e00e03e98 100644
--- a/lib/spack/spack/spec.py
+++ b/lib/spack/spack/spec.py
@@ -4258,29 +4258,21 @@ class Spec:
csv = query_parameters.pop().strip()
query_parameters = re.split(r"\s*,\s*", csv)
- # In some cases a package appears multiple times in the same DAG for *distinct*
- # specs. For example, a build-type dependency may itself depend on a package
- # the current spec depends on, but their specs may differ. Therefore we iterate
- # in an order here that prioritizes the build, test and runtime dependencies;
- # only when we don't find the package do we consider the full DAG.
order = lambda: itertools.chain(
- self.traverse(deptype="link"),
- self.dependencies(deptype=dt.BUILD | dt.RUN | dt.TEST),
- self.traverse(), # fall back to a full search
+ self.traverse_edges(deptype=dt.LINK, order="breadth", cover="edges"),
+ self.edges_to_dependencies(depflag=dt.BUILD | dt.RUN | dt.TEST),
+ self.traverse_edges(deptype=dt.ALL, order="breadth", cover="edges"),
)
+ # Consider runtime dependencies and direct build/test deps before transitive dependencies,
+ # and prefer matches closest to the root.
try:
child: Spec = next(
- itertools.chain(
- # Regular specs
- (x for x in order() if x.name == name),
- (
- x
- for x in order()
- if (not x.virtual)
- and any(name in edge.virtuals for edge in x.edges_from_dependents())
- ),
- (x for x in order() if (not x.virtual) and x.package.provides(name)),
+ e.spec
+ for e in itertools.chain(
+ (e for e in order() if e.spec.name == name or name in e.virtuals),
+ # for historical reasons
+ (e for e in order() if e.spec.concrete and e.spec.package.provides(name)),
)
)
except StopIteration:
diff --git a/lib/spack/spack/test/spec_dag.py b/lib/spack/spack/test/spec_dag.py
index da85be6d1c..39e9ff66a1 100644
--- a/lib/spack/spack/test/spec_dag.py
+++ b/lib/spack/spack/test/spec_dag.py
@@ -1105,3 +1105,23 @@ def test_indexing_prefers_direct_or_transitive_link_deps():
# Ensure that the full DAG is still searched
assert root["a2"]
+
+
+def test_getitem_sticks_to_subdag():
+ """Test that indexing on Spec by virtual does not traverse outside the dag, which happens in
+ the unlikely case someone would rewrite __getitem__ in terms of edges_from_dependents instead
+ of edges_to_dependencies."""
+ x, y, z = Spec("x"), Spec("y"), Spec("z")
+ x.add_dependency_edge(z, depflag=dt.LINK, virtuals=("virtual",))
+ y.add_dependency_edge(z, depflag=dt.LINK, virtuals=())
+ assert x["virtual"].name == "z"
+ with pytest.raises(KeyError):
+ y["virtual"]
+
+
+def test_getitem_finds_transitive_virtual():
+ x, y, z = Spec("x"), Spec("y"), Spec("z")
+ x.add_dependency_edge(z, depflag=dt.LINK, virtuals=())
+ x.add_dependency_edge(y, depflag=dt.LINK, virtuals=())
+ y.add_dependency_edge(z, depflag=dt.LINK, virtuals=("virtual",))
+ assert x["virtual"].name == "z"