mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
[enh] Implement package version specifier and use it for min_version
This commit is contained in:
parent
4fd7a69d68
commit
cc4dc54ed3
2 changed files with 202 additions and 9 deletions
|
@ -40,7 +40,7 @@ from moulinette.core import MoulinetteError
|
||||||
from moulinette.utils.log import getActionLogger
|
from moulinette.utils.log import getActionLogger
|
||||||
|
|
||||||
from yunohost.service import service_log
|
from yunohost.service import service_log
|
||||||
from yunohost.utils.packages import has_min_version
|
from yunohost.utils.packages import meets_version_specifier
|
||||||
|
|
||||||
logger = getActionLogger('yunohost.app')
|
logger = getActionLogger('yunohost.app')
|
||||||
|
|
||||||
|
@ -360,7 +360,8 @@ def app_upgrade(auth, app=[], url=None, file=None):
|
||||||
|
|
||||||
# Check min version
|
# Check min version
|
||||||
if 'min_version' in manifest \
|
if 'min_version' in manifest \
|
||||||
and not has_min_version(manifest['min_version']):
|
and not meets_version_specifier(
|
||||||
|
'yunohost', '>> {0}'.format(manifest['min_version'])):
|
||||||
raise MoulinetteError(errno.EPERM,
|
raise MoulinetteError(errno.EPERM,
|
||||||
m18n.n('app_recent_version_required',
|
m18n.n('app_recent_version_required',
|
||||||
app=app_id))
|
app=app_id))
|
||||||
|
@ -452,7 +453,8 @@ def app_install(auth, app, label=None, args=None):
|
||||||
|
|
||||||
# Check min version
|
# Check min version
|
||||||
if 'min_version' in manifest \
|
if 'min_version' in manifest \
|
||||||
and not has_min_version(manifest['min_version']):
|
and not meets_version_specifier(
|
||||||
|
'yunohost', '>> {0}'.format(manifest['min_version'])):
|
||||||
raise MoulinetteError(errno.EPERM,
|
raise MoulinetteError(errno.EPERM,
|
||||||
m18n.n('app_recent_version_required',
|
m18n.n('app_recent_version_required',
|
||||||
app=app_id))
|
app=app_id))
|
||||||
|
|
|
@ -18,11 +18,15 @@
|
||||||
along with this program; if not, see http://www.gnu.org/licenses
|
along with this program; if not, see http://www.gnu.org/licenses
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
import re
|
||||||
|
import logging
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
import apt
|
import apt
|
||||||
from apt_pkg import version_compare
|
from apt_pkg import version_compare
|
||||||
|
|
||||||
|
logger = logging.getLogger('yunohost.utils.packages')
|
||||||
|
|
||||||
|
|
||||||
# Exceptions -----------------------------------------------------------------
|
# Exceptions -----------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -44,13 +48,202 @@ class PackageException(Exception):
|
||||||
|
|
||||||
|
|
||||||
class UnknownPackage(PackageException):
|
class UnknownPackage(PackageException):
|
||||||
|
"""The package is not found in the cache."""
|
||||||
message_key = 'package_unknown'
|
message_key = 'package_unknown'
|
||||||
|
|
||||||
|
|
||||||
class UninstalledPackage(PackageException):
|
class UninstalledPackage(PackageException):
|
||||||
|
"""The package is not installed."""
|
||||||
message_key = 'package_not_installed'
|
message_key = 'package_not_installed'
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidSpecifier(ValueError):
|
||||||
|
"""An invalid specifier was found."""
|
||||||
|
|
||||||
|
|
||||||
|
# Version specifier ----------------------------------------------------------
|
||||||
|
# The packaging package has been a nice inspiration for the following classes.
|
||||||
|
# See: https://github.com/pypa/packaging
|
||||||
|
|
||||||
|
class Specifier(object):
|
||||||
|
"""Unique package version specifier
|
||||||
|
|
||||||
|
Restrict a package version according to the `spec`. It must be a string
|
||||||
|
containing a relation from the list below followed by a version number
|
||||||
|
value. The relations allowed are, as defined by the Debian Policy Manual:
|
||||||
|
|
||||||
|
- `<<` for strictly lower
|
||||||
|
- `<=` for lower or equal
|
||||||
|
- `=` for exactly equal
|
||||||
|
- `>=` for greater or equal
|
||||||
|
- `>>` for strictly greater
|
||||||
|
|
||||||
|
"""
|
||||||
|
_regex_str = (
|
||||||
|
r"""
|
||||||
|
(?P<relation>(<<|<=|=|>=|>>))
|
||||||
|
\s*
|
||||||
|
(?P<version>[^,;\s)]*)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
_regex = re.compile(
|
||||||
|
r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE)
|
||||||
|
|
||||||
|
_relations = {
|
||||||
|
"<<": "lower_than",
|
||||||
|
"<=": "lower_or_equal_than",
|
||||||
|
"=": "equal",
|
||||||
|
">=": "greater_or_equal_than",
|
||||||
|
">>": "greater_than",
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, spec):
|
||||||
|
match = self._regex.search(spec)
|
||||||
|
if not match:
|
||||||
|
raise InvalidSpecifier("Invalid specifier: '{0}'".format(spec))
|
||||||
|
|
||||||
|
self._spec = (
|
||||||
|
match.group("relation").strip(),
|
||||||
|
match.group("version").strip(),
|
||||||
|
)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<Specifier({1!r})>".format(str(self))
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "{0}{1}".format(*self._spec)
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self._spec)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if isinstance(other, basestring):
|
||||||
|
try:
|
||||||
|
other = self.__class__(other)
|
||||||
|
except InvalidSpecifier:
|
||||||
|
return NotImplemented
|
||||||
|
elif not isinstance(other, self.__class__):
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
return self._spec == other._spec
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
if isinstance(other, basestring):
|
||||||
|
try:
|
||||||
|
other = self.__class__(other)
|
||||||
|
except InvalidSpecifier:
|
||||||
|
return NotImplemented
|
||||||
|
elif not isinstance(other, self.__class__):
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
return self._spec != other._spec
|
||||||
|
|
||||||
|
def _get_relation(self, op):
|
||||||
|
return getattr(self, "_compare_{0}".format(self._relations[op]))
|
||||||
|
|
||||||
|
def _compare_lower_than(self, version, spec):
|
||||||
|
return version_compare(version, spec) < 0
|
||||||
|
|
||||||
|
def _compare_lower_or_equal_than(self, version, spec):
|
||||||
|
return version_compare(version, spec) <= 0
|
||||||
|
|
||||||
|
def _compare_equal(self, version, spec):
|
||||||
|
return version_compare(version, spec) == 0
|
||||||
|
|
||||||
|
def _compare_greater_or_equal_than(self, version, spec):
|
||||||
|
return version_compare(version, spec) >= 0
|
||||||
|
|
||||||
|
def _compare_greater_than(self, version, spec):
|
||||||
|
return version_compare(version, spec) > 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def relation(self):
|
||||||
|
return self._spec[0]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def version(self):
|
||||||
|
return self._spec[1]
|
||||||
|
|
||||||
|
def __contains__(self, item):
|
||||||
|
return self.contains(item)
|
||||||
|
|
||||||
|
def contains(self, item):
|
||||||
|
return self._get_relation(self.relation)(item, self.version)
|
||||||
|
|
||||||
|
|
||||||
|
class SpecifierSet(object):
|
||||||
|
"""A set of package version specifiers
|
||||||
|
|
||||||
|
Combine several Specifier separated by a comma. It allows to restrict
|
||||||
|
more precisely a package version. Each package version specifier must be
|
||||||
|
meet. Note than an empty set of specifiers will always be meet.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, specifiers):
|
||||||
|
specifiers = [s.strip() for s in specifiers.split(",") if s.strip()]
|
||||||
|
|
||||||
|
parsed = set()
|
||||||
|
for specifier in specifiers:
|
||||||
|
parsed.add(Specifier(specifier))
|
||||||
|
|
||||||
|
self._specs = frozenset(parsed)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<SpecifierSet({1!r})>".format(str(self))
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return ",".join(sorted(str(s) for s in self._specs))
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self._specs)
|
||||||
|
|
||||||
|
def __and__(self, other):
|
||||||
|
if isinstance(other, basestring):
|
||||||
|
other = SpecifierSet(other)
|
||||||
|
elif not isinstance(other, SpecifierSet):
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
specifier = SpecifierSet()
|
||||||
|
specifier._specs = frozenset(self._specs | other._specs)
|
||||||
|
return specifiers
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if isinstance(other, basestring):
|
||||||
|
other = SpecifierSet(other)
|
||||||
|
elif isinstance(other, Specifier):
|
||||||
|
other = SpecifierSet(str(other))
|
||||||
|
elif not isinstance(other, SpecifierSet):
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
return self._specs == other._specs
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
if isinstance(other, basestring):
|
||||||
|
other = SpecifierSet(other)
|
||||||
|
elif isinstance(other, Specifier):
|
||||||
|
other = SpecifierSet(str(other))
|
||||||
|
elif not isinstance(other, SpecifierSet):
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
return self._specs != other._specs
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self._specs)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self._specs)
|
||||||
|
|
||||||
|
def __contains__(self, item):
|
||||||
|
return self.contains(item)
|
||||||
|
|
||||||
|
def contains(self, item):
|
||||||
|
return all(
|
||||||
|
s.contains(item)
|
||||||
|
for s in self._specs
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# Packages and cache helpers -------------------------------------------------
|
# Packages and cache helpers -------------------------------------------------
|
||||||
|
|
||||||
def get_installed_version(*pkgnames, **kwargs):
|
def get_installed_version(*pkgnames, **kwargs):
|
||||||
|
@ -83,12 +276,10 @@ def get_installed_version(*pkgnames, **kwargs):
|
||||||
return versions[pkgnames[0]]
|
return versions[pkgnames[0]]
|
||||||
return versions
|
return versions
|
||||||
|
|
||||||
def has_min_version(min_version, package='yunohost'):
|
def meets_version_specifier(pkgname, specifier):
|
||||||
"""Check if a package has a minimum installed version"""
|
"""Check if a package installed version meets specifier"""
|
||||||
version = get_installed_version(package)
|
spec = SpecifierSet(specifier)
|
||||||
if version_compare(version, min_version) > 0:
|
return get_installed_version(pkgname) in spec
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
# YunoHost related methods ---------------------------------------------------
|
# YunoHost related methods ---------------------------------------------------
|
||||||
|
|
Loading…
Add table
Reference in a new issue