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.
This commit is contained in:
parent
00ae74f40e
commit
09e0bd55c2
@ -3970,12 +3970,23 @@ def __getitem__(self, name):
|
||||
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:
|
||||
|
@ -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"]
|
||||
|
Loading…
Reference in New Issue
Block a user