Sync with upstream RPM dist generator

This commit is contained in:
Tomas Orsava 2020-04-30 15:57:15 +02:00
parent c8249102ec
commit 1639424a51

View File

@ -3,6 +3,7 @@
# #
# Copyright 2010 Per Øyvind Karlsen <proyvind@moondrake.org> # Copyright 2010 Per Øyvind Karlsen <proyvind@moondrake.org>
# Copyright 2015 Neal Gompa <ngompa13@gmail.com> # Copyright 2015 Neal Gompa <ngompa13@gmail.com>
# Copyright 2020 SUSE LLC
# #
# This program is free software. It may be redistributed and/or modified under # This program is free software. It may be redistributed and/or modified under
# the terms of the LGPL version 2.1 (or later). # the terms of the LGPL version 2.1 (or later).
@ -11,7 +12,7 @@
# #
from __future__ import print_function from __future__ import print_function
from getopt import getopt import argparse
from os.path import basename, dirname, isdir, sep from os.path import basename, dirname, isdir, sep
from sys import argv, stdin, version from sys import argv, stdin, version
from distutils.sysconfig import get_python_lib from distutils.sysconfig import get_python_lib
@ -57,6 +58,7 @@ class RpmVersion():
rpm_suffix = '' rpm_suffix = ''
return '{}{}{}'.format(rpm_epoch, rpm_version, rpm_suffix) return '{}{}{}'.format(rpm_epoch, rpm_version, rpm_suffix)
def convert_compatible(name, operator, version_id): def convert_compatible(name, operator, version_id):
if version_id.endswith('.*'): if version_id.endswith('.*'):
print('Invalid requirement: {} {} {}'.format(name, operator, version_id)) print('Invalid requirement: {} {} {}'.format(name, operator, version_id))
@ -71,6 +73,7 @@ def convert_compatible(name, operator, version_id):
return '({} >= {} with {} < {})'.format( return '({} >= {} with {} < {})'.format(
name, version, name, upper_version) name, version, name, upper_version)
def convert_equal(name, operator, version_id): def convert_equal(name, operator, version_id):
if version_id.endswith('.*'): if version_id.endswith('.*'):
version_id = version_id[:-2] + '.0' version_id = version_id[:-2] + '.0'
@ -78,6 +81,7 @@ def convert_equal(name, operator, version_id):
version = RpmVersion(version_id) version = RpmVersion(version_id)
return '{} = {}'.format(name, version) return '{} = {}'.format(name, version)
def convert_arbitrary_equal(name, operator, version_id): def convert_arbitrary_equal(name, operator, version_id):
if version_id.endswith('.*'): if version_id.endswith('.*'):
print('Invalid requirement: {} {} {}'.format(name, operator, version_id)) print('Invalid requirement: {} {} {}'.format(name, operator, version_id))
@ -85,6 +89,7 @@ def convert_arbitrary_equal(name, operator, version_id):
version = RpmVersion(version_id) version = RpmVersion(version_id)
return '{} = {}'.format(name, version) return '{} = {}'.format(name, version)
def convert_not_equal(name, operator, version_id): def convert_not_equal(name, operator, version_id):
if version_id.endswith('.*'): if version_id.endswith('.*'):
version_id = version_id[:-2] version_id = version_id[:-2]
@ -96,6 +101,7 @@ def convert_not_equal(name, operator, version_id):
return '({} < {} or {} > {})'.format( return '({} < {} or {} > {})'.format(
name, version, name, lower_version) name, version, name, lower_version)
def convert_ordered(name, operator, version_id): def convert_ordered(name, operator, version_id):
if version_id.endswith('.*'): if version_id.endswith('.*'):
# PEP 440 does not define semantics for prefix matching # PEP 440 does not define semantics for prefix matching
@ -112,6 +118,7 @@ def convert_ordered(name, operator, version_id):
version = RpmVersion(version_id) version = RpmVersion(version_id)
return '{} {} {}'.format(name, operator, version) return '{} {} {}'.format(name, operator, version)
OPERATORS = {'~=': convert_compatible, OPERATORS = {'~=': convert_compatible,
'==': convert_equal, '==': convert_equal,
'===': convert_arbitrary_equal, '===': convert_arbitrary_equal,
@ -121,72 +128,36 @@ OPERATORS = {'~=': convert_compatible,
'>=': convert_ordered, '>=': convert_ordered,
'>': convert_ordered} '>': convert_ordered}
def convert(name, operator, version_id): def convert(name, operator, version_id):
return OPERATORS[operator](name, operator, version_id) return OPERATORS[operator](name, operator, version_id)
opts, args = getopt(
argv[1:], 'hPRrCEMmLl:',
['help', 'provides', 'requires', 'recommends', 'conflicts', 'extras', 'majorver-provides', 'majorver-only', 'legacy-provides' , 'legacy'])
Provides = False
Requires = False
Recommends = False
Conflicts = False
Extras = False
Provides_PyMajorVer_Variant = False
PyMajorVer_Deps = False
legacy_Provides = False
legacy = False
def normalize_name(name): def normalize_name(name):
"""https://www.python.org/dev/peps/pep-0503/#normalized-names""" """https://www.python.org/dev/peps/pep-0503/#normalized-names"""
import re import re
return re.sub(r'[-_.]+', '-', name).lower() return re.sub(r'[-_.]+', '-', name).lower()
for o, a in opts:
if o in ('-h', '--help'):
print('-h, --help\tPrint help')
print('-P, --provides\tPrint Provides')
print('-R, --requires\tPrint Requires')
print('-r, --recommends\tPrint Recommends')
print('-C, --conflicts\tPrint Conflicts')
print('-E, --extras\tPrint Extras ')
print('-M, --majorver-provides\tPrint extra Provides with Python major version only')
print('-m, --majorver-only\tPrint Provides/Requires with Python major version only')
print('-L, --legacy-provides\tPrint extra legacy pythonegg Provides')
print('-l, --legacy\tPrint legacy pythonegg Provides/Requires instead')
exit(1)
elif o in ('-P', '--provides'):
Provides = True
elif o in ('-R', '--requires'):
Requires = True
elif o in ('-r', '--recommends'):
Recommends = True
elif o in ('-C', '--conflicts'):
Conflicts = True
elif o in ('-E', '--extras'):
Extras = True
elif o in ('-M', '--majorver-provides'):
Provides_PyMajorVer_Variant = True
elif o in ('-m', '--majorver-only'):
PyMajorVer_Deps = True
elif o in ('-L', '--legacy-provides'):
legacy_Provides = True
elif o in ('-l', '--legacy'):
legacy = True
if Requires: parser = argparse.ArgumentParser(prog=argv[0])
py_abi = True group = parser.add_mutually_exclusive_group(required=True)
else: group.add_argument('-P', '--provides', action='store_true', help='Print Provides')
py_abi = False group.add_argument('-R', '--requires', action='store_true', help='Print Requires')
group.add_argument('-r', '--recommends', action='store_true', help='Print Recommends')
group.add_argument('-C', '--conflicts', action='store_true', help='Print Conflicts')
group.add_argument('-E', '--extras', action='store_true', help='Print Extras')
parser.add_argument('-M', '--majorver-provides', action='store_true', help='Print extra Provides with Python major version only')
parser.add_argument('-m', '--majorver-only', action='store_true', help='Print Provides/Requires with Python major version only')
parser.add_argument('-L', '--legacy-provides', action='store_true', help='Print extra legacy pythonegg Provides')
parser.add_argument('-l', '--legacy', action='store_true', help='Print legacy pythonegg Provides/Requires instead')
parser.add_argument('files', nargs=argparse.REMAINDER)
args = parser.parse_args()
py_abi = args.requires
py_deps = {} py_deps = {}
if args:
files = args
else:
files = stdin.readlines()
for f in files:
for f in (args.files or stdin.readlines()):
f = f.strip() f = f.strip()
lower = f.lower() lower = f.lower()
name = 'python(abi)' name = 'python(abi)'
@ -235,7 +206,16 @@ for f in files:
warn("Version for {!r} has not been found".format(dist), RuntimeWarning) warn("Version for {!r} has not been found".format(dist), RuntimeWarning)
continue continue
# XXX: https://github.com/pypa/setuptools/pull/1275 # pkg_resources use platform.python_version to evaluate if a
# dependency is relevant based on environment markers [1],
# e.g. requirement `argparse;python_version<"2.7"`
#
# Since we're running this script on one Python version while
# possibly evaluating packages for different versions, we mock the
# platform.python_version function. Discussed upstream [2].
#
# [1] https://www.python.org/dev/peps/pep-0508/#environment-markers
# [2] https://github.com/pypa/setuptools/pull/1275
import platform import platform
platform.python_version = lambda: dist.py_version platform.python_version = lambda: dist.py_version
@ -246,31 +226,31 @@ for f in files:
# See https://bugzilla.redhat.com/show_bug.cgi?id=1791530 # See https://bugzilla.redhat.com/show_bug.cgi?id=1791530
normalized_name = normalize_name(dist.project_name) normalized_name = normalize_name(dist.project_name)
if Provides_PyMajorVer_Variant or PyMajorVer_Deps or legacy_Provides or legacy: if args.majorver_provides or args.majorver_only or args.legacy_provides or args.legacy:
# Get the Python major version # Get the Python major version
pyver_major = dist.py_version.split('.')[0] pyver_major = dist.py_version.split('.')[0]
if Provides: if args.provides:
# If egg/dist metadata says package name is python, we provide python(abi) # If egg/dist metadata says package name is python, we provide python(abi)
if dist.key == 'python': if dist.key == 'python':
name = 'python(abi)' name = 'python(abi)'
if name not in py_deps: if name not in py_deps:
py_deps[name] = [] py_deps[name] = []
py_deps[name].append(('==', dist.py_version)) py_deps[name].append(('==', dist.py_version))
if not legacy or not PyMajorVer_Deps: if not args.legacy or not args.majorver_only:
name = 'python{}dist({})'.format(dist.py_version, dist.key) name = 'python{}dist({})'.format(dist.py_version, dist.key)
if name not in py_deps: if name not in py_deps:
py_deps[name] = [] py_deps[name] = []
name_ = 'python{}dist({})'.format(dist.py_version, normalized_name) name_ = 'python{}dist({})'.format(dist.py_version, normalized_name)
if name_ not in py_deps: if name_ not in py_deps:
py_deps[name_] = [] py_deps[name_] = []
if Provides_PyMajorVer_Variant or PyMajorVer_Deps: if args.majorver_provides or args.majorver_only:
pymajor_name = 'python{}dist({})'.format(pyver_major, dist.key) pymajor_name = 'python{}dist({})'.format(pyver_major, dist.key)
if pymajor_name not in py_deps: if pymajor_name not in py_deps:
py_deps[pymajor_name] = [] py_deps[pymajor_name] = []
pymajor_name_ = 'python{}dist({})'.format(pyver_major, normalized_name) pymajor_name_ = 'python{}dist({})'.format(pyver_major, normalized_name)
if pymajor_name_ not in py_deps: if pymajor_name_ not in py_deps:
py_deps[pymajor_name_] = [] py_deps[pymajor_name_] = []
if legacy or legacy_Provides: if args.legacy or args.legacy_provides:
legacy_name = 'pythonegg({})({})'.format(pyver_major, dist.key) legacy_name = 'pythonegg({})({})'.format(pyver_major, dist.key)
if legacy_name not in py_deps: if legacy_name not in py_deps:
py_deps[legacy_name] = [] py_deps[legacy_name] = []
@ -278,17 +258,17 @@ for f in files:
version = dist.version version = dist.version
spec = ('==', version) spec = ('==', version)
if spec not in py_deps[name]: if spec not in py_deps[name]:
if not legacy: if not args.legacy:
py_deps[name].append(spec) py_deps[name].append(spec)
if name != name_: if name != name_:
py_deps[name_].append(spec) py_deps[name_].append(spec)
if Provides_PyMajorVer_Variant: if args.majorver_provides:
py_deps[pymajor_name].append(spec) py_deps[pymajor_name].append(spec)
if pymajor_name != pymajor_name_: if pymajor_name != pymajor_name_:
py_deps[pymajor_name_].append(spec) py_deps[pymajor_name_].append(spec)
if legacy or legacy_Provides: if args.legacy or args.legacy_provides:
py_deps[legacy_name].append(spec) py_deps[legacy_name].append(spec)
if Requires or (Recommends and dist.extras): if args.requires or (args.recommends and dist.extras):
name = 'python(abi)' name = 'python(abi)'
# If egg/dist metadata says package name is python, we don't add dependency on python(abi) # If egg/dist metadata says package name is python, we don't add dependency on python(abi)
if dist.key == 'python': if dist.key == 'python':
@ -302,9 +282,9 @@ for f in files:
if spec not in py_deps[name]: if spec not in py_deps[name]:
py_deps[name].append(spec) py_deps[name].append(spec)
deps = dist.requires() deps = dist.requires()
if Recommends: if args.recommends:
depsextras = dist.requires(extras=dist.extras) depsextras = dist.requires(extras=dist.extras)
if not Requires: if not args.requires:
for dep in reversed(depsextras): for dep in reversed(depsextras):
if dep in deps: if dep in deps:
depsextras.remove(dep) depsextras.remove(dep)
@ -318,10 +298,10 @@ for f in files:
deps.insert(0, Requirement.parse('setuptools')) deps.insert(0, Requirement.parse('setuptools'))
# add requires/recommends based on egg/dist metadata # add requires/recommends based on egg/dist metadata
for dep in deps: for dep in deps:
if legacy: if args.legacy:
name = 'pythonegg({})({})'.format(pyver_major, dep.key) name = 'pythonegg({})({})'.format(pyver_major, dep.key)
else: else:
if PyMajorVer_Deps: if args.majorver_only:
name = 'python{}dist({})'.format(pyver_major, dep.key) name = 'python{}dist({})'.format(pyver_major, dep.key)
else: else:
name = 'python{}dist({})'.format(dist.py_version, dep.key) name = 'python{}dist({})'.format(dist.py_version, dep.key)
@ -334,7 +314,7 @@ for f in files:
py_deps[name] = [] py_deps[name] = []
# Unused, for automatic sub-package generation based on 'extras' from egg/dist metadata # Unused, for automatic sub-package generation based on 'extras' from egg/dist metadata
# TODO: implement in rpm later, or...? # TODO: implement in rpm later, or...?
if Extras: if args.extras:
deps = dist.requires() deps = dist.requires()
extras = dist.extras extras = dist.extras
print(extras) print(extras)
@ -356,7 +336,7 @@ for f in files:
print('%%description\t{}'.format(extra)) print('%%description\t{}'.format(extra))
print('{} extra for {} python package'.format(extra, dist.key)) print('{} extra for {} python package'.format(extra, dist.key))
print('%%files\t\textras-{}\n'.format(extra)) print('%%files\t\textras-{}\n'.format(extra))
if Conflicts: if args.conflicts:
# Should we really add conflicts for extras? # Should we really add conflicts for extras?
# Creating a meta package per extra with recommends on, which has # Creating a meta package per extra with recommends on, which has
# the requires/conflicts in stead might be a better solution... # the requires/conflicts in stead might be a better solution...