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 import llnl.util.tty as tty
from llnl.util.lang import classproperty, memoized from llnl.util.lang import classproperty, memoized
import spack
import spack.compilers import spack.compilers
import spack.config import spack.config
import spack.dependency import spack.dependency
@ -60,6 +61,7 @@
from spack.util.package_hash import package_hash from spack.util.package_hash import package_hash
from spack.util.typing import SupportsRichComparison from spack.util.typing import SupportsRichComparison
from spack.version import GitVersion, StandardVersion from spack.version import GitVersion, StandardVersion
from spack.version import ver as version_from_str
FLAG_HANDLER_RETURN_TYPE = Tuple[ FLAG_HANDLER_RETURN_TYPE = Tuple[
Optional[Iterable[str]], Optional[Iterable[str]], Optional[Iterable[str]] 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. #: TestSuite instance used to manage stand-alone tests for 1+ specs.
test_suite: Optional[Any] = None 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): def __init__(self, spec):
# this determines how the package should be built. # this determines how the package should be built.
self.spec: spack.spec.Spec = spec 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 # Allow custom staging paths for packages
self.path = None self.path = None

View File

@ -32,6 +32,7 @@
import llnl.util.tty as tty import llnl.util.tty as tty
from llnl.util.filesystem import working_dir from llnl.util.filesystem import working_dir
import spack
import spack.caches import spack.caches
import spack.config import spack.config
import spack.error import spack.error
@ -40,6 +41,7 @@
import spack.spec import spack.spec
import spack.tag import spack.tag
import spack.tengine import spack.tengine
import spack.version
import spack.util.file_cache import spack.util.file_cache
import spack.util.git import spack.util.git
import spack.util.naming as nm import spack.util.naming as nm
@ -49,6 +51,11 @@
#: Package modules are imported as spack.pkg.<repo-namespace>.<pkg-name> #: Package modules are imported as spack.pkg.<repo-namespace>.<pkg-name>
ROOT_PYTHON_NAMESPACE = "spack.pkg" 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): def python_package_for_repo(namespace):
"""Returns the full namespace of a repository, given its relative one """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) 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}'") 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() config = self._read_config()
check( check(
"namespace" in config, "namespace" in config,
@ -965,6 +972,19 @@ def check(condition, msg):
"Namespaces must be valid python identifiers separated by '.'", "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 # Set up 'full_namespace' to include the super-namespace
self.full_namespace = python_package_for_repo(self.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 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): def test_repo_getpkg(mutable_mock_repo):
mutable_mock_repo.get_pkg_class("pkg-a") mutable_mock_repo.get_pkg_class("pkg-a")
mutable_mock_repo.get_pkg_class("builtin.mock.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 # foo is not there, raise
with pytest.raises(spack.repo.UnknownNamespaceError): with pytest.raises(spack.repo.UnknownNamespaceError):
repo.get_repo("foo") 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: repo:
namespace: builtin.mock namespace: builtin.mock
version: 0
required_spack_version: '0.23:1.0.0.dev0'

View File

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