summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorHarmen Stoppels <harmenstoppels@gmail.com>2022-10-25 16:47:50 +0200
committerGitHub <noreply@github.com>2022-10-25 16:47:50 +0200
commit09e0bd55c2ab2d4678efc693c3d3b79ef7c238f4 (patch)
tree063e5f885811c9817d0219e8bca89a1232f4f3f2 /lib
parent00ae74f40e4ac918f4826bf3fb61da6f28af5051 (diff)
downloadspack-09e0bd55c2ab2d4678efc693c3d3b79ef7c238f4.tar.gz
spack-09e0bd55c2ab2d4678efc693c3d3b79ef7c238f4.tar.bz2
spack-09e0bd55c2ab2d4678efc693c3d3b79ef7c238f4.tar.xz
spack-09e0bd55c2ab2d4678efc693c3d3b79ef7c238f4.zip
spec.py: prefer transitive link and direct build/run/test deps (#33498)
Due to reuse concretization, we may generate DAGs with two occurrences of the same package corresponding to distinct specs. This happens when build dependencies are reused, since their dependencies are ignored in concretization. This caused a regression, for example: `spec['openssl']` would take the 'openssl' of the build dep `cmake`, instead of the direct `openssl` dependency, simply because the edge to `cmake` was traversed first and we do depth first traversal. One solution that was discussed is to limit `spec[name]` to just direct deps, or direct deps + transitive link deps, but this is too breaking. Instead, this PR simply prioritizes transitive link and direct build/run/test deps, and then falls back to a full DAG traversal. So, it's just about order of iteration.
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/spack/spec.py15
-rw-r--r--lib/spack/spack/test/spec_dag.py35
2 files changed, 48 insertions, 2 deletions
diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py
index 4c1421e1e8..9256be5803 100644
--- a/lib/spack/spack/spec.py
+++ b/lib/spack/spack/spec.py
@@ -3970,12 +3970,23 @@ class Spec(object):
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=("build", "run", "test")),
+ self.traverse(), # fall back to a full search
+ )
+
try:
value = next(
itertools.chain(
# Regular specs
- (x for x in self.traverse() if x.name == name),
- (x for x in self.traverse() if (not x.virtual) and x.package.provides(name)),
+ (x for x in order() if x.name == name),
+ (x for x in order() if (not x.virtual) and x.package.provides(name)),
)
)
except StopIteration:
diff --git a/lib/spack/spack/test/spec_dag.py b/lib/spack/spack/test/spec_dag.py
index 66e5a0bc2f..898ae15f74 100644
--- a/lib/spack/spack/test/spec_dag.py
+++ b/lib/spack/spack/test/spec_dag.py
@@ -1068,3 +1068,38 @@ def test_adding_same_deptype_with_the_same_name_raises(
p.add_dependency_edge(c1, deptype=c1_deptypes)
with pytest.raises(spack.error.SpackError):
p.add_dependency_edge(c2, deptype=c2_deptypes)
+
+
+@pytest.mark.regression("33499")
+def test_indexing_prefers_direct_or_transitive_link_deps():
+ # Test whether spec indexing prefers direct/transitive link type deps over deps of
+ # build/run/test deps, and whether it does fall back to a full dag search.
+ root = Spec("root")
+
+ # Use a and z to since we typically traverse by edges sorted alphabetically.
+ a1 = Spec("a1")
+ a2 = Spec("a2")
+ z1 = Spec("z1")
+ z2 = Spec("z2")
+
+ # Same package, different spec.
+ z3_flavor_1 = Spec("z3 +through_a1")
+ z3_flavor_2 = Spec("z3 +through_z1")
+
+ root.add_dependency_edge(a1, deptype=("build", "run", "test"))
+
+ # unique package as a dep of a build/run/test type dep.
+ a1.add_dependency_edge(a2, deptype="all")
+ a1.add_dependency_edge(z3_flavor_1, deptype="all")
+
+ # chain of link type deps root -> z1 -> z2 -> z3
+ root.add_dependency_edge(z1, deptype="link")
+ z1.add_dependency_edge(z2, deptype="link")
+ z2.add_dependency_edge(z3_flavor_2, deptype="link")
+
+ # Indexing should prefer the link-type dep.
+ assert "through_z1" in root["z3"].variants
+ assert "through_a1" in a1["z3"].variants
+
+ # Ensure that the full DAG is still searched
+ assert root["a2"]