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