diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py index 7d8266400e..1f30f7e923 100644 --- a/lib/spack/spack/spec.py +++ b/lib/spack/spack/spec.py @@ -3898,9 +3898,13 @@ def satisfies(self, other: Union[str, "Spec"], deps: bool = True) -> bool: if not self._dependencies: return False - # If we arrived here, then rhs is abstract. At the moment we don't care about the edge - # structure of an abstract DAG, so we check if any edge could satisfy the properties - # we ask for. + # If we arrived here, the lhs root node satisfies the rhs root node. Now we need to check + # all the edges that have an abstract parent, and verify that they match some edge in the + # lhs. + # + # It might happen that the rhs brings in concrete sub-DAGs. For those we don't need to + # verify the edge properties, cause everything is encoded in the hash of the nodes that + # will be verified later. lhs_edges: Dict[str, Set[DependencySpec]] = collections.defaultdict(set) for rhs_edge in other.traverse_edges(root=False, cover="edges"): # If we are checking for ^mpi we need to verify if there is any edge @@ -3910,6 +3914,10 @@ def satisfies(self, other: Union[str, "Spec"], deps: bool = True) -> bool: if not rhs_edge.virtuals: continue + # Skip edges from a concrete sub-DAG + if rhs_edge.parent.concrete: + continue + if not lhs_edges: # Construct a map of the link/run subDAG + direct "build" edges, # keyed by dependency name diff --git a/lib/spack/spack/test/concretize.py b/lib/spack/spack/test/concretize.py index cf20934a00..cc4bec8e3c 100644 --- a/lib/spack/spack/test/concretize.py +++ b/lib/spack/spack/test/concretize.py @@ -2448,6 +2448,20 @@ def _default_libc(self): s = Spec("a %gcc@=13.2.0").concretized() assert s.satisfies("%gcc@13.2.0") + @pytest.mark.regression("43267") + def test_spec_with_build_dep_from_json(self, tmp_path): + """Tests that we can correctly concretize a spec, when we express its dependency as a + concrete spec to be read from JSON. + + The bug was triggered by missing virtuals on edges that were trimmed from pure build + dependencies. + """ + build_dep = Spec("dttop").concretized() + json_file = tmp_path / "build.json" + json_file.write_text(build_dep.to_json()) + s = Spec(f"dtuse ^{str(json_file)}").concretized() + assert s["dttop"].dag_hash() == build_dep.dag_hash() + @pytest.fixture() def duplicates_test_repository(): diff --git a/var/spack/repos/builtin.mock/packages/dtbuild1/package.py b/var/spack/repos/builtin.mock/packages/dtbuild1/package.py index 0f9c6e2e99..cb83bbc9f1 100644 --- a/var/spack/repos/builtin.mock/packages/dtbuild1/package.py +++ b/var/spack/repos/builtin.mock/packages/dtbuild1/package.py @@ -16,6 +16,6 @@ class Dtbuild1(Package): version("1.0", md5="0123456789abcdef0123456789abcdef") version("0.5", md5="fedcba9876543210fedcba9876543210") - depends_on("dtbuild2", type="build") + depends_on("vdtbuild2", type="build") depends_on("dtlink2") depends_on("dtrun2", type="run") diff --git a/var/spack/repos/builtin.mock/packages/dtbuild2/package.py b/var/spack/repos/builtin.mock/packages/dtbuild2/package.py index 7aae2109ce..59316434f8 100644 --- a/var/spack/repos/builtin.mock/packages/dtbuild2/package.py +++ b/var/spack/repos/builtin.mock/packages/dtbuild2/package.py @@ -13,3 +13,5 @@ class Dtbuild2(Package): url = "http://www.example.com/dtbuild2-1.0.tar.gz" version("1.0", md5="0123456789abcdef0123456789abcdef") + + provides("vdtbuild2")