Feature: use git branches/tags as versions (#31200)
Building on #24639, this allows versions to be prefixed by `git.`. If a version begins `git.`, it is treated as a git ref, and handled as git commits are starting in the referenced PR. An exception is made for versions that are `git.develop`, `git.main`, `git.master`, `git.head`, or `git.trunk`. Those are assumed to be greater than all other versions, as those prefixed strings are in other contexts.
This commit is contained in:
parent
7dd2ca0207
commit
df44045fdb
9 changed files with 306 additions and 122 deletions
|
@ -16,7 +16,7 @@
|
|||
import spack.util.crypto
|
||||
from spack.package_base import preferred_version
|
||||
from spack.util.naming import valid_fully_qualified_module_name
|
||||
from spack.version import Version, ver
|
||||
from spack.version import VersionBase, ver
|
||||
|
||||
description = "checksum available versions of a package"
|
||||
section = "packaging"
|
||||
|
@ -65,7 +65,7 @@ def checksum(parser, args):
|
|||
remote_versions = None
|
||||
for version in versions:
|
||||
version = ver(version)
|
||||
if not isinstance(version, Version):
|
||||
if not isinstance(version, VersionBase):
|
||||
tty.die("Cannot generate checksums for version lists or "
|
||||
"version ranges. Use unambiguous versions.")
|
||||
url = pkg.find_valid_url_for_version(version)
|
||||
|
|
|
@ -46,7 +46,7 @@ class OpenMpi(Package):
|
|||
from spack.dependency import Dependency, canonical_deptype, default_deptype
|
||||
from spack.fetch_strategy import from_kwargs
|
||||
from spack.resource import Resource
|
||||
from spack.version import Version, VersionChecksumError
|
||||
from spack.version import GitVersion, Version, VersionChecksumError, VersionLookupError
|
||||
|
||||
__all__ = ['DirectiveError', 'DirectiveMeta', 'version', 'conflicts', 'depends_on',
|
||||
'extends', 'provides', 'patch', 'variant', 'resource']
|
||||
|
@ -330,7 +330,17 @@ def _execute_version(pkg):
|
|||
kwargs['checksum'] = checksum
|
||||
|
||||
# Store kwargs for the package to later with a fetch_strategy.
|
||||
pkg.versions[Version(ver)] = kwargs
|
||||
version = Version(ver)
|
||||
if isinstance(version, GitVersion):
|
||||
if not hasattr(pkg, 'git') and 'git' not in kwargs:
|
||||
msg = "Spack version directives cannot include git hashes fetched from"
|
||||
msg += " URLs. Error in package '%s'\n" % pkg.name
|
||||
msg += " version('%s', " % version.string
|
||||
msg += ', '.join("%s='%s'" % (argname, value)
|
||||
for argname, value in kwargs.items())
|
||||
msg += ")"
|
||||
raise VersionLookupError(msg)
|
||||
pkg.versions[version] = kwargs
|
||||
return _execute_version
|
||||
|
||||
|
||||
|
|
|
@ -1575,16 +1575,30 @@ def for_package_version(pkg, version):
|
|||
|
||||
check_pkg_attributes(pkg)
|
||||
|
||||
if not isinstance(version, spack.version.Version):
|
||||
if not isinstance(version, spack.version.VersionBase):
|
||||
version = spack.version.Version(version)
|
||||
|
||||
# if it's a commit, we must use a GitFetchStrategy
|
||||
if version.is_commit and hasattr(pkg, "git"):
|
||||
if isinstance(version, spack.version.GitVersion):
|
||||
if not hasattr(pkg, "git"):
|
||||
raise FetchError(
|
||||
"Cannot fetch git version for %s. Package has no 'git' attribute" %
|
||||
pkg.name
|
||||
)
|
||||
# Populate the version with comparisons to other commits
|
||||
version.generate_commit_lookup(pkg.name)
|
||||
version.generate_git_lookup(pkg.name)
|
||||
|
||||
# For GitVersion, we have no way to determine whether a ref is a branch or tag
|
||||
# Fortunately, we handle branches and tags identically, except tags are
|
||||
# handled slightly more conservatively for older versions of git.
|
||||
# We call all non-commit refs tags in this context, at the cost of a slight
|
||||
# performance hit for branches on older versions of git.
|
||||
# Branches cannot be cached, so we tell the fetcher not to cache tags/branches
|
||||
ref_type = 'commit' if version.is_commit else 'tag'
|
||||
kwargs = {
|
||||
'git': pkg.git,
|
||||
'commit': str(version)
|
||||
ref_type: version.ref,
|
||||
'no_cache': True,
|
||||
}
|
||||
kwargs['submodules'] = getattr(pkg, 'submodules', False)
|
||||
fetcher = GitFetchStrategy(**kwargs)
|
||||
|
|
|
@ -62,7 +62,7 @@
|
|||
from spack.util.executable import ProcessError, which
|
||||
from spack.util.package_hash import package_hash
|
||||
from spack.util.prefix import Prefix
|
||||
from spack.version import Version
|
||||
from spack.version import GitVersion, Version, VersionBase
|
||||
|
||||
if sys.version_info[0] >= 3:
|
||||
FLAG_HANDLER_RETURN_TYPE = Tuple[
|
||||
|
@ -1041,7 +1041,7 @@ def all_urls_for_version(self, version, custom_url_for_version=None):
|
|||
return self._implement_all_urls_for_version(version, uf)
|
||||
|
||||
def _implement_all_urls_for_version(self, version, custom_url_for_version=None):
|
||||
if not isinstance(version, Version):
|
||||
if not isinstance(version, VersionBase):
|
||||
version = Version(version)
|
||||
|
||||
urls = []
|
||||
|
@ -1505,7 +1505,7 @@ def do_fetch(self, mirror_only=False):
|
|||
checksum = spack.config.get('config:checksum')
|
||||
fetch = self.stage.managed_by_spack
|
||||
if checksum and fetch and (self.version not in self.versions) \
|
||||
and (not self.version.is_commit):
|
||||
and (not isinstance(self.version, GitVersion)):
|
||||
tty.warn("There is no checksum on file to fetch %s safely." %
|
||||
self.spec.cformat('{name}{@version}'))
|
||||
|
||||
|
|
|
@ -1429,11 +1429,16 @@ def key_fn(item):
|
|||
continue
|
||||
|
||||
known_versions = self.possible_versions[dep.name]
|
||||
if (not dep.version.is_commit and
|
||||
if (not isinstance(dep.version, spack.version.GitVersion) and
|
||||
any(v.satisfies(dep.version) for v in known_versions)):
|
||||
# some version we know about satisfies this constraint, so we
|
||||
# should use that one. e.g, if the user asks for qt@5 and we
|
||||
# know about qt@5.5.
|
||||
# know about qt@5.5. This ensures we don't add under-specified
|
||||
# versions to the solver
|
||||
#
|
||||
# For git versions, we know the version is already fully specified
|
||||
# so we don't have to worry about whether it's an under-specified
|
||||
# version
|
||||
continue
|
||||
|
||||
# if there is a concrete version on the CLI *that we know nothing
|
||||
|
@ -1678,7 +1683,7 @@ def define_virtual_constraints(self):
|
|||
|
||||
# extract all the real versions mentioned in version ranges
|
||||
def versions_for(v):
|
||||
if isinstance(v, spack.version.Version):
|
||||
if isinstance(v, spack.version.VersionBase):
|
||||
return [v]
|
||||
elif isinstance(v, spack.version.VersionRange):
|
||||
result = [v.start] if v.start else []
|
||||
|
@ -2187,8 +2192,8 @@ def build_specs(self, function_tuples):
|
|||
# concretization process)
|
||||
for root in self._specs.values():
|
||||
for spec in root.traverse():
|
||||
if spec.version.is_commit:
|
||||
spec.version.generate_commit_lookup(spec.fullname)
|
||||
if isinstance(spec.version, spack.version.GitVersion):
|
||||
spec.version.generate_git_lookup(spec.fullname)
|
||||
|
||||
return self._specs
|
||||
|
||||
|
|
|
@ -5154,9 +5154,9 @@ def do_parse(self):
|
|||
# Note: VersionRange(x, x) is currently concrete, hence isinstance(...).
|
||||
if (
|
||||
spec.name and spec.versions.concrete and
|
||||
isinstance(spec.version, vn.Version) and spec.version.is_commit
|
||||
isinstance(spec.version, vn.GitVersion)
|
||||
):
|
||||
spec.version.generate_commit_lookup(spec.fullname)
|
||||
spec.version.generate_git_lookup(spec.fullname)
|
||||
|
||||
return specs
|
||||
|
||||
|
|
|
@ -17,7 +17,14 @@
|
|||
import spack.package_base
|
||||
import spack.spec
|
||||
from spack.util.executable import which
|
||||
from spack.version import Version, VersionList, VersionRange, ver
|
||||
from spack.version import (
|
||||
GitVersion,
|
||||
Version,
|
||||
VersionBase,
|
||||
VersionList,
|
||||
VersionRange,
|
||||
ver,
|
||||
)
|
||||
|
||||
|
||||
def assert_ver_lt(a, b):
|
||||
|
@ -520,7 +527,7 @@ def test_repr_and_str():
|
|||
|
||||
def check_repr_and_str(vrs):
|
||||
a = Version(vrs)
|
||||
assert repr(a) == "Version('" + vrs + "')"
|
||||
assert repr(a) == "VersionBase('" + vrs + "')"
|
||||
b = eval(repr(a))
|
||||
assert a == b
|
||||
assert str(a) == vrs
|
||||
|
@ -544,19 +551,19 @@ def test_get_item():
|
|||
assert isinstance(a[1], int)
|
||||
# Test slicing
|
||||
b = a[0:2]
|
||||
assert isinstance(b, Version)
|
||||
assert isinstance(b, VersionBase)
|
||||
assert b == Version('0.1')
|
||||
assert repr(b) == "Version('0.1')"
|
||||
assert repr(b) == "VersionBase('0.1')"
|
||||
assert str(b) == '0.1'
|
||||
b = a[0:3]
|
||||
assert isinstance(b, Version)
|
||||
assert isinstance(b, VersionBase)
|
||||
assert b == Version('0.1_2')
|
||||
assert repr(b) == "Version('0.1_2')"
|
||||
assert repr(b) == "VersionBase('0.1_2')"
|
||||
assert str(b) == '0.1_2'
|
||||
b = a[1:]
|
||||
assert isinstance(b, Version)
|
||||
assert isinstance(b, VersionBase)
|
||||
assert b == Version('1_2-3')
|
||||
assert repr(b) == "Version('1_2-3')"
|
||||
assert repr(b) == "VersionBase('1_2-3')"
|
||||
assert str(b) == '1_2-3'
|
||||
# Raise TypeError on tuples
|
||||
with pytest.raises(TypeError):
|
||||
|
@ -597,7 +604,7 @@ def test_versions_from_git(mock_git_version_info, monkeypatch, mock_packages):
|
|||
spec = spack.spec.Spec('git-test-commit@%s' % commit)
|
||||
version = spec.version
|
||||
comparator = [str(v) if not isinstance(v, int) else v
|
||||
for v in version._cmp(version.commit_lookup)]
|
||||
for v in version._cmp(version.ref_lookup)]
|
||||
|
||||
with working_dir(repo_path):
|
||||
which('git')('checkout', commit)
|
||||
|
@ -637,6 +644,43 @@ def test_git_hash_comparisons(
|
|||
assert spec4.satisfies('@1.0:1.2')
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.platform == 'win32',
|
||||
reason="Not supported on Windows (yet)")
|
||||
def test_git_ref_comparisons(
|
||||
mock_git_version_info, install_mockery, mock_packages, monkeypatch):
|
||||
"""Check that hashes compare properly to versions
|
||||
"""
|
||||
repo_path, filename, commits = mock_git_version_info
|
||||
monkeypatch.setattr(spack.package_base.PackageBase,
|
||||
'git', 'file://%s' % repo_path,
|
||||
raising=False)
|
||||
|
||||
# Spec based on tag v1.0
|
||||
spec_tag = spack.spec.Spec('git-test-commit@git.v1.0')
|
||||
spec_tag.concretize()
|
||||
assert spec_tag.satisfies('@1.0')
|
||||
assert not spec_tag.satisfies('@1.1:')
|
||||
assert str(spec_tag.version) == 'git.v1.0'
|
||||
|
||||
# Spec based on branch 1.x
|
||||
spec_branch = spack.spec.Spec('git-test-commit@git.1.x')
|
||||
spec_branch.concretize()
|
||||
assert spec_branch.satisfies('@1.2')
|
||||
assert spec_branch.satisfies('@1.1:1.3')
|
||||
assert str(spec_branch.version) == 'git.1.x'
|
||||
|
||||
|
||||
@pytest.mark.parametrize('string,git', [
|
||||
('1.2.9', False),
|
||||
('gitmain', False),
|
||||
('git.foo', True),
|
||||
('git.abcdabcdabcdabcdabcdabcdabcdabcdabcdabcd', True),
|
||||
('abcdabcdabcdabcdabcdabcdabcdabcdabcdabcd', True),
|
||||
])
|
||||
def test_version_git_vs_base(string, git):
|
||||
assert isinstance(Version(string), GitVersion) == git
|
||||
|
||||
|
||||
def test_version_range_nonempty():
|
||||
assert Version('1.2.9') in VersionRange('1.2.0', '1.2')
|
||||
assert Version('1.1.1') in ver('1.0:1')
|
||||
|
|
|
@ -67,11 +67,11 @@
|
|||
def coerce_versions(a, b):
|
||||
"""
|
||||
Convert both a and b to the 'greatest' type between them, in this order:
|
||||
Version < VersionRange < VersionList
|
||||
VersionBase < GitVersion < VersionRange < VersionList
|
||||
This is used to simplify comparison operations below so that we're always
|
||||
comparing things that are of the same type.
|
||||
"""
|
||||
order = (Version, VersionRange, VersionList)
|
||||
order = (VersionBase, GitVersion, VersionRange, VersionList)
|
||||
ta, tb = type(a), type(b)
|
||||
|
||||
def check_type(t):
|
||||
|
@ -83,12 +83,16 @@ def check_type(t):
|
|||
if ta == tb:
|
||||
return (a, b)
|
||||
elif order.index(ta) > order.index(tb):
|
||||
if ta == VersionRange:
|
||||
if ta == GitVersion:
|
||||
return (a, GitVersion(b))
|
||||
elif ta == VersionRange:
|
||||
return (a, VersionRange(b, b))
|
||||
else:
|
||||
return (a, VersionList([b]))
|
||||
else:
|
||||
if tb == VersionRange:
|
||||
if tb == GitVersion:
|
||||
return (GitVersion(a), b)
|
||||
elif tb == VersionRange:
|
||||
return (VersionRange(a, a), b)
|
||||
else:
|
||||
return (VersionList([a]), b)
|
||||
|
@ -165,15 +169,29 @@ def __gt__(self, other):
|
|||
return not self.__lt__(other)
|
||||
|
||||
|
||||
class Version(object):
|
||||
def is_git_version(string):
|
||||
if string.startswith('git.'):
|
||||
return True
|
||||
elif len(string) == 40 and COMMIT_VERSION.match(string):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def Version(string): # capitalized for backwards compatibility
|
||||
if not isinstance(string, str):
|
||||
string = str(string) # to handle VersionBase and GitVersion types
|
||||
|
||||
if is_git_version(string):
|
||||
return GitVersion(string)
|
||||
return VersionBase(string)
|
||||
|
||||
|
||||
class VersionBase(object):
|
||||
"""Class to represent versions"""
|
||||
__slots__ = [
|
||||
"version",
|
||||
"separators",
|
||||
"string",
|
||||
"_commit_lookup",
|
||||
"is_commit",
|
||||
"commit_version",
|
||||
]
|
||||
|
||||
def __init__(self, string):
|
||||
|
@ -188,36 +206,12 @@ def __init__(self, string):
|
|||
if string and not VALID_VERSION.match(string):
|
||||
raise ValueError("Bad characters in version string: %s" % string)
|
||||
|
||||
# An object that can lookup git commits to compare them to versions
|
||||
self._commit_lookup = None
|
||||
self.commit_version = None
|
||||
segments = SEGMENT_REGEX.findall(string)
|
||||
self.version = tuple(
|
||||
int(m[0]) if m[0] else VersionStrComponent(m[1]) for m in segments
|
||||
)
|
||||
self.separators = tuple(m[2] for m in segments)
|
||||
|
||||
self.is_commit = len(self.string) == 40 and COMMIT_VERSION.match(self.string)
|
||||
|
||||
def _cmp(self, other_lookups=None):
|
||||
commit_lookup = self.commit_lookup or other_lookups
|
||||
|
||||
if self.is_commit and commit_lookup:
|
||||
if self.commit_version is not None:
|
||||
return self.commit_version
|
||||
commit_info = commit_lookup.get(self.string)
|
||||
if commit_info:
|
||||
prev_version, distance = commit_info
|
||||
|
||||
# Extend previous version by empty component and distance
|
||||
# If commit is exactly a known version, no distance suffix
|
||||
prev_tuple = Version(prev_version).version if prev_version else ()
|
||||
dist_suffix = (VersionStrComponent(''), distance) if distance else ()
|
||||
self.commit_version = prev_tuple + dist_suffix
|
||||
return self.commit_version
|
||||
|
||||
return self.version
|
||||
|
||||
@property
|
||||
def dotted(self):
|
||||
"""The dotted representation of the version.
|
||||
|
@ -230,7 +224,7 @@ def dotted(self):
|
|||
Returns:
|
||||
Version: The version with separator characters replaced by dots
|
||||
"""
|
||||
return Version(self.string.replace('-', '.').replace('_', '.'))
|
||||
return type(self)(self.string.replace('-', '.').replace('_', '.'))
|
||||
|
||||
@property
|
||||
def underscored(self):
|
||||
|
@ -245,7 +239,7 @@ def underscored(self):
|
|||
Version: The version with separator characters replaced by
|
||||
underscores
|
||||
"""
|
||||
return Version(self.string.replace('.', '_').replace('-', '_'))
|
||||
return type(self)(self.string.replace('.', '_').replace('-', '_'))
|
||||
|
||||
@property
|
||||
def dashed(self):
|
||||
|
@ -259,7 +253,7 @@ def dashed(self):
|
|||
Returns:
|
||||
Version: The version with separator characters replaced by dashes
|
||||
"""
|
||||
return Version(self.string.replace('.', '-').replace('_', '-'))
|
||||
return type(self)(self.string.replace('.', '-').replace('_', '-'))
|
||||
|
||||
@property
|
||||
def joined(self):
|
||||
|
@ -273,7 +267,7 @@ def joined(self):
|
|||
Returns:
|
||||
Version: The version with separator characters removed
|
||||
"""
|
||||
return Version(
|
||||
return type(self)(
|
||||
self.string.replace('.', '').replace('-', '').replace('_', ''))
|
||||
|
||||
def up_to(self, index):
|
||||
|
@ -323,13 +317,9 @@ def satisfies(self, other):
|
|||
gcc@4.7 so that when a user asks to build with gcc@4.7, we can find
|
||||
a suitable compiler.
|
||||
"""
|
||||
self_cmp = self._cmp(other.commit_lookup)
|
||||
other_cmp = other._cmp(self.commit_lookup)
|
||||
|
||||
# Do the final comparison
|
||||
nself = len(self_cmp)
|
||||
nother = len(other_cmp)
|
||||
return nother <= nself and self_cmp[:nother] == other_cmp
|
||||
nself = len(self.version)
|
||||
nother = len(other.version)
|
||||
return nother <= nself and self.version[:nother] == other.version
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.version)
|
||||
|
@ -356,19 +346,19 @@ def __getitem__(self, idx):
|
|||
string_arg = ''.join(string_arg)
|
||||
return cls(string_arg)
|
||||
else:
|
||||
return Version('')
|
||||
return VersionBase('')
|
||||
|
||||
message = '{cls.__name__} indices must be integers'
|
||||
raise TypeError(message.format(cls=cls))
|
||||
|
||||
def __repr__(self):
|
||||
return 'Version(' + repr(self.string) + ')'
|
||||
return 'VersionBase(' + repr(self.string) + ')'
|
||||
|
||||
def __str__(self):
|
||||
return self.string
|
||||
|
||||
def __format__(self, format_spec):
|
||||
return self.string.format(format_spec)
|
||||
return str(self).format(format_spec)
|
||||
|
||||
@property
|
||||
def concrete(self):
|
||||
|
@ -384,22 +374,16 @@ def __lt__(self, other):
|
|||
if other is None:
|
||||
return False
|
||||
|
||||
# If either is a commit and we haven't indexed yet, can't compare
|
||||
if (other.is_commit or self.is_commit) and not (self.commit_lookup or
|
||||
other.commit_lookup):
|
||||
return False
|
||||
|
||||
# Use tuple comparison assisted by VersionStrComponent for performance
|
||||
return self._cmp(other.commit_lookup) < other._cmp(self.commit_lookup)
|
||||
return self.version < other.version
|
||||
|
||||
@coerced
|
||||
def __eq__(self, other):
|
||||
|
||||
# Cut out early if we don't have a version
|
||||
if other is None or type(other) != Version:
|
||||
if other is None or type(other) != VersionBase:
|
||||
return False
|
||||
|
||||
return self._cmp(other.commit_lookup) == other._cmp(self.commit_lookup)
|
||||
return self.version == other.version
|
||||
|
||||
@coerced
|
||||
def __ne__(self, other):
|
||||
|
@ -425,24 +409,23 @@ def __contains__(self, other):
|
|||
if other is None:
|
||||
return False
|
||||
|
||||
self_cmp = self._cmp(other.commit_lookup)
|
||||
return other._cmp(self.commit_lookup)[:len(self_cmp)] == self_cmp
|
||||
return other.version[:len(self.version)] == self.version
|
||||
|
||||
@coerced
|
||||
def is_predecessor(self, other):
|
||||
"""True if the other version is the immediate predecessor of this one.
|
||||
That is, NO non-commit versions v exist such that:
|
||||
That is, NO non-git versions v exist such that:
|
||||
(self < v < other and v not in self).
|
||||
"""
|
||||
self_cmp = self._cmp(self.commit_lookup)
|
||||
other_cmp = other._cmp(other.commit_lookup)
|
||||
|
||||
if self_cmp[:-1] != other_cmp[:-1]:
|
||||
if self.version[:-1] != other.version[:-1]:
|
||||
return False
|
||||
|
||||
sl = self_cmp[-1]
|
||||
ol = other_cmp[-1]
|
||||
sl = self.version[-1]
|
||||
ol = other.version[-1]
|
||||
# TODO: extend this to consecutive letters, z/0, and infinity versions
|
||||
return type(sl) == int and type(ol) == int and (ol - sl == 1)
|
||||
|
||||
@coerced
|
||||
def is_successor(self, other):
|
||||
return other.is_predecessor(self)
|
||||
|
||||
|
@ -468,13 +451,135 @@ def intersection(self, other):
|
|||
else:
|
||||
return VersionList()
|
||||
|
||||
@property
|
||||
def commit_lookup(self):
|
||||
if self._commit_lookup:
|
||||
self._commit_lookup.get(self.string)
|
||||
return self._commit_lookup
|
||||
|
||||
def generate_commit_lookup(self, pkg_name):
|
||||
class GitVersion(VersionBase):
|
||||
"""Class to represent versions interpreted from git refs.
|
||||
|
||||
Non-git versions may be coerced to GitVersion for comparison, but no Spec will ever
|
||||
have a GitVersion that is not actually referencing a version from git."""
|
||||
def __init__(self, string):
|
||||
if not isinstance(string, str):
|
||||
string = str(string) # In case we got a VersionBase or GitVersion object
|
||||
|
||||
git_prefix = string.startswith('git.')
|
||||
self.ref = string[4:] if git_prefix else string
|
||||
|
||||
self.is_commit = len(self.ref) == 40 and COMMIT_VERSION.match(self.ref)
|
||||
self.is_ref = git_prefix # is_ref False only for comparing to VersionBase
|
||||
self.is_ref |= bool(self.is_commit)
|
||||
|
||||
# ensure git.<hash> and <hash> are treated the same by dropping 'git.'
|
||||
canonical_string = self.ref if self.is_commit else string
|
||||
super(GitVersion, self).__init__(canonical_string)
|
||||
|
||||
# An object that can lookup git refs to compare them to versions
|
||||
self._ref_lookup = None
|
||||
self.ref_version = None
|
||||
|
||||
def _cmp(self, other_lookups=None):
|
||||
# No need to rely on git comparisons for develop-like refs
|
||||
if len(self.version) == 2 and self.isdevelop():
|
||||
return self.version
|
||||
|
||||
# If we've already looked this version up, return cached value
|
||||
if self.ref_version is not None:
|
||||
return self.ref_version
|
||||
|
||||
ref_lookup = self.ref_lookup or other_lookups
|
||||
|
||||
if self.is_ref and ref_lookup:
|
||||
ref_info = ref_lookup.get(self.ref)
|
||||
if ref_info:
|
||||
prev_version, distance = ref_info
|
||||
|
||||
# Extend previous version by empty component and distance
|
||||
# If commit is exactly a known version, no distance suffix
|
||||
prev_tuple = VersionBase(prev_version).version if prev_version else ()
|
||||
dist_suffix = (VersionStrComponent(''), distance) if distance else ()
|
||||
self.ref_version = prev_tuple + dist_suffix
|
||||
return self.ref_version
|
||||
|
||||
return self.version
|
||||
|
||||
@coerced
|
||||
def satisfies(self, other):
|
||||
"""A Version 'satisfies' another if it is at least as specific and has
|
||||
a common prefix. e.g., we want gcc@4.7.3 to satisfy a request for
|
||||
gcc@4.7 so that when a user asks to build with gcc@4.7, we can find
|
||||
a suitable compiler.
|
||||
"""
|
||||
self_cmp = self._cmp(other.ref_lookup)
|
||||
other_cmp = other._cmp(self.ref_lookup)
|
||||
|
||||
# Do the final comparison
|
||||
nself = len(self_cmp)
|
||||
nother = len(other_cmp)
|
||||
return nother <= nself and self_cmp[:nother] == other_cmp
|
||||
|
||||
def __repr__(self):
|
||||
return 'GitVersion(' + repr(self.string) + ')'
|
||||
|
||||
@coerced
|
||||
def __lt__(self, other):
|
||||
"""Version comparison is designed for consistency with the way RPM
|
||||
does things. If you need more complicated versions in installed
|
||||
packages, you should override your package's version string to
|
||||
express it more sensibly.
|
||||
"""
|
||||
if other is None:
|
||||
return False
|
||||
|
||||
# If we haven't indexed yet, can't compare
|
||||
# If we called this, we know at least one is a git ref
|
||||
if not (self.ref_lookup or other.ref_lookup):
|
||||
return False
|
||||
|
||||
# Use tuple comparison assisted by VersionStrComponent for performance
|
||||
return self._cmp(other.ref_lookup) < other._cmp(self.ref_lookup)
|
||||
|
||||
@coerced
|
||||
def __eq__(self, other):
|
||||
# Cut out early if we don't have a git version
|
||||
if other is None or type(other) != GitVersion:
|
||||
return False
|
||||
|
||||
return self._cmp(other.ref_lookup) == other._cmp(self.ref_lookup)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(str(self))
|
||||
|
||||
@coerced
|
||||
def __contains__(self, other):
|
||||
if other is None:
|
||||
return False
|
||||
|
||||
self_cmp = self._cmp(other.ref_lookup)
|
||||
return other._cmp(self.ref_lookup)[:len(self_cmp)] == self_cmp
|
||||
|
||||
@coerced
|
||||
def is_predecessor(self, other):
|
||||
"""True if the other version is the immediate predecessor of this one.
|
||||
That is, NO non-commit versions v exist such that:
|
||||
(self < v < other and v not in self).
|
||||
"""
|
||||
self_cmp = self._cmp(self.ref_lookup)
|
||||
other_cmp = other._cmp(other.ref_lookup)
|
||||
|
||||
if self_cmp[:-1] != other_cmp[:-1]:
|
||||
return False
|
||||
|
||||
sl = self_cmp[-1]
|
||||
ol = other_cmp[-1]
|
||||
return type(sl) == int and type(ol) == int and (ol - sl == 1)
|
||||
|
||||
@property
|
||||
def ref_lookup(self):
|
||||
if self._ref_lookup:
|
||||
# Get operation ensures dict is populated
|
||||
self._ref_lookup.get(self.ref)
|
||||
return self._ref_lookup
|
||||
|
||||
def generate_git_lookup(self, pkg_name):
|
||||
"""
|
||||
Use the git fetcher to look up a version for a commit.
|
||||
|
||||
|
@ -492,11 +597,11 @@ def generate_commit_lookup(self, pkg_name):
|
|||
"""
|
||||
|
||||
# Sanity check we have a commit
|
||||
if not self.is_commit:
|
||||
tty.die("%s is not a commit." % self)
|
||||
if not self.is_ref:
|
||||
tty.die("%s is not a git version." % self)
|
||||
|
||||
# Generate a commit looker-upper
|
||||
self._commit_lookup = CommitLookup(pkg_name)
|
||||
self._ref_lookup = CommitLookup(pkg_name)
|
||||
|
||||
|
||||
class VersionRange(object):
|
||||
|
@ -715,7 +820,7 @@ def __init__(self, vlist=None):
|
|||
self.add(ver(v))
|
||||
|
||||
def add(self, version):
|
||||
if type(version) in (Version, VersionRange):
|
||||
if type(version) in (VersionBase, GitVersion, VersionRange):
|
||||
# This normalizes single-value version ranges.
|
||||
if version.concrete:
|
||||
version = version.concrete
|
||||
|
@ -968,7 +1073,7 @@ def ver(obj):
|
|||
return _string_to_version(obj)
|
||||
elif isinstance(obj, (int, float)):
|
||||
return _string_to_version(str(obj))
|
||||
elif type(obj) in (Version, VersionRange, VersionList):
|
||||
elif type(obj) in (VersionBase, GitVersion, VersionRange, VersionList):
|
||||
return obj
|
||||
else:
|
||||
raise TypeError("ver() can't convert %s to version!" % type(obj))
|
||||
|
@ -990,8 +1095,8 @@ class CommitLookup(object):
|
|||
"""An object for cached lookups of git commits
|
||||
|
||||
CommitLookup objects delegate to the misc_cache for locking.
|
||||
CommitLookup objects may be attached to a Version object for which
|
||||
Version.is_commit returns True to allow for comparisons between git commits
|
||||
CommitLookup objects may be attached to a GitVersion object for which
|
||||
Version.is_ref returns True to allow for comparisons between git refs
|
||||
and versions as represented by tags in the git repository.
|
||||
"""
|
||||
def __init__(self, pkg_name):
|
||||
|
@ -1074,17 +1179,17 @@ def load_data(self):
|
|||
with spack.caches.misc_cache.read_transaction(self.cache_key) as cache_file:
|
||||
self.data = sjson.load(cache_file)
|
||||
|
||||
def get(self, commit):
|
||||
def get(self, ref):
|
||||
if not self.data:
|
||||
self.load_data()
|
||||
|
||||
if commit not in self.data:
|
||||
self.data[commit] = self.lookup_commit(commit)
|
||||
if ref not in self.data:
|
||||
self.data[ref] = self.lookup_ref(ref)
|
||||
self.save()
|
||||
|
||||
return self.data[commit]
|
||||
return self.data[ref]
|
||||
|
||||
def lookup_commit(self, commit):
|
||||
def lookup_ref(self, ref):
|
||||
"""Lookup the previous version and distance for a given commit.
|
||||
|
||||
We use git to compare the known versions from package to the git tags,
|
||||
|
@ -1111,13 +1216,19 @@ def lookup_commit(self, commit):
|
|||
# remote instance, simply adding '-f' may not be sufficient
|
||||
# (if commits are deleted on the remote, this command alone
|
||||
# won't properly update the local rev-list)
|
||||
self.fetcher.git("fetch", '--tags')
|
||||
self.fetcher.git("fetch", '--tags', output=os.devnull, error=os.devnull)
|
||||
|
||||
# Ensure commit is an object known to git
|
||||
# Note the brackets are literals, the commit replaces the format string
|
||||
# This will raise a ProcessError if the commit does not exist
|
||||
# We may later design a custom error to re-raise
|
||||
self.fetcher.git('cat-file', '-e', '%s^{commit}' % commit)
|
||||
# Ensure ref is a commit object known to git
|
||||
# Note the brackets are literals, the ref replaces the format string
|
||||
try:
|
||||
self.fetcher.git(
|
||||
'cat-file', '-e', '%s^{commit}' % ref,
|
||||
output=os.devnull, error=os.devnull
|
||||
)
|
||||
except spack.util.executable.ProcessError:
|
||||
raise VersionLookupError(
|
||||
"%s is not a valid git ref for %s" % (ref, self.pkg_name)
|
||||
)
|
||||
|
||||
# List tags (refs) by date, so last reference of a tag is newest
|
||||
tag_info = self.fetcher.git(
|
||||
|
@ -1148,11 +1259,11 @@ def lookup_commit(self, commit):
|
|||
ancestor_commits = []
|
||||
for tag_commit in commit_to_version:
|
||||
self.fetcher.git(
|
||||
'merge-base', '--is-ancestor', tag_commit, commit,
|
||||
'merge-base', '--is-ancestor', tag_commit, ref,
|
||||
ignore_errors=[1])
|
||||
if self.fetcher.git.returncode == 0:
|
||||
distance = self.fetcher.git(
|
||||
'rev-list', '%s..%s' % (tag_commit, commit), '--count',
|
||||
'rev-list', '%s..%s' % (tag_commit, ref), '--count',
|
||||
output=str, error=str).strip()
|
||||
ancestor_commits.append((tag_commit, int(distance)))
|
||||
|
||||
|
@ -1164,14 +1275,14 @@ def lookup_commit(self, commit):
|
|||
else:
|
||||
# Get list of all commits, this is in reverse order
|
||||
# We use this to get the first commit below
|
||||
commit_info = self.fetcher.git("log", "--all", "--pretty=format:%H",
|
||||
ref_info = self.fetcher.git("log", "--all", "--pretty=format:%H",
|
||||
output=str)
|
||||
commits = [c for c in commit_info.split('\n') if c]
|
||||
commits = [c for c in ref_info.split('\n') if c]
|
||||
|
||||
# No previous version and distance from first commit
|
||||
prev_version = None
|
||||
distance = int(self.fetcher.git(
|
||||
'rev-list', '%s..%s' % (commits[-1], commit), '--count',
|
||||
'rev-list', '%s..%s' % (commits[-1], ref), '--count',
|
||||
output=str, error=str
|
||||
).strip())
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ class Libcatalyst(CMakePackage):
|
|||
maintainers = ['mathstuf']
|
||||
|
||||
# master as of 2021-05-12
|
||||
version('8456ccd6015142b5a7705f79471361d4f5644fa7', sha256='5a01f12b271d9d9e9b89f31d45a5f4b8426904483639d38754893adfd3547bab')
|
||||
version('2021-05-12', sha256='5a01f12b271d9d9e9b89f31d45a5f4b8426904483639d38754893adfd3547bab')
|
||||
|
||||
variant('mpi', default=False, description='Enable MPI support')
|
||||
variant('python3', default=False, description='Enable Python3 support')
|
||||
|
|
Loading…
Reference in a new issue