Support for prereleases (#43140)
This adds support for prereleases. Alpha, beta and release candidate suffixes are ordered in the intuitive way: ``` 1.2.0-alpha < 1.2.0-alpha.1 < 1.2.0-beta.2 < 1.2.0-rc.3 < 1.2.0 < 1.2.0-xyz ``` Alpha, beta and rc prereleases are defined as follows: split the version string into components like before (on delimiters and string boundaries). If there's a string component `alpha`, `beta` or `rc` followed by an optional numeric component at the end, then the version is prerelease. So `1.2.0-alpha.1 == 1.2.0alpha1 == 1.2.0.alpha1` are all the same, as usual. The strings `alpha`, `beta` and `rc` are chosen because they match semver, they are sufficiently long to be unambiguous, and and all contain at least one non-hex character so distinguish them from shasum/digest type suffixes. The comparison key is now stored as `(release_tuple, prerelease_tuple)`, so in the above example: ``` ((1,2,0),(ALPHA,)) < ((1,2,0),(ALPHA,1)) < ((1,2,0),(BETA,2)) < ((1,2,0),(RC,3)) < ((1,2,0),(FINAL,)) < ((1,2,0,"xyz"), (FINAL,)) ``` The version ranges `@1.2.0:` and `@:1.1` do *not* include prereleases of `1.2.0`. So for packaging, if the `1.2.0alpha` and `1.2.0` versions have the same constraints on dependencies, it's best to write ```python depends_on("x@1:", when="@1.2.0alpha:") ``` However, `@1.2:` does include `1.2.0alpha`. This is because Spack considers `1.2 < 1.2.0` as distinct versions, with `1.2 < 1.2.0alpha < 1.2.0` as a consequence. Alternatively, the above `depends_on` statement can thus be written ```python depends_on("x@1:", when="@1.2:") ``` which can be useful too. A short-hand to include prereleases, but you can still be explicit to exclude the prerelease by specifying the patch version number. ### Concretization Concretization uses a different version order than `<`. Prereleases are ordered between final releases and develop versions. That way, users should not have to set `preferred=True` on every final release if they add just one prerelease to a package. The concretizer is unlikely to pick a prerelease when final releases are possible. ### Limitations 1. You can't express a range that includes all alpha release but excludes all beta releases. Only alternative is good old repeated nines: `@:1.2.0alpha99`. 2. The Python ecosystem defaults to `a`, `b`, `rc` strings, so translation of Python versions to Spack versions requires expansion to `alpha`, `beta`, `rc`. It's mildly annoying, because this means we may need to compute URLs differently (not done in this commit). ### Hash Care is taken not to break hashes of versions that do not have a prerelease suffix.
This commit is contained in:
parent
397334a4be
commit
c3eaf4d6cf
@ -1119,6 +1119,9 @@ and ``3.4.2``. Similarly, ``@4.2:`` means any version above and including
|
||||
``4.2``. As a short-hand, ``@3`` is equivalent to the range ``@3:3`` and
|
||||
includes any version with major version ``3``.
|
||||
|
||||
Versions are ordered lexicograpically by its components. For more details
|
||||
on the order, see :ref:`the packaging guide <version-comparison>`.
|
||||
|
||||
Notice that you can distinguish between the specific version ``@=3.2`` and
|
||||
the range ``@3.2``. This is useful for packages that follow a versioning
|
||||
scheme that omits the zero patch version number: ``3.2``, ``3.2.1``,
|
||||
|
@ -893,26 +893,50 @@ as an option to the ``version()`` directive. Example situations would be a
|
||||
"snapshot"-like Version Control System (VCS) tag, a VCS branch such as
|
||||
``v6-16-00-patches``, or a URL specifying a regularly updated snapshot tarball.
|
||||
|
||||
|
||||
.. _version-comparison:
|
||||
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
Version comparison
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Spack imposes a generic total ordering on the set of versions,
|
||||
independently from the package they are associated with.
|
||||
|
||||
Most Spack versions are numeric, a tuple of integers; for example,
|
||||
``0.1``, ``6.96`` or ``1.2.3.1``. Spack knows how to compare and sort
|
||||
numeric versions.
|
||||
``0.1``, ``6.96`` or ``1.2.3.1``. In this very basic case, version
|
||||
comparison is lexicographical on the numeric components:
|
||||
``1.2 < 1.2.1 < 1.2.2 < 1.10``.
|
||||
|
||||
Some Spack versions involve slight extensions of numeric syntax; for
|
||||
example, ``py-sphinx-rtd-theme@=0.1.10a0``. In this case, numbers are
|
||||
always considered to be "newer" than letters. This is for consistency
|
||||
with `RPM <https://bugzilla.redhat.com/show_bug.cgi?id=50977>`_.
|
||||
Spack can also supports string components such as ``1.1.1a`` and
|
||||
``1.y.0``. String components are considered less than numeric
|
||||
components, so ``1.y.0 < 1.0``. This is for consistency with
|
||||
`RPM <https://bugzilla.redhat.com/show_bug.cgi?id=50977>`_. String
|
||||
components do not have to be separated by dots or any other delimiter.
|
||||
So, the contrived version ``1y0`` is identical to ``1.y.0``.
|
||||
|
||||
Spack versions may also be arbitrary non-numeric strings, for example
|
||||
``develop``, ``master``, ``local``.
|
||||
Pre-release suffixes also contain string parts, but they are handled
|
||||
in a special way. For example ``1.2.3alpha1`` is parsed as a pre-release
|
||||
of the version ``1.2.3``. This allows Spack to order it before the
|
||||
actual release: ``1.2.3alpha1 < 1.2.3``. Spack supports alpha, beta and
|
||||
release candidate suffixes: ``1.2alpha1 < 1.2beta1 < 1.2rc1 < 1.2``. Any
|
||||
suffix not recognized as a pre-release is treated as an ordinary
|
||||
string component, so ``1.2 < 1.2-mysuffix``.
|
||||
|
||||
The order on versions is defined as follows. A version string is split
|
||||
into a list of components based on delimiters such as ``.``, ``-`` etc.
|
||||
Lists are then ordered lexicographically, where components are ordered
|
||||
as follows:
|
||||
Finally, there are a few special string components that are considered
|
||||
"infinity versions". They include ``develop``, ``main``, ``master``,
|
||||
``head``, ``trunk``, and ``stable``. For example: ``1.2 < develop``.
|
||||
These are useful for specifying the most recent development version of
|
||||
a package (often a moving target like a git branch), without assigning
|
||||
a specific version number. Infinity versions are not automatically used when determining the latest version of a package unless explicitly required by another package or user.
|
||||
|
||||
More formally, the order on versions is defined as follows. A version
|
||||
string is split into a list of components based on delimiters such as
|
||||
``.`` and ``-`` and string boundaries. The components are split into
|
||||
the **release** and a possible **pre-release** (if the last component
|
||||
is numeric and the second to last is a string ``alpha``, ``beta`` or ``rc``).
|
||||
The release components are ordered lexicographically, with comparsion
|
||||
between different types of components as follows:
|
||||
|
||||
#. The following special strings are considered larger than any other
|
||||
numeric or non-numeric version component, and satisfy the following
|
||||
@ -925,6 +949,9 @@ as follows:
|
||||
#. All other non-numeric components are less than numeric components,
|
||||
and are ordered alphabetically.
|
||||
|
||||
Finally, if the release components are equal, the pre-release components
|
||||
are used to break the tie, in the obvious way.
|
||||
|
||||
The logic behind this sort order is two-fold:
|
||||
|
||||
#. Non-numeric versions are usually used for special cases while
|
||||
|
@ -541,6 +541,7 @@ def _concretization_version_order(version_info: Tuple[GitOrStandardVersion, dict
|
||||
info.get("preferred", False),
|
||||
not info.get("deprecated", False),
|
||||
not version.isdevelop(),
|
||||
not version.is_prerelease(),
|
||||
version,
|
||||
)
|
||||
|
||||
@ -2158,7 +2159,7 @@ def versions_for(v):
|
||||
if isinstance(v, vn.StandardVersion):
|
||||
return [v]
|
||||
elif isinstance(v, vn.ClosedOpenRange):
|
||||
return [v.lo, vn.prev_version(v.hi)]
|
||||
return [v.lo, vn._prev_version(v.hi)]
|
||||
elif isinstance(v, vn.VersionList):
|
||||
return sum((versions_for(e) for e in v), [])
|
||||
else:
|
||||
|
@ -155,5 +155,6 @@ def optimization_flags(self, compiler):
|
||||
# log this and just return compiler.version instead
|
||||
tty.debug(str(e))
|
||||
|
||||
compiler_version = compiler_version.dotted.force_numeric
|
||||
return self.microarchitecture.optimization_flags(compiler.name, str(compiler_version))
|
||||
return self.microarchitecture.optimization_flags(
|
||||
compiler.name, compiler_version.dotted_numeric_string
|
||||
)
|
||||
|
@ -2613,3 +2613,28 @@ def test_reusable_externals_different_spec(mock_packages, tmpdir):
|
||||
{"mpich": {"externals": [{"spec": "mpich@4.1 +debug", "prefix": tmpdir.strpath}]}},
|
||||
local=False,
|
||||
)
|
||||
|
||||
|
||||
def test_concretization_version_order():
|
||||
versions = [
|
||||
(Version("develop"), {}),
|
||||
(Version("1.0"), {}),
|
||||
(Version("2.0"), {"deprecated": True}),
|
||||
(Version("1.1"), {}),
|
||||
(Version("1.1alpha1"), {}),
|
||||
(Version("0.9"), {"preferred": True}),
|
||||
]
|
||||
result = [
|
||||
v
|
||||
for v, _ in sorted(
|
||||
versions, key=spack.solver.asp._concretization_version_order, reverse=True
|
||||
)
|
||||
]
|
||||
assert result == [
|
||||
Version("0.9"), # preferred
|
||||
Version("1.1"), # latest non-deprecated final version
|
||||
Version("1.0"), # latest non-deprecated final version
|
||||
Version("1.1alpha1"), # prereleases
|
||||
Version("develop"), # likely development version
|
||||
Version("2.0"), # deprecated
|
||||
]
|
||||
|
@ -210,16 +210,10 @@ def test_from_list_url(mock_packages, config, spec, url, digest, _fetch_method):
|
||||
@pytest.mark.parametrize(
|
||||
"requested_version,tarball,digest",
|
||||
[
|
||||
# This version is in the web data path (test/data/web/4.html), but not in the
|
||||
# These versions are in the web data path (test/data/web/4.html), but not in the
|
||||
# url-list-test package. We expect Spack to generate a URL with the new version.
|
||||
("=4.5.0", "foo-4.5.0.tar.gz", None),
|
||||
# This version is in web data path and not in the package file, BUT the 2.0.0b2
|
||||
# version in the package file satisfies 2.0.0, so Spack will use the known version.
|
||||
# TODO: this is *probably* not what the user wants, but it's here as an example
|
||||
# TODO: for that reason. We can't express "exactly 2.0.0" right now, and we don't
|
||||
# TODO: have special cases that would make 2.0.0b2 less than 2.0.0. We should
|
||||
# TODO: probably revisit this in our versioning scheme.
|
||||
("2.0.0", "foo-2.0.0b2.tar.gz", "000000000000000000000000000200b2"),
|
||||
("=2.0.0", "foo-2.0.0.tar.gz", None),
|
||||
],
|
||||
)
|
||||
@pytest.mark.only_clingo("Original concretizer doesn't resolve concrete versions to known ones")
|
||||
@ -228,7 +222,7 @@ def test_new_version_from_list_url(
|
||||
):
|
||||
"""Test non-specific URLs from the url-list-test package."""
|
||||
with spack.config.override("config:url_fetch_method", _fetch_method):
|
||||
s = Spec("url-list-test @%s" % requested_version).concretized()
|
||||
s = Spec(f"url-list-test @{requested_version}").concretized()
|
||||
fetch_strategy = fs.from_list_url(s.package)
|
||||
|
||||
assert isinstance(fetch_strategy, fs.URLFetchStrategy)
|
||||
|
@ -213,12 +213,24 @@ def test_nums_and_patch():
|
||||
assert_ver_gt("=6.5p1", "=5.6p1")
|
||||
|
||||
|
||||
def test_rc_versions():
|
||||
assert_ver_gt("=6.0.rc1", "=6.0")
|
||||
assert_ver_lt("=6.0", "=6.0.rc1")
|
||||
def test_prereleases():
|
||||
# pre-releases are special: they are less than final releases
|
||||
assert_ver_lt("=6.0alpha", "=6.0alpha0")
|
||||
assert_ver_lt("=6.0alpha0", "=6.0alpha1")
|
||||
assert_ver_lt("=6.0alpha1", "=6.0alpha2")
|
||||
assert_ver_lt("=6.0alpha2", "=6.0beta")
|
||||
assert_ver_lt("=6.0beta", "=6.0beta0")
|
||||
assert_ver_lt("=6.0beta0", "=6.0beta1")
|
||||
assert_ver_lt("=6.0beta1", "=6.0beta2")
|
||||
assert_ver_lt("=6.0beta2", "=6.0rc")
|
||||
assert_ver_lt("=6.0rc", "=6.0rc0")
|
||||
assert_ver_lt("=6.0rc0", "=6.0rc1")
|
||||
assert_ver_lt("=6.0rc1", "=6.0rc2")
|
||||
assert_ver_lt("=6.0rc2", "=6.0")
|
||||
|
||||
|
||||
def test_alpha_beta():
|
||||
# these are not pre-releases, but ordinary string components.
|
||||
assert_ver_gt("=10b2", "=10a1")
|
||||
assert_ver_lt("=10a2", "=10b2")
|
||||
|
||||
@ -277,6 +289,39 @@ def test_version_ranges():
|
||||
assert_ver_gt("1.5:1.6", "1.2:1.4")
|
||||
|
||||
|
||||
def test_version_range_with_prereleases():
|
||||
# 1.2.1: means from the 1.2.1 release onwards
|
||||
assert_does_not_satisfy("1.2.1alpha1", "1.2.1:")
|
||||
assert_does_not_satisfy("1.2.1beta2", "1.2.1:")
|
||||
assert_does_not_satisfy("1.2.1rc3", "1.2.1:")
|
||||
|
||||
# Pre-releases of 1.2.1 are included in the 1.2.0: range
|
||||
assert_satisfies("1.2.1alpha1", "1.2.0:")
|
||||
assert_satisfies("1.2.1beta1", "1.2.0:")
|
||||
assert_satisfies("1.2.1rc3", "1.2.0:")
|
||||
|
||||
# In Spack 1.2 and 1.2.0 are distinct with 1.2 < 1.2.0. So a lowerbound on 1.2 includes
|
||||
# pre-releases of 1.2.0 as well.
|
||||
assert_satisfies("1.2.0alpha1", "1.2:")
|
||||
assert_satisfies("1.2.0beta2", "1.2:")
|
||||
assert_satisfies("1.2.0rc3", "1.2:")
|
||||
|
||||
# An upperbound :1.1 does not include 1.2.0 pre-releases
|
||||
assert_does_not_satisfy("1.2.0alpha1", ":1.1")
|
||||
assert_does_not_satisfy("1.2.0beta2", ":1.1")
|
||||
assert_does_not_satisfy("1.2.0rc3", ":1.1")
|
||||
|
||||
assert_satisfies("1.2.0alpha1", ":1.2")
|
||||
assert_satisfies("1.2.0beta2", ":1.2")
|
||||
assert_satisfies("1.2.0rc3", ":1.2")
|
||||
|
||||
# You can also construct ranges from prereleases
|
||||
assert_satisfies("1.2.0alpha2:1.2.0beta1", "1.2.0alpha1:1.2.0beta2")
|
||||
assert_satisfies("1.2.0", "1.2.0alpha1:")
|
||||
assert_satisfies("=1.2.0", "1.2.0alpha1:")
|
||||
assert_does_not_satisfy("=1.2.0", ":1.2.0rc345")
|
||||
|
||||
|
||||
def test_contains():
|
||||
assert_in("=1.3", "1.2:1.4")
|
||||
assert_in("=1.2.5", "1.2:1.4")
|
||||
@ -417,12 +462,12 @@ def test_basic_version_satisfaction():
|
||||
assert_satisfies("4.7.3", "4.7.3")
|
||||
|
||||
assert_satisfies("4.7.3", "4.7")
|
||||
assert_satisfies("4.7.3b2", "4.7")
|
||||
assert_satisfies("4.7b6", "4.7")
|
||||
assert_satisfies("4.7.3v2", "4.7")
|
||||
assert_satisfies("4.7v6", "4.7")
|
||||
|
||||
assert_satisfies("4.7.3", "4")
|
||||
assert_satisfies("4.7.3b2", "4")
|
||||
assert_satisfies("4.7b6", "4")
|
||||
assert_satisfies("4.7.3v2", "4")
|
||||
assert_satisfies("4.7v6", "4")
|
||||
|
||||
assert_does_not_satisfy("4.8.0", "4.9")
|
||||
assert_does_not_satisfy("4.8", "4.9")
|
||||
@ -433,12 +478,12 @@ def test_basic_version_satisfaction_in_lists():
|
||||
assert_satisfies(["4.7.3"], ["4.7.3"])
|
||||
|
||||
assert_satisfies(["4.7.3"], ["4.7"])
|
||||
assert_satisfies(["4.7.3b2"], ["4.7"])
|
||||
assert_satisfies(["4.7b6"], ["4.7"])
|
||||
assert_satisfies(["4.7.3v2"], ["4.7"])
|
||||
assert_satisfies(["4.7v6"], ["4.7"])
|
||||
|
||||
assert_satisfies(["4.7.3"], ["4"])
|
||||
assert_satisfies(["4.7.3b2"], ["4"])
|
||||
assert_satisfies(["4.7b6"], ["4"])
|
||||
assert_satisfies(["4.7.3v2"], ["4"])
|
||||
assert_satisfies(["4.7v6"], ["4"])
|
||||
|
||||
assert_does_not_satisfy(["4.8.0"], ["4.9"])
|
||||
assert_does_not_satisfy(["4.8"], ["4.9"])
|
||||
@ -507,6 +552,11 @@ def test_formatted_strings():
|
||||
assert v.dotted.joined.string == "123b"
|
||||
|
||||
|
||||
def test_dotted_numeric_string():
|
||||
assert Version("1a2b3").dotted_numeric_string == "1.0.2.0.3"
|
||||
assert Version("1a2b3alpha4").dotted_numeric_string == "1.0.2.0.3.0.4"
|
||||
|
||||
|
||||
def test_up_to():
|
||||
v = Version("1.23-4_5b")
|
||||
|
||||
@ -548,9 +598,18 @@ def check_repr_and_str(vrs):
|
||||
check_repr_and_str("R2016a.2-3_4")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"version_str", ["1.2string3", "1.2-3xyz_4-alpha.5", "1.2beta", "1_x_rc-4"]
|
||||
)
|
||||
def test_stringify_version(version_str):
|
||||
v = Version(version_str)
|
||||
v.string = None
|
||||
assert str(v) == version_str
|
||||
|
||||
|
||||
def test_len():
|
||||
a = Version("1.2.3.4")
|
||||
assert len(a) == len(a.version)
|
||||
assert len(a) == len(a.version[0])
|
||||
assert len(a) == 4
|
||||
b = Version("2018.0")
|
||||
assert len(b) == 2
|
||||
|
@ -30,9 +30,9 @@
|
||||
Version,
|
||||
VersionList,
|
||||
VersionRange,
|
||||
_next_version,
|
||||
_prev_version,
|
||||
from_string,
|
||||
next_version,
|
||||
prev_version,
|
||||
ver,
|
||||
)
|
||||
|
||||
@ -46,8 +46,8 @@
|
||||
"from_string",
|
||||
"is_git_version",
|
||||
"infinity_versions",
|
||||
"prev_version",
|
||||
"next_version",
|
||||
"_prev_version",
|
||||
"_next_version",
|
||||
"VersionList",
|
||||
"ClosedOpenRange",
|
||||
"StandardVersion",
|
||||
|
@ -15,6 +15,14 @@
|
||||
|
||||
iv_min_len = min(len(s) for s in infinity_versions)
|
||||
|
||||
ALPHA = 0
|
||||
BETA = 1
|
||||
RC = 2
|
||||
FINAL = 3
|
||||
|
||||
PRERELEASE_TO_STRING = ["alpha", "beta", "rc"]
|
||||
STRING_TO_PRERELEASE = {"alpha": ALPHA, "beta": BETA, "rc": RC, "final": FINAL}
|
||||
|
||||
|
||||
def is_git_version(string: str) -> bool:
|
||||
return (
|
||||
|
@ -11,7 +11,11 @@
|
||||
from spack.util.spack_yaml import syaml_dict
|
||||
|
||||
from .common import (
|
||||
ALPHA,
|
||||
COMMIT_VERSION,
|
||||
FINAL,
|
||||
PRERELEASE_TO_STRING,
|
||||
STRING_TO_PRERELEASE,
|
||||
EmptyRangeError,
|
||||
VersionLookupError,
|
||||
infinity_versions,
|
||||
@ -88,21 +92,50 @@ def parse_string_components(string: str) -> Tuple[tuple, tuple]:
|
||||
raise ValueError("Bad characters in version string: %s" % string)
|
||||
|
||||
segments = SEGMENT_REGEX.findall(string)
|
||||
version = tuple(int(m[0]) if m[0] else VersionStrComponent.from_string(m[1]) for m in segments)
|
||||
separators = tuple(m[2] for m in segments)
|
||||
return version, separators
|
||||
prerelease: Tuple[int, ...]
|
||||
|
||||
# <version>(alpha|beta|rc)<number>
|
||||
if len(segments) >= 3 and segments[-2][1] in STRING_TO_PRERELEASE and segments[-1][0]:
|
||||
prerelease = (STRING_TO_PRERELEASE[segments[-2][1]], int(segments[-1][0]))
|
||||
segments = segments[:-2]
|
||||
|
||||
# <version>(alpha|beta|rc)
|
||||
elif len(segments) >= 2 and segments[-1][1] in STRING_TO_PRERELEASE:
|
||||
prerelease = (STRING_TO_PRERELEASE[segments[-1][1]],)
|
||||
segments = segments[:-1]
|
||||
|
||||
# <version>
|
||||
else:
|
||||
prerelease = (FINAL,)
|
||||
|
||||
release = tuple(int(m[0]) if m[0] else VersionStrComponent.from_string(m[1]) for m in segments)
|
||||
|
||||
return (release, prerelease), separators
|
||||
|
||||
|
||||
class ConcreteVersion:
|
||||
pass
|
||||
|
||||
|
||||
def _stringify_version(versions: Tuple[tuple, tuple], separators: tuple) -> str:
|
||||
release, prerelease = versions
|
||||
string = ""
|
||||
for i in range(len(release)):
|
||||
string += f"{release[i]}{separators[i]}"
|
||||
if prerelease[0] != FINAL:
|
||||
string += f"{PRERELEASE_TO_STRING[prerelease[0]]}{separators[len(release)]}"
|
||||
if len(prerelease) > 1:
|
||||
string += str(prerelease[1])
|
||||
return string
|
||||
|
||||
|
||||
class StandardVersion(ConcreteVersion):
|
||||
"""Class to represent versions"""
|
||||
|
||||
__slots__ = ["version", "string", "separators"]
|
||||
|
||||
def __init__(self, string: Optional[str], version: tuple, separators: tuple):
|
||||
def __init__(self, string: Optional[str], version: Tuple[tuple, tuple], separators: tuple):
|
||||
self.string = string
|
||||
self.version = version
|
||||
self.separators = separators
|
||||
@ -113,11 +146,13 @@ def from_string(string: str):
|
||||
|
||||
@staticmethod
|
||||
def typemin():
|
||||
return StandardVersion("", (), ())
|
||||
return StandardVersion("", ((), (ALPHA,)), ("",))
|
||||
|
||||
@staticmethod
|
||||
def typemax():
|
||||
return StandardVersion("infinity", (VersionStrComponent(len(infinity_versions)),), ())
|
||||
return StandardVersion(
|
||||
"infinity", ((VersionStrComponent(len(infinity_versions)),), (FINAL,)), ("",)
|
||||
)
|
||||
|
||||
def __bool__(self):
|
||||
return True
|
||||
@ -164,21 +199,23 @@ def __gt__(self, other):
|
||||
return NotImplemented
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.version)
|
||||
return iter(self.version[0])
|
||||
|
||||
def __len__(self):
|
||||
return len(self.version)
|
||||
return len(self.version[0])
|
||||
|
||||
def __getitem__(self, idx):
|
||||
cls = type(self)
|
||||
|
||||
release = self.version[0]
|
||||
|
||||
if isinstance(idx, numbers.Integral):
|
||||
return self.version[idx]
|
||||
return release[idx]
|
||||
|
||||
elif isinstance(idx, slice):
|
||||
string_arg = []
|
||||
|
||||
pairs = zip(self.version[idx], self.separators[idx])
|
||||
pairs = zip(release[idx], self.separators[idx])
|
||||
for token, sep in pairs:
|
||||
string_arg.append(str(token))
|
||||
string_arg.append(str(sep))
|
||||
@ -193,22 +230,16 @@ def __getitem__(self, idx):
|
||||
message = "{cls.__name__} indices must be integers"
|
||||
raise TypeError(message.format(cls=cls))
|
||||
|
||||
def _stringify(self):
|
||||
string = ""
|
||||
for index in range(len(self.version)):
|
||||
string += str(self.version[index])
|
||||
string += str(self.separators[index])
|
||||
return string
|
||||
|
||||
def __str__(self):
|
||||
return self.string or self._stringify()
|
||||
return self.string or _stringify_version(self.version, self.separators)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
# Print indirect repr through Version(...)
|
||||
return f'Version("{str(self)}")'
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.version)
|
||||
# If this is a final release, do not hash the prerelease part for backward compat.
|
||||
return hash(self.version if self.is_prerelease() else self.version[0])
|
||||
|
||||
def __contains__(rhs, lhs):
|
||||
# We should probably get rid of `x in y` for versions, since
|
||||
@ -257,23 +288,22 @@ def intersection(self, other: Union["ClosedOpenRange", "StandardVersion"]):
|
||||
def isdevelop(self):
|
||||
"""Triggers on the special case of the `@develop-like` version."""
|
||||
return any(
|
||||
isinstance(p, VersionStrComponent) and isinstance(p.data, int) for p in self.version
|
||||
isinstance(p, VersionStrComponent) and isinstance(p.data, int) for p in self.version[0]
|
||||
)
|
||||
|
||||
def is_prerelease(self) -> bool:
|
||||
return self.version[1][0] != FINAL
|
||||
|
||||
@property
|
||||
def force_numeric(self):
|
||||
"""Replaces all non-numeric components of the version with 0
|
||||
def dotted_numeric_string(self) -> str:
|
||||
"""Replaces all non-numeric components of the version with 0.
|
||||
|
||||
This can be used to pass Spack versions to libraries that have stricter version schema.
|
||||
"""
|
||||
numeric = tuple(0 if isinstance(v, VersionStrComponent) else v for v in self.version)
|
||||
# null separators except the final one have to be converted to avoid concatenating ints
|
||||
# default to '.' as most common delimiter for versions
|
||||
separators = tuple(
|
||||
"." if s == "" and i != len(self.separators) - 1 else s
|
||||
for i, s in enumerate(self.separators)
|
||||
)
|
||||
return type(self)(None, numeric, separators)
|
||||
numeric = tuple(0 if isinstance(v, VersionStrComponent) else v for v in self.version[0])
|
||||
if self.is_prerelease():
|
||||
numeric += (0, *self.version[1][1:])
|
||||
return ".".join(str(v) for v in numeric)
|
||||
|
||||
@property
|
||||
def dotted(self):
|
||||
@ -591,6 +621,9 @@ def __getitem__(self, idx):
|
||||
def isdevelop(self):
|
||||
return self.ref_version.isdevelop()
|
||||
|
||||
def is_prerelease(self) -> bool:
|
||||
return self.ref_version.is_prerelease()
|
||||
|
||||
@property
|
||||
def dotted(self) -> StandardVersion:
|
||||
return self.ref_version.dotted
|
||||
@ -622,14 +655,14 @@ def __init__(self, lo: StandardVersion, hi: StandardVersion):
|
||||
def from_version_range(cls, lo: StandardVersion, hi: StandardVersion):
|
||||
"""Construct ClosedOpenRange from lo:hi range."""
|
||||
try:
|
||||
return ClosedOpenRange(lo, next_version(hi))
|
||||
return ClosedOpenRange(lo, _next_version(hi))
|
||||
except EmptyRangeError as e:
|
||||
raise EmptyRangeError(f"{lo}:{hi} is an empty range") from e
|
||||
|
||||
def __str__(self):
|
||||
# This simplifies 3.1:<3.2 to 3.1:3.1 to 3.1
|
||||
# 3:3 -> 3
|
||||
hi_prev = prev_version(self.hi)
|
||||
hi_prev = _prev_version(self.hi)
|
||||
if self.lo != StandardVersion.typemin() and self.lo == hi_prev:
|
||||
return str(self.lo)
|
||||
lhs = "" if self.lo == StandardVersion.typemin() else str(self.lo)
|
||||
@ -641,7 +674,7 @@ def __repr__(self):
|
||||
|
||||
def __hash__(self):
|
||||
# prev_version for backward compat.
|
||||
return hash((self.lo, prev_version(self.hi)))
|
||||
return hash((self.lo, _prev_version(self.hi)))
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, StandardVersion):
|
||||
@ -823,7 +856,7 @@ def concrete_range_as_version(self) -> Optional[ConcreteVersion]:
|
||||
v = self[0]
|
||||
if isinstance(v, ConcreteVersion):
|
||||
return v
|
||||
if isinstance(v, ClosedOpenRange) and next_version(v.lo) == v.hi:
|
||||
if isinstance(v, ClosedOpenRange) and _next_version(v.lo) == v.hi:
|
||||
return v.lo
|
||||
return None
|
||||
|
||||
@ -994,7 +1027,7 @@ def __repr__(self):
|
||||
return str(self.versions)
|
||||
|
||||
|
||||
def next_str(s: str) -> str:
|
||||
def _next_str(s: str) -> str:
|
||||
"""Produce the next string of A-Z and a-z characters"""
|
||||
return (
|
||||
(s + "A")
|
||||
@ -1003,7 +1036,7 @@ def next_str(s: str) -> str:
|
||||
)
|
||||
|
||||
|
||||
def prev_str(s: str) -> str:
|
||||
def _prev_str(s: str) -> str:
|
||||
"""Produce the previous string of A-Z and a-z characters"""
|
||||
return (
|
||||
s[:-1]
|
||||
@ -1012,7 +1045,7 @@ def prev_str(s: str) -> str:
|
||||
)
|
||||
|
||||
|
||||
def next_version_str_component(v: VersionStrComponent) -> VersionStrComponent:
|
||||
def _next_version_str_component(v: VersionStrComponent) -> VersionStrComponent:
|
||||
"""
|
||||
Produce the next VersionStrComponent, where
|
||||
masteq -> mastes
|
||||
@ -1025,14 +1058,14 @@ def next_version_str_component(v: VersionStrComponent) -> VersionStrComponent:
|
||||
|
||||
# Find the next non-infinity string.
|
||||
while True:
|
||||
data = next_str(data)
|
||||
data = _next_str(data)
|
||||
if data not in infinity_versions:
|
||||
break
|
||||
|
||||
return VersionStrComponent(data)
|
||||
|
||||
|
||||
def prev_version_str_component(v: VersionStrComponent) -> VersionStrComponent:
|
||||
def _prev_version_str_component(v: VersionStrComponent) -> VersionStrComponent:
|
||||
"""
|
||||
Produce the previous VersionStrComponent, where
|
||||
mastes -> masteq
|
||||
@ -1045,47 +1078,56 @@ def prev_version_str_component(v: VersionStrComponent) -> VersionStrComponent:
|
||||
|
||||
# Find the next string.
|
||||
while True:
|
||||
data = prev_str(data)
|
||||
data = _prev_str(data)
|
||||
if data not in infinity_versions:
|
||||
break
|
||||
|
||||
return VersionStrComponent(data)
|
||||
|
||||
|
||||
def next_version(v: StandardVersion) -> StandardVersion:
|
||||
if len(v.version) == 0:
|
||||
nxt = VersionStrComponent("A")
|
||||
elif isinstance(v.version[-1], VersionStrComponent):
|
||||
nxt = next_version_str_component(v.version[-1])
|
||||
def _next_version(v: StandardVersion) -> StandardVersion:
|
||||
release, prerelease = v.version
|
||||
separators = v.separators
|
||||
prerelease_type = prerelease[0]
|
||||
if prerelease_type != FINAL:
|
||||
prerelease = (prerelease_type, prerelease[1] + 1 if len(prerelease) > 1 else 0)
|
||||
elif len(release) == 0:
|
||||
release = (VersionStrComponent("A"),)
|
||||
separators = ("",)
|
||||
elif isinstance(release[-1], VersionStrComponent):
|
||||
release = release[:-1] + (_next_version_str_component(release[-1]),)
|
||||
else:
|
||||
nxt = v.version[-1] + 1
|
||||
|
||||
# Construct a string-version for printing
|
||||
string_components = []
|
||||
for part, sep in zip(v.version[:-1], v.separators):
|
||||
string_components.append(str(part))
|
||||
string_components.append(str(sep))
|
||||
string_components.append(str(nxt))
|
||||
|
||||
return StandardVersion("".join(string_components), v.version[:-1] + (nxt,), v.separators)
|
||||
release = release[:-1] + (release[-1] + 1,)
|
||||
components = [""] * (2 * len(release))
|
||||
components[::2] = release
|
||||
components[1::2] = separators[: len(release)]
|
||||
if prerelease_type != FINAL:
|
||||
components.extend((PRERELEASE_TO_STRING[prerelease_type], prerelease[1]))
|
||||
return StandardVersion("".join(str(c) for c in components), (release, prerelease), separators)
|
||||
|
||||
|
||||
def prev_version(v: StandardVersion) -> StandardVersion:
|
||||
if len(v.version) == 0:
|
||||
def _prev_version(v: StandardVersion) -> StandardVersion:
|
||||
# this function does not deal with underflow, because it's always called as
|
||||
# _prev_version(_next_version(v)).
|
||||
release, prerelease = v.version
|
||||
separators = v.separators
|
||||
prerelease_type = prerelease[0]
|
||||
if prerelease_type != FINAL:
|
||||
prerelease = (
|
||||
(prerelease_type,) if prerelease[1] == 0 else (prerelease_type, prerelease[1] - 1)
|
||||
)
|
||||
elif len(release) == 0:
|
||||
return v
|
||||
elif isinstance(v.version[-1], VersionStrComponent):
|
||||
prev = prev_version_str_component(v.version[-1])
|
||||
elif isinstance(release[-1], VersionStrComponent):
|
||||
release = release[:-1] + (_prev_version_str_component(release[-1]),)
|
||||
else:
|
||||
prev = v.version[-1] - 1
|
||||
|
||||
# Construct a string-version for printing
|
||||
string_components = []
|
||||
for part, sep in zip(v.version[:-1], v.separators):
|
||||
string_components.append(str(part))
|
||||
string_components.append(str(sep))
|
||||
string_components.append(str(prev))
|
||||
|
||||
return StandardVersion("".join(string_components), v.version[:-1] + (prev,), v.separators)
|
||||
release = release[:-1] + (release[-1] - 1,)
|
||||
components = [""] * (2 * len(release))
|
||||
components[::2] = release
|
||||
components[1::2] = separators[: len(release)]
|
||||
if prerelease_type != FINAL:
|
||||
components.extend((PRERELEASE_TO_STRING[prerelease_type], *prerelease[1:]))
|
||||
return StandardVersion("".join(str(c) for c in components), (release, prerelease), separators)
|
||||
|
||||
|
||||
def Version(string: Union[str, int]) -> Union[GitVersion, StandardVersion]:
|
||||
|
@ -76,7 +76,7 @@ class PyAzureCli(PythonPackage):
|
||||
depends_on("py-azure-mgmt-recoveryservices@0.4.0:0.4", type=("build", "run"))
|
||||
depends_on("py-azure-mgmt-recoveryservicesbackup@0.6.0:0.6", type=("build", "run"))
|
||||
depends_on("py-azure-mgmt-redhatopenshift@0.1.0", type=("build", "run"))
|
||||
depends_on("py-azure-mgmt-redis@7.0.0:7.0", type=("build", "run"))
|
||||
depends_on("py-azure-mgmt-redis@7.0", type=("build", "run"))
|
||||
depends_on("py-azure-mgmt-relay@0.1.0:0.1", type=("build", "run"))
|
||||
depends_on("py-azure-mgmt-reservations@0.6.0", type=("build", "run"))
|
||||
depends_on("py-azure-mgmt-search@2.0:2", type=("build", "run"))
|
||||
|
@ -28,7 +28,7 @@ class PyGtdbtk(PythonPackage):
|
||||
depends_on("py-pydantic@1.9.2:1", type=("build", "run"), when="@2.3.0:")
|
||||
depends_on("prodigal@2.6.2:", type=("build", "run"))
|
||||
depends_on("hmmer@3.1b2:", type=("build", "run"))
|
||||
depends_on("pplacer@1.1:", type=("build", "run"))
|
||||
depends_on("pplacer@1.1alpha:", type=("build", "run"))
|
||||
depends_on("fastani@1.32:", type=("build", "run"))
|
||||
depends_on("fasttree@2.1.9:", type=("build", "run"))
|
||||
depends_on("mash@2.2:", type=("build", "run"))
|
||||
|
Loading…
Reference in New Issue
Block a user