Compare commits

...

3 Commits

Author SHA1 Message Date
Gregory Becker
2cc0e533c0 add test for incompatible version from specific package 2025-02-10 20:41:37 -08:00
Gregory Becker
7a8eaa1657 add test for incompatible repos 2025-02-10 18:04:42 -08:00
Gregory Becker
d8baa193b3 repo compat: initial draft 2025-02-10 15:08:51 -08:00
5 changed files with 81 additions and 1 deletions

View File

@ -31,6 +31,7 @@
import llnl.util.tty as tty
from llnl.util.lang import classproperty, memoized
import spack
import spack.compilers
import spack.config
import spack.dependency
@ -60,6 +61,7 @@
from spack.util.package_hash import package_hash
from spack.util.typing import SupportsRichComparison
from spack.version import GitVersion, StandardVersion
from spack.version import ver as version_from_str
FLAG_HANDLER_RETURN_TYPE = Tuple[
Optional[Iterable[str]], Optional[Iterable[str]], Optional[Iterable[str]]
@ -731,10 +733,22 @@ class PackageBase(WindowsRPath, PackageViewMixin, metaclass=PackageMeta):
#: TestSuite instance used to manage stand-alone tests for 1+ specs.
test_suite: Optional[Any] = None
#: compatibility requirements with Spack
#: if value is ``None``, requirements from repo are still applied
required_spack_version = None
def __init__(self, spec):
# this determines how the package should be built.
self.spec: spack.spec.Spec = spec
# is this package more restrictive in compatibility than the repo is
if self.required_spack_version:
spack_version = version_from_str(spack.spack_version)
required_version = version_from_str(self.required_spack_version)
if not spack_version.satisfies(required_version):
msg = f"Package {self.name} requires Spack version {self.required_spack_version}."
raise PackageError(msg)
# Allow custom staging paths for packages
self.path = None

View File

@ -32,6 +32,7 @@
import llnl.util.tty as tty
from llnl.util.filesystem import working_dir
import spack
import spack.caches
import spack.config
import spack.error
@ -40,6 +41,7 @@
import spack.spec
import spack.tag
import spack.tengine
import spack.version
import spack.util.file_cache
import spack.util.git
import spack.util.naming as nm
@ -49,6 +51,11 @@
#: Package modules are imported as spack.pkg.<repo-namespace>.<pkg-name>
ROOT_PYTHON_NAMESPACE = "spack.pkg"
_required_repo_version = "0:"
#: Version of the repo interface that this version of Spack is compatible with
required_repo_version = spack.version.ver(_required_repo_version)
def python_package_for_repo(namespace):
"""Returns the full namespace of a repository, given its relative one
@ -951,7 +958,7 @@ def check(condition, msg):
self.config_file = os.path.join(self.root, repo_config_name)
check(os.path.isfile(self.config_file), f"No {repo_config_name} found in '{root}'")
# Read configuration and validate namespace
# Read configuration and validate
config = self._read_config()
check(
"namespace" in config,
@ -965,6 +972,19 @@ def check(condition, msg):
"Namespaces must be valid python identifiers separated by '.'",
)
required_version = spack.version.ver(config.get("required_spack_version", ":"))
spack_version = spack.version.ver(spack.spack_version)
check(
spack_version.satisfies(required_version),
f"Repo {self.namespace} requires Spack version {required_version}",
)
repo_version = spack.version.ver(config.get("version", "0"))
check(
repo_version.satisfies(required_repo_version),
f"Spack requires repo version {required_repo_version}",
)
# Set up 'full_namespace' to include the super-namespace
self.full_namespace = python_package_for_repo(self.namespace)

View File

@ -38,6 +38,27 @@ def extra_repo(tmp_path_factory, request):
return spack.repo.Repo(str(repo_dir), cache=repo_cache), request.param
@pytest.fixture(scope="function")
def versioned_repo(tmp_path_factory, request):
def _execute(spack_version, repo_version):
repo_namespace = "extra_test_repo"
repo_dir = tmp_path_factory.mktemp(repo_namespace)
cache_dir = tmp_path_factory.mktemp("cache")
(repo_dir / "packages").mkdir(parents=True, exist_ok=True)
(repo_dir / "repo.yaml").write_text(
f"""
repo:
namespace: extra_test_repo
required_spack_version: '{spack_version}'
version: '{repo_version}'
"""
)
repo_cache = spack.util.file_cache.FileCache(str(cache_dir))
return spack.repo.Repo(str(repo_dir), cache=repo_cache)
return _execute
def test_repo_getpkg(mutable_mock_repo):
mutable_mock_repo.get_pkg_class("pkg-a")
mutable_mock_repo.get_pkg_class("builtin.mock.pkg-a")
@ -303,3 +324,24 @@ def test_get_repo(self, mock_test_cache):
# foo is not there, raise
with pytest.raises(spack.repo.UnknownNamespaceError):
repo.get_repo("foo")
def test_incompatible_repo(versioned_repo):
with pytest.raises(spack.repo.BadRepoError, match="requires Spack version"):
# test added after Spack passed version 0.22
versioned_repo(":0.22", ":")
with pytest.raises(spack.repo.BadRepoError, match="requires repo version"):
# ":a" < "0", and all Spack versions require at least "0:"
versioned_repo(":", ":a")
def test_incompatible_package_version(mock_packages, monkeypatch):
spec = spack.concretize.concretize_one("pkg-a")
package = spack.repo.PATH.get(spec)
pkg_class = spec.package_class
monkeypatch.setattr(pkg_class, "required_spack_version", ":0.22")
with pytest.raises(spack.error.PackageError, match="requires Spack version"):
_ = spack.repo.PATH.get(spec)

View File

@ -1,2 +1,4 @@
repo:
namespace: builtin.mock
version: 0
required_spack_version: '0.23:1.0.0.dev0'

View File

@ -1,2 +1,4 @@
repo:
namespace: builtin
version: 0
required_spack_version: '0.23:1.0.0.dev0'