|
|
838e4d |
# Copyright 2019 Gordon Messmer <gordon.messmer@gmail.com>
|
|
|
838e4d |
#
|
|
|
838e4d |
# Upstream: https://github.com/gordonmessmer/pyreq2rpm
|
|
|
838e4d |
#
|
|
|
838e4d |
# Permission is hereby granted, free of charge, to any person
|
|
|
838e4d |
# obtaining a copy of this software and associated documentation files
|
|
|
838e4d |
# (the "Software"), to deal in the Software without restriction,
|
|
|
838e4d |
# including without limitation the rights to use, copy, modify, merge,
|
|
|
838e4d |
# publish, distribute, sublicense, and/or sell copies of the Software,
|
|
|
838e4d |
# and to permit persons to whom the Software is furnished to do so,
|
|
|
838e4d |
# subject to the following conditions:
|
|
|
838e4d |
#
|
|
|
838e4d |
# The above copyright notice and this permission notice shall be
|
|
|
838e4d |
# included in all copies or substantial portions of the Software.
|
|
|
838e4d |
#
|
|
|
838e4d |
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
|
838e4d |
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
|
838e4d |
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
|
838e4d |
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
|
838e4d |
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
|
838e4d |
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
|
838e4d |
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
838e4d |
# SOFTWARE.
|
|
|
838e4d |
|
|
|
838e4d |
from packaging.requirements import Requirement
|
|
|
838e4d |
from packaging.version import parse as parse_version
|
|
|
838e4d |
|
|
|
838e4d |
class RpmVersion():
|
|
|
838e4d |
def __init__(self, version_id):
|
|
|
838e4d |
version = parse_version(version_id)
|
|
|
838e4d |
if isinstance(version._version, str):
|
|
|
838e4d |
self.version = version._version
|
|
|
838e4d |
else:
|
|
|
838e4d |
self.epoch = version._version.epoch
|
|
|
838e4d |
self.version = list(version._version.release)
|
|
|
838e4d |
self.pre = version._version.pre
|
|
|
838e4d |
self.dev = version._version.dev
|
|
|
838e4d |
self.post = version._version.post
|
|
|
98862f |
# version.local is ignored as it is not expected to appear
|
|
|
98862f |
# in public releases
|
|
|
98862f |
# https://www.python.org/dev/peps/pep-0440/#local-version-identifiers
|
|
|
98862f |
|
|
|
98862f |
def is_legacy(self):
|
|
|
98862f |
return isinstance(self.version, str)
|
|
|
838e4d |
|
|
|
838e4d |
def increment(self):
|
|
|
838e4d |
self.version[-1] += 1
|
|
|
838e4d |
self.pre = None
|
|
|
838e4d |
self.dev = None
|
|
|
838e4d |
self.post = None
|
|
|
838e4d |
return self
|
|
|
838e4d |
|
|
|
838e4d |
def __str__(self):
|
|
|
98862f |
if self.is_legacy():
|
|
|
838e4d |
return self.version
|
|
|
838e4d |
if self.epoch:
|
|
|
838e4d |
rpm_epoch = str(self.epoch) + ':'
|
|
|
838e4d |
else:
|
|
|
838e4d |
rpm_epoch = ''
|
|
|
838e4d |
while len(self.version) > 1 and self.version[-1] == 0:
|
|
|
838e4d |
self.version.pop()
|
|
|
838e4d |
rpm_version = '.'.join(str(x) for x in self.version)
|
|
|
838e4d |
if self.pre:
|
|
|
838e4d |
rpm_suffix = '~{}'.format(''.join(str(x) for x in self.pre))
|
|
|
838e4d |
elif self.dev:
|
|
|
838e4d |
rpm_suffix = '~~{}'.format(''.join(str(x) for x in self.dev))
|
|
|
838e4d |
elif self.post:
|
|
|
838e4d |
rpm_suffix = '^post{}'.format(self.post[1])
|
|
|
838e4d |
else:
|
|
|
838e4d |
rpm_suffix = ''
|
|
|
838e4d |
return '{}{}{}'.format(rpm_epoch, rpm_version, rpm_suffix)
|
|
|
838e4d |
|
|
|
838e4d |
def convert_compatible(name, operator, version_id):
|
|
|
838e4d |
if version_id.endswith('.*'):
|
|
|
838e4d |
return 'Invalid version'
|
|
|
838e4d |
version = RpmVersion(version_id)
|
|
|
98862f |
if version.is_legacy():
|
|
|
98862f |
# LegacyVersions are not supported in this context
|
|
|
98862f |
return 'Invalid version'
|
|
|
838e4d |
if len(version.version) == 1:
|
|
|
838e4d |
return 'Invalid version'
|
|
|
838e4d |
upper_version = RpmVersion(version_id)
|
|
|
838e4d |
upper_version.version.pop()
|
|
|
838e4d |
upper_version.increment()
|
|
|
838e4d |
return '({} >= {} with {} < {})'.format(
|
|
|
838e4d |
name, version, name, upper_version)
|
|
|
838e4d |
|
|
|
838e4d |
def convert_equal(name, operator, version_id):
|
|
|
838e4d |
if version_id.endswith('.*'):
|
|
|
838e4d |
version_id = version_id[:-2] + '.0'
|
|
|
838e4d |
return convert_compatible(name, '~=', version_id)
|
|
|
838e4d |
version = RpmVersion(version_id)
|
|
|
838e4d |
return '{} = {}'.format(name, version)
|
|
|
838e4d |
|
|
|
838e4d |
def convert_arbitrary_equal(name, operator, version_id):
|
|
|
838e4d |
if version_id.endswith('.*'):
|
|
|
838e4d |
return 'Invalid version'
|
|
|
838e4d |
version = RpmVersion(version_id)
|
|
|
838e4d |
return '{} = {}'.format(name, version)
|
|
|
838e4d |
|
|
|
838e4d |
def convert_not_equal(name, operator, version_id):
|
|
|
838e4d |
if version_id.endswith('.*'):
|
|
|
838e4d |
version_id = version_id[:-2]
|
|
|
838e4d |
version = RpmVersion(version_id)
|
|
|
98862f |
if version.is_legacy():
|
|
|
98862f |
# LegacyVersions are not supported in this context
|
|
|
98862f |
return 'Invalid version'
|
|
|
98862f |
version_gt = RpmVersion(version_id).increment()
|
|
|
98862f |
version_gt_operator = '>='
|
|
|
98862f |
# Prevent dev and pre-releases from satisfying a < requirement
|
|
|
98862f |
version = '{}~~'.format(version)
|
|
|
838e4d |
else:
|
|
|
838e4d |
version = RpmVersion(version_id)
|
|
|
98862f |
version_gt = version
|
|
|
98862f |
version_gt_operator = '>'
|
|
|
98862f |
return '({} < {} or {} {} {})'.format(
|
|
|
98862f |
name, version, name, version_gt_operator, version_gt)
|
|
|
838e4d |
|
|
|
838e4d |
def convert_ordered(name, operator, version_id):
|
|
|
838e4d |
if version_id.endswith('.*'):
|
|
|
838e4d |
# PEP 440 does not define semantics for prefix matching
|
|
|
838e4d |
# with ordered comparisons
|
|
|
98862f |
# see: https://github.com/pypa/packaging/issues/320
|
|
|
98862f |
# and: https://github.com/pypa/packaging/issues/321
|
|
|
98862f |
# This style of specifier is officially "unsupported",
|
|
|
98862f |
# even though it is processed. Support may be removed
|
|
|
98862f |
# in version 21.0.
|
|
|
838e4d |
version_id = version_id[:-2]
|
|
|
838e4d |
version = RpmVersion(version_id)
|
|
|
838e4d |
if operator == '>':
|
|
|
838e4d |
# distutils will allow a prefix match with '>'
|
|
|
838e4d |
operator = '>='
|
|
|
838e4d |
if operator == '<=':
|
|
|
838e4d |
# distutils will not allow a prefix match with '<='
|
|
|
838e4d |
operator = '<'
|
|
|
838e4d |
else:
|
|
|
838e4d |
version = RpmVersion(version_id)
|
|
|
98862f |
# For backwards compatibility, fallback to previous behavior with LegacyVersions
|
|
|
98862f |
if not version.is_legacy():
|
|
|
98862f |
# Prevent dev and pre-releases from satisfying a < requirement
|
|
|
98862f |
if operator == '<' and not version.pre and not version.dev and not version.post:
|
|
|
98862f |
version = '{}~~'.format(version)
|
|
|
98862f |
# Prevent post-releases from satisfying a > requirement
|
|
|
98862f |
if operator == '>' and not version.pre and not version.dev and not version.post:
|
|
|
98862f |
version = '{}.0'.format(version)
|
|
|
838e4d |
return '{} {} {}'.format(name, operator, version)
|
|
|
838e4d |
|
|
|
838e4d |
OPERATORS = {'~=': convert_compatible,
|
|
|
838e4d |
'==': convert_equal,
|
|
|
838e4d |
'===': convert_arbitrary_equal,
|
|
|
838e4d |
'!=': convert_not_equal,
|
|
|
838e4d |
'<=': convert_ordered,
|
|
|
838e4d |
'<': convert_ordered,
|
|
|
838e4d |
'>=': convert_ordered,
|
|
|
838e4d |
'>': convert_ordered}
|
|
|
838e4d |
|
|
|
838e4d |
def convert(name, operator, version_id):
|
|
|
838e4d |
return OPERATORS[operator](name, operator, version_id)
|
|
|
838e4d |
|
|
|
838e4d |
def convert_requirement(req):
|
|
|
838e4d |
parsed_req = Requirement.parse(req)
|
|
|
838e4d |
reqs = []
|
|
|
838e4d |
for spec in parsed_req.specs:
|
|
|
838e4d |
reqs.append(convert(parsed_req.project_name, spec[0], spec[1]))
|
|
|
838e4d |
if len(reqs) == 0:
|
|
|
838e4d |
return parsed_req.project_name
|
|
|
838e4d |
if len(reqs) == 1:
|
|
|
838e4d |
return reqs[0]
|
|
|
838e4d |
else:
|
|
|
838e4d |
reqs.sort()
|
|
|
838e4d |
return '({})'.format(' with '.join(reqs))
|