Compare commits

...

No commits in common. "c8" and "c9-beta" have entirely different histories.
c8 ... c9-beta

8 changed files with 817 additions and 300 deletions

View File

@ -1,4 +1,27 @@
%__python_provides %{_rpmconfigdir}/pythondistdeps.py --provides --majorver-provides-versions @MAJORVER-PROVIDES-VERSIONS@ %__python_provides() %{lua:
%__python_requires %{_rpmconfigdir}/pythondeps.sh --requires -- Match buildroot/payload paths of the form
%__python_path ^((/usr/lib(64)?/python[[:digit:]]+\\.[[:digit:]]+/site-packages/[^/]+\\.(dist-info|egg-info|egg-link))|(/usr/lib(64)?/python[[:digit:]]+\\.[[:digit:]]+/.*\\.(py[oc]?|so))|(%{_bindir}/python[[:digit:]]+\\.[[:digit:]]+))$ -- /PATH/OF/BUILDROOT/usr/bin/pythonMAJOR.MINOR
%__python_magic [Pp]ython.*(executable|byte-compiled) -- generating a line of the form
-- python(abi) = MAJOR.MINOR
-- (Don't match against -config tools e.g. /usr/bin/python2.6-config)
local path = rpm.expand('%1')
if path:match('/usr/bin/python%d+%.%d+$') then
local provides = path:gsub('.*/usr/bin/python(%d+%.%d+)', 'python(abi) = %1')
print(provides)
end
}
%__python_requires() %{lua:
-- Match buildroot paths of the form
-- /PATH/OF/BUILDROOT/usr/lib/pythonMAJOR.MINOR/ and
-- /PATH/OF/BUILDROOT/usr/lib64/pythonMAJOR.MINOR/
-- generating a line of the form:
-- python(abi) = MAJOR.MINOR
local path = rpm.expand('%1')
if path:match('/usr/lib%d*/python%d+%.%d+/.*') then
local requires = path:gsub('.*/usr/lib%d*/python(%d+%.%d+)/.*', 'python(abi) = %1')
print(requires)
end
}
%__python_path ^((%{_prefix}/lib(64)?/python[[:digit:]]+\\.[[:digit:]]+/.*\\.(py[oc]?|so))|(%{_bindir}/python[[:digit:]]+\\.[[:digit:]]+))$

85
SOURCES/pythonbundles.py Executable file
View File

@ -0,0 +1,85 @@
#!/usr/bin/python3 -sB
# (imports pythondistdeps from /usr/lib/rpm, hence -B)
#
# This program is free software.
#
# It is placed in the public domain or under the CC0-1.0-Universal license,
# whichever is more permissive.
#
# Alternatively, it may be redistributed and/or modified under the terms of
# the LGPL version 2.1 (or later) or GPL version 2 (or later).
#
# Use this script to generate bundled provides, e.g.:
# ./pythonbundles.py setuptools-47.1.1/pkg_resources/_vendor/vendored.txt
import pathlib
import sys
import pythondistdeps
def generate_bundled_provides(path, namespace):
provides = set()
for line in path.read_text().splitlines():
line, _, comment = line.partition('#')
if comment.startswith('egg='):
# not a real comment
# e.g. git+https://github.com/monty/spam.git@master#egg=spam&...
egg, *_ = comment.strip().partition(' ')
egg, *_ = egg.strip().partition('&')
name = pythondistdeps.normalize_name(egg[4:])
provides.add(f'Provides: bundled({namespace}({name}))')
continue
line = line.strip()
if line:
name, _, version = line.partition('==')
name = pythondistdeps.normalize_name(name)
bundled_name = f"bundled({namespace}({name}))"
python_provide = pythondistdeps.convert(bundled_name, '==', version)
provides.add(f'Provides: {python_provide}')
return provides
def compare(expected, given):
stripped = (l.strip() for l in given)
no_comments = set(l for l in stripped if not l.startswith('#'))
no_comments.discard('')
if expected == no_comments:
return True
extra_expected = expected - no_comments
extra_given = no_comments - expected
if extra_expected:
print('Missing expected provides:', file=sys.stderr)
for provide in sorted(extra_expected):
print(f' - {provide}', file=sys.stderr)
if extra_given:
print('Redundant unexpected provides:', file=sys.stderr)
for provide in sorted(extra_given):
print(f' + {provide}', file=sys.stderr)
return False
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser(prog=sys.argv[0],
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('vendored', metavar='VENDORED.TXT',
help='Upstream information about vendored libraries')
parser.add_argument('-c', '--compare-with', action='store',
help='A string value to compare with and verify')
parser.add_argument('-n', '--namespace', action='store',
help='What namespace of provides will used', default='python3dist')
args = parser.parse_args()
provides = generate_bundled_provides(pathlib.Path(args.vendored), args.namespace)
if args.compare_with:
given = args.compare_with.splitlines()
same = compare(provides, given)
if not same:
sys.exit(1)
else:
for provide in sorted(provides):
print(provide)

View File

@ -1,32 +0,0 @@
#!/bin/bash
[ $# -ge 1 ] || {
cat > /dev/null
exit 0
}
case $1 in
-P|--provides)
shift
# Match buildroot/payload paths of the form
# /PATH/OF/BUILDROOT/usr/bin/pythonMAJOR.MINOR
# generating a line of the form
# python(abi) = MAJOR.MINOR
# (Don't match against -config tools e.g. /usr/bin/python2.6-config)
grep "/usr/bin/python.\..$" \
| sed -e "s|.*/usr/bin/python\(.\..\)|python(abi) = \1|"
;;
-R|--requires)
shift
# Match buildroot paths of the form
# /PATH/OF/BUILDROOT/usr/lib/pythonMAJOR.MINOR/ and
# /PATH/OF/BUILDROOT/usr/lib64/pythonMAJOR.MINOR/
# generating (uniqely) lines of the form:
# python(abi) = MAJOR.MINOR
grep -E "/usr/lib[^/]*/python[[:digit:]]+\.[[:digit:]]+/.*" \
| sed -Ee "s|.*/usr/lib[^/]*/python([[:digit:]]+\.[[:digit:]]+)/.*|python(abi) = \1|g" \
| sort | uniq
;;
esac
exit 0

3
SOURCES/pythondist.attr Normal file
View File

@ -0,0 +1,3 @@
%__pythondist_provides %{_rpmconfigdir}/pythondistdeps.py --provides --normalized-names-format pep503 --package-name %{name} --normalized-names-provide-both --majorver-provides-versions %{__default_python3_version}
%__pythondist_requires %{_rpmconfigdir}/pythondistdeps.py --requires --normalized-names-format pep503 --package-name %{name} %{?!_python_no_extras_requires:--require-extras-subpackages} --console-scripts-nodep-setuptools-since 3.10
%__pythondist_path ^/usr/lib(64)?/python[3-9]\\.[[:digit:]]+/site-packages/[^/]+\\.(dist-info|egg-info|egg-link)$

View File

@ -1,8 +1,9 @@
#!/usr/libexec/platform-python #!/usr/bin/python3 -s
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# 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,246 +12,535 @@
# #
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 dirname, sep
from sys import argv, stdin, version import re
from distutils.sysconfig import get_python_lib from sys import argv, stdin, stderr, version_info
from sysconfig import get_path
from warnings import warn from warnings import warn
from packaging.requirements import Requirement as Requirement_
from packaging.version import parse
import packaging.markers
opts, args = getopt( # Monkey patching packaging.markers to handle extras names in a
argv[1:], 'hPRrCEMmLl:', # case-insensitive manner:
['help', 'provides', 'requires', 'recommends', 'conflicts', 'extras', 'majorver-provides', 'majorver-provides-versions=', 'majorver-only', 'legacy-provides' , 'legacy']) # pip considers dnspython[DNSSEC] and dnspython[dnssec] to be equal, but
# packaging markers treat extras in a case-sensitive manner. To solve this
# issue, we introduce a comparison operator that compares case-insensitively
# if both sides of the comparison are strings. And then we inject this
# operator into packaging.markers to be used when comparing names of extras.
# Fedora BZ: https://bugzilla.redhat.com/show_bug.cgi?id=1936875
# Upstream issue: https://discuss.python.org/t/what-extras-names-are-treated-as-equal-and-why/7614
# - After it's established upstream what is the canonical form of an extras
# name, we plan to open an issue with packaging to hopefully solve this
# there without having to resort to monkeypatching.
def str_lower_eq(a, b):
if isinstance(a, str) and isinstance(b, str):
return a.lower() == b.lower()
else:
return a == b
packaging.markers._operators["=="] = str_lower_eq
Provides = False try:
Requires = False from importlib.metadata import PathDistribution
Recommends = False except ImportError:
Conflicts = False from importlib_metadata import PathDistribution
Extras = False
Provides_PyMajorVer_Variant = False
Provides_PyMajorVer_Versions = None
PyMajorVer_Deps = False
legacy_Provides = False
legacy = False
for o, a in opts: try:
if o in ('-h', '--help'): from pathlib import Path
print('-h, --help\tPrint help') except ImportError:
print('-P, --provides\tPrint Provides') from pathlib2 import Path
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 for all Python versions')
print(' --majorver-provides-versions VERSIONS\n'
' \tPrint extra Provides with Python major version only for listed Python VERSIONS (comma separated, no spaces, e.g. 2.7,3.6)')
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 ('--majorver-provides-versions'):
Provides_PyMajorVer_Versions = a.split(",")
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 Provides_PyMajorVer_Variant and Provides_PyMajorVer_Versions:
print("Error, options --majorver-provides and --majorver-provides-versions are mutually incompatible.")
exit(2)
if Requires: def normalize_name(name):
py_abi = True """https://www.python.org/dev/peps/pep-0503/#normalized-names"""
else: return re.sub(r'[-_.]+', '-', name).lower()
py_abi = False
py_deps = {}
if args:
files = args
else:
files = stdin.readlines()
for f in files:
f = f.strip()
lower = f.lower()
name = 'python(abi)'
# add dependency based on path, versioned if within versioned python directory
if py_abi and (lower.endswith('.py') or lower.endswith('.pyc') or lower.endswith('.pyo')):
if name not in py_deps:
py_deps[name] = []
purelib = get_python_lib(standard_lib=0, plat_specific=0).split(version[:3])[0]
platlib = get_python_lib(standard_lib=0, plat_specific=1).split(version[:3])[0]
for lib in (purelib, platlib):
if lib in f:
spec = ('==', f.split(lib)[1].split(sep)[0])
if spec not in py_deps[name]:
py_deps[name].append(spec)
# XXX: hack to workaround RPM internal dependency generator not passing directories def legacy_normalize_name(name):
lower_dir = dirname(lower) """Like pkg_resources Distribution.key property"""
if lower_dir.endswith('.egg') or \ return re.sub(r'[-_]+', '-', name).lower()
lower_dir.endswith('.egg-info') or \
lower_dir.endswith('.dist-info'):
lower = lower_dir class Requirement(Requirement_):
f = dirname(f) def __init__(self, requirement_string):
# Determine provide, requires, conflicts & recommends based on egg/dist metadata super(Requirement, self).__init__(requirement_string)
if lower.endswith('.egg') or \ self.normalized_name = normalize_name(self.name)
lower.endswith('.egg-info') or \ self.legacy_normalized_name = legacy_normalize_name(self.name)
lower.endswith('.dist-info'):
# This import is very slow, so only do it if needed
from pkg_resources import Distribution, FileMetadata, PathMetadata class Distribution(PathDistribution):
dist_name = basename(f) def __init__(self, path):
if isdir(f): super(Distribution, self).__init__(Path(path))
path_item = dirname(f) self.normalized_name = normalize_name(self.name)
metadata = PathMetadata(path_item, f) self.legacy_normalized_name = legacy_normalize_name(self.name)
self.requirements = [Requirement(r) for r in self.requires or []]
self.extras = [
v.lower() for k, v in self.metadata.items() if k == 'Provides-Extra']
self.py_version = self._parse_py_version(path)
# `name` is defined as a property exactly like this in Python 3.10 in the
# PathDistribution class. Due to that we can't redefine `name` as a normal
# attribute. So we copied the Python 3.10 definition here into the code so
# that it works also on previous Python/importlib_metadata versions.
@property
def name(self):
"""Return the 'Name' metadata for the distribution package."""
return self.metadata['Name']
def _parse_py_version(self, path):
# Try to parse the Python version from the path the metadata
# resides at (e.g. /usr/lib/pythonX.Y/site-packages/...)
res = re.search(r"/python(?P<pyver>\d+\.\d+)/", path)
if res:
return res.group('pyver')
# If that hasn't worked, attempt to parse it from the metadata
# directory name
res = re.search(r"-py(?P<pyver>\d+.\d+)[.-]egg-info$", path)
if res:
return res.group('pyver')
return None
def requirements_for_extra(self, extra):
extra_deps = []
for req in self.requirements:
if not req.marker:
continue
if req.marker.evaluate(get_marker_env(self, extra)):
extra_deps.append(req)
return extra_deps
def __repr__(self):
return '{} from {}'.format(self.name, self._path)
class RpmVersion():
def __init__(self, version_id):
version = parse(version_id)
if isinstance(version._version, str):
self.version = version._version
else: else:
path_item = f self.epoch = version._version.epoch
metadata = FileMetadata(f) self.version = list(version._version.release)
dist = Distribution.from_location(path_item, dist_name, metadata) self.pre = version._version.pre
# Check if py_version is defined in the metadata file/directory name self.dev = version._version.dev
if not dist.py_version: self.post = version._version.post
# Try to parse the Python version from the path the metadata
# resides at (e.g. /usr/lib/pythonX.Y/site-packages/...) def increment(self):
import re self.version[-1] += 1
res = re.search(r"/python(?P<pyver>\d+\.\d+)/", path_item) self.pre = None
if res: self.dev = None
dist.py_version = res.group('pyver') self.post = None
else: return self
def __str__(self):
if isinstance(self.version, str):
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('.*'):
print("*** INVALID_REQUIREMENT_ERROR___SEE_STDERR ***")
print('Invalid requirement: {} {} {}'.format(name, operator, version_id), file=stderr)
exit(65) # os.EX_DATAERR
version = RpmVersion(version_id)
if len(version.version) == 1:
print("*** INVALID_REQUIREMENT_ERROR___SEE_STDERR ***")
print('Invalid requirement: {} {} {}'.format(name, operator, version_id), file=stderr)
exit(65) # os.EX_DATAERR
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('.*'):
print("*** INVALID_REQUIREMENT_ERROR___SEE_STDERR ***")
print('Invalid requirement: {} {} {}'.format(name, operator, version_id), file=stderr)
exit(65) # os.EX_DATAERR
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)
lower_version = RpmVersion(version_id).increment()
else:
version = RpmVersion(version_id)
lower_version = version
return '({} < {} or {} > {})'.format(
name, version, name, lower_version)
def convert_ordered(name, operator, version_id):
if version_id.endswith('.*'):
# PEP 440 does not define semantics for prefix matching
# with ordered comparisons
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)
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):
try:
return OPERATORS[operator](name, operator, version_id)
except Exception as exc:
raise RuntimeError("Cannot process Python package version `{}` for name `{}`".
format(version_id, name)) from exc
def get_marker_env(dist, extra):
# packaging uses a default environment using
# 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
# set up an environment with the version we want to evaluate.
#
# [1] https://www.python.org/dev/peps/pep-0508/#environment-markers
return {"python_full_version": dist.py_version,
"python_version": dist.py_version,
"extra": extra}
if __name__ == "__main__":
"""To allow this script to be importable (and its classes/functions
reused), actions are performed only when run as a main script."""
parser = argparse.ArgumentParser(prog=argv[0])
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('-P', '--provides', action='store_true', help='Print Provides')
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='[Unused] Generate spec file snippets for extras subpackages')
group_majorver = parser.add_mutually_exclusive_group()
group_majorver.add_argument('-M', '--majorver-provides', action='store_true', help='Print extra Provides with Python major version only')
group_majorver.add_argument('--majorver-provides-versions', action='append',
help='Print extra Provides with Python major version only for listed '
'Python VERSIONS (appended or comma separated without spaces, e.g. 2.7,3.9)')
parser.add_argument('-m', '--majorver-only', action='store_true', help='Print Provides/Requires with Python major version only')
parser.add_argument('-n', '--normalized-names-format', action='store',
default="legacy-dots", choices=["pep503", "legacy-dots"],
help='Format of normalized names according to pep503 or legacy format that allows dots [default]')
parser.add_argument('--normalized-names-provide-both', action='store_true',
help='Provide both `pep503` and `legacy-dots` format of normalized names (useful for a transition period)')
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('--console-scripts-nodep-setuptools-since', action='store',
help='An optional Python version (X.Y), at least 3.8. '
'For that version and any newer version, '
'a dependency on "setuptools" WILL NOT be generated for packages with console_scripts/gui_scripts entry points. '
'By setting this flag, you guarantee that setuptools >= 47.2.0 is used '
'during the build of packages for this and any newer Python version.')
parser.add_argument('--require-extras-subpackages', action='store_true',
help="If there is a dependency on a package with extras functionality, require the extras subpackage")
parser.add_argument('--package-name', action='store', help="Name of the RPM package that's being inspected. Required for extras requires/provides to work.")
parser.add_argument('files', nargs=argparse.REMAINDER, help="Files from the RPM package that are to be inspected, can also be supplied on stdin")
args = parser.parse_args()
py_abi = args.requires
py_deps = {}
if args.majorver_provides_versions:
# Go through the arguments (can be specified multiple times),
# and parse individual versions (can be comma-separated)
args.majorver_provides_versions = [v for vstring in args.majorver_provides_versions
for v in vstring.split(",")]
# If normalized_names_require_pep503 is True we require the pep503
# normalized name, if it is False we provide the legacy normalized name
normalized_names_require_pep503 = args.normalized_names_format == "pep503"
# If normalized_names_provide_pep503/legacy is True we provide the
# pep503/legacy normalized name, if it is False we don't
normalized_names_provide_pep503 = \
args.normalized_names_format == "pep503" or args.normalized_names_provide_both
normalized_names_provide_legacy = \
args.normalized_names_format == "legacy-dots" or args.normalized_names_provide_both
# At least one type of normalization must be provided
assert normalized_names_provide_pep503 or normalized_names_provide_legacy
if args.console_scripts_nodep_setuptools_since:
nodep_setuptools_pyversion = parse(args.console_scripts_nodep_setuptools_since)
if nodep_setuptools_pyversion < parse("3.8"):
print("Only version 3.8+ is supported in --console-scripts-nodep-setuptools-since", file=stderr)
print("*** PYTHON_EXTRAS_ARGUMENT_ERROR___SEE_STDERR ***")
exit(65) # os.EX_DATAERR
else:
nodep_setuptools_pyversion = None
# Is this script being run for an extras subpackage?
extras_subpackage = None
if args.package_name and '+' in args.package_name:
# The extras names are encoded in the package names after the + sign.
# We take the part after the rightmost +, ignoring when empty,
# this allows packages like nicotine+ or c++ to work fine.
# While packages with names like +spam or foo+bar would break,
# names started with the plus sign are not very common
# and pluses in the middle can be easily replaced with dashes.
# Python extras names don't contain pluses according to PEP 508.
package_name_parts = args.package_name.rpartition('+')
extras_subpackage = package_name_parts[2].lower() or None
for f in (args.files or stdin.readlines()):
f = f.strip()
lower = f.lower()
name = 'python(abi)'
# add dependency based on path, versioned if within versioned python directory
if py_abi and (lower.endswith('.py') or lower.endswith('.pyc') or lower.endswith('.pyo')):
if name not in py_deps:
py_deps[name] = []
running_python_version = '{}.{}'.format(*version_info[:2])
purelib = get_path('purelib').split(running_python_version)[0]
platlib = get_path('platlib').split(running_python_version)[0]
for lib in (purelib, platlib):
if lib in f:
spec = ('==', f.split(lib)[1].split(sep)[0])
if spec not in py_deps[name]:
py_deps[name].append(spec)
# XXX: hack to workaround RPM internal dependency generator not passing directories
lower_dir = dirname(lower)
if lower_dir.endswith('.egg') or \
lower_dir.endswith('.egg-info') or \
lower_dir.endswith('.dist-info'):
lower = lower_dir
f = dirname(f)
# Determine provide, requires, conflicts & recommends based on egg/dist metadata
if lower.endswith('.egg') or \
lower.endswith('.egg-info') or \
lower.endswith('.dist-info'):
dist = Distribution(f)
if not dist.py_version:
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 # If processing an extras subpackage:
import platform # Check that the extras name is declared in the metadata, or
platform.python_version = lambda: dist.py_version # that there are some dependencies associated with the extras
# name in the requires.txt (this is an outdated way to declare
# extras packages).
# - If there is an extras package declared only in requires.txt
# without any dependencies, this check will fail. In that case
# make sure to use updated metadata and declare the extras
# package there.
if extras_subpackage and extras_subpackage not in dist.extras and not dist.requirements_for_extra(extras_subpackage):
print("*** PYTHON_EXTRAS_NOT_FOUND_ERROR___SEE_STDERR ***")
print(f"\nError: The package name contains an extras name `{extras_subpackage}` that was not found in the metadata.\n"
"Check if the extras were removed from the project. If so, consider removing the subpackage and obsoleting it from another.\n", file=stderr)
exit(65) # os.EX_DATAERR
if Provides_PyMajorVer_Variant or PyMajorVer_Deps or legacy_Provides or legacy or Provides_PyMajorVer_Versions: if args.majorver_provides or args.majorver_provides_versions or \
# Get the Python major version args.majorver_only or args.legacy_provides or args.legacy:
pyver_major = dist.py_version.split('.')[0] # Get the Python major version
if Provides: pyver_major = dist.py_version.split('.')[0]
# If egg/dist metadata says package name is python, we provide python(abi) if args.provides:
if dist.key == 'python': extras_suffix = f"[{extras_subpackage}]" if extras_subpackage else ""
# If egg/dist metadata says package name is python, we provide python(abi)
if dist.normalized_name == 'python':
name = 'python(abi)'
if name not in py_deps:
py_deps[name] = []
py_deps[name].append(('==', dist.py_version))
if not args.legacy or not args.majorver_only:
if normalized_names_provide_legacy:
name = 'python{}dist({}{})'.format(dist.py_version, dist.legacy_normalized_name, extras_suffix)
if name not in py_deps:
py_deps[name] = []
if normalized_names_provide_pep503:
name_ = 'python{}dist({}{})'.format(dist.py_version, dist.normalized_name, extras_suffix)
if name_ not in py_deps:
py_deps[name_] = []
if args.majorver_provides or args.majorver_only or \
(args.majorver_provides_versions and dist.py_version in args.majorver_provides_versions):
if normalized_names_provide_legacy:
pymajor_name = 'python{}dist({}{})'.format(pyver_major, dist.legacy_normalized_name, extras_suffix)
if pymajor_name not in py_deps:
py_deps[pymajor_name] = []
if normalized_names_provide_pep503:
pymajor_name_ = 'python{}dist({}{})'.format(pyver_major, dist.normalized_name, extras_suffix)
if pymajor_name_ not in py_deps:
py_deps[pymajor_name_] = []
if args.legacy or args.legacy_provides:
legacy_name = 'pythonegg({})({})'.format(pyver_major, dist.legacy_normalized_name)
if legacy_name not in py_deps:
py_deps[legacy_name] = []
if dist.version:
version = dist.version
spec = ('==', version)
if normalized_names_provide_legacy:
if spec not in py_deps[name]:
py_deps[name].append(spec)
if args.majorver_provides or \
(args.majorver_provides_versions and dist.py_version in args.majorver_provides_versions):
py_deps[pymajor_name].append(spec)
if normalized_names_provide_pep503:
if spec not in py_deps[name_]:
py_deps[name_].append(spec)
if args.majorver_provides or \
(args.majorver_provides_versions and dist.py_version in args.majorver_provides_versions):
py_deps[pymajor_name_].append(spec)
if args.legacy or args.legacy_provides:
if spec not in py_deps[legacy_name]:
py_deps[legacy_name].append(spec)
if args.requires or (args.recommends and dist.extras):
name = 'python(abi)' name = 'python(abi)'
if name not in py_deps: # If egg/dist metadata says package name is python, we don't add dependency on python(abi)
py_deps[name] = [] if dist.normalized_name == 'python':
py_deps[name].append(('==', dist.py_version)) py_abi = False
if not legacy or not PyMajorVer_Deps: if name in py_deps:
name = 'python{}dist({})'.format(dist.py_version, dist.key) py_deps.pop(name)
if name not in py_deps: elif py_abi and dist.py_version:
py_deps[name] = [] if name not in py_deps:
if Provides_PyMajorVer_Variant or PyMajorVer_Deps or \ py_deps[name] = []
(Provides_PyMajorVer_Versions and dist.py_version in Provides_PyMajorVer_Versions): spec = ('==', dist.py_version)
pymajor_name = 'python{}dist({})'.format(pyver_major, dist.key) if spec not in py_deps[name]:
if pymajor_name not in py_deps:
py_deps[pymajor_name] = []
if legacy or legacy_Provides:
legacy_name = 'pythonegg({})({})'.format(pyver_major, dist.key)
if legacy_name not in py_deps:
py_deps[legacy_name] = []
if dist.version:
spec = ('==', dist.version)
if spec not in py_deps[name]:
if not legacy:
py_deps[name].append(spec) py_deps[name].append(spec)
if Provides_PyMajorVer_Variant or \
(Provides_PyMajorVer_Versions and dist.py_version in Provides_PyMajorVer_Versions): if extras_subpackage:
py_deps[pymajor_name].append(spec) deps = [d for d in dist.requirements_for_extra(extras_subpackage)]
if legacy or legacy_Provides:
py_deps[legacy_name].append(spec)
if Requires or (Recommends and dist.extras):
name = 'python(abi)'
# If egg/dist metadata says package name is python, we don't add dependency on python(abi)
if dist.key == 'python':
py_abi = False
if name in py_deps:
py_deps.pop(name)
elif py_abi and dist.py_version:
if name not in py_deps:
py_deps[name] = []
spec = ('==', dist.py_version)
if spec not in py_deps[name]:
py_deps[name].append(spec)
deps = dist.requires()
if Recommends:
depsextras = dist.requires(extras=dist.extras)
if not Requires:
for dep in reversed(depsextras):
if dep in deps:
depsextras.remove(dep)
deps = depsextras
# add requires/recommends based on egg/dist metadata
for dep in deps:
if legacy:
name = 'pythonegg({})({})'.format(pyver_major, dep.key)
else: else:
if PyMajorVer_Deps: deps = dist.requirements
name = 'python{}dist({})'.format(pyver_major, dep.key)
else: # console_scripts/gui_scripts entry points needed pkg_resources from setuptools
name = 'python{}dist({})'.format(dist.py_version, dep.key) # on new Python/setuptools versions, this is no longer required
for spec in dep.specs: if nodep_setuptools_pyversion is None or parse(dist.py_version) < nodep_setuptools_pyversion:
if spec[0] != '!=': if (dist.entry_points and
if name not in py_deps: (lower.endswith('.egg') or
py_deps[name] = [] lower.endswith('.egg-info'))):
if spec not in py_deps[name]: groups = {ep.group for ep in dist.entry_points}
py_deps[name].append(spec) if {"console_scripts", "gui_scripts"} & groups:
if not dep.specs: # stick them first so any more specific requirement
py_deps[name] = [] # overrides it
# Unused, for automatic sub-package generation based on 'extras' from egg/dist metadata deps.insert(0, Requirement('setuptools'))
# TODO: implement in rpm later, or...? # add requires/recommends based on egg/dist metadata
if Extras:
deps = dist.requires()
extras = dist.extras
print(extras)
for extra in extras:
print('%%package\textras-{}'.format(extra))
print('Summary:\t{} extra for {} python package'.format(extra, dist.key))
print('Group:\t\tDevelopment/Python')
depsextras = dist.requires(extras=[extra])
for dep in reversed(depsextras):
if dep in deps:
depsextras.remove(dep)
deps = depsextras
for dep in deps: for dep in deps:
for spec in dep.specs: # Even if we're requiring `foo[bar]`, also require `foo`
if spec[0] == '!=': # to be safe, and to make it discoverable through
print('Conflicts:\t{} {} {}'.format(dep.key, '==', spec[1])) # `repoquery --whatrequires`
extras_suffixes = [""]
if args.require_extras_subpackages and dep.extras:
# A dependency can have more than one extras,
# i.e. foo[bar,baz], so let's go through all of them
extras_suffixes += [f"[{e.lower()}]" for e in dep.extras]
for extras_suffix in extras_suffixes:
if normalized_names_require_pep503:
dep_normalized_name = dep.normalized_name
else: else:
print('Requires:\t{} {} {}'.format(dep.key, spec[0], spec[1])) dep_normalized_name = dep.legacy_normalized_name
print('%%description\t{}'.format(extra))
print('{} extra for {} python package'.format(extra, dist.key)) if args.legacy:
print('%%files\t\textras-{}\n'.format(extra)) name = 'pythonegg({})({})'.format(pyver_major, dep.legacy_normalized_name)
if Conflicts: else:
# Should we really add conflicts for extras? if args.majorver_only:
# Creating a meta package per extra with recommends on, which has name = 'python{}dist({}{})'.format(pyver_major, dep_normalized_name, extras_suffix)
# the requires/conflicts in stead might be a better solution... else:
for dep in dist.requires(extras=dist.extras): name = 'python{}dist({}{})'.format(dist.py_version, dep_normalized_name, extras_suffix)
name = dep.key
for spec in dep.specs: if dep.marker and not args.recommends and not extras_subpackage:
if spec[0] == '!=': if not dep.marker.evaluate(get_marker_env(dist, '')):
continue
if name not in py_deps: if name not in py_deps:
py_deps[name] = [] py_deps[name] = []
spec = ('==', spec[1]) for spec in dep.specifier:
if spec not in py_deps[name]: if (spec.operator, spec.version) not in py_deps[name]:
py_deps[name].append(spec) py_deps[name].append((spec.operator, spec.version))
names = list(py_deps.keys())
names.sort() # Unused, for automatic sub-package generation based on 'extras' from egg/dist metadata
for name in names: # TODO: implement in rpm later, or...?
if py_deps[name]: if args.extras:
# Print out versioned provides, requires, recommends, conflicts print(dist.extras)
for spec in py_deps[name]: for extra in dist.extras:
print('{} {} {}'.format(name, spec[0], spec[1])) print('%%package\textras-{}'.format(extra))
else: print('Summary:\t{} extra for {} python package'.format(extra, dist.legacy_normalized_name))
# Print out unversioned provides, requires, recommends, conflicts print('Group:\t\tDevelopment/Python')
print(name) for dep in dist.requirements_for_extra(extra):
for spec in dep.specifier:
if spec.operator == '!=':
print('Conflicts:\t{} {} {}'.format(dep.legacy_normalized_name, '==', spec.version))
else:
print('Requires:\t{} {} {}'.format(dep.legacy_normalized_name, spec.operator, spec.version))
print('%%description\t{}'.format(extra))
print('{} extra for {} python package'.format(extra, dist.legacy_normalized_name))
print('%%files\t\textras-{}\n'.format(extra))
if args.conflicts:
# Should we really add conflicts for extras?
# Creating a meta package per extra with recommends on, which has
# the requires/conflicts in stead might be a better solution...
for dep in dist.requirements:
for spec in dep.specifier:
if spec.operator == '!=':
if dep.legacy_normalized_name not in py_deps:
py_deps[dep.legacy_normalized_name] = []
spec = ('==', spec.version)
if spec not in py_deps[dep.legacy_normalized_name]:
py_deps[dep.legacy_normalized_name].append(spec)
for name in sorted(py_deps):
if py_deps[name]:
# Print out versioned provides, requires, recommends, conflicts
spec_list = []
for spec in py_deps[name]:
spec_list.append(convert(name, spec[0], spec[1]))
if len(spec_list) == 1:
print(spec_list[0])
else:
# Sort spec_list so that the results can be tested easily
print('({})'.format(' with '.join(sorted(spec_list))))
else:
# Print out unversioned provides, requires, recommends, conflicts
print(name)

42
SOURCES/pythonname.attr Normal file
View File

@ -0,0 +1,42 @@
%__pythonname_provides() %{lua:
local python = require 'fedora.srpm.python'
-- this macro is called for each file in a package, the path being in %1
-- but we don't need to know the path, so we would get for each file: Macro %1 defined but not used within scope
-- in here, we expand %name conditionally on %1 to suppress the warning
local name = rpm.expand('%{?1:%{name}}')
local evr = rpm.expand('%{?epoch:%{epoch}:}%{version}-%{release}')
local provides = python.python_altprovides_once(name, evr)
-- provides is either an array/table or nil
-- nil means the function was already called with the same arguments:
-- either with another file in %1 or manually via %py_provides
if provides then
for i, provide in ipairs(provides) do
print(provide .. ' ')
end
end
}
%__pythonname_obsoletes() %{?rhel:%{lua:
-- On CentOS/RHEL we automatically generate Obsoletes tags in the form:
-- package python3-foo -> Obsoletes: python3.XY-foo
-- This provides a clean upgrade path between major versions of CentOS/RHEL.
-- In Fedora this is not needed as we don't ship ecosystem packages
-- for alternative Python interpreters.
local python = require 'fedora.srpm.python'
-- this macro is called for each file in a package, the path being in %1
-- but we don't need to know the path, so we would get for each file: Macro %1 defined but not used within scope
-- in here, we expand %name conditionally on %1 to suppress the warning
local name = rpm.expand('%{?1:%{name}}')
local evr = rpm.expand('%{?epoch:%{epoch}:}%{version}-%{release}')
local obsoletes = python.python_altobsoletes_once(name, evr)
-- obsoletes is either an array/table or nil
-- nil means the function was already called with the same arguments:
-- either with another file in %1 or manually via %py_provides
if obsoletes then
for i, obsolete in ipairs(obsoletes) do
print(obsolete .. ' ')
end
end
}}
%__pythonname_path ^/

View File

@ -1,11 +1,7 @@
# Disable automatic bytecompilation. We install only one script and we will
# never "import" it.
%undefine py_auto_byte_compile
Name: python-rpm-generators Name: python-rpm-generators
Summary: Dependency generators for Python RPMs Summary: Dependency generators for Python RPMs
Version: 5 Version: 12
Release: 8%{?dist} Release: 9%{?dist}
# Originally all those files were part of RPM, so license is kept here # Originally all those files were part of RPM, so license is kept here
License: GPLv2+ License: GPLv2+
@ -13,8 +9,10 @@ Url: https://src.fedoraproject.org/python-rpm-generators
# Commit is the last change in following files # Commit is the last change in following files
Source0: https://raw.githubusercontent.com/rpm-software-management/rpm/102eab50b3d0d6546dfe082eac0ade21e6b3dbf1/COPYING Source0: https://raw.githubusercontent.com/rpm-software-management/rpm/102eab50b3d0d6546dfe082eac0ade21e6b3dbf1/COPYING
Source1: python.attr Source1: python.attr
Source2: pythondeps.sh Source2: pythondist.attr
Source3: pythondistdeps.py Source3: pythonname.attr
Source4: pythondistdeps.py
Source5: pythonbundles.py
BuildArch: noarch BuildArch: noarch
@ -23,13 +21,11 @@ BuildArch: noarch
%package -n python3-rpm-generators %package -n python3-rpm-generators
Summary: %{summary} Summary: %{summary}
%if 0%{?rhel} && 0%{?rhel} >= 8 Requires: python3-packaging
Requires: platform-python-setuptools # We have parametric macro generators, we need RPM 4.16 (4.15.90+ is 4.16 alpha)
%else Requires: rpm > 4.15.90-0
Requires: python3-setuptools # This contains the Lua functions we use:
%endif Requires: python-srpm-macros >= 3.9-49
# The point of split
Conflicts: rpm-build < 4.13.0.1-2
%description -n python3-rpm-generators %description -n python3-rpm-generators
%{summary}. %{summary}.
@ -38,47 +34,157 @@ Conflicts: rpm-build < 4.13.0.1-2
%autosetup -c -T %autosetup -c -T
cp -a %{sources} . cp -a %{sources} .
# Set which Python versions should have the major-version provides
# (pythonXdist...) generated
sed -i 's/@MAJORVER-PROVIDES-VERSIONS@/2.7,3.6/' python.attr
%install %install
install -Dpm0644 -t %{buildroot}%{_fileattrsdir} python.attr install -Dpm0644 -t %{buildroot}%{_fileattrsdir} *.attr
install -Dpm0755 -t %{buildroot}%{_rpmconfigdir} pythondeps.sh pythondistdeps.py install -Dpm0755 -t %{buildroot}%{_rpmconfigdir} *.py
%files -n python3-rpm-generators %files -n python3-rpm-generators
%license COPYING %license COPYING
%{_fileattrsdir}/python.attr %{_fileattrsdir}/python.attr
%{_rpmconfigdir}/pythondeps.sh %{_fileattrsdir}/pythondist.attr
%{_fileattrsdir}/pythonname.attr
%{_rpmconfigdir}/pythondistdeps.py %{_rpmconfigdir}/pythondistdeps.py
%{_rpmconfigdir}/pythonbundles.py
%changelog %changelog
* Mon Nov 14 2022 Charalampos Stratakis <cstratak@redhat.com> - 5-8 * Fri Sep 22 2023 Tomáš Hrnčiar <thrnciar@redhat.com> - 12-9
- Fix the pythondeps.sh and pythondistdeps.py scripts for multiple digits python versions - Avoid needless pkg_resources import in pythonbundles.py
- Resolves: rhbz#2143990 - Resolves: RHEL-6110
* Tue Jun 15 2021 Tomas Orsava <torsava@redhat.com> - 5-7 * Wed Jan 26 2022 Tomas Orsava <torsava@redhat.com> - 12-8
- Do not parse nested dist/egg-info metadata - From `python3-foo` packages automatically generate `python3.X-foo` Obsoletes
- Resolves: rhbz#1916172 tags on CentOS/RHEL
- Resolves: rhbz#1990421
* Thu Dec 12 2019 Tomas Orsava <torsava@redhat.com> - 5-6 * Tue Aug 10 2021 Mohan Boddu <mboddu@redhat.com> - 12-7
- Enabled gating - Rebuilt for IMA sigs, glibc 2.34, aarch64 flags
- Related: rhbz#1776941 Related: rhbz#1991688
* Wed Nov 27 2019 Tomas Orsava <torsava@redhat.com> - 5-5 * Mon Apr 19 2021 Miro Hrončok <mhroncok@redhat.com> - 12-6
- Create major-version provides only on major Python versions (2.7, 3.6) - Get rid of distutils deprecation warning (by not using it)
- Fix an extra parenthesis in python.attr - The distutils module is deprecated in Python 3.10+
- Resolves: rhbz#1776941 - https://www.python.org/dev/peps/pep-0632/
* Fri Nov 16 2018 Lumír Balhar <lbalhar@redhat.com> - 5-4 * Fri Apr 16 2021 Miro Hrončok <mhroncok@redhat.com> - 12-5.1
- Require platform-python-setuptools instead of python3-setuptools - Do not generate setuptools requirement for console_scripts on Python 3.10+
- Resolves: rhbz#1650544 - See https://fedoraproject.org/wiki/Changes/Reduce_dependencies_on_python3-setuptools
* Sat Jul 28 2018 Miro Hrončok <mhroncok@redhat.com> - 5-3 * Fri Apr 16 2021 Mohan Boddu <mboddu@redhat.com> - 12-5
- Rebuilt for RHEL 9 BETA on Apr 15th 2021. Related: rhbz#1947937
* Thu Mar 11 2021 Tomas Orsava <torsava@redhat.com> - 12-4
- scripts/pythondistdeps: Treat extras names case-insensitively and always
output them in lower case (#1936875)
* Mon Feb 22 2021 Tomas Orsava <torsava@redhat.com> - 12-3
- scripts/pythondistdeps: Fix for Python 3.10
* Wed Feb 17 2021 Tomas Orsava <torsava@redhat.com> - 12-2
- scripts/pythondistdeps: Switch from using pkg_resources to importlib.metadata
for reading the egg/dist-info metadata
- The script no longer requires setuptools but instead requires packaging
* Wed Feb 03 2021 Miro Hrončok <mhroncok@redhat.com> - 12-1
- Disable the dist generators for Python 2
- https://fedoraproject.org/wiki/Changes/Disable_Python_2_Dist_RPM_Generators_and_Freeze_Python_2_Macros
* Wed Jan 27 2021 Fedora Release Engineering <releng@fedoraproject.org> - 11-13
- Rebuilt for https://fedoraproject.org/wiki/Fedora_34_Mass_Rebuild
* Mon Oct 19 2020 Tomas Orsava <torsava@redhat.com> - 11-12
- Run scripts in an isolated Python environment (#1889080)
* Wed Jul 29 2020 Fedora Release Engineering <releng@fedoraproject.org> - 11-11
- Rebuilt for https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild
* Tue Jul 21 2020 Miro Hrončok <mhroncok@redhat.com> - 11-10
- pythondistdeps: Split Python Extras names after the rightmost plus sign
- pythondistdeps: Handle edge cases of version comparisons more closely to
upstream, despite irrationality
See: https://github.com/pypa/packaging/issues/320
* Fri Jul 10 2020 Tomas Orsava <torsava@redhat.com> - 11-9
- pythondistdeps: Implement provides/requires for extras packages
- Enable --require-extras-subpackages
- Adapt Python version marker workaround for setuptools 42+
* Fri Jun 26 2020 Miro Hrončok <mhroncok@redhat.com> - 11-8
- Fix python(abi) requires generator, it picked files from almost good directories
- Add a script to generate Python bundled provides
* Thu May 21 2020 Miro Hrončok <mhroncok@redhat.com> - 11-7
- Use PEP 503 names for requires
* Tue May 05 2020 Miro Hrončok <mhroncok@redhat.com> - 11-6
- Deduplicate automatically provided names trough Python RPM Lua macros
* Wed Apr 29 2020 Tomas Orsava <torsava@redhat.com> - 11-5
- Backporting proposed upstream changes
https://github.com/rpm-software-management/rpm/pull/1195
- Only provide python3dist(..) for the main Python versions (BZ#1812083)
- Preparation for the proper handling of normalized names (BZ#1791530)
- Add a test suite (and enable it in Fedora CI)
- Better error messages for unsupported package versions
- Fix sorting of dev versions
* Tue Apr 28 2020 Miro Hrončok <mhroncok@redhat.com> - 11-4
- Don't define global Lua variables from Python generator
* Mon Apr 20 2020 Gordon Messmer <gordon.messmer@gmail.com> - 11-3
- Handle all-zero versions without crashing
* Tue Apr 07 2020 Miro Hrončok <mhroncok@redhat.com> - 11-2
- Use dynamic %%_prefix value when matching files for python(abi) provides
- Sync with upstream RPM dist generator
* Wed Apr 01 2020 Miro Hrončok <mhroncok@redhat.com> - 11-1
- Rewrite python(abi) generators to Lua to make them faster
- RPM 4.16+ is needed
- Automatically call %%python_provide
* Thu Jan 30 2020 Fedora Release Engineering <releng@fedoraproject.org> - 10-4
- Rebuilt for https://fedoraproject.org/wiki/Fedora_32_Mass_Rebuild
* Fri Jan 17 2020 Miro Hrončok <mhroncok@redhat.com> - 10-3
- Also provide pythonXdist() with PEP 503 normalized names (#1791530)
* Fri Jan 03 2020 Miro Hrončok <mhroncok@redhat.com> - 10-2
- Fix more complicated requirement expressions by adding parenthesis
* Wed Jan 01 2020 Miro Hrončok <mhroncok@redhat.com> - 10-1
- Handle version ending with ".*" (#1758141)
- Handle compatible-release operator "~=" (#1758141)
- Use rich deps for semantically versioned dependencies
- Match Python version if minor has multiple digits (e.g. 3.10, #1777382)
- Only add setuptools requirement for egg-info packages
* Fri Jul 26 2019 Fedora Release Engineering <releng@fedoraproject.org> - 9-2
- Rebuilt for https://fedoraproject.org/wiki/Fedora_31_Mass_Rebuild
* Mon Jun 24 2019 Tomas Orsava <torsava@redhat.com> - 9-1
- Canonicalize Python versions and properly handle != spec
* Wed Apr 17 2019 Miro Hrončok <mhroncok@redhat.com> - 8-1
- console_scripts entry points to require setuptools
https://github.com/rpm-software-management/rpm/pull/666
* Sat Feb 02 2019 Fedora Release Engineering <releng@fedoraproject.org> - 7-2
- Rebuilt for https://fedoraproject.org/wiki/Fedora_30_Mass_Rebuild
* Thu Dec 20 2018 Igor Gnatenko <ignatenkobrain@fedoraproject.org> - 7-1
- Enable requires generator
* Wed Oct 03 2018 Igor Gnatenko <ignatenkobrain@fedoraproject.org> - 6-1
- Tighten regex for depgen
* Sat Jul 28 2018 Miro Hrončok <mhroncok@redhat.com> - 5-4
- Use nonstandardlib for purelib definition (#1609492) - Use nonstandardlib for purelib definition (#1609492)
* Tue Jun 05 2018 Tomas Orsava <torsava@redhat.com> - 5-2 * Sat Jul 28 2018 Igor Gnatenko <ignatenkobrain@fedoraproject.org> - 5-3
- Switch the pythondistdeps.py script to /usr/libexec/platform-python - Add pythondist generator
* Sat Jul 14 2018 Fedora Release Engineering <releng@fedoraproject.org> - 5-2
- Rebuilt for https://fedoraproject.org/wiki/Fedora_29_Mass_Rebuild
* Sun Feb 11 2018 Igor Gnatenko <ignatenkobrain@fedoraproject.org> - 5-1 * Sun Feb 11 2018 Igor Gnatenko <ignatenkobrain@fedoraproject.org> - 5-1
- Fork upstream generators - Fork upstream generators