Compare commits
2 Commits
v0.22.5
...
backports/
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9a6e51ccf0 | ||
![]() |
dd3ee5ffef |
@@ -4,9 +4,21 @@
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
#: PEP440 canonical <major>.<minor>.<micro>.<devN> string
|
||||
__version__ = "0.22.5"
|
||||
__version__ = "0.22.6.dev0"
|
||||
spack_version = __version__
|
||||
|
||||
#: The current Package API version implemented by this version of Spack. The Package API defines
|
||||
#: the Python interface for packages as well as the layout of package repositories. The minor
|
||||
#: version is incremented when the package API is extended in a backwards-compatible way. The major
|
||||
#: version is incremented upon breaking changes. This version is changed independently from the
|
||||
#: Spack version.
|
||||
package_api_version = (1, 0)
|
||||
|
||||
#: The minimum Package API version that this version of Spack is compatible with. This should
|
||||
#: always be a tuple of the form ``(major, 0)``, since compatibility with vX.Y implies
|
||||
#: compatibility with vX.0.
|
||||
min_package_api_version = (1, 0)
|
||||
|
||||
|
||||
def __try_int(v):
|
||||
try:
|
||||
@@ -19,4 +31,4 @@ def __try_int(v):
|
||||
spack_version_info = tuple([__try_int(v) for v in __version__.split(".")])
|
||||
|
||||
|
||||
__all__ = ["spack_version_info", "spack_version"]
|
||||
__all__ = ["spack_version_info", "spack_version", "package_api_version", "min_package_api_version"]
|
||||
|
@@ -33,6 +33,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
|
||||
@@ -49,6 +50,8 @@
|
||||
#: Package modules are imported as spack.pkg.<repo-namespace>.<pkg-name>
|
||||
ROOT_PYTHON_NAMESPACE = "spack.pkg"
|
||||
|
||||
_API_REGEX = re.compile(r"^v(\d+)\.(\d+)$")
|
||||
|
||||
|
||||
def python_package_for_repo(namespace):
|
||||
"""Returns the full namespace of a repository, given its relative one
|
||||
@@ -909,17 +912,49 @@ def __contains__(self, pkg_name):
|
||||
return self.exists(pkg_name)
|
||||
|
||||
|
||||
def _parse_package_api_version(
|
||||
config: Dict[str, Any],
|
||||
min_api: Tuple[int, int] = spack.min_package_api_version,
|
||||
max_api: Tuple[int, int] = spack.package_api_version,
|
||||
) -> Tuple[int, int]:
|
||||
api = config.get("api")
|
||||
if api is None:
|
||||
package_api = (1, 0)
|
||||
else:
|
||||
if not isinstance(api, str):
|
||||
raise BadRepoError(f"Invalid Package API version '{api}'. Must be of the form vX.Y")
|
||||
api_match = _API_REGEX.match(api)
|
||||
if api_match is None:
|
||||
raise BadRepoError(f"Invalid Package API version '{api}'. Must be of the form vX.Y")
|
||||
package_api = (int(api_match.group(1)), int(api_match.group(2)))
|
||||
|
||||
if min_api <= package_api <= max_api:
|
||||
return package_api
|
||||
|
||||
min_str = ".".join(str(i) for i in min_api)
|
||||
max_str = ".".join(str(i) for i in max_api)
|
||||
curr_str = ".".join(str(i) for i in package_api)
|
||||
raise BadRepoError(
|
||||
f"Package API v{curr_str} is not supported by this version of Spack ("
|
||||
f"must be between v{min_str} and v{max_str})"
|
||||
)
|
||||
|
||||
|
||||
class Repo:
|
||||
"""Class representing a package repository in the filesystem.
|
||||
|
||||
Each package repository must have a top-level configuration file
|
||||
called `repo.yaml`.
|
||||
Each package repository must have a top-level configuration file called `repo.yaml`.
|
||||
|
||||
Currently, `repo.yaml` this must define:
|
||||
It contains the following keys:
|
||||
|
||||
`namespace`:
|
||||
A Python namespace where the repository's packages should live.
|
||||
|
||||
`api`:
|
||||
A string of the form vX.Y that indicates the Package API version. The default is "v1.0".
|
||||
For the repo to be compatible with the current version of Spack, the version must be
|
||||
greater than or equal to :py:data:`spack.min_package_api_version` and less than or equal to
|
||||
:py:data:`spack.package_api_version`.
|
||||
"""
|
||||
|
||||
def __init__(self, root, cache=None):
|
||||
@@ -948,7 +983,7 @@ def check(condition, msg):
|
||||
"%s must define a namespace." % os.path.join(root, repo_config_name),
|
||||
)
|
||||
|
||||
self.namespace = config["namespace"]
|
||||
self.namespace: str = config["namespace"]
|
||||
check(
|
||||
re.match(r"[a-zA-Z][a-zA-Z0-9_.]+", self.namespace),
|
||||
("Invalid namespace '%s' in repo '%s'. " % (self.namespace, self.root))
|
||||
@@ -961,13 +996,15 @@ def check(condition, msg):
|
||||
# Keep name components around for checking prefixes.
|
||||
self._names = self.full_namespace.split(".")
|
||||
|
||||
packages_dir = config.get("subdirectory", packages_dir_name)
|
||||
packages_dir: str = config.get("subdirectory", packages_dir_name)
|
||||
self.packages_path = os.path.join(self.root, packages_dir)
|
||||
check(
|
||||
os.path.isdir(self.packages_path),
|
||||
"No directory '%s' found in '%s'" % (packages_dir, root),
|
||||
)
|
||||
|
||||
self.package_api = _parse_package_api_version(config)
|
||||
|
||||
# These are internal cache variables.
|
||||
self._modules = {}
|
||||
self._classes = {}
|
||||
@@ -1010,7 +1047,7 @@ def is_prefix(self, fullname):
|
||||
parts = fullname.split(".")
|
||||
return self._names[: len(parts)] == parts
|
||||
|
||||
def _read_config(self):
|
||||
def _read_config(self) -> Dict[str, Any]:
|
||||
"""Check for a YAML config file in this db's root directory."""
|
||||
try:
|
||||
with open(self.config_file) as reponame_file:
|
||||
|
@@ -195,3 +195,47 @@ def test_path_computation_with_names(method_name, mock_repo_path):
|
||||
unqualified = method("mpileaks")
|
||||
qualified = method("builtin.mock.mpileaks")
|
||||
assert qualified == unqualified
|
||||
|
||||
|
||||
def test_parse_package_api_version():
|
||||
"""Test that we raise an error if a repository has a version that is not supported."""
|
||||
# valid version
|
||||
assert spack.repo._parse_package_api_version(
|
||||
{"api": "v1.2"}, min_api=(1, 0), max_api=(2, 3)
|
||||
) == (1, 2)
|
||||
# too new and too old
|
||||
with pytest.raises(
|
||||
spack.repo.BadRepoError,
|
||||
match=r"Package API v2.4 is not supported .* \(must be between v1.0 and v2.3\)",
|
||||
):
|
||||
spack.repo._parse_package_api_version({"api": "v2.4"}, min_api=(1, 0), max_api=(2, 3))
|
||||
with pytest.raises(
|
||||
spack.repo.BadRepoError,
|
||||
match=r"Package API v0.9 is not supported .* \(must be between v1.0 and v2.3\)",
|
||||
):
|
||||
spack.repo._parse_package_api_version({"api": "v0.9"}, min_api=(1, 0), max_api=(2, 3))
|
||||
# default to v1.0 if not specified
|
||||
assert spack.repo._parse_package_api_version({}, min_api=(1, 0), max_api=(2, 3)) == (1, 0)
|
||||
# if v1.0 support is dropped we should also raise
|
||||
with pytest.raises(
|
||||
spack.repo.BadRepoError,
|
||||
match=r"Package API v1.0 is not supported .* \(must be between v2.0 and v2.3\)",
|
||||
):
|
||||
spack.repo._parse_package_api_version({}, min_api=(2, 0), max_api=(2, 3))
|
||||
# finally test invalid input
|
||||
with pytest.raises(spack.repo.BadRepoError, match="Invalid Package API version"):
|
||||
spack.repo._parse_package_api_version({"api": "v2"}, min_api=(1, 0), max_api=(3, 3))
|
||||
with pytest.raises(spack.repo.BadRepoError, match="Invalid Package API version"):
|
||||
spack.repo._parse_package_api_version({"api": 2.0}, min_api=(1, 0), max_api=(3, 3))
|
||||
|
||||
|
||||
def test_repo_package_api_version(tmp_path):
|
||||
"""Test that we can specify the API version of a repository."""
|
||||
(tmp_path / "example" / "packages").mkdir(parents=True)
|
||||
(tmp_path / "example" / "repo.yaml").write_text(
|
||||
"""\
|
||||
repo:
|
||||
namespace: example
|
||||
"""
|
||||
)
|
||||
assert spack.repo.Repo(str(tmp_path / "example"), cache=None).package_api == (1, 0)
|
||||
|
Reference in New Issue
Block a user