Compare commits

..

No commits in common. "rawhide" and "f33" have entirely different histories.

30 changed files with 385 additions and 1018 deletions

View file

@ -1 +0,0 @@
1

3
.gitignore vendored
View file

@ -1,6 +1,3 @@
/test-sources-2020-04-29.tar.gz
/__pycache__/
/tests/__pycache__/
/tests/data/scripts_pythondistdeps/usr/
/test-sources-2021-03-11.tar.gz
/test-sources-2023-01-04.tar.gz

View file

@ -1,45 +0,0 @@
execute:
how: tmt
discover:
- name: same_repo
how: shell
dist-git-source: true
dist-git-download-only: true
tests:
- name: pythonabi
path: /tests
test: ./pythonabi.sh
- name: pythonname
path: /tests
test: ./pythonname.sh
- name: pythondist
path: /tests
test: ./pythondist.sh
- name: console_script
path: /tests
test: ./console_script.sh
- name: pytest
test: cd $TMT_SOURCE_DIR && ./tests/download_data_and_run_pytest.sh
prepare:
- name: Install dependencies
how: install
package:
- rpm-build
- rpmdevtools
- fedpkg-minimal
- python3-devel
- python3-pip
- python3-pytest
- python3-pyyaml
- python3-setuptools
- python3-wheel
- dnf
- name: Update packages
how: shell
script: dnf upgrade -y
- name: rpm_qa
order: 100
how: shell
script: rpm -qa | sort | tee $TMT_PLAN_DATA/rpmqa.txt

View file

@ -1,28 +1,19 @@
Name: python-rpm-generators
Summary: Dependency generators for Python RPMs
Version: 14
Release: 13%{?dist}
Version: 11
Release: 11%{?dist}
Url: https://src.fedoraproject.org/rpms/python-rpm-generators
# Originally the following files were part of RPM, so the license is inherited: GPL-2.0-or-later
# The COPYING file is grabbed from the last commit that changed the files
# Originally all those files were part of RPM, so license is kept here
License: GPLv2+
Url: https://src.fedoraproject.org/python-rpm-generators
# Commit is the last change in following files
Source0: https://raw.githubusercontent.com/rpm-software-management/rpm/102eab50b3d0d6546dfe082eac0ade21e6b3dbf1/COPYING
Source1: python.attr
Source2: pythondist.attr
# This was crafted in-place as a fork of python.attr, hence also GPL-2.0-or-later
Source3: pythonname.attr
# This one is also originally from RPM, but it has its own license declaration: LGPL-2.1-or-later
Source4: pythondistdeps.py
# This was crafted in-place with the following license declaration:
# LicenseRef-Fedora-Public-Domain OR CC0-1.0 OR LGPL-2.1-or-later OR GPL-2.0-or-later
# Note that CC0-1.0 is not allowed for code in Fedora, so we skip it in the package License tag
Source5: pythonbundles.py
# See individual licenses above Source declarations
# Originally, this was simplified to GPL-2.0-or-later, but "effective license" analysis is no longer allowed
License: GPL-2.0-or-later AND LGPL-2.1-or-later AND (LicenseRef-Fedora-Public-Domain OR LGPL-2.1-or-later OR GPL-2.0-or-later)
BuildArch: noarch
%description
@ -30,11 +21,11 @@ BuildArch: noarch
%package -n python3-rpm-generators
Summary: %{summary}
Requires: python3-packaging
Requires: python3-setuptools
# We have parametric macro generators, we need RPM 4.16 (4.15.90+ is 4.16 alpha)
Requires: rpm > 4.15.90-0
# This contains the Lua functions we use:
Requires: python-srpm-macros >= 3.10-15
Requires: python-srpm-macros >= 3.8-5
%description -n python3-rpm-generators
%{summary}.
@ -56,114 +47,6 @@ install -Dpm0755 -t %{buildroot}%{_rpmconfigdir} *.py
%{_rpmconfigdir}/pythonbundles.py
%changelog
* Fri Jul 25 2025 Fedora Release Engineering <releng@fedoraproject.org> - 14-13
- Rebuilt for https://fedoraproject.org/wiki/Fedora_43_Mass_Rebuild
* Sat Jan 18 2025 Fedora Release Engineering <releng@fedoraproject.org> - 14-12
- Rebuilt for https://fedoraproject.org/wiki/Fedora_42_Mass_Rebuild
* Fri Jul 19 2024 Fedora Release Engineering <releng@fedoraproject.org> - 14-11
- Rebuilt for https://fedoraproject.org/wiki/Fedora_41_Mass_Rebuild
* Fri Jan 26 2024 Fedora Release Engineering <releng@fedoraproject.org> - 14-10
- Rebuilt for https://fedoraproject.org/wiki/Fedora_40_Mass_Rebuild
* Mon Jan 22 2024 Fedora Release Engineering <releng@fedoraproject.org> - 14-9
- Rebuilt for https://fedoraproject.org/wiki/Fedora_40_Mass_Rebuild
* Tue Oct 03 2023 Miro Hrončok <mhroncok@redhat.com> - 14-8
- Avoid DeprecationWarning: Implicit None on return values is deprecated and will raise KeyErrors
* Fri Jul 21 2023 Fedora Release Engineering <releng@fedoraproject.org> - 14-7
- Rebuilt for https://fedoraproject.org/wiki/Fedora_39_Mass_Rebuild
* Wed May 24 2023 Todd Zullinger <tmz@pobox.com> - 14-6
- Fix URL tag
* Fri May 05 2023 Miro Hrončok <mhroncok@redhat.com> - 14-5
- Declare the license via a complex SPDX expression rather than "effective license"
* Mon Apr 17 2023 Kalev Lember <klember@redhat.com> - 14-4
- Generate provides for /app-installed flatpak builds
* Tue Mar 07 2023 Miro Hrončok <mhroncok@redhat.com> - 14-3
- Avoid needless pkg_resources import in pythonbundles.py
- Ignore environment markers in pythonbundles.py
* Fri Jan 20 2023 Fedora Release Engineering <releng@fedoraproject.org> - 14-2
- Rebuilt for https://fedoraproject.org/wiki/Fedora_38_Mass_Rebuild
* Thu Dec 22 2022 Karolina Surma <ksurma@redhat.com> - 14-1
- https://fedoraproject.org/wiki/Changes/Prevent-Providing-python3dist(pkg)=0
* Fri Jul 22 2022 Fedora Release Engineering <releng@fedoraproject.org> - 13-2
- Rebuilt for https://fedoraproject.org/wiki/Fedora_37_Mass_Rebuild
* Thu Jun 02 2022 Miro Hrončok <mhroncok@redhat.com> - 13-1
- https://fedoraproject.org/wiki/Changes/PythonDistPEP503ProvidesOnly
* Fri May 27 2022 Miro Hrončok <mhroncok@redhat.com> - 12-15
- Don't include all requirements with True-evaluating markers in extras subpackages
- Fixes: rhbz#2090186
* Thu Feb 10 2022 Sandro Mani <manisandro@gmail.com> - 12-14
- Add namespace option to pythodistdeps.py to allow mingw-python generatros
* Wed Jan 26 2022 Tomas Orsava <torsava@redhat.com> - 12-13
- From `python3-foo` packages automatically generate `python3.X-foo` Obsoletes
tags on CentOS/RHEL
* Fri Jan 21 2022 Fedora Release Engineering <releng@fedoraproject.org> - 12-12
- Rebuilt for https://fedoraproject.org/wiki/Fedora_36_Mass_Rebuild
* Sun Dec 19 2021 Gordon Messmer <gordon.messmer@gmail.com> - 12-11
- Handle legacy version specifiers that would previously raise exceptions.
* Fri Oct 29 2021 Gordon Messmer <gordon.messmer@gmail.com> - 12-10
- Additional fix for dev releases.
* Thu Oct 28 2021 Gordon Messmer <gordon.messmer@gmail.com> - 12-9
- Sync dependency conversion with upstream pyreq2rpm.
- Improve handling of > and < operators, and != operator with prefix matching
* Fri Jul 23 2021 Fedora Release Engineering <releng@fedoraproject.org> - 12-8
- Rebuilt for https://fedoraproject.org/wiki/Fedora_35_Mass_Rebuild
* Tue May 25 2021 Tomas Orsava <torsava@redhat.com> - 12-7
- pythondistdeps.py: Detect missing or corrupted metadata
- pythondistdeps.py: Catch all exceptions and terminate the build if one is raised
* Mon Apr 19 2021 Miro Hrončok <mhroncok@redhat.com> - 12-6
- Get rid of distutils deprecation warning (by not using it)
- The distutils module is deprecated in Python 3.10+
- https://www.python.org/dev/peps/pep-0632/
* Wed Mar 31 2021 Miro Hrončok <mhroncok@redhat.com> - 12-5
- Do not generate setuptools requirement for console_scripts on Python 3.10+
- See https://fedoraproject.org/wiki/Changes/Reduce_dependencies_on_python3-setuptools
* 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

View file

@ -5,10 +5,8 @@
-- python(abi) = MAJOR.MINOR
-- (Don't match against -config tools e.g. /usr/bin/python2.6-config)
local path = rpm.expand('%1')
-- Use /usr prefix by default, and /app for flatpak builds
local prefix = rpm.expand('%{?!flatpak:/usr}%{?flatpak:/app}')
if path:match(prefix .. '/bin/python%d+%.%d+$') then
local provides = path:gsub('.*' .. prefix .. '/bin/python(%d+%.%d+)', 'python(abi) = %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
}
@ -20,12 +18,10 @@
-- generating a line of the form:
-- python(abi) = MAJOR.MINOR
local path = rpm.expand('%1')
-- Use /usr prefix by default, and /app for flatpak builds
local prefix = rpm.expand('%{?!flatpak:/usr}%{?flatpak:/app}')
if path:match(prefix .. '/lib%d*/python%d+%.%d+/.*') then
local requires = path:gsub('.*' .. prefix .. '/lib%d*/python(%d+%.%d+)/.*', 'python(abi) = %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 ^((%{?!flatpak:/usr}%{?flatpak:/app}/lib(64)?/python[[:digit:]]+\\.[[:digit:]]+/.*\\.(py[oc]?|so))|(%{_bindir}/python[[:digit:]]+\\.[[:digit:]]+))$
%__python_path ^((%{_prefix}/lib(64)?/python[[:digit:]]+\\.[[:digit:]]+/.*\\.(py[oc]?|so))|(%{_bindir}/python[[:digit:]]+\\.[[:digit:]]+))$

58
pythonbundles.py Executable file → Normal file
View file

@ -1,10 +1,10 @@
#!/usr/bin/python3 -sB
#!/usr/bin/python3 -B
# (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 you choose.
# 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).
@ -15,37 +15,33 @@
import pathlib
import sys
from packaging import requirements
# inject parse_version import to pythondistdeps
# not the nicest API, but :/
from pkg_resources import parse_version
import pythondistdeps
pythondistdeps.parse_version = parse_version
def generate_bundled_provides(paths, namespace):
def generate_bundled_provides(path, namespace):
provides = set()
for path in paths:
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:
requirement = requirements.Requirement(line)
for spec in requirement.specifier:
if spec.operator == '==':
version = spec.version
break
else:
raise ValueError('pythonbundles.py only handles exactly one == requirement')
name = pythondistdeps.normalize_name(requirement.name)
bundled_name = f"bundled({namespace}({name}))"
python_provide = pythondistdeps.convert(bundled_name, '==', version)
provides.add(f'Provides: {python_provide}')
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
@ -74,7 +70,7 @@ if __name__ == '__main__':
parser = argparse.ArgumentParser(prog=sys.argv[0],
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('vendored', metavar='VENDORED.TXT', nargs='+', type=pathlib.Path,
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')
@ -82,7 +78,7 @@ if __name__ == '__main__':
help='What namespace of provides will used', default='python3dist')
args = parser.parse_args()
provides = generate_bundled_provides(args.vendored, args.namespace)
provides = generate_bundled_provides(pathlib.Path(args.vendored), args.namespace)
if args.compare_with:
given = args.compare_with.splitlines()

View file

@ -1,3 +1,3 @@
%__pythondist_provides %{_rpmconfigdir}/pythondistdeps.py --provides --normalized-names-format pep503 --package-name %{name} --majorver-provides-versions %{__default_python3_version} %{?!_python_dist_allow_version_zero:--fail-if-zero}
%__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 ^%{?!flatpak:/usr}%{?flatpak:/app}/lib(64)?/python[3-9]\\.[[:digit:]]+/site-packages/[^/]+\\.(dist-info|egg-info|egg-link)$
%__pythondist_provides %{_rpmconfigdir}/pythondistdeps.py --provides --normalized-names-format pep503 --package-name %{name} --normalized-names-provide-both --majorver-provides-versions 2.7,%{__default_python3_version}
%__pythondist_requires %{_rpmconfigdir}/pythondistdeps.py --requires --normalized-names-format pep503 --package-name %{name} %{?!_python_no_extras_requires:--require-extras-subpackages}
%__pythondist_path ^/usr/lib(64)?/python[[:digit:]]\\.[[:digit:]]+/site-packages/[^/]+\\.(dist-info|egg-info|egg-link)$

View file

@ -1,4 +1,4 @@
#!/usr/bin/python3 -s
#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Copyright 2010 Per Øyvind Karlsen <proyvind@moondrake.org>
@ -11,128 +11,21 @@
# RPM python dependency generator, using .egg-info/.egg-link/.dist-info data
#
# Please know:
# - Notes from an attempted rewrite from pkg_resources to importlib.metadata in
# 2020 can be found in the message of the commit that added this line.
from __future__ import print_function
import argparse
from os.path import dirname, sep
import re
from sys import argv, stdin, stderr, version_info
from sysconfig import get_path
from os.path import basename, dirname, isdir, sep
from sys import argv, stdin, stderr, version
from distutils.sysconfig import get_python_lib
from warnings import warn
from packaging.requirements import Requirement as Requirement_
from packaging.version import parse
import packaging.markers
# Monkey patching packaging.markers to handle extras names in a
# case-insensitive manner:
# 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
try:
from importlib.metadata import PathDistribution
except ImportError:
from importlib_metadata import PathDistribution
try:
from pathlib import Path
except ImportError:
from pathlib2 import Path
def normalize_name(name):
"""https://www.python.org/dev/peps/pep-0503/#normalized-names"""
return re.sub(r'[-_.]+', '-', name).lower()
def legacy_normalize_name(name):
"""Like pkg_resources Distribution.key property"""
return re.sub(r'[-_]+', '-', name).lower()
class Requirement(Requirement_):
def __init__(self, requirement_string):
super(Requirement, self).__init__(requirement_string)
self.normalized_name = normalize_name(self.name)
self.legacy_normalized_name = legacy_normalize_name(self.name)
class Distribution(PathDistribution):
def __init__(self, path):
super(Distribution, self).__init__(Path(path))
# Check that the initialization went well and metadata are not missing or corrupted
# name is the most important attribute, if it doesn't exist, import failed
if not self.name or not isinstance(self.name, str):
print("*** PYTHON_METADATA_FAILED_TO_PARSE_ERROR___SEE_STDERR ***")
print('Error: Python metadata at `{}` are missing or corrupted.'.format(path), file=stderr)
exit(65) # os.EX_DATAERR
self.normalized_name = normalize_name(self.name)
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 or None."""
return self.metadata.get('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 = []
# we are only interested in dependencies with extra == 'our_extra' marker
for req in self.requirements:
# no marker at all, nothing to evaluate
if not req.marker:
continue
# does the marker include extra == 'our_extra'?
# we can only evaluate the marker as a whole,
# so we evaluate it twice (using 2 different marker_envs)
# and see if it only evaluates to True with our extra
if (req.marker.evaluate(get_marker_env(self, extra)) and
not req.marker.evaluate(get_marker_env(self, None))):
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)
version = parse_version(version_id)
if isinstance(version._version, str):
self.version = version._version
else:
@ -141,12 +34,6 @@ class RpmVersion():
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
@ -155,11 +42,8 @@ class RpmVersion():
self.post = None
return self
def is_zero(self):
return self.__str__() == '0'
def __str__(self):
if self.is_legacy():
if isinstance(self.version, str):
return self.version
if self.epoch:
rpm_epoch = str(self.epoch) + ':'
@ -185,11 +69,6 @@ def convert_compatible(name, operator, version_id):
print('Invalid requirement: {} {} {}'.format(name, operator, version_id), file=stderr)
exit(65) # os.EX_DATAERR
version = RpmVersion(version_id)
if version.is_legacy():
# LegacyVersions are not supported in this context
print("*** INVALID_REQUIREMENT_ERROR___SEE_STDERR ***")
print('Invalid requirement: {} {} {}'.format(name, operator, version_id), file=stderr)
exit(65) # os.EX_DATAERR
if len(version.version) == 1:
print("*** INVALID_REQUIREMENT_ERROR___SEE_STDERR ***")
print('Invalid requirement: {} {} {}'.format(name, operator, version_id), file=stderr)
@ -222,32 +101,18 @@ 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
print("*** INVALID_REQUIREMENT_ERROR___SEE_STDERR ***")
print('Invalid requirement: {} {} {}'.format(name, operator, version_id), file=stderr)
exit(65) # os.EX_DATAERR
version_gt = RpmVersion(version_id).increment()
version_gt_operator = '>='
# Prevent dev and pre-releases from satisfying a < requirement
version = '{}~~'.format(version)
lower_version = RpmVersion(version_id).increment()
else:
version = RpmVersion(version_id)
version_gt = version
version_gt_operator = '>'
return '({} < {} or {} {} {})'.format(
name, version, name, version_gt_operator, version_gt)
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
# 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 == '>':
@ -258,14 +123,6 @@ def convert_ordered(name, operator, version_id):
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)
@ -287,26 +144,16 @@ def convert(name, operator, version_id):
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}
def normalize_name(name):
"""https://www.python.org/dev/peps/pep-0503/#normalized-names"""
import re
return re.sub(r'[-_.]+', '-', name).lower()
def main():
if __name__ == "__main__":
"""To allow this script to be importable (and its classes/functions
reused), actions are defined in the main function and are performed only
when run as a main script."""
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')
@ -327,23 +174,12 @@ def main():
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('--namespace', action='store', help="Namespace for the printed Requires, Provides, Recommends and Conflicts")
parser.add_argument('--fail-if-zero', action='store_true', help='Fail the script if the automatically generated Provides version was 0, which usually indicates a packaging error.')
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()
if args.fail_if_zero and not args.provides:
raise parser.error('--fail-if-zero only works with --provides')
py_abi = args.requires
py_deps = {}
@ -367,15 +203,6 @@ def main():
# 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:
@ -387,9 +214,7 @@ def main():
# 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
namespace = (args.namespace + "({})") if args.namespace else "{}"
extras_subpackage = package_name_parts[2] or None
for f in (args.files or stdin.readlines()):
f = f.strip()
@ -399,9 +224,8 @@ def main():
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]
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])
@ -419,21 +243,52 @@ def main():
if lower.endswith('.egg') or \
lower.endswith('.egg-info') or \
lower.endswith('.dist-info'):
dist = Distribution(f)
# This import is very slow, so only do it if needed
# - Notes from an attempted rewrite from pkg_resources to
# importlib.metadata in 2020 can be found in the message of
# the commit that added this line.
from pkg_resources import Distribution, FileMetadata, PathMetadata, Requirement, parse_version
dist_name = basename(f)
if isdir(f):
path_item = dirname(f)
metadata = PathMetadata(path_item, f)
else:
path_item = f
metadata = FileMetadata(f)
dist = Distribution.from_location(path_item, dist_name, metadata)
# Check if py_version is defined in the metadata file/directory name
if not dist.py_version:
warn("Version for {!r} has not been found".format(dist), RuntimeWarning)
continue
# Try to parse the Python version from the path the metadata
# resides at (e.g. /usr/lib/pythonX.Y/site-packages/...)
import re
res = re.search(r"/python(?P<pyver>\d+\.\d+)/", path_item)
if res:
dist.py_version = res.group('pyver')
else:
warn("Version for {!r} has not been found".format(dist), RuntimeWarning)
continue
# If processing an extras subpackage:
# Check that the extras name is declared in the metadata, or
# 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):
# 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
platform.python_version = lambda: dist.py_version
platform.python_version_tuple = lambda: tuple(dist.py_version.split('.'))
# This is the PEP 503 normalized name.
# It does also convert dots to dashes, unlike dist.key.
# See https://bugzilla.redhat.com/show_bug.cgi?id=1791530
normalized_name = normalize_name(dist.project_name)
# If we're processing an extras subpackage, check that the extras exists
if extras_subpackage and extras_subpackage not in dist.extras:
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)
@ -446,48 +301,37 @@ def main():
if args.provides:
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 = namespace.format('python(abi)')
if dist.key == '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 = namespace.format('python{}dist({}{})').format(dist.py_version, dist.legacy_normalized_name, extras_suffix)
name = 'python{}dist({}{})'.format(dist.py_version, dist.key, extras_suffix)
if name not in py_deps:
py_deps[name] = []
if normalized_names_provide_pep503:
name_ = namespace.format('python{}dist({}{})').format(dist.py_version, dist.normalized_name, extras_suffix)
name_ = 'python{}dist({}{})'.format(dist.py_version, 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 = namespace.format('python{}dist({}{})').format(pyver_major, dist.legacy_normalized_name, extras_suffix)
pymajor_name = 'python{}dist({}{})'.format(pyver_major, dist.key, extras_suffix)
if pymajor_name not in py_deps:
py_deps[pymajor_name] = []
if normalized_names_provide_pep503:
pymajor_name_ = namespace.format('python{}dist({}{})').format(pyver_major, dist.normalized_name, extras_suffix)
pymajor_name_ = 'python{}dist({}{})'.format(pyver_major, normalized_name, extras_suffix)
if pymajor_name_ not in py_deps:
py_deps[pymajor_name_] = []
if args.legacy or args.legacy_provides:
legacy_name = namespace.format('pythonegg({})({})').format(pyver_major, dist.legacy_normalized_name)
legacy_name = 'pythonegg({})({})'.format(pyver_major, dist.key)
if legacy_name not in py_deps:
py_deps[legacy_name] = []
if dist.version:
version = dist.version
spec = ('==', version)
if args.fail_if_zero:
if RpmVersion(version).is_zero():
print('*** PYTHON_PROVIDED_VERSION_NORMALIZES_TO_ZERO___SEE_STDERR ***')
print(f'\nError: The version in the Python package metadata {version} normalizes to zero.\n'
'It\'s likely a packaging error caused by missing version information\n'
'(e.g. when using a version control system snapshot as a source).\n'
'Try providing the version information manually when building the Python package,\n'
'for example by setting the SETUPTOOLS_SCM_PRETEND_VERSION environment variable if the package uses setuptools_scm.\n'
'If you are confident that the version of the Python package is intentionally zero,\n'
'you may %define the _python_dist_allow_version_zero macro in the spec file to disable this check.\n', file=stderr)
exit(65) # os.EX_DATAERR
if normalized_names_provide_legacy:
if spec not in py_deps[name]:
@ -505,9 +349,9 @@ def main():
if spec not in py_deps[legacy_name]:
py_deps[legacy_name].append(spec)
if args.requires or (args.recommends and dist.extras):
name = namespace.format('python(abi)')
name = 'python(abi)'
# If egg/dist metadata says package name is python, we don't add dependency on python(abi)
if dist.normalized_name == 'python':
if dist.key == 'python':
py_abi = False
if name in py_deps:
py_deps.pop(name)
@ -517,23 +361,24 @@ def main():
spec = ('==', dist.py_version)
if spec not in py_deps[name]:
py_deps[name].append(spec)
if extras_subpackage:
deps = [d for d in dist.requirements_for_extra(extras_subpackage)]
else:
deps = dist.requirements
# console_scripts/gui_scripts entry points needed pkg_resources from setuptools
# on new Python/setuptools versions, this is no longer required
if nodep_setuptools_pyversion is None or parse(dist.py_version) < nodep_setuptools_pyversion:
if (dist.entry_points and
(lower.endswith('.egg') or
lower.endswith('.egg-info'))):
groups = {ep.group for ep in dist.entry_points}
if {"console_scripts", "gui_scripts"} & groups:
# stick them first so any more specific requirement
# overrides it
deps.insert(0, Requirement('setuptools'))
deps = dist.requires()
if args.recommends:
depsextras = dist.requires(extras=dist.extras)
if not args.requires:
for dep in reversed(depsextras):
if dep in deps:
depsextras.remove(dep)
deps = depsextras
elif extras_subpackage:
# Extras requires also contain the base requires included
deps = [d for d in dist.requires(extras=[extras_subpackage]) if d not in dist.requires()]
# console_scripts/gui_scripts entry points need pkg_resources from setuptools
if ((dist.get_entry_map('console_scripts') or
dist.get_entry_map('gui_scripts')) and
(lower.endswith('.egg') or
lower.endswith('.egg-info'))):
# stick them first so any more specific requirement overrides it
deps.insert(0, Requirement.parse('setuptools'))
# add requires/recommends based on egg/dist metadata
for dep in deps:
# Even if we're requiring `foo[bar]`, also require `foo`
@ -543,63 +388,68 @@ def main():
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]
extras_suffixes += [f"[{e}]" for e in dep.extras]
for extras_suffix in extras_suffixes:
if normalized_names_require_pep503:
dep_normalized_name = dep.normalized_name
dep_normalized_name = normalize_name(dep.project_name)
else:
dep_normalized_name = dep.legacy_normalized_name
dep_normalized_name = dep.key
if args.legacy:
name = namespace.format('pythonegg({})({})').format(pyver_major, dep.legacy_normalized_name)
name = 'pythonegg({})({})'.format(pyver_major, dep.key)
else:
if args.majorver_only:
name = namespace.format('python{}dist({}{})').format(pyver_major, dep_normalized_name, extras_suffix)
name = 'python{}dist({}{})'.format(pyver_major, dep_normalized_name, extras_suffix)
else:
name = namespace.format('python{}dist({}{})').format(dist.py_version, dep_normalized_name, extras_suffix)
if dep.marker and not args.recommends and not extras_subpackage:
if not dep.marker.evaluate(get_marker_env(dist, '')):
continue
if name not in py_deps:
name = 'python{}dist({}{})'.format(dist.py_version, dep_normalized_name, extras_suffix)
for spec in dep.specs:
if name not in py_deps:
py_deps[name] = []
if spec not in py_deps[name]:
py_deps[name].append(spec)
if not dep.specs:
py_deps[name] = []
for spec in dep.specifier:
if (spec.operator, spec.version) not in py_deps[name]:
py_deps[name].append((spec.operator, spec.version))
# Unused, for automatic sub-package generation based on 'extras' from egg/dist metadata
# TODO: implement in rpm later, or...?
if args.extras:
print(dist.extras)
for extra in dist.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.legacy_normalized_name))
print('Summary:\t{} extra for {} python package'.format(extra, dist.key))
print('Group:\t\tDevelopment/Python')
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))
depsextras = dist.requires(extras=[extra])
for dep in reversed(depsextras):
if dep in deps:
depsextras.remove(dep)
deps = depsextras
for dep in deps:
for spec in dep.specs:
if spec[0] == '!=':
print('Conflicts:\t{} {} {}'.format(dep.key, '==', spec[1]))
else:
print('Requires:\t{} {} {}'.format(dep.legacy_normalized_name, spec.operator, spec.version))
print('Requires:\t{} {} {}'.format(dep.key, spec[0], spec[1]))
print('%%description\t{}'.format(extra))
print('{} extra for {} python package'.format(extra, dist.legacy_normalized_name))
print('{} extra for {} python package'.format(extra, dist.key))
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 dep in dist.requires(extras=dist.extras):
name = dep.key
for spec in dep.specs:
if spec[0] == '!=':
if name not in py_deps:
py_deps[name] = []
spec = ('==', spec[1])
if spec not in py_deps[name]:
py_deps[name].append(spec)
for name in sorted(py_deps):
names = list(py_deps.keys())
names.sort()
for name in names:
if py_deps[name]:
# Print out versioned provides, requires, recommends, conflicts
spec_list = []
@ -613,14 +463,3 @@ def main():
else:
# Print out unversioned provides, requires, recommends, conflicts
print(name)
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."""
try:
main()
except Exception as exc:
print("*** PYTHONDISTDEPS_GENERATORS_FAILED ***", flush=True)
raise RuntimeError("Error: pythondistdeps.py generator encountered an unhandled exception and was terminated.") from exc

View file

@ -1,11 +1,14 @@
%__pythonname_provides() %{lua:
local python = require 'fedora.srpm.python'
local name = rpm.expand('%{name}')
-- 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
-- either with another file in %1 or manually via %py_provide
if provides then
for i, provide in ipairs(provides) do
print(provide .. ' ')
@ -13,24 +16,4 @@
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'
local name = rpm.expand('%{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,7 +0,0 @@
# completely disabled inspections:
inspections:
# there is no upstream and the files are changed from time to time
addedfiles: off
changedfiles: off
filesize: off
upstream: off

View file

@ -1 +1 @@
SHA512 (test-sources-2023-01-04.tar.gz) = ca25c35970e91adeaed0873c045f4335c33b96a4ef4c56a36bfb2fd9e1d4799142cf0513abb066479fdb14d2d184b3b825c1d90119ac8e8d0e9aa1a4423701d1
SHA512 (test-sources-2020-04-29.tar.gz) = a5539fbe05a4f7128b4f82e960c3f1392a55ad53086dfd7fbc436d2743feaf64784e08667237baed3a32f149db25bc63e4ab3efc2b0270f969c59550b75102b1

View file

@ -1,17 +0,0 @@
#!/usr/bin/bash -eux
RPMDIR=$(rpm --eval '%_topdir')/RPMS/noarch
RPMPKG="${RPMDIR}/isort-5.7.0-0.noarch.rpm"
mkdir -p $(rpm --eval '%_topdir')/SOURCES/
spectool -g -R isort.spec
for py_version in 3.6 3.7 3.8 3.9; do
rpmbuild -ba --define "python3_test_version ${py_version}" isort.spec
rpm -qp --requires ${RPMPKG} | grep "python${py_version}dist(setuptools)"
done
for py_version in 3.10 3.11; do
rpmbuild -ba --define "python3_test_version ${py_version}" isort.spec
rpm -qp --requires ${RPMPKG} | grep "python${py_version}dist(setuptools)" && exit 1 || true
done

View file

@ -31,7 +31,7 @@ requirementslib==1.5.11
distlib==0.3.0
packaging==20.3
pyparsing==2.4.7
plette[validation]==0.2.3
plette==0.2.3
tomlkit==0.5.11
shellingham==1.3.2
six==1.14.0

View file

@ -1,6 +0,0 @@
Provides: bundled(python3dist(appdirs)) = 1.4.3
Provides: bundled(python3dist(ordered-set)) = 3.1.1
Provides: bundled(python3dist(packaging)) = 16.8
Provides: bundled(python3dist(pyparsing)) = 2.2.1
Provides: bundled(python3dist(six)) = 1.10
Provides: bundled(python3dist(tomli)) = 1.2.3

View file

@ -1,4 +0,0 @@
packaging==16.8
pyparsing==2.2.1
ordered-set==3.1.1
tomli==1.2.3;python_version<"3.11"

View file

@ -1,4 +0,0 @@
Provides: bundled(python3dist(ordered-set)) = 3.1.1
Provides: bundled(python3dist(packaging)) = 16.8
Provides: bundled(python3dist(pyparsing)) = 2.2.1
Provides: bundled(python3dist(tomli)) = 1.2.3

View file

@ -1 +0,0 @@
Corrupted dist-info metadata

View file

@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: pyreq2rpm.tests
Version: 2020.04.07.024
Version: 2020.04.07.024dab0
Summary: Test package to verify conversion of dependencies from pip/python to rpm format, data taken from pyreq2rpm
Author: Tomas Orsava (author of this metapackage)
Home-page: https://github.com/gordonmessmer/pyreq2rpm

View file

@ -1,3 +1,4 @@
# Taken from pyreq2rpm, removed tests that are expected to fail
foobar0~=2.4.8
foobar1~=2.4.8.0
foobar2~=2.4.8.1
@ -45,8 +46,10 @@ foobar43!=2.0.post1
foobar44<=2.4.8
foobar45<=2.4.8.0
foobar46<=2.4.8.1
foobar47<=2.4.8.*
foobar48<=2.0
foobar49<=2
foobar50<=2.*
foobar51<=2.4.8b5
foobar52<=2.0.0b5
foobar53<=2.4.8.post1
@ -54,8 +57,10 @@ foobar54<=2.0.post1
foobar55<2.4.8
foobar56<2.4.8.0
foobar57<2.4.8.1
foobar58<2.4.8.*
foobar59<2.0
foobar60<2
foobar61<2.*
foobar62<2.4.8b5
foobar63<2.0.0b5
foobar64<2.4.8.post1
@ -63,8 +68,10 @@ foobar65<2.0.post1
foobar66>=2.4.8
foobar67>=2.4.8.0
foobar68>=2.4.8.1
foobar69>=2.4.8.*
foobar70>=2.0
foobar71>=2
foobar72>=2.*
foobar73>=2.4.8b5
foobar74>=2.0.0b5
foobar75>=2.4.8.post1
@ -72,8 +79,10 @@ foobar76>=2.0.post1
foobar77>2.4.8
foobar78>2.4.8.0
foobar79>2.4.8.1
foobar80>2.4.8.*
foobar81>2.0
foobar82>2
foobar83>2.*
foobar84>2.4.8b5
foobar85>2.0.0b5
foobar86>2.4.8.post1
@ -82,15 +91,12 @@ pyparsing0
pyparsing1>=2.0.1,!=2.0.4,!=2.1.2,!=2.1.6
babel>=1.3,!=2.0
# Tests for breakages in Fedora
fedora-python-nb2plots==0+unknown
# Other tests
hugo1==1.0.0.dev7
hugo2<=8a4
hugo3!=11.1.1b14
hugo4>11rc0
hugo5===11.1.0.post3
test___multiple__underscores==1
test_underscores==1
dnspython[DNSSEC]

View file

@ -1,5 +1,101 @@
--requires:
--provides:
pyreq2rpm.tests-2020.04.07.024dab0-py3.9.egg-info:
provides: python3.9dist(pyreq2rpm.tests) = 2020.04.07.024dab0
requires: |-
python(abi) = 3.9
((python3.9dist(babel) < 2 or python3.9dist(babel) > 2) with python3.9dist(babel) >= 1.3)
python3.9dist(fedora-python-nb2plots) = 0
(python3.9dist(foobar0) >= 2.4.8 with python3.9dist(foobar0) < 2.5)
(python3.9dist(foobar1) >= 2.4.8 with python3.9dist(foobar1) < 2.4.9)
(python3.9dist(foobar10) >= 2^post1 with python3.9dist(foobar10) < 3)
python3.9dist(foobar11) = 2.4.8
python3.9dist(foobar12) = 2.4.8
python3.9dist(foobar13) = 2.4.8.1
(python3.9dist(foobar14) >= 2.4.8 with python3.9dist(foobar14) < 2.4.9)
python3.9dist(foobar15) = 2
python3.9dist(foobar16) = 2
(python3.9dist(foobar17) >= 2 with python3.9dist(foobar17) < 3)
python3.9dist(foobar18) = 2.4.8~b5
python3.9dist(foobar19) = 2~b5
(python3.9dist(foobar2) >= 2.4.8.1 with python3.9dist(foobar2) < 2.4.9)
python3.9dist(foobar20) = 2.4.8^post1
python3.9dist(foobar21) = 2^post1
python3.9dist(foobar22) = 2.4.8
python3.9dist(foobar23) = 2.4.8
python3.9dist(foobar24) = 2.4.8.1
python3.9dist(foobar26) = 2
python3.9dist(foobar27) = 2
python3.9dist(foobar29) = 2.4.8~b5
python3.9dist(foobar30) = 2~b5
python3.9dist(foobar31) = 2.4.8^post1
python3.9dist(foobar32) = 2^post1
(python3.9dist(foobar33) < 2.4.8 or python3.9dist(foobar33) > 2.4.8)
(python3.9dist(foobar34) < 2.4.8 or python3.9dist(foobar34) > 2.4.8)
(python3.9dist(foobar35) < 2.4.8.1 or python3.9dist(foobar35) > 2.4.8.1)
(python3.9dist(foobar36) < 2.4.8 or python3.9dist(foobar36) > 2.4.9)
(python3.9dist(foobar37) < 2 or python3.9dist(foobar37) > 2)
(python3.9dist(foobar38) < 2 or python3.9dist(foobar38) > 2)
(python3.9dist(foobar39) < 2 or python3.9dist(foobar39) > 3)
(python3.9dist(foobar4) >= 2 with python3.9dist(foobar4) < 3)
(python3.9dist(foobar40) < 2.4.8~b5 or python3.9dist(foobar40) > 2.4.8~b5)
(python3.9dist(foobar41) < 2~b5 or python3.9dist(foobar41) > 2~b5)
(python3.9dist(foobar42) < 2.4.8^post1 or python3.9dist(foobar42) > 2.4.8^post1)
(python3.9dist(foobar43) < 2^post1 or python3.9dist(foobar43) > 2^post1)
python3.9dist(foobar44) <= 2.4.8
python3.9dist(foobar45) <= 2.4.8
python3.9dist(foobar46) <= 2.4.8.1
python3.9dist(foobar47) < 2.4.8
python3.9dist(foobar48) <= 2
python3.9dist(foobar49) <= 2
python3.9dist(foobar50) < 2
python3.9dist(foobar51) <= 2.4.8~b5
python3.9dist(foobar52) <= 2~b5
python3.9dist(foobar53) <= 2.4.8^post1
python3.9dist(foobar54) <= 2^post1
python3.9dist(foobar55) < 2.4.8
python3.9dist(foobar56) < 2.4.8
python3.9dist(foobar57) < 2.4.8.1
python3.9dist(foobar58) < 2.4.8
python3.9dist(foobar59) < 2
python3.9dist(foobar60) < 2
python3.9dist(foobar61) < 2
python3.9dist(foobar62) < 2.4.8~b5
python3.9dist(foobar63) < 2~b5
python3.9dist(foobar64) < 2.4.8^post1
python3.9dist(foobar65) < 2^post1
python3.9dist(foobar66) >= 2.4.8
python3.9dist(foobar67) >= 2.4.8
python3.9dist(foobar68) >= 2.4.8.1
python3.9dist(foobar69) >= 2.4.8
(python3.9dist(foobar7) >= 2.4.8~b5 with python3.9dist(foobar7) < 2.5)
python3.9dist(foobar70) >= 2
python3.9dist(foobar71) >= 2
python3.9dist(foobar72) >= 2
python3.9dist(foobar73) >= 2.4.8~b5
python3.9dist(foobar74) >= 2~b5
python3.9dist(foobar75) >= 2.4.8^post1
python3.9dist(foobar76) >= 2^post1
python3.9dist(foobar77) > 2.4.8
python3.9dist(foobar78) > 2.4.8
python3.9dist(foobar79) > 2.4.8.1
(python3.9dist(foobar8) >= 2~b5 with python3.9dist(foobar8) < 2.1)
python3.9dist(foobar80) >= 2.4.8
python3.9dist(foobar81) > 2
python3.9dist(foobar82) > 2
python3.9dist(foobar83) >= 2
python3.9dist(foobar84) > 2.4.8~b5
python3.9dist(foobar85) > 2~b5
python3.9dist(foobar86) > 2.4.8^post1
python3.9dist(foobar87) > 2^post1
(python3.9dist(foobar9) >= 2.4.8^post1 with python3.9dist(foobar9) < 2.5)
python3.9dist(hugo1) = 1~~dev7
python3.9dist(hugo2) <= 8~a4
(python3.9dist(hugo3) < 11.1.1~b14 or python3.9dist(hugo3) > 11.1.1~b14)
python3.9dist(hugo4) > 11~rc0
python3.9dist(hugo5) = 11.1^post3
python3.9dist(pyparsing0)
((python3.9dist(pyparsing1) < 2.0.4 or python3.9dist(pyparsing1) > 2.0.4) with (python3.9dist(pyparsing1) < 2.1.2 or python3.9dist(pyparsing1) > 2.1.2) with (python3.9dist(pyparsing1) < 2.1.6 or python3.9dist(pyparsing1) > 2.1.6) with python3.9dist(pyparsing1) >= 2.0.1)
usr/lib/python3.9/site-packages/taskotron_python_versions-0.1.dev6.dist-info:
provides: python3.9dist(taskotron-python-versions) = 0.1~~dev6
requires: |-
@ -28,7 +124,7 @@
python2.7dist(setuptools) >= 21
python2.7dist(six) >= 1.9
python2.7dist(urllib3) >= 1.24.2
((python2.7dist(websocket-client) < 0.40 or python2.7dist(websocket-client) > 0.40) with (python2.7dist(websocket-client) < 0.41~~ or python2.7dist(websocket-client) >= 0.42) with (python2.7dist(websocket-client) < 0.42~~ or python2.7dist(websocket-client) >= 0.43) with python2.7dist(websocket-client) >= 0.32)
((python2.7dist(websocket-client) < 0.40 or python2.7dist(websocket-client) > 0.40) with (python2.7dist(websocket-client) < 0.41 or python2.7dist(websocket-client) > 0.42) with (python2.7dist(websocket-client) < 0.42 or python2.7dist(websocket-client) > 0.43) with python2.7dist(websocket-client) >= 0.32)
usr/lib/python2.7/site-packages/mistune-0.8.4-py2.7.egg-info:
provides: |-
python2.7dist(mistune) = 0.8.4
@ -75,12 +171,12 @@
python2dist(tox) = 3.14
requires: |-
python(abi) = 2.7
(python2.7dist(filelock) < 4~~ with python2.7dist(filelock) >= 3)
(python2.7dist(importlib-metadata) < 1~~ with python2.7dist(importlib-metadata) >= 0.12)
(python2.7dist(filelock) < 4 with python2.7dist(filelock) >= 3)
(python2.7dist(importlib-metadata) < 1 with python2.7dist(importlib-metadata) >= 0.12)
python2.7dist(packaging) >= 14
(python2.7dist(pluggy) < 1~~ with python2.7dist(pluggy) >= 0.12)
(python2.7dist(py) < 2~~ with python2.7dist(py) >= 1.4.17)
(python2.7dist(six) < 2~~ with python2.7dist(six) >= 1)
(python2.7dist(pluggy) < 1 with python2.7dist(pluggy) >= 0.12)
(python2.7dist(py) < 2 with python2.7dist(py) >= 1.4.17)
(python2.7dist(six) < 2 with python2.7dist(six) >= 1)
python2.7dist(toml) >= 0.9.4
python2.7dist(virtualenv) >= 14
usr/lib/python2.7/site-packages/urllib3-1.25.7-py2.7.egg-info:
@ -105,7 +201,7 @@
python(abi) = 3.7
(python3.7dist(lazy-object-proxy) >= 1.4 with python3.7dist(lazy-object-proxy) < 1.5)
(python3.7dist(six) >= 1.12 with python3.7dist(six) < 2)
(python3.7dist(typed-ast) < 1.5~~ with python3.7dist(typed-ast) >= 1.4)
(python3.7dist(typed-ast) < 1.5 with python3.7dist(typed-ast) >= 1.4)
(python3.7dist(wrapt) >= 1.11 with python3.7dist(wrapt) < 1.12)
usr/lib/python3.7/site-packages/packaging-19.0.dist-info:
provides: |-
@ -148,12 +244,12 @@
python3dist(tox) = 3.14
requires: |-
python(abi) = 3.7
(python3.7dist(filelock) < 4~~ with python3.7dist(filelock) >= 3)
(python3.7dist(importlib-metadata) < 1~~ with python3.7dist(importlib-metadata) >= 0.12)
(python3.7dist(filelock) < 4 with python3.7dist(filelock) >= 3)
(python3.7dist(importlib-metadata) < 1 with python3.7dist(importlib-metadata) >= 0.12)
python3.7dist(packaging) >= 14
(python3.7dist(pluggy) < 1~~ with python3.7dist(pluggy) >= 0.12)
(python3.7dist(py) < 2~~ with python3.7dist(py) >= 1.4.17)
(python3.7dist(six) < 2~~ with python3.7dist(six) >= 1)
(python3.7dist(pluggy) < 1 with python3.7dist(pluggy) >= 0.12)
(python3.7dist(py) < 2 with python3.7dist(py) >= 1.4.17)
(python3.7dist(six) < 2 with python3.7dist(six) >= 1)
python3.7dist(toml) >= 0.9.4
python3.7dist(virtualenv) >= 14
usr/lib/python3.9/site-packages/astroid-2.3.3.dist-info:
@ -205,7 +301,7 @@
python3.9dist(setuptools) >= 21
python3.9dist(six) >= 1.9
python3.9dist(urllib3) >= 1.24.2
((python3.9dist(websocket-client) < 0.40 or python3.9dist(websocket-client) > 0.40) with (python3.9dist(websocket-client) < 0.41~~ or python3.9dist(websocket-client) >= 0.42) with (python3.9dist(websocket-client) < 0.42~~ or python3.9dist(websocket-client) >= 0.43) with python3.9dist(websocket-client) >= 0.32)
((python3.9dist(websocket-client) < 0.40 or python3.9dist(websocket-client) > 0.40) with (python3.9dist(websocket-client) < 0.41 or python3.9dist(websocket-client) > 0.42) with (python3.9dist(websocket-client) < 0.42 or python3.9dist(websocket-client) > 0.43) with python3.9dist(websocket-client) >= 0.32)
usr/lib/python3.9/site-packages/mistune-0.8.4-py3.9.egg-info:
provides: |-
python3.9dist(mistune) = 0.8.4
@ -260,11 +356,11 @@
python3dist(tox) = 3.14
requires: |-
python(abi) = 3.9
(python3.9dist(filelock) < 4~~ with python3.9dist(filelock) >= 3)
(python3.9dist(filelock) < 4 with python3.9dist(filelock) >= 3)
python3.9dist(packaging) >= 14
(python3.9dist(pluggy) < 1~~ with python3.9dist(pluggy) >= 0.12)
(python3.9dist(py) < 2~~ with python3.9dist(py) >= 1.4.17)
(python3.9dist(six) < 2~~ with python3.9dist(six) >= 1)
(python3.9dist(pluggy) < 1 with python3.9dist(pluggy) >= 0.12)
(python3.9dist(py) < 2 with python3.9dist(py) >= 1.4.17)
(python3.9dist(six) < 2 with python3.9dist(six) >= 1)
python3.9dist(toml) >= 0.9.4
python3.9dist(virtualenv) >= 14
usr/lib/python3.9/site-packages/urllib3-1.25.7-py3.9.egg-info:
@ -437,7 +533,7 @@
python2.7dist(setuptools) >= 21
python2.7dist(six) >= 1.9
python2.7dist(urllib3) >= 1.24.2
((python2.7dist(websocket-client) < 0.40 or python2.7dist(websocket-client) > 0.40) with (python2.7dist(websocket-client) < 0.41~~ or python2.7dist(websocket-client) >= 0.42) with (python2.7dist(websocket-client) < 0.42~~ or python2.7dist(websocket-client) >= 0.43) with python2.7dist(websocket-client) >= 0.32)
((python2.7dist(websocket-client) < 0.40 or python2.7dist(websocket-client) > 0.40) with (python2.7dist(websocket-client) < 0.41 or python2.7dist(websocket-client) > 0.42) with (python2.7dist(websocket-client) < 0.42 or python2.7dist(websocket-client) > 0.43) with python2.7dist(websocket-client) >= 0.32)
usr/lib/python2.7/site-packages/mistune-0.8.4-py2.7.egg-info:
provides: |-
python2.7dist(mistune) = 0.8.4
@ -489,7 +585,7 @@
python(abi) = 3.7
(python3.7dist(lazy-object-proxy) >= 1.4 with python3.7dist(lazy-object-proxy) < 1.5)
(python3.7dist(six) >= 1.12 with python3.7dist(six) < 2)
(python3.7dist(typed-ast) < 1.5~~ with python3.7dist(typed-ast) >= 1.4)
(python3.7dist(typed-ast) < 1.5 with python3.7dist(typed-ast) >= 1.4)
(python3.7dist(wrapt) >= 1.11 with python3.7dist(wrapt) < 1.12)
usr/lib/python3.7/site-packages/packaging-19.0.dist-info:
provides: python3.7dist(packaging) = 19
@ -554,11 +650,11 @@
python3dist(tox) = 3.14
requires: |-
python(abi) = 3.9
(python3.9dist(filelock) < 4~~ with python3.9dist(filelock) >= 3)
(python3.9dist(filelock) < 4 with python3.9dist(filelock) >= 3)
python3.9dist(packaging) >= 14
(python3.9dist(pluggy) < 1~~ with python3.9dist(pluggy) >= 0.12)
(python3.9dist(py) < 2~~ with python3.9dist(py) >= 1.4.17)
(python3.9dist(six) < 2~~ with python3.9dist(six) >= 1)
(python3.9dist(pluggy) < 1 with python3.9dist(pluggy) >= 0.12)
(python3.9dist(py) < 2 with python3.9dist(py) >= 1.4.17)
(python3.9dist(six) < 2 with python3.9dist(six) >= 1)
python3.9dist(toml) >= 0.9.4
python3.9dist(virtualenv) >= 14
usr/lib/python3.9/site-packages/urllib3-1.25.7-py3.9.egg-info:
@ -611,12 +707,12 @@
python2dist(tox) = 3.14
requires: |-
python(abi) = 2.7
(python2.7dist(filelock) < 4~~ with python2.7dist(filelock) >= 3)
(python2.7dist(importlib-metadata) < 1~~ with python2.7dist(importlib-metadata) >= 0.12)
(python2.7dist(filelock) < 4 with python2.7dist(filelock) >= 3)
(python2.7dist(importlib-metadata) < 1 with python2.7dist(importlib-metadata) >= 0.12)
python2.7dist(packaging) >= 14
(python2.7dist(pluggy) < 1~~ with python2.7dist(pluggy) >= 0.12)
(python2.7dist(py) < 2~~ with python2.7dist(py) >= 1.4.17)
(python2.7dist(six) < 2~~ with python2.7dist(six) >= 1)
(python2.7dist(pluggy) < 1 with python2.7dist(pluggy) >= 0.12)
(python2.7dist(py) < 2 with python2.7dist(py) >= 1.4.17)
(python2.7dist(six) < 2 with python2.7dist(six) >= 1)
python2.7dist(toml) >= 0.9.4
python2.7dist(virtualenv) >= 14
usr/lib/python2.7/site-packages/urllib3-1.25.7-py2.7.egg-info:
@ -653,12 +749,12 @@
provides: python3.7dist(tox) = 3.14
requires: |-
python(abi) = 3.7
(python3.7dist(filelock) < 4~~ with python3.7dist(filelock) >= 3)
(python3.7dist(importlib-metadata) < 1~~ with python3.7dist(importlib-metadata) >= 0.12)
(python3.7dist(filelock) < 4 with python3.7dist(filelock) >= 3)
(python3.7dist(importlib-metadata) < 1 with python3.7dist(importlib-metadata) >= 0.12)
python3.7dist(packaging) >= 14
(python3.7dist(pluggy) < 1~~ with python3.7dist(pluggy) >= 0.12)
(python3.7dist(py) < 2~~ with python3.7dist(py) >= 1.4.17)
(python3.7dist(six) < 2~~ with python3.7dist(six) >= 1)
(python3.7dist(pluggy) < 1 with python3.7dist(pluggy) >= 0.12)
(python3.7dist(py) < 2 with python3.7dist(py) >= 1.4.17)
(python3.7dist(six) < 2 with python3.7dist(six) >= 1)
python3.7dist(toml) >= 0.9.4
python3.7dist(virtualenv) >= 14
usr/lib64/python2.7/site-packages/scipy-1.2.1.dist-info:
@ -762,24 +858,7 @@
python3.9dist(simplejson) = 3.16
python3dist(simplejson) = 3.16
requires: python(abi) = 3.9
--requires --namespace mingw64:
--provides --namespace mingw64:
usr/lib/python3.9/site-packages/taskotron_python_versions-0.1.dev6.dist-info:
provides: mingw64(python3.9dist(taskotron-python-versions)) = 0.1~~dev6
requires: |-
mingw64(python(abi)) = 3.9
mingw64(python3.9dist(libarchive-c))
mingw64(python3.9dist(python-bugzilla))
--requires --console-scripts-nodep-setuptools-since 3.7:
--provides --console-scripts-nodep-setuptools-since 3.6:
usr/lib/python3.9/site-packages/zope.component-4.3.0-py3.9.egg-info:
stderr:
provides: Only version 3.8+ is supported in --console-scripts-nodep-setuptools-since
requires: Only version 3.8+ is supported in --console-scripts-nodep-setuptools-since
stdout:
provides: '*** PYTHON_EXTRAS_ARGUMENT_ERROR___SEE_STDERR ***'
requires: '*** PYTHON_EXTRAS_ARGUMENT_ERROR___SEE_STDERR ***'
--requires --normalized-names-format legacy-dots --console-scripts-nodep-setuptools-since 3.10:
--requires --normalized-names-format legacy-dots:
--provides --majorver-provides --normalized-names-format legacy-dots:
usr/lib/python2.7/site-packages/zope.component-4.3.0-py2.7.egg-info:
provides: |-
@ -817,19 +896,16 @@
provides: |-
python3.10dist(setuptools) = 41.6
python3dist(setuptools) = 41.6
requires: python(abi) = 3.10
requires: |-
python(abi) = 3.10
python3.10dist(setuptools)
usr/lib/python3.11/site-packages/pip-20.0.2-py3.11.egg-info:
provides: |-
python3.11dist(pip) = 20.0.2
python3dist(pip) = 20.0.2
requires: python(abi) = 3.11
usr/lib/python3.8/site-packages/pip-20.0.2-py3.8.egg-info:
provides: |-
python3.8dist(pip) = 20.0.2
python3dist(pip) = 20.0.2
requires: |-
python(abi) = 3.8
python3.8dist(setuptools)
python(abi) = 3.11
python3.11dist(setuptools)
usr/lib/python3.9/site-packages/zope.component-4.3.0-py3.9.egg-info:
provides: |-
python3.9dist(zope.component) = 4.3
@ -1138,49 +1214,6 @@
python3dist(backports-range) = 3.7.2
python3dist(backports.range) = 3.7.2
requires: python(abi) = 3.7
--requires --normalized-names-format pep503 --package-name python3-dns+DNSSEC:
--provides --majorver-provides --normalized-names-format pep503 --package-name python3-dns+DNSSEC:
usr/lib/python3.9/site-packages/dnspython-2.1.0-py3.9.egg-info:
provides: |-
python3.9dist(dnspython[dnssec]) = 2.1
python3dist(dnspython[dnssec]) = 2.1
requires: |-
python(abi) = 3.9
python3.9dist(cryptography) >= 2.6
--requires --normalized-names-format pep503 --package-name python3-dns+Dnssec:
--provides --majorver-provides --normalized-names-format pep503 --package-name python3-dns+Dnssec:
usr/lib/python3.9/site-packages/dnspython-2.1.0-py3.9.egg-info:
provides: |-
python3.9dist(dnspython[dnssec]) = 2.1
python3dist(dnspython[dnssec]) = 2.1
requires: |-
python(abi) = 3.9
python3.9dist(cryptography) >= 2.6
--requires --normalized-names-format pep503 --package-name python3-dns+dnssec:
--provides --majorver-provides --normalized-names-format pep503 --package-name python3-dns+dnssec:
usr/lib/python3.9/site-packages/dnspython-2.1.0-py3.9.egg-info:
provides: |-
python3.9dist(dnspython[dnssec]) = 2.1
python3dist(dnspython[dnssec]) = 2.1
requires: |-
python(abi) = 3.9
python3.9dist(cryptography) >= 2.6
usr/lib/python3.9/site-packages/dnspython-2.1.0.dist-info:
provides: |-
python3.9dist(dnspython[dnssec]) = 2.1
python3dist(dnspython[dnssec]) = 2.1
requires: |-
python(abi) = 3.9
python3.9dist(cryptography) >= 2.6
--requires --normalized-names-format pep503 --package-name python3-setuptools+certs:
--provides --majorver-provides --normalized-names-format pep503 --package-name python3-setuptools+certs:
usr/lib/python3.9/site-packages/setuptools-41.6.0.dist-info:
provides: |-
python3.9dist(setuptools[certs]) = 41.6
python3dist(setuptools[certs]) = 41.6
requires: |-
python(abi) = 3.9
python3.9dist(certifi) = 2016.9.26
--requires --normalized-names-format pep503 --package-name python3-zope-component+testing:
--provides --majorver-provides --normalized-names-format pep503 --package-name python3-zope-component+testing:
usr/lib/python3.9/site-packages/zope.component-4.3.0-py3.9.egg-info:
@ -1213,109 +1246,6 @@
python3.9dist(coverage)
python3.9dist(nose)
python3.9dist(zope-testing)
--requires --normalized-names-format pep503 --require-extras-subpackages:
--provides --normalized-names-format pep503:
corrupted.dist-info:
stderr:
provides: |-
Error: Python metadata at `*/corrupted.dist-info` are missing or corrupted.
requires: |-
Error: Python metadata at `*/corrupted.dist-info` are missing or corrupted.
stdout:
provides: '*** PYTHON_METADATA_FAILED_TO_PARSE_ERROR___SEE_STDERR ***'
requires: '*** PYTHON_METADATA_FAILED_TO_PARSE_ERROR___SEE_STDERR ***'
pyreq2rpm.tests-2020.04.07.024-py3.9.egg-info:
provides: python3.9dist(pyreq2rpm-tests) = 2020.4.7.24
requires: |-
python(abi) = 3.9
((python3.9dist(babel) < 2 or python3.9dist(babel) > 2) with python3.9dist(babel) >= 1.3)
python3.9dist(dnspython)
python3.9dist(dnspython[dnssec])
python3.9dist(fedora-python-nb2plots) = 0
(python3.9dist(foobar0) >= 2.4.8 with python3.9dist(foobar0) < 2.5)
(python3.9dist(foobar1) >= 2.4.8 with python3.9dist(foobar1) < 2.4.9)
(python3.9dist(foobar10) >= 2^post1 with python3.9dist(foobar10) < 3)
python3.9dist(foobar11) = 2.4.8
python3.9dist(foobar12) = 2.4.8
python3.9dist(foobar13) = 2.4.8.1
(python3.9dist(foobar14) >= 2.4.8 with python3.9dist(foobar14) < 2.4.9)
python3.9dist(foobar15) = 2
python3.9dist(foobar16) = 2
(python3.9dist(foobar17) >= 2 with python3.9dist(foobar17) < 3)
python3.9dist(foobar18) = 2.4.8~b5
python3.9dist(foobar19) = 2~b5
(python3.9dist(foobar2) >= 2.4.8.1 with python3.9dist(foobar2) < 2.4.9)
python3.9dist(foobar20) = 2.4.8^post1
python3.9dist(foobar21) = 2^post1
python3.9dist(foobar22) = 2.4.8
python3.9dist(foobar23) = 2.4.8
python3.9dist(foobar24) = 2.4.8.1
python3.9dist(foobar26) = 2
python3.9dist(foobar27) = 2
python3.9dist(foobar29) = 2.4.8~b5
python3.9dist(foobar30) = 2~b5
python3.9dist(foobar31) = 2.4.8^post1
python3.9dist(foobar32) = 2^post1
(python3.9dist(foobar33) < 2.4.8 or python3.9dist(foobar33) > 2.4.8)
(python3.9dist(foobar34) < 2.4.8 or python3.9dist(foobar34) > 2.4.8)
(python3.9dist(foobar35) < 2.4.8.1 or python3.9dist(foobar35) > 2.4.8.1)
(python3.9dist(foobar36) < 2.4.8~~ or python3.9dist(foobar36) >= 2.4.9)
(python3.9dist(foobar37) < 2 or python3.9dist(foobar37) > 2)
(python3.9dist(foobar38) < 2 or python3.9dist(foobar38) > 2)
(python3.9dist(foobar39) < 2~~ or python3.9dist(foobar39) >= 3)
(python3.9dist(foobar4) >= 2 with python3.9dist(foobar4) < 3)
(python3.9dist(foobar40) < 2.4.8~b5 or python3.9dist(foobar40) > 2.4.8~b5)
(python3.9dist(foobar41) < 2~b5 or python3.9dist(foobar41) > 2~b5)
(python3.9dist(foobar42) < 2.4.8^post1 or python3.9dist(foobar42) > 2.4.8^post1)
(python3.9dist(foobar43) < 2^post1 or python3.9dist(foobar43) > 2^post1)
python3.9dist(foobar44) <= 2.4.8
python3.9dist(foobar45) <= 2.4.8
python3.9dist(foobar46) <= 2.4.8.1
python3.9dist(foobar48) <= 2
python3.9dist(foobar49) <= 2
python3.9dist(foobar51) <= 2.4.8~b5
python3.9dist(foobar52) <= 2~b5
python3.9dist(foobar53) <= 2.4.8^post1
python3.9dist(foobar54) <= 2^post1
python3.9dist(foobar55) < 2.4.8~~
python3.9dist(foobar56) < 2.4.8~~
python3.9dist(foobar57) < 2.4.8.1~~
python3.9dist(foobar59) < 2~~
python3.9dist(foobar60) < 2~~
python3.9dist(foobar62) < 2.4.8~b5
python3.9dist(foobar63) < 2~b5
python3.9dist(foobar64) < 2.4.8^post1
python3.9dist(foobar65) < 2^post1
python3.9dist(foobar66) >= 2.4.8
python3.9dist(foobar67) >= 2.4.8
python3.9dist(foobar68) >= 2.4.8.1
(python3.9dist(foobar7) >= 2.4.8~b5 with python3.9dist(foobar7) < 2.5)
python3.9dist(foobar70) >= 2
python3.9dist(foobar71) >= 2
python3.9dist(foobar73) >= 2.4.8~b5
python3.9dist(foobar74) >= 2~b5
python3.9dist(foobar75) >= 2.4.8^post1
python3.9dist(foobar76) >= 2^post1
python3.9dist(foobar77) > 2.4.8.0
python3.9dist(foobar78) > 2.4.8.0
python3.9dist(foobar79) > 2.4.8.1.0
(python3.9dist(foobar8) >= 2~b5 with python3.9dist(foobar8) < 2.1)
python3.9dist(foobar81) > 2.0
python3.9dist(foobar82) > 2.0
python3.9dist(foobar84) > 2.4.8~b5
python3.9dist(foobar85) > 2~b5
python3.9dist(foobar86) > 2.4.8^post1
python3.9dist(foobar87) > 2^post1
(python3.9dist(foobar9) >= 2.4.8^post1 with python3.9dist(foobar9) < 2.5)
python3.9dist(hugo1) = 1~~dev7
python3.9dist(hugo2) <= 8~a4
(python3.9dist(hugo3) < 11.1.1~b14 or python3.9dist(hugo3) > 11.1.1~b14)
python3.9dist(hugo4) > 11~rc0
python3.9dist(hugo5) = 11.1^post3
python3.9dist(pyparsing0)
((python3.9dist(pyparsing1) < 2.0.4 or python3.9dist(pyparsing1) > 2.0.4) with (python3.9dist(pyparsing1) < 2.1.2 or python3.9dist(pyparsing1) > 2.1.2) with (python3.9dist(pyparsing1) < 2.1.6 or python3.9dist(pyparsing1) > 2.1.6) with python3.9dist(pyparsing1) >= 2.0.1)
python3.9dist(test-multiple-underscores) = 1
python3.9dist(test-underscores) = 1
--requires --normalized-names-format pep503 --require-extras-subpackages --package-name python3-zope-component+missing:
--provides --majorver-provides --normalized-names-format pep503 --package-name python3-zope-component+missing:
usr/lib/python3.9/site-packages/zope.component-4.3.0-py3.9.egg-info:
@ -1345,48 +1275,4 @@
python3.9dist(zope-component[security])
python3.9dist(zope-component[zcml])
python3.9dist(zope-testing)
--requires --normalized-names-format pep503 --require-extras-subpackages --package-name python3-build+virtualenv:
--provides --majorver-provides --normalized-names-format pep503 --package-name python3-build+virtualenv:
usr/lib/python3.10/site-packages/build-0.8.0.dist-info:
provides: |-
python3.10dist(build[virtualenv]) = 0.8
python3dist(build[virtualenv]) = 0.8
requires: |-
python(abi) = 3.10
python3.10dist(virtualenv) >= 20.0.35
--requires --fail-if-zero:
--provides --majorver-provides --fail-if-zero:
usr/lib/python3.11/site-packages/importlib_metadata-0.0-py3.11.egg-info:
stderr:
provides: |-
Error: The version in the Python package metadata 0.0 normalizes to zero.
It's likely a packaging error caused by missing version information
(e.g. when using a version control system snapshot as a source).
Try providing the version information manually when building the Python package,
for example by setting the SETUPTOOLS_SCM_PRETEND_VERSION environment variable if the package uses setuptools_scm.
If you are confident that the version of the Python package is intentionally zero,
you may %define the _python_dist_allow_version_zero macro in the spec file to disable this check.
requires: '*error: --fail-if-zero only works with --provides*'
stdout:
provides: '*** PYTHON_PROVIDED_VERSION_NORMALIZES_TO_ZERO___SEE_STDERR ***'
requires: ''
--requires:
--provides --majorver-provides:
usr/lib/python3.11/site-packages/importlib_metadata-0.0-py3.11.egg-info:
provides: |-
python3.11dist(importlib-metadata) = 0
python3dist(importlib-metadata) = 0
requires: |-
python(abi) = 3.11
python3.11dist(setuptools)
python3.11dist(wheel)
--requires:
--provides --majorver-provides --fail-if-zero:
usr/lib/python3.11/site-packages/importlib_metadata-0.1-py3.11.egg-info:
provides: |-
python3.11dist(importlib-metadata) = 0.1
python3dist(importlib-metadata) = 0.1
requires: |-
python(abi) = 3.11
python3.11dist(setuptools)
python3.11dist(wheel)

View file

@ -8,7 +8,7 @@ pip:
'19.1.1': ['2.7', '3.7']
'20.0.2': ['3.9']
sdist:
'20.0.2': ['3.8', '3.11']
'20.0.2': ['3.11']
packaging:
wheel:
'19.0': ['2.7', '3.7']
@ -95,15 +95,3 @@ fsleyes:
taskotron-python-versions:
wheel:
'0.1.dev6': ['3.9']
dnspython:
sdist:
'2.1.0': ['3.9']
wheel:
'2.1.0': ['3.9']
build:
wheel:
'0.8.0': ['3.10']
importlib_metadata:
sdist:
'0.0': ['3.11']
'0.1': ['3.11']

View file

@ -1,15 +0,0 @@
#!/usr/bin/bash -eux
# Use update-test-sources.sh to update the test data
# When the tests run in python-rpm-generators,
# the structure on disk does not match the dist-git repository.
# We apparently must use the standard-test-source role to grab the sources.
# OTOH in other packages, we must use fedpkg(-minimal) or centpkg(-minimal),
# depending on the destination OS.
# The --force flag is required in full-blown fedpkg/centpkg (the source is unused in spec),
# and it is ignored in fedpkg/centpkg-minimal (all sources are always downloaded).
test -f test-sources-*.tar.gz || fedpkg sources --force || centpkg sources --force
tar -xvf test-sources-*.tar.gz -C ./tests/data/scripts_pythondistdeps/
cd tests/
python3 -m pytest -vvv

View file

@ -1,36 +0,0 @@
Name: isort
Version: 5.7.0
Release: 0
Summary: A Python package with a console_scripts entrypoint
License: MIT
Source0: %{pypi_source}
BuildArch: noarch
BuildRequires: python3-devel
BuildRequires: python3-setuptools
# Turn off Python bytecode compilation because the build would fail without Python %%{python3_test_version}
%define __brp_python_bytecompile %{nil}
%description
...
%prep
%autosetup
%build
%py3_build
%install
%py3_install
# A fake installation by a different Python version:
%if "%{python3_version}" != "%{python3_test_version}"
mv %{buildroot}%{_prefix}/lib/python%{python3_version} \
%{buildroot}%{_prefix}/lib/python%{python3_test_version}
mv %{buildroot}%{_prefix}/lib/python%{python3_test_version}/site-packages/%{name}-%{version}-py%{python3_version}.egg-info \
%{buildroot}%{_prefix}/lib/python%{python3_test_version}/site-packages/%{name}-%{version}-py%{python3_test_version}.egg-info
%endif
%files
%{_bindir}/%{name}*
%{_prefix}/lib/python%{python3_test_version}/site-packages/%{name}*

View file

@ -8,9 +8,9 @@ spectool -g -R pythondist.spec
rpmbuild -ba pythondist.spec
rpm -qp --provides ${RPMDIR}/python3-zope-component-4.3.0-0.noarch.rpm | grep '^python3dist(zope\.component)' && exit 1 || true
rpm -qp --provides ${RPMDIR}/python3-zope-component-4.3.0-0.noarch.rpm | grep '^python3dist(zope\.component)'
rpm -qp --provides ${RPMDIR}/python3-zope-component-4.3.0-0.noarch.rpm | grep '^python3dist(zope-component)'
rpm -qp --provides ${RPMDIR}/python3-zope-component-4.3.0-0.noarch.rpm | grep '^python'$X_Y'dist(zope\.component)' && exit 1 || true
rpm -qp --provides ${RPMDIR}/python3-zope-component-4.3.0-0.noarch.rpm | grep '^python'$X_Y'dist(zope\.component)'
rpm -qp --provides ${RPMDIR}/python3-zope-component-4.3.0-0.noarch.rpm | grep '^python'$X_Y'dist(zope-component)'
rpm -qp --requires ${RPMDIR}/python3-zope-component-4.3.0-0.noarch.rpm | grep '^python'$X_Y'dist(zope-event)'
@ -19,28 +19,17 @@ rpm -qp --requires ${RPMDIR}/python3-zope-component-4.3.0-0.noarch.rpm | grep '^
rpm -qp --provides ${RPMDIR}/python3.7-zope-component-4.3.0-0.noarch.rpm | grep '^python3dist(zope\.component)' && exit 1 || true
rpm -qp --provides ${RPMDIR}/python3.7-zope-component-4.3.0-0.noarch.rpm | grep '^python3dist(zope-component)' && exit 1 || true
rpm -qp --provides ${RPMDIR}/python3.7-zope-component-4.3.0-0.noarch.rpm | grep '^python3\.7dist(zope\.component)' && exit 1 || true
rpm -qp --provides ${RPMDIR}/python3.7-zope-component-4.3.0-0.noarch.rpm | grep '^python3\.7dist(zope\.component)'
rpm -qp --provides ${RPMDIR}/python3.7-zope-component-4.3.0-0.noarch.rpm | grep '^python3\.7dist(zope-component)'
rpm -qp --requires ${RPMDIR}/python3.7-zope-component-4.3.0-0.noarch.rpm | grep '^python3\.7dist(zope-event)'
rpm -qp --requires ${RPMDIR}/python3.7-zope-component-4.3.0-0.noarch.rpm | grep '^python3\.7dist(zope-interface)'
if [ "$X_Y" != "3.9" ]; then
rpm -qp --provides ${RPMDIR}/python3.9-zope-component-4.3.0-0.noarch.rpm | grep '^python3dist(zope\.component)' && exit 1 || true
rpm -qp --provides ${RPMDIR}/python3.9-zope-component-4.3.0-0.noarch.rpm | grep '^python3dist(zope-component)' && exit 1 || true
rpm -qp --provides ${RPMDIR}/python3.9-zope-component-4.3.0-0.noarch.rpm | grep '^python3\.9dist(zope\.component)' && exit 1 || true
rpm -qp --provides ${RPMDIR}/python3.9-zope-component-4.3.0-0.noarch.rpm | grep '^python3\.9dist(zope-component)'
rpm -qp --requires ${RPMDIR}/python3.9-zope-component-4.3.0-0.noarch.rpm | grep '^python3\.9dist(zope-event)'
rpm -qp --requires ${RPMDIR}/python3.9-zope-component-4.3.0-0.noarch.rpm | grep '^python3\.9dist(zope-interface)'
fi
if [ "$X_Y" != "3.10" ]; then
rpm -qp --provides ${RPMDIR}/python3.10-zope-component-4.3.0-0.noarch.rpm | grep '^python3dist(zope\.component)' && exit 1 || true
rpm -qp --provides ${RPMDIR}/python3.10-zope-component-4.3.0-0.noarch.rpm | grep '^python3dist(zope-component)' && exit 1 || true
rpm -qp --provides ${RPMDIR}/python3.10-zope-component-4.3.0-0.noarch.rpm | grep '^python3\.10dist(zope\.component)' && exit 1 || true
rpm -qp --provides ${RPMDIR}/python3.10-zope-component-4.3.0-0.noarch.rpm | grep '^python3\.10dist(zope\.component)'
rpm -qp --provides ${RPMDIR}/python3.10-zope-component-4.3.0-0.noarch.rpm | grep '^python3\.10dist(zope-component)'
rpm -qp --requires ${RPMDIR}/python3.10-zope-component-4.3.0-0.noarch.rpm | grep '^python3\.10dist(zope-event)'
rpm -qp --requires ${RPMDIR}/python3.10-zope-component-4.3.0-0.noarch.rpm | grep '^python3\.10dist(zope-interface)'
fi

View file

@ -24,19 +24,10 @@ Summary: ...
%description -n python3.7-zope-component
...
%if v"%{python3_version}" != v"3.9"
%package -n python3.9-zope-component
Summary: ...
%description -n python3.9-zope-component
...
%endif
%if v"%{python3_version}" != v"3.10"
%package -n python3.10-zope-component
Summary: ...
%description -n python3.10-zope-component
...
%endif
%prep
%autosetup -n zope.component-%{version}
@ -51,17 +42,9 @@ mkdir -p %{buildroot}/usr/lib/python3.7/site-packages
cp -a %{buildroot}%{python3_sitelib}/zope.component-%{version}-py%{python3_version}.egg-info \
%{buildroot}/usr/lib/python3.7/site-packages/zope.component-%{version}-py3.7.egg-info
%if v"%{python3_version}" != v"3.9"
mkdir -p %{buildroot}/usr/lib/python3.9/site-packages
cp -a %{buildroot}%{python3_sitelib}/zope.component-%{version}-py%{python3_version}.egg-info \
%{buildroot}/usr/lib/python3.9/site-packages/zope.component-%{version}-py3.9.egg-info
%endif
%if v"%{python3_version}" != v"3.10"
mkdir -p %{buildroot}/usr/lib/python3.10/site-packages
cp -a %{buildroot}%{python3_sitelib}/zope.component-%{version}-py%{python3_version}.egg-info \
%{buildroot}/usr/lib/python3.10/site-packages/zope.component-%{version}-py3.10.egg-info
%endif
%files -n python3-zope-component
%license LICENSE.txt
@ -71,14 +54,6 @@ cp -a %{buildroot}%{python3_sitelib}/zope.component-%{version}-py%{python3_versi
%license LICENSE.txt
/usr/lib/python3.7/site-packages/zope.component-%{version}-py3.7.egg-info/
%if v"%{python3_version}" != v"3.9"
%files -n python3.9-zope-component
%license LICENSE.txt
/usr/lib/python3.9/site-packages/zope.component-%{version}-py3.9.egg-info/
%endif
%if v"%{python3_version}" != v"3.10"
%files -n python3.10-zope-component
%license LICENSE.txt
/usr/lib/python3.10/site-packages/zope.component-%{version}-py3.10.egg-info/
%endif

View file

@ -34,43 +34,3 @@ test $(rpm -qp --provides ${RPMDIR}/python3-python_provide-0-0.noarch.rpm | grep
echo "Provides for python3-py_provides"
rpm -qp --provides ${RPMDIR}/python3-py_provides-0-0.noarch.rpm
test $(rpm -qp --provides ${RPMDIR}/python3-py_provides-0-0.noarch.rpm | grep python-py_provides | wc -l) -eq 1
echo "Obsoletes for python${X_Y}-foo"
rpm -qp --obsoletes ${RPMDIR}/python${X_Y}-foo-0-0.noarch.rpm
test $(rpm -qp --obsoletes ${RPMDIR}/python${X_Y}-foo-0-0.noarch.rpm | wc -l) -eq 0
echo "Obsoletes for python3-foo"
rpm -qp --obsoletes ${RPMDIR}/python3-foo-0-0.noarch.rpm
# In ELN/RHEL the pythonX.Y-* Obsoletes is generated, but not in Fedora, so we check for it explicitly.
rpm -qp --obsoletes ${RPMDIR}/python3-foo-0-0.noarch.rpm | grep -q '^python'${X_Y}'-foo < 0-0$' && exit 1 || true
test $(rpm -qp --obsoletes ${RPMDIR}/python3-foo-0-0.noarch.rpm | wc -l) -eq 0
echo "Obsoletes for python2-foo"
rpm -qp --obsoletes ${RPMDIR}/python2-foo-0-0.noarch.rpm
test $(rpm -qp --obsoletes ${RPMDIR}/python2-foo-0-0.noarch.rpm | wc -l) -eq 0
echo "Obsoletes for python-foo"
rpm -qp --obsoletes ${RPMDIR}/python-foo-0-0.noarch.rpm
test $(rpm -qp --obsoletes ${RPMDIR}/python-foo-0-0.noarch.rpm | wc -l) -eq 0
echo "Obsoletes for python3.5-foo"
rpm -qp --obsoletes ${RPMDIR}/python3.5-foo-0-0.noarch.rpm
test $(rpm -qp --obsoletes ${RPMDIR}/python3.5-foo-0-0.noarch.rpm | wc -l) -eq 0
echo "Obsoletes for python3-python_provide"
rpm -qp --obsoletes ${RPMDIR}/python3-python_provide-0-0.noarch.rpm
# The deprecated %python_provide macro always obsoletes python-foo
rpm -qp --obsoletes ${RPMDIR}/python3-python_provide-0-0.noarch.rpm | grep -q '^python-python_provide < 0-0$'
# In ELN/RHEL the pythonX.Y-* Obsoletes is generated, but not in Fedora, so we check for it explicitly.
rpm -qp --obsoletes ${RPMDIR}/python3-python_provide-0-0.noarch.rpm | grep -q '^python'${X_Y}'-python_provide < 0-0$' && exit 1 || true
test $(rpm -qp --obsoletes ${RPMDIR}/python3-python_provide-0-0.noarch.rpm | grep python-python_provide | wc -l) -eq 1
test $(rpm -qp --obsoletes ${RPMDIR}/python3-python_provide-0-0.noarch.rpm | wc -l) -eq 1
echo "Obsoletes for python3-py_provides"
rpm -qp --obsoletes ${RPMDIR}/python3-py_provides-0-0.noarch.rpm
rpm -qp --obsoletes ${RPMDIR}/python3-py_provides-0-0.noarch.rpm | grep -q '^python-py_provides < 0-0$' && exit 1 || true
# In ELN/RHEL the pythonX.Y-* Obsoletes is generated, but not in Fedora, so we check for it explicitly.
rpm -qp --obsoletes ${RPMDIR}/python3-py_provides-0-0.noarch.rpm | grep -q '^python'${X_Y}'-py_provides < 0-0$' && exit 1 || true
test $(rpm -qp --obsoletes ${RPMDIR}/python3-py_provides-0-0.noarch.rpm | wc -l) -eq 0

View file

@ -34,7 +34,7 @@ def run_pythonbundles(*args, success=True):
return cp
projects = pytest.mark.parametrize('project', ('pkg_resources', 'pip', 'pipenv', 'setuptools'))
projects = pytest.mark.parametrize('project', ('pkg_resources', 'pip', 'pipenv'))
@projects
@ -97,23 +97,3 @@ def test_compare_with_unexpected(project):
cp = run_pythonbundles(TEST_DATA / f'{project}.in', '--compare-with', longer, success=False)
assert cp.stdout == '', cp.stdout
assert cp.stderr == f'Redundant unexpected provides:\n + {unexpected}\n', cp.stderr
combo_order = pytest.mark.parametrize('projects', ['pkg_resources-setuptools', 'setuptools-pkg_resources'])
@combo_order
def test_multiple_vendor_files_output(projects):
cp = run_pythonbundles(*(TEST_DATA / f'{p}.in' for p in projects.split('-')))
expected = (TEST_DATA / 'pkg_resources_setuptools.out').read_text()
assert cp.stdout == expected, cp.stdout
assert cp.stderr == '', cp.stderr
@combo_order
def test_multiple_vendor_files_compare_with(projects):
expected = (TEST_DATA / 'pkg_resources_setuptools.out').read_text()
cp = run_pythonbundles(*(TEST_DATA / f'{p}.in' for p in projects.split('-')),
'--compare-with', expected)
assert cp.stdout == '', cp.stdout
assert cp.stderr == '', cp.stderr

View file

@ -29,7 +29,6 @@
from pathlib import Path
from fnmatch import fnmatch
import pytest
import shlex
import shutil
@ -53,11 +52,6 @@ def run_pythondistdeps(provides_params, requires_params, dist_egg_info_path, exp
requires = subprocess.run((sys.executable, PYTHONDISTDEPS_PATH, *shlex.split(requires_params)),
input=files, capture_output=True, check=False, encoding="utf-8")
print(provides_params, provides.stdout, sep=':\n', file=sys.stdout)
print(requires_params, requires.stdout, sep=':\n', file=sys.stdout)
print(provides_params, provides.stderr, sep=':\n', file=sys.stderr)
print(requires_params, requires.stderr, sep=':\n', file=sys.stderr)
if expect_failure:
if provides.returncode == 0 or requires.returncode == 0:
raise RuntimeError(f"pythondistdeps.py did not exit with a non-zero code as expected.\n"
@ -230,21 +224,8 @@ def fixture_check_and_install_test_data():
def test_pythondistdeps(provides_params, requires_params, dist_egg_info_path, expected):
"""Runs pythondistdeps with the given parameters and dist-info/egg-info
path, compares the results with the expected results"""
expect_failure = "stderr" in expected
tested = run_pythondistdeps(provides_params, requires_params, dist_egg_info_path, expect_failure)
if expect_failure:
for k1, k2 in ((k1, k2) for k1 in expected.keys() for k2 in expected[k1].keys()):
if k1 == "stderr":
# Some stderr messages contain full file paths. To get around
# this, asterisk is used in the test-data and we compare with
# fnmatch that understands Unix-style wildcards.
assert fnmatch(tested[k1][k2], expected[k1][k2])
else:
assert expected[k1][k2] == tested[k1][k2]
else:
assert expected == tested
assert expected == run_pythondistdeps(provides_params, requires_params, dist_egg_info_path, expect_failure)
if __name__ == "__main__":

45
tests/tests.yml Normal file
View file

@ -0,0 +1,45 @@
---
- hosts: localhost
tags:
- classic
tasks:
- dnf:
name: "*"
state: latest
- hosts: localhost
tags:
- classic
pre_tasks:
- import_role:
name: standard-test-source
vars:
fetch_only: True
roles:
- role: standard-test-basic
tests:
- pythonabi:
dir: .
run: ./pythonabi.sh
- pythonname:
dir: .
run: ./pythonname.sh
- pythondist:
dir: .
run: ./pythondist.sh
- prepare-test-data:
dir: .
run: tar -xvf test-sources-*.tar.gz -C ./tests/data/scripts_pythondistdeps/
- pytest:
dir: ./tests
# Use update-test-sources.sh to update the test data
run: python3 -m pytest --capture=no -vvv
required_packages:
- rpm-build
- rpmdevtools
- python3-devel
- python3-pip
- python3-pytest
- python3-pyyaml
- python3-setuptools
- python3-wheel

View file

@ -3,7 +3,6 @@
#
# Requirements:
# - pip >= 20.0.1
# - poetry # Due to bug: https://github.com/pypa/pip/issues/9701
#
# First prune old test data