summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/spack/binary_distribution.py6
-rw-r--r--lib/spack/spack/solver/asp.py33
-rw-r--r--lib/spack/spack/test/concretize.py78
3 files changed, 114 insertions, 3 deletions
diff --git a/lib/spack/spack/binary_distribution.py b/lib/spack/spack/binary_distribution.py
index 8cfb891640..159b07eb94 100644
--- a/lib/spack/spack/binary_distribution.py
+++ b/lib/spack/spack/binary_distribution.py
@@ -230,7 +230,11 @@ class BinaryCacheIndex:
)
return
- spec_list = db.query_local(installed=False, in_buildcache=True)
+ spec_list = [
+ s
+ for s in db.query_local(installed=any, in_buildcache=any)
+ if s.external or db.query_local_by_spec_hash(s.dag_hash()).in_buildcache
+ ]
for indexed_spec in spec_list:
dag_hash = indexed_spec.dag_hash()
diff --git a/lib/spack/spack/solver/asp.py b/lib/spack/spack/solver/asp.py
index 8ad8d5c5c0..4d9b5a0c39 100644
--- a/lib/spack/spack/solver/asp.py
+++ b/lib/spack/spack/solver/asp.py
@@ -3134,6 +3134,25 @@ def _develop_specs_from_env(spec, env):
spec.constrain(dev_info["spec"])
+def _is_reusable_external(packages, spec: spack.spec.Spec) -> bool:
+ """Returns true iff spec is an external that can be reused.
+
+ Arguments:
+ packages: the packages configuration
+ spec: the spec to check
+ """
+ for name in {spec.name, *(p.name for p in spec.package.provided)}:
+ for entry in packages.get(name, {}).get("externals", []):
+ if (
+ spec.satisfies(entry["spec"])
+ and spec.external_path == entry.get("prefix")
+ and spec.external_modules == entry.get("modules")
+ ):
+ return True
+
+ return False
+
+
class Solver:
"""This is the main external interface class for solving.
@@ -3181,8 +3200,18 @@ class Solver:
# Specs from buildcaches
try:
- index = spack.binary_distribution.update_cache_and_get_specs()
- reusable_specs.extend(index)
+ # Specs in a build cache that depend on externals are reusable as long as local
+ # config has matching externals. This should guard against picking up binaries
+ # linked against externals not available locally, while still supporting the use
+ # case of distributing binaries across machines with similar externals.
+ packages = spack.config.get("packages")
+ reusable_specs.extend(
+ [
+ s
+ for s in spack.binary_distribution.update_cache_and_get_specs()
+ if not s.external or _is_reusable_external(packages, s)
+ ]
+ )
except (spack.binary_distribution.FetchCacheError, IndexError):
# this is raised when no mirrors had indices.
# TODO: update mirror configuration so it can indicate that the
diff --git a/lib/spack/spack/test/concretize.py b/lib/spack/spack/test/concretize.py
index 2818aad042..2eb75edb6c 100644
--- a/lib/spack/spack/test/concretize.py
+++ b/lib/spack/spack/test/concretize.py
@@ -2427,3 +2427,81 @@ class TestConcretizeEdges:
s = Spec("blas-only-client ^openblas").concretized()
assert s.satisfies("^[virtuals=blas] openblas")
assert not s.satisfies("^[virtuals=blas,lapack] openblas")
+
+
+def test_reusable_externals_match(mock_packages, tmpdir):
+ spec = Spec("mpich@4.1%gcc@13.1.0~debug build_system=generic arch=linux-ubuntu23.04-zen2")
+ spec.external_path = tmpdir.strpath
+ spec.external_modules = ["mpich/4.1"]
+ spec._mark_concrete()
+ assert spack.solver.asp._is_reusable_external(
+ {
+ "mpich": {
+ "externals": [
+ {"spec": "mpich@4.1", "prefix": tmpdir.strpath, "modules": ["mpich/4.1"]}
+ ]
+ }
+ },
+ spec,
+ )
+
+
+def test_reusable_externals_match_virtual(mock_packages, tmpdir):
+ spec = Spec("mpich@4.1%gcc@13.1.0~debug build_system=generic arch=linux-ubuntu23.04-zen2")
+ spec.external_path = tmpdir.strpath
+ spec.external_modules = ["mpich/4.1"]
+ spec._mark_concrete()
+ assert spack.solver.asp._is_reusable_external(
+ {
+ "mpi": {
+ "externals": [
+ {"spec": "mpich@4.1", "prefix": tmpdir.strpath, "modules": ["mpich/4.1"]}
+ ]
+ }
+ },
+ spec,
+ )
+
+
+def test_reusable_externals_different_prefix(mock_packages, tmpdir):
+ spec = Spec("mpich@4.1%gcc@13.1.0~debug build_system=generic arch=linux-ubuntu23.04-zen2")
+ spec.external_path = "/other/path"
+ spec.external_modules = ["mpich/4.1"]
+ spec._mark_concrete()
+ assert not spack.solver.asp._is_reusable_external(
+ {
+ "mpich": {
+ "externals": [
+ {"spec": "mpich@4.1", "prefix": tmpdir.strpath, "modules": ["mpich/4.1"]}
+ ]
+ }
+ },
+ spec,
+ )
+
+
+@pytest.mark.parametrize("modules", [None, ["mpich/4.1", "libfabric/1.19"]])
+def test_reusable_externals_different_modules(mock_packages, tmpdir, modules):
+ spec = Spec("mpich@4.1%gcc@13.1.0~debug build_system=generic arch=linux-ubuntu23.04-zen2")
+ spec.external_path = tmpdir.strpath
+ spec.external_modules = modules
+ spec._mark_concrete()
+ assert not spack.solver.asp._is_reusable_external(
+ {
+ "mpich": {
+ "externals": [
+ {"spec": "mpich@4.1", "prefix": tmpdir.strpath, "modules": ["mpich/4.1"]}
+ ]
+ }
+ },
+ spec,
+ )
+
+
+def test_reusable_externals_different_spec(mock_packages, tmpdir):
+ spec = Spec("mpich@4.1%gcc@13.1.0~debug build_system=generic arch=linux-ubuntu23.04-zen2")
+ spec.external_path = tmpdir.strpath
+ spec._mark_concrete()
+ assert not spack.solver.asp._is_reusable_external(
+ {"mpich": {"externals": [{"spec": "mpich@4.1 +debug", "prefix": tmpdir.strpath}]}}, spec
+ )