diff --git a/lib/spack/spack/solver/asp.py b/lib/spack/spack/solver/asp.py index 92734e9afd5..940aae0a72b 100644 --- a/lib/spack/spack/solver/asp.py +++ b/lib/spack/spack/solver/asp.py @@ -3829,8 +3829,16 @@ def execute_explicit_splices(self): for splice_set in splice_config: target = splice_set["target"] replacement = spack.spec.Spec(splice_set["replacement"]) - assert replacement.abstract_hash - replacement.replace_hash() + + if not replacement.abstract_hash: + location = getattr( + splice_set["replacement"], "_start_mark", " at unknown line number" + ) + msg = f"Explicit splice replacement '{replacement}' does not include a hash.\n" + msg += f"{location}\n\n" + msg += " Splice replacements must be specified by hash" + raise InvalidSpliceError(msg) + transitive = splice_set.get("transitive", False) splice_triples.append((target, replacement, transitive)) @@ -3841,6 +3849,10 @@ def execute_explicit_splices(self): if target in current_spec: # matches root or non-root # e.g. mvapich2%gcc + + # The first iteration, we need to replace the abstract hash + if not replacement.concrete: + replacement.replace_hash() current_spec = current_spec.splice(replacement, transitive) new_key = NodeArgument(id=key.id, pkg=current_spec.name) specs[new_key] = current_spec @@ -4226,3 +4238,7 @@ def __init__(self, provided, conflicts): # Add attribute expected of the superclass interface self.required = None self.constraint_type = None + + +class InvalidSpliceError(spack.error.SpackError): + """For cases in which the splice configuration is invalid.""" diff --git a/lib/spack/spack/test/concretize.py b/lib/spack/spack/test/concretize.py index b56f05b6019..553d8fd6426 100644 --- a/lib/spack/spack/test/concretize.py +++ b/lib/spack/spack/test/concretize.py @@ -2306,6 +2306,30 @@ def test_explicit_splices( assert "hdf5 ^zmpi" in captured.err assert str(spec) in captured.err + def test_explicit_splice_fails_nonexistent(mutable_config, mock_packages, mock_store): + splice_info = {"target": "mpi", "replacement": "mpich/doesnotexist"} + spack.config.CONFIG.set("concretizer", {"splice": {"explicit": [splice_info]}}) + + with pytest.raises(spack.spec.InvalidHashError): + _ = spack.spec.Spec("hdf5^zmpi").concretized() + + def test_explicit_splice_fails_no_hash(mutable_config, mock_packages, mock_store): + splice_info = {"target": "mpi", "replacement": "mpich"} + spack.config.CONFIG.set("concretizer", {"splice": {"explicit": [splice_info]}}) + + with pytest.raises(spack.solver.asp.InvalidSpliceError, match="must be specified by hash"): + _ = spack.spec.Spec("hdf5^zmpi").concretized() + + def test_explicit_splice_non_match_nonexistent_succeeds( + mutable_config, mock_packages, mock_store + ): + """When we have a nonexistent splice configured but are not using it, don't fail.""" + splice_info = {"target": "will_not_match", "replacement": "nonexistent/doesnotexist"} + spack.config.CONFIG.set("concretizer", {"splice": {"explicit": [splice_info]}}) + spec = spack.spec.Spec("zlib").concretized() + # the main test is that it does not raise + assert not spec.spliced + @pytest.mark.db @pytest.mark.parametrize( "spec_str,mpi_name",