Compare commits

..

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

10 changed files with 205 additions and 647 deletions

View File

View File

@ -163,43 +163,6 @@ The `%pyproject_buildrequires` macro also accepts the `-r` flag for backward com
it means "include runtime dependencies" which has been the default since version 0-53. it means "include runtime dependencies" which has been the default since version 0-53.
Passing config settings to build backends
-----------------------------------------
The `%pyproject_buildrequires` and `%pyproject_wheel` macros accept a `-C` flag
to pass [configuration settings][config_settings] to the build backend.
Options take the form of `-C KEY`, `-C KEY=VALUE`, or `-C--option-with-dashes`.
Pass `-C` multiple times to specify multiple options.
This option is equivalent to pip's `--config-settings` flag.
These are passed on to PEP 517 hooks' `config_settings` argument as a Python
dictionary.
The `%pyproject_buildrequires` macro passes these options to the
`get_requires_for_build_wheel` and `prepare_metadata_for_build_wheel` hooks.
Passing `-C` to `%pyproject_buildrequires` is incompatible with `-N` which does
not call these hooks at all.
The `%pyproject_wheel` macro passes these options to the `build_wheel` hook.
Consult the project's upstream documentation and/or the corresponding build
backend's documentation for more information.
Note that some projects don't use config settings at all
and other projects may only accept config settings for one of the two steps.
Note that the current implementation of the macros uses `pip` to build wheels.
On some systems (notably on RHEL 9 with Python 3.9),
`pip` is too old to understand `--config-settings`.
Using the `-C` option for `%pyproject_wheel` (or `%pyproject_buildrequires -w`)
is not supported there and will result to an error like:
Usage:
/usr/bin/python3 -m pip wheel [options] <requirement specifier> ...
...
no such option: --config-settings
[config_settings]: https://peps.python.org/pep-0517/#config-settings
Running tox based tests Running tox based tests
----------------------- -----------------------
@ -288,12 +251,6 @@ However, in Fedora packages, always list executables explicitly to avoid uninten
and language (`*.mo`) files with `%lang` macro and appropriate language code. and language (`*.mo`) files with `%lang` macro and appropriate language code.
Only license files declared via [PEP 639] `License-File` field are detected. Only license files declared via [PEP 639] `License-File` field are detected.
[PEP 639] is still a draft and can be changed in the future. [PEP 639] is still a draft and can be changed in the future.
It is possible to use the `-l` flag to declare that a missing license should
terminate the build or `-L` (the default) to explicitly disable this check.
Packagers are encouraged to use the `-l` flag when the `%license` file is not manually listed in `%files`
to avoid accidentally losing the file in a future version.
When the `%license` file is manually listed in `%files`,
packagers can use the `-L` flag to ensure future compatibility in case the `-l` behavior eventually becomes a default.
Note that `%pyproject_save_files` uses data from the [RECORD file](https://www.python.org/dev/peps/pep-0627/). Note that `%pyproject_save_files` uses data from the [RECORD file](https://www.python.org/dev/peps/pep-0627/).
If you wish to rename, remove or otherwise change the installed files of a package If you wish to rename, remove or otherwise change the installed files of a package
@ -348,12 +305,6 @@ The `%pyproject_check_import` macro also accepts positional arguments with
additional qualified module names to check, useful for example if some modules are installed manually. additional qualified module names to check, useful for example if some modules are installed manually.
Note that filtering by `-t`/`-e` also applies to the positional arguments. Note that filtering by `-t`/`-e` also applies to the positional arguments.
Another macro, `%_pyproject_check_import_allow_no_modules` allows to pass the import check,
even if no Python modules are detected in the package.
This may be a valid case for packages containing e.g. typing stubs.
Don't use this macro in Fedora packages.
It's only intended to be used in automated build environments such as Copr.
Generating Extras subpackages Generating Extras subpackages
----------------------------- -----------------------------
@ -385,6 +336,91 @@ These arguments are still required:
Multiple subpackages are generated when multiple names are provided. Multiple subpackages are generated when multiple names are provided.
PROVISIONAL: Importing just-built (extension) modules in %build
---------------------------------------------------------------
Sometimes, it is desired to be able to import the *just-built* extension modules
in the `%build` section, e.g. to build the documentation with Sphinx.
%build
%pyproject_wheel
... build the docs here ...
With pure Python packages, it might be possible to set `PYTHONPATH=${PWD}` or `PYTHONPATH=${PWD}/src`.
However, it is a bit more complicated with extension modules.
The location of just-built modules might differ depending on Python version, architecture, pip version, etc.
Hence, the macro `%{pyproject_build_lib}` exists to be used like this:
%build
%pyproject_wheel
PYTHONPATH=%{pyproject_build_lib} ... build the docs here ...
This macro is currently **provisional** and the behavior might change.
Please subscribe to Fedora's [python-devel list] if you use the macro.
The `%{pyproject_build_lib}` macro expands to an Shell `$(...)` expression and does not work when put into single quotes (`'`).
Depending on the pip version, the expanded value will differ:
[python-devel list]: https://lists.fedoraproject.org/archives/list/python-devel@lists.fedoraproject.org/
### New pip 21.3+ with in-tree-build and setuptools 62.1+ (Fedora 37+)
Always use the macro from the same directory where you called `%pyproject_wheel` from.
The value will expand to something like:
* `/builddir/build/BUILD/%{name}-%{version}/build/lib.linux-x86_64-cpython-311` for wheels with extension modules
* `/builddir/build/BUILD/%{name}-%{version}/build/lib` for pure Python wheels
If multiple wheels were built from the same directory,
some pure Python and some with extension modules,
the expanded value will be combined with `:`:
* `/builddir/build/BUILD/%{name}-%{version}/build/lib.linux-x86_64-cypthon-311:/builddir/build/BUILD/%{name}-%{version}/build/lib`
If multiple wheels were built from different directories,
the value will differ depending on the current directory.
### New pip 21.3+ with in-tree-build and older setuptools (Fedora 36)
Always use the macro from the same directory where you called `%pyproject_wheel` from.
The value will expand to something like:
* `/builddir/build/BUILD/%{name}-%{version}/build/lib.linux-x86_64-3.10` for wheels with extension modules
* `/builddir/build/BUILD/%{name}-%{version}/build/lib` for pure Python wheels
If multiple wheels were built from the same directory,
some pure Python and some with extension modules,
the expanded value will be combined with `:`:
* `/builddir/build/BUILD/%{name}-%{version}/build/lib.linux-x86_64-3.10:/builddir/build/BUILD/%{name}-%{version}/build/lib`
If multiple wheels were built from different directories,
the value will differ depending on the current directory.
### Older pip with out-of-tree-build (Fedora 35 and EL 9)
The value will expand to something like:
* `/builddir/build/BUILD/%{name}-%{version}/.pyproject-builddir/pip-req-build-xxxxxxxx/build/lib.linux-x86_64-3.10` for wheels with extension modules
* `/builddir/build/BUILD/%{name}-%{version}/.pyproject-builddir/pip-req-build-xxxxxxxx/build/lib` for pure Python wheels
Note that the exact value is **not stable** between builds
(the `xxxxxxxx` part is randomly generated,
neither you should consider the `.pyproject-builddir` directory to remain stable).
If multiple wheels are built,
the expanded value will always be combined with `:` regardless of the current directory, e.g.:
* `/builddir/build/BUILD/%{name}-%{version}/.pyproject-builddir/pip-req-build-xxxxxxxx/build/lib.linux-x86_64-3.10:/builddir/build/BUILD/%{name}-%{version}/.pyproject-builddir/pip-req-build-yyyyyyyy/build/lib.linux-x86_64-3.10:/builddir/build/BUILD/%{name}-%{version}/.pyproject-builddir/pip-req-build-zzzzzzzz/build/lib`
**Note:** If you manage to build some wheels with in-tree-build and some with out-of-tree-build option,
the expanded value will contain all relevant directories.
Limitations Limitations
----------- -----------
@ -437,12 +473,6 @@ so be prepared for problems.
[pip's documentation]: https://pip.pypa.io/en/stable/cli/pip_install/#vcs-support [pip's documentation]: https://pip.pypa.io/en/stable/cli/pip_install/#vcs-support
Deprecated
----------
The `%{pyproject_build_lib}` macro is deprecated, don't use it.
Testing the macros Testing the macros
------------------ ------------------

View File

@ -4,4 +4,4 @@
# this macro will cause the package with the real macro to be installed. # this macro will cause the package with the real macro to be installed.
# When macros.pyproject is installed, it overrides this macro. # When macros.pyproject is installed, it overrides this macro.
# Note: This needs to maintain the same set of options as the real macro. # Note: This needs to maintain the same set of options as the real macro.
%pyproject_buildrequires(rRxtNwe:C:) echo 'pyproject-rpm-macros' && exit 0 %pyproject_buildrequires(rRxtNwe:) echo 'pyproject-rpm-macros' && exit 0

View File

@ -1,9 +1,5 @@
# This is a backward-compatible suffix used in all pyproject-rpm-macros directories
# For the main Python it's empty, for all others it's "-3.X"
%_pyproject_files_pkgversion %{expr:"%{python3_pkgversion}" != "3" ? "-%{python3_pkgversion}" : ""}
# This is a directory where wheels are stored and installed from, absolute # This is a directory where wheels are stored and installed from, absolute
%_pyproject_wheeldir %{_builddir}%{?buildsubdir:/%{buildsubdir}}/pyproject-wheeldir%{_pyproject_files_pkgversion} %_pyproject_wheeldir %{_builddir}%{?buildsubdir:/%{buildsubdir}}/pyproject-wheeldir
# This is a directory used as TMPDIR, where pip copies sources to and builds from, relative to PWD # This is a directory used as TMPDIR, where pip copies sources to and builds from, relative to PWD
# For proper debugsource packages, we create TMPDIR within PWD # For proper debugsource packages, we create TMPDIR within PWD
@ -12,40 +8,32 @@
# This will be used in debugsource package paths (applies to extension modules only) # This will be used in debugsource package paths (applies to extension modules only)
# NB: pytest collects tests from here if not hidden # NB: pytest collects tests from here if not hidden
# https://docs.pytest.org/en/latest/reference.html#confval-norecursedirs # https://docs.pytest.org/en/latest/reference.html#confval-norecursedirs
%_pyproject_builddir %{_builddir}%{?buildsubdir:/%{buildsubdir}}/.pyproject-builddir%{_pyproject_files_pkgversion} %_pyproject_builddir %{_builddir}%{?buildsubdir:/%{buildsubdir}}/.pyproject-builddir
# We prefix all created files with this value to make them unique # We prefix all created files with this value to make them unique
# Ideally, we would put them into %%{buildsubdir}, but that value changes during the spec # Ideally, we would put them into %%{buildsubdir}, but that value changes during the spec
# The used value is similar to the one used to define the default %%buildroot # The used value is similar to the one used to define the default %%buildroot
%_pyproject_files_prefix %{name}-%{version}-%{release}.%{_arch}%{_pyproject_files_pkgversion} %_pyproject_files_prefix %{name}-%{version}-%{release}.%{_arch}
%pyproject_files %{_builddir}/%{_pyproject_files_prefix}-pyproject-files %pyproject_files %{_builddir}/%{_pyproject_files_prefix}-pyproject-files
%_pyproject_modules %{_builddir}/%{_pyproject_files_prefix}-pyproject-modules %_pyproject_modules %{_builddir}/%{_pyproject_files_prefix}-pyproject-modules
%_pyproject_ghost_distinfo %{_builddir}/%{_pyproject_files_prefix}-pyproject-ghost-distinfo %_pyproject_ghost_distinfo %{_builddir}/%{_pyproject_files_prefix}-pyproject-ghost-distinfo
%_pyproject_record %{_builddir}/%{_pyproject_files_prefix}-pyproject-record %_pyproject_record %{_builddir}/%{_pyproject_files_prefix}-pyproject-record
%_pyproject_buildrequires %{_builddir}/%{_pyproject_files_prefix}-pyproject-buildrequires
# Avoid leaking %%{_pyproject_builddir} to pytest collection # Avoid leaking %%{_pyproject_builddir} to pytest collection
# https://bugzilla.redhat.com/show_bug.cgi?id=1935212 # https://bugzilla.redhat.com/show_bug.cgi?id=1935212
# The value is read and used by the %%pytest and %%tox macros: # The value is read and used by the %%pytest and %%tox macros:
%_set_pytest_addopts %global __pytest_addopts --ignore=%{_pyproject_builddir} %_set_pytest_addopts %global __pytest_addopts --ignore=%{_pyproject_builddir}
%pyproject_wheel(C:) %{expand:\\\ %pyproject_wheel() %{expand:\\\
%_set_pytest_addopts %_set_pytest_addopts
mkdir -p "%{_pyproject_builddir}" mkdir -p "%{_pyproject_builddir}"
CFLAGS="${CFLAGS:-${RPM_OPT_FLAGS}}" LDFLAGS="${LDFLAGS:-${RPM_LD_FLAGS}}" TMPDIR="%{_pyproject_builddir}" \\\ CFLAGS="${CFLAGS:-${RPM_OPT_FLAGS}}" LDFLAGS="${LDFLAGS:-${RPM_LD_FLAGS}}" TMPDIR="%{_pyproject_builddir}" \\\
%{__python3} -Bs %{_rpmconfigdir}/redhat/pyproject_wheel.py %{?**} %{_pyproject_wheeldir} %{__python3} -Bs %{_rpmconfigdir}/redhat/pyproject_wheel.py %{_pyproject_wheeldir}
} }
%pyproject_build_lib %{!?__pyproject_build_lib_warned:%{warn:The %%{pyproject_build_lib} macro is deprecated. %pyproject_build_lib %{expand:\\\
It only works with setuptools and is not build-backend-agnostic.
The macro is not scheduled for removal, but there is a possibility of incompatibilities with future versions of setuptools.
As a replacement for the macro for the setuptools backend on Fedora 37+, you can use $PWD/build/lib for pure Python packages,
or $PWD/build/lib.%%{python3_platform}-cpython-%%{python3_version_nodots} for packages with extension modules.
Other build backends and older distributions may need different paths.
See https://lists.fedoraproject.org/archives/list/python-devel@lists.fedoraproject.org/thread/HMLOPAU3RZLXD4BOJHTIPKI3I4U6U7OE/ for details.
}%global __pyproject_build_lib_warned 1}%{expand:\\\
$( $(
pyproject_build_lib=() pyproject_build_lib=()
if [ -d build/lib.%{python3_platform}-cpython-%{python3_version_nodots} ]; then if [ -d build/lib.%{python3_platform}-cpython-%{python3_version_nodots} ]; then
@ -69,10 +57,6 @@ echo $(IFS=:; echo "${pyproject_build_lib[*]}")
%pyproject_install() %{expand:\\\ %pyproject_install() %{expand:\\\
specifier=$(ls %{_pyproject_wheeldir}/*.whl | xargs basename --multiple | sed -E 's/([^-]+)-([^-]+)-.+\\\.whl/\\\1==\\\2/') specifier=$(ls %{_pyproject_wheeldir}/*.whl | xargs basename --multiple | sed -E 's/([^-]+)-([^-]+)-.+\\\.whl/\\\1==\\\2/')
if [ -z $specifier ]; then
echo 'ERROR: %%%%pyproject_install found no wheel in %%%%{_pyproject_wheeldir} %{_pyproject_wheeldir}' >&2
exit 1
fi
TMPDIR="%{_pyproject_builddir}" %{__python3} -m pip install --root %{buildroot} --prefix %{_prefix} --no-deps --disable-pip-version-check --progress-bar off --verbose --ignore-installed --no-warn-script-location --no-index --no-cache-dir --find-links %{_pyproject_wheeldir} $specifier TMPDIR="%{_pyproject_builddir}" %{__python3} -m pip install --root %{buildroot} --prefix %{_prefix} --no-deps --disable-pip-version-check --progress-bar off --verbose --ignore-installed --no-warn-script-location --no-index --no-cache-dir --find-links %{_pyproject_wheeldir} $specifier
if [ -d %{buildroot}%{_bindir} ]; then if [ -d %{buildroot}%{_bindir} ]; then
%py3_shebang_fix %{buildroot}%{_bindir}/* %py3_shebang_fix %{buildroot}%{_bindir}/*
@ -112,12 +96,7 @@ fi
%pyproject_extras_subpkg(n:i:f:F) %{expand:%{?python_extras_subpkg:%{python_extras_subpkg%{?!-i:%{?!-f:%{?!-F: -f %{_pyproject_ghost_distinfo}}}} %**}}} %pyproject_extras_subpkg(n:i:f:F) %{expand:%{?python_extras_subpkg:%{python_extras_subpkg%{?!-i:%{?!-f:%{?!-F: -f %{_pyproject_ghost_distinfo}}}} %**}}}
# Escaping an actual percentage sign in path by 8 signs has been verified in RPM 4.16 and 4.17. %pyproject_save_files() %{expand:\\\
# See this thread http://lists.rpm.org/pipermail/rpm-list/2021-June/002048.html
# Since RPM 4.19, 2 signs are needed instead. 4.18.90+ is a pre-release of RPM 4.19.
# On the CI, we build tests/escape_percentages.spec to verify the assumptions.
%pyproject_save_files(lL) %{expand:\\\
%{expr:v"0%{?rpmversion}" >= v"4.18.90" ? "RPM_PERCENTAGES_COUNT=2" : "RPM_PERCENTAGES_COUNT=8" } \\
%{__python3} %{_rpmconfigdir}/redhat/pyproject_save_files.py \\ %{__python3} %{_rpmconfigdir}/redhat/pyproject_save_files.py \\
--output-files "%{pyproject_files}" \\ --output-files "%{pyproject_files}" \\
--output-modules "%{_pyproject_modules}" \\ --output-modules "%{_pyproject_modules}" \\
@ -127,7 +106,7 @@ fi
--python-version "%{python3_version}" \\ --python-version "%{python3_version}" \\
--pyproject-record "%{_pyproject_record}" \\ --pyproject-record "%{_pyproject_record}" \\
--prefix "%{_prefix}" \\ --prefix "%{_prefix}" \\
%{**} %{*}
} }
# -t - Process only top-level modules # -t - Process only top-level modules
@ -141,20 +120,12 @@ fi
} }
%_pyproject_check_import_allow_no_modules(e:t) \
if [ -z "$(cat %{_pyproject_modules})" ]; then\
echo "No modules to check found, exiting check"\
else\
%pyproject_check_import %{?**}\
fi
%default_toxenv py%{python3_version_nodots} %default_toxenv py%{python3_version_nodots}
%toxenv %{default_toxenv} %toxenv %{default_toxenv}
# Note: Keep the options in sync with this macro from macros.aaa-pyproject-srpm # Note: Keep the options in sync with this macro from macros.aaa-pyproject-srpm
%pyproject_buildrequires(rRxtNwe:C:) %{expand:\\\ %pyproject_buildrequires(rRxtNwe:) %{expand:\\\
%_set_pytest_addopts %_set_pytest_addopts
# The _auto_set_build_flags feature does not do this in %%generate_buildrequires section, # The _auto_set_build_flags feature does not do this in %%generate_buildrequires section,
# but we want to get an environment consistent with %%build: # but we want to get an environment consistent with %%build:
@ -164,9 +135,6 @@ fi
%{?_package_note_flags:%_generate_package_note_file} %{?_package_note_flags:%_generate_package_note_file}
%{-R: %{-R:
%{-r:%{error:The -R and -r options are mutually exclusive}} %{-r:%{error:The -R and -r options are mutually exclusive}}
%{-x:%{error:The -R and -x options are mutually exclusive}}
%{-e:%{error:The -R and -e options are mutually exclusive}}
%{-t:%{error:The -R and -t options are mutually exclusive}}
%{-w:%{error:The -R and -w options are mutually exclusive}} %{-w:%{error:The -R and -w options are mutually exclusive}}
} }
%{-N: %{-N:
@ -175,7 +143,6 @@ fi
%{-e:%{error:The -N and -e options are mutually exclusive}} %{-e:%{error:The -N and -e options are mutually exclusive}}
%{-t:%{error:The -N and -t options are mutually exclusive}} %{-t:%{error:The -N and -t options are mutually exclusive}}
%{-w:%{error:The -N and -w options are mutually exclusive}} %{-w:%{error:The -N and -w options are mutually exclusive}}
%{-C:%{error:The -N and -C options are mutually exclusive}}
} }
%{-e:%{expand:%global toxenv %(%{__python3} -s %{_rpmconfigdir}/redhat/pyproject_construct_toxenv.py %{?**})}} %{-e:%{expand:%global toxenv %(%{__python3} -s %{_rpmconfigdir}/redhat/pyproject_construct_toxenv.py %{?**})}}
echo 'pyproject-rpm-macros' # first stdout line matches the implementation in macros.aaa-pyproject-srpm echo 'pyproject-rpm-macros' # first stdout line matches the implementation in macros.aaa-pyproject-srpm
@ -184,9 +151,9 @@ echo 'python%{python3_pkgversion}dist(pip) >= 19'
echo 'python%{python3_pkgversion}dist(packaging)' echo 'python%{python3_pkgversion}dist(packaging)'
%{!-N:if [ -f pyproject.toml ]; then %{!-N:if [ -f pyproject.toml ]; then
%["%{python3_pkgversion}" == "3" %["%{python3_pkgversion}" == "3"
? "echo '(python%{python3_pkgversion}dist(tomli) if python%{python3_pkgversion}-devel < 3.11)'" ? "echo '(python%{python3_pkgversion}dist(toml) if python%{python3_pkgversion}-devel < 3.11)'"
: "%[v"%{python3_pkgversion}" < v"3.11" : "%[v"%{python3_pkgversion}" < v"3.11"
? "echo 'python%{python3_pkgversion}dist(tomli)'" ? "echo 'python%{python3_pkgversion}dist(toml)'"
: "true # will use tomllib, echo nothing" : "true # will use tomllib, echo nothing"
]" ]"
] ]
@ -202,13 +169,9 @@ fi}
rm -rfv *.dist-info/ >&2 rm -rfv *.dist-info/ >&2
if [ -f %{__python3} ]; then if [ -f %{__python3} ]; then
mkdir -p "%{_pyproject_builddir}" mkdir -p "%{_pyproject_builddir}"
echo -n > %{_pyproject_buildrequires}
CFLAGS="${CFLAGS:-${RPM_OPT_FLAGS}}" LDFLAGS="${LDFLAGS:-${RPM_LD_FLAGS}}" TMPDIR="%{_pyproject_builddir}" \\\ CFLAGS="${CFLAGS:-${RPM_OPT_FLAGS}}" LDFLAGS="${LDFLAGS:-${RPM_LD_FLAGS}}" TMPDIR="%{_pyproject_builddir}" \\\
RPM_TOXENV="%{toxenv}" HOSTNAME="rpmbuild" %{__python3} -Bs %{_rpmconfigdir}/redhat/pyproject_buildrequires.py %{?!_python_no_extras_requires:--generate-extras} --python3_pkgversion %{python3_pkgversion} --wheeldir %{_pyproject_wheeldir} --output %{_pyproject_buildrequires} %{?**} >&2 RPM_TOXENV="%{toxenv}" HOSTNAME="rpmbuild" %{__python3} -Bs %{_rpmconfigdir}/redhat/pyproject_buildrequires.py %{?!_python_no_extras_requires:--generate-extras} --python3_pkgversion %{python3_pkgversion} --wheeldir %{_pyproject_wheeldir} %{?**}
cat %{_pyproject_buildrequires}
fi fi
# Incomplete .dist-info dir might confuse importlib.metadata
rm -rfv *.dist-info/ >&2
} }

View File

@ -4,7 +4,9 @@ import os
import sys import sys
import importlib.metadata import importlib.metadata
import argparse import argparse
import tempfile
import traceback import traceback
import contextlib
import json import json
import subprocess import subprocess
import re import re
@ -14,7 +16,6 @@ import pathlib
import zipfile import zipfile
from pyproject_requirements_txt import convert_requirements_txt from pyproject_requirements_txt import convert_requirements_txt
from pyproject_wheel import parse_config_settings_args
# Some valid Python version specifiers are not supported. # Some valid Python version specifiers are not supported.
@ -45,6 +46,39 @@ except ImportError as e:
from pyproject_convert import convert from pyproject_convert import convert
@contextlib.contextmanager
def hook_call():
"""Context manager that records all stdout content (on FD level)
and prints it to stderr at the end, with a 'HOOK STDOUT: ' prefix."""
tmpfile = io.TextIOWrapper(
tempfile.TemporaryFile(buffering=0),
encoding='utf-8',
errors='replace',
write_through=True,
)
stdout_fd = 1
stdout_fd_dup = os.dup(stdout_fd)
stdout_orig = sys.stdout
# begin capture
sys.stdout = tmpfile
os.dup2(tmpfile.fileno(), stdout_fd)
try:
yield
finally:
# end capture
sys.stdout = stdout_orig
os.dup2(stdout_fd_dup, stdout_fd)
tmpfile.seek(0) # rewind
for line in tmpfile:
print_err('HOOK STDOUT:', line, end='')
tmpfile.close()
def guess_reason_for_invalid_requirement(requirement_str): def guess_reason_for_invalid_requirement(requirement_str):
if ':' in requirement_str: if ':' in requirement_str:
message = ( message = (
@ -66,11 +100,10 @@ def guess_reason_for_invalid_requirement(requirement_str):
class Requirements: class Requirements:
"""Requirement gatherer. The macro will eventually print out output_lines.""" """Requirement printer"""
def __init__(self, get_installed_version, extras=None, def __init__(self, get_installed_version, extras=None,
generate_extras=False, python3_pkgversion='3', config_settings=None): generate_extras=False, python3_pkgversion='3'):
self.get_installed_version = get_installed_version self.get_installed_version = get_installed_version
self.output_lines = []
self.extras = set() self.extras = set()
if extras: if extras:
@ -78,11 +111,9 @@ class Requirements:
self.add_extras(*extra.split(',')) self.add_extras(*extra.split(','))
self.missing_requirements = False self.missing_requirements = False
self.ignored_alien_requirements = []
self.generate_extras = generate_extras self.generate_extras = generate_extras
self.python3_pkgversion = python3_pkgversion self.python3_pkgversion = python3_pkgversion
self.config_settings = config_settings
def add_extras(self, *extras): def add_extras(self, *extras):
self.extras |= set(e.strip() for e in extras) self.extras |= set(e.strip() for e in extras)
@ -99,7 +130,7 @@ class Requirements:
return True return True
return False return False
def add(self, requirement_str, *, package_name=None, source=None): def add(self, requirement_str, *, source=None):
"""Output a Python-style requirement string as RPM dep""" """Output a Python-style requirement string as RPM dep"""
print_err(f'Handling {requirement_str} from {source}') print_err(f'Handling {requirement_str} from {source}')
@ -121,21 +152,6 @@ class Requirements:
if (requirement.marker is not None and if (requirement.marker is not None and
not self.evaluate_all_environments(requirement)): not self.evaluate_all_environments(requirement)):
print_err(f'Ignoring alien requirement:', requirement_str) print_err(f'Ignoring alien requirement:', requirement_str)
self.ignored_alien_requirements.append(requirement_str)
return
# Handle self-referencing requirements
if package_name and canonicalize_name(package_name) == name:
# Self-referential extras need to be handled specially
if requirement.extras:
if not (requirement.extras <= self.extras): # only handle it if needed
# let all further requirements know we want those extras
self.add_extras(*requirement.extras)
# re-add all of the alien requirements ignored in the past
# they might no longer be alien now
self.readd_ignored_alien_requirements(package_name=package_name)
else:
print_err(f'Ignoring self-referential requirement without extras:', requirement_str)
return return
# We need to always accept pre-releases as satisfying the requirement # We need to always accept pre-releases as satisfying the requirement
@ -176,12 +192,12 @@ class Requirements:
together.append(convert(python3dist(name, python3_pkgversion=self.python3_pkgversion), together.append(convert(python3dist(name, python3_pkgversion=self.python3_pkgversion),
specifier.operator, specifier.version)) specifier.operator, specifier.version))
if len(together) == 0: if len(together) == 0:
dep = python3dist(name, python3_pkgversion=self.python3_pkgversion) print(python3dist(name,
self.output_lines.append(dep) python3_pkgversion=self.python3_pkgversion))
elif len(together) == 1: elif len(together) == 1:
self.output_lines.append(together[0]) print(together[0])
else: else:
self.output_lines.append(f"({' with '.join(together)})") print(f"({' with '.join(together)})")
def check(self, *, source=None): def check(self, *, source=None):
"""End current pass if any unsatisfied dependencies were output""" """End current pass if any unsatisfied dependencies were output"""
@ -194,25 +210,23 @@ class Requirements:
for req_str in requirement_strs: for req_str in requirement_strs:
self.add(req_str, **kwargs) self.add(req_str, **kwargs)
def readd_ignored_alien_requirements(self, **kwargs):
"""add() previously ignored alien requirements again."""
requirements, self.ignored_alien_requirements = self.ignored_alien_requirements, []
kwargs.setdefault('source', 'Previously ignored alien requirements')
self.extend(requirements, **kwargs)
def toml_load(opened_binary_file): def toml_load(opened_binary_file):
try: try:
# tomllib is in the standard library since 3.11.0b1 # tomllib is in the standard library since 3.11.0b1
import tomllib import tomllib as toml_module
load_from = opened_binary_file
except ImportError: except ImportError:
try: try:
import tomli as tomllib # note: we could use tomli here,
# but for backwards compatibility with RHEL 9, we use toml instead
import toml as toml_module
load_from = io.TextIOWrapper(opened_binary_file, encoding='utf-8')
except ImportError as e: except ImportError as e:
print_err('Import error:', e) print_err('Import error:', e)
# already echoed by the %pyproject_buildrequires macro # already echoed by the %pyproject_buildrequires macro
sys.exit(0) sys.exit(0)
return tomllib.load(opened_binary_file) return toml_module.load(load_from)
def get_backend(requirements): def get_backend(requirements):
@ -271,30 +285,17 @@ def get_backend(requirements):
def generate_build_requirements(backend, requirements): def generate_build_requirements(backend, requirements):
get_requires = getattr(backend, 'get_requires_for_build_wheel', None) get_requires = getattr(backend, 'get_requires_for_build_wheel', None)
if get_requires: if get_requires:
new_reqs = get_requires(config_settings=requirements.config_settings) with hook_call():
new_reqs = get_requires()
requirements.extend(new_reqs, source='get_requires_for_build_wheel') requirements.extend(new_reqs, source='get_requires_for_build_wheel')
requirements.check(source='get_requires_for_build_wheel') requirements.check(source='get_requires_for_build_wheel')
def parse_metadata_file(metadata_file): def requires_from_metadata_file(metadata_file):
return email.parser.Parser().parse(metadata_file, headersonly=True) message = email.parser.Parser().parse(metadata_file, headersonly=True)
def requires_from_parsed_metadata_file(message):
return {k: message.get_all(k, ()) for k in ('Requires', 'Requires-Dist')} return {k: message.get_all(k, ()) for k in ('Requires', 'Requires-Dist')}
def package_name_from_parsed_metadata_file(message):
return message.get('name')
def package_name_and_requires_from_metadata_file(metadata_file):
message = parse_metadata_file(metadata_file)
package_name = package_name_from_parsed_metadata_file(message)
requires = requires_from_parsed_metadata_file(message)
return package_name, requires
def generate_run_requirements_hook(backend, requirements): def generate_run_requirements_hook(backend, requirements):
hook_name = 'prepare_metadata_for_build_wheel' hook_name = 'prepare_metadata_for_build_wheel'
prepare_metadata = getattr(backend, hook_name, None) prepare_metadata = getattr(backend, hook_name, None)
@ -305,13 +306,11 @@ def generate_run_requirements_hook(backend, requirements):
'Use the provisional -w flag to build the wheel and parse the metadata from it, ' 'Use the provisional -w flag to build the wheel and parse the metadata from it, '
'or use the -R flag not to generate runtime dependencies.' 'or use the -R flag not to generate runtime dependencies.'
) )
dir_basename = prepare_metadata('.', config_settings=requirements.config_settings) with hook_call():
dir_basename = prepare_metadata('.')
with open(dir_basename + '/METADATA') as metadata_file: with open(dir_basename + '/METADATA') as metadata_file:
name, requires = package_name_and_requires_from_metadata_file(metadata_file) for key, requires in requires_from_metadata_file(metadata_file).items():
for key, req in requires.items(): requirements.extend(requires, source=f'hook generated metadata: {key}')
requirements.extend(req,
package_name=name,
source=f'hook generated metadata: {key} ({name})')
def find_built_wheel(wheeldir): def find_built_wheel(wheeldir):
@ -328,17 +327,8 @@ def generate_run_requirements_wheel(backend, requirements, wheeldir):
# Reuse the wheel from the previous round of %pyproject_buildrequires (if it exists) # Reuse the wheel from the previous round of %pyproject_buildrequires (if it exists)
wheel = find_built_wheel(wheeldir) wheel = find_built_wheel(wheeldir)
if not wheel: if not wheel:
# pip is already echoed from the macro
# but we need to explicitly restart if has not yet been installed
# see https://bugzilla.redhat.com/2169855
requirements.add('pip >= 19', source='%pyproject_buildrequires -w')
requirements.check(source='%pyproject_buildrequires -w')
import pyproject_wheel import pyproject_wheel
returncode = pyproject_wheel.build_wheel( returncode = pyproject_wheel.build_wheel(wheeldir=wheeldir, stdout=sys.stderr)
wheeldir=wheeldir,
stdout=sys.stderr,
config_settings=requirements.config_settings,
)
if returncode != 0: if returncode != 0:
raise RuntimeError('Failed to build the wheel for %pyproject_buildrequires -w.') raise RuntimeError('Failed to build the wheel for %pyproject_buildrequires -w.')
wheel = find_built_wheel(wheeldir) wheel = find_built_wheel(wheeldir)
@ -350,11 +340,8 @@ def generate_run_requirements_wheel(backend, requirements, wheeldir):
for name in wheelfile.namelist(): for name in wheelfile.namelist():
if name.count('/') == 1 and name.endswith('.dist-info/METADATA'): if name.count('/') == 1 and name.endswith('.dist-info/METADATA'):
with io.TextIOWrapper(wheelfile.open(name), encoding='utf-8') as metadata_file: with io.TextIOWrapper(wheelfile.open(name), encoding='utf-8') as metadata_file:
name, requires = package_name_and_requires_from_metadata_file(metadata_file) for key, requires in requires_from_metadata_file(metadata_file).items():
for key, req in requires.items(): requirements.extend(requires, source=f'built wheel metadata: {key}')
requirements.extend(req,
package_name=name,
source=f'built wheel metadata: {key} ({name})')
break break
else: else:
raise RuntimeError('Could not find *.dist-info/METADATA in built wheel.') raise RuntimeError('Could not find *.dist-info/METADATA in built wheel.')
@ -391,7 +378,7 @@ def generate_tox_requirements(toxenv, requirements):
provision_content = provision.read() provision_content = provision.read()
if provision_content and r.returncode != 0: if provision_content and r.returncode != 0:
provision_requires = json.loads(provision_content) provision_requires = json.loads(provision_content)
if provision_requires.get('minversion') is not None: if 'minversion' in provision_requires:
requirements.add(f'tox >= {provision_requires["minversion"]}', requirements.add(f'tox >= {provision_requires["minversion"]}',
source='tox provision (minversion)') source='tox provision (minversion)')
if 'requires' in provision_requires: if 'requires' in provision_requires:
@ -425,20 +412,16 @@ def python3dist(name, op=None, version=None, python3_pkgversion="3"):
def generate_requires( def generate_requires(
*, include_runtime=False, build_wheel=False, wheeldir=None, toxenv=None, extras=None, *, include_runtime=False, build_wheel=False, wheeldir=None, toxenv=None, extras=None,
get_installed_version=importlib.metadata.version, # for dep injection get_installed_version=importlib.metadata.version, # for dep injection
generate_extras=False, python3_pkgversion="3", requirement_files=None, use_build_system=True, generate_extras=False, python3_pkgversion="3", requirement_files=None, use_build_system=True
output, config_settings=None,
): ):
"""Generate the BuildRequires for the project in the current directory """Generate the BuildRequires for the project in the current directory
The generated BuildRequires are written to the provided output.
This is the main Python entry point. This is the main Python entry point.
""" """
requirements = Requirements( requirements = Requirements(
get_installed_version, extras=extras or [], get_installed_version, extras=extras or [],
generate_extras=generate_extras, generate_extras=generate_extras,
python3_pkgversion=python3_pkgversion, python3_pkgversion=python3_pkgversion
config_settings=config_settings,
) )
try: try:
@ -461,8 +444,6 @@ def generate_requires(
generate_run_requirements(backend, requirements, build_wheel=build_wheel, wheeldir=wheeldir) generate_run_requirements(backend, requirements, build_wheel=build_wheel, wheeldir=wheeldir)
except EndPass: except EndPass:
return return
finally:
output.write_text(os.linesep.join(requirements.output_lines) + os.linesep)
def main(argv): def main(argv):
@ -488,9 +469,6 @@ def main(argv):
'-p', '--python3_pkgversion', metavar='PYTHON3_PKGVERSION', '-p', '--python3_pkgversion', metavar='PYTHON3_PKGVERSION',
default="3", help=argparse.SUPPRESS, default="3", help=argparse.SUPPRESS,
) )
parser.add_argument(
'--output', type=pathlib.Path, required=True, help=argparse.SUPPRESS,
)
parser.add_argument( parser.add_argument(
'--wheeldir', metavar='PATH', default=None, '--wheeldir', metavar='PATH', default=None,
help=argparse.SUPPRESS, help=argparse.SUPPRESS,
@ -528,12 +506,6 @@ def main(argv):
metavar='REQUIREMENTS.TXT', metavar='REQUIREMENTS.TXT',
help=('Add buildrequires from file'), help=('Add buildrequires from file'),
) )
parser.add_argument(
'-C',
dest='config_settings',
action='append',
help='Configuration settings to pass to the PEP 517 backend',
)
args = parser.parse_args(argv) args = parser.parse_args(argv)
@ -567,8 +539,6 @@ def main(argv):
python3_pkgversion=args.python3_pkgversion, python3_pkgversion=args.python3_pkgversion,
requirement_files=args.requirement_files, requirement_files=args.requirement_files,
use_build_system=args.use_build_system, use_build_system=args.use_build_system,
output=args.output,
config_settings=parse_config_settings_args(args.config_settings),
) )
except Exception: except Exception:
# Log the traceback explicitly (it's useful debug info) # Log the traceback explicitly (it's useful debug info)

View File

@ -17,7 +17,7 @@ Insufficient version of setuptools:
installed: installed:
setuptools: 5 setuptools: 5
wheel: 1 wheel: 1
tomli: 1 toml: 1
pyproject.toml: | pyproject.toml: |
# empty # empty
setup.py: | setup.py: |
@ -42,7 +42,7 @@ Default build system, empty setup.py:
installed: installed:
setuptools: 50 setuptools: 50
wheel: 1 wheel: 1
tomli: 1 toml: 1
include_runtime: false include_runtime: false
pyproject.toml: | pyproject.toml: |
# empty # empty
@ -58,7 +58,7 @@ pyproject.toml with build-backend and setup.py:
installed: installed:
setuptools: 50 setuptools: 50
wheel: 1 wheel: 1
tomli: 1 toml: 1
setup.py: | setup.py: |
# empty # empty
pyproject.toml: | pyproject.toml: |
@ -81,7 +81,7 @@ Erroring setup.py:
Bad character in version: Bad character in version:
installed: installed:
tomli: 1 toml: 1
pyproject.toml: | pyproject.toml: |
[build-system] [build-system]
requires = ["pkg == 0.$.^.*"] requires = ["pkg == 0.$.^.*"]
@ -89,7 +89,7 @@ Bad character in version:
Single value version with unsupported compatible operator: Single value version with unsupported compatible operator:
installed: installed:
tomli: 1 toml: 1
pyproject.toml: | pyproject.toml: |
[build-system] [build-system]
requires = ["pkg ~= 42", "foo"] requires = ["pkg ~= 42", "foo"]
@ -98,7 +98,7 @@ Single value version with unsupported compatible operator:
Asterisk in version with unsupported compatible operator: Asterisk in version with unsupported compatible operator:
installed: installed:
tomli: 1 toml: 1
pyproject.toml: | pyproject.toml: |
[build-system] [build-system]
requires = ["pkg ~= 0.1.*", "foo"] requires = ["pkg ~= 0.1.*", "foo"]
@ -107,7 +107,7 @@ Asterisk in version with unsupported compatible operator:
Local path as requirement: Local path as requirement:
installed: installed:
tomli: 1 toml: 1
pyproject.toml: | pyproject.toml: |
[build-system] [build-system]
requires = ["./pkg-1.2.3.tar.gz", "foo"] requires = ["./pkg-1.2.3.tar.gz", "foo"]
@ -116,7 +116,7 @@ Local path as requirement:
Pip's egg=pkgName requirement not in requirements file: Pip's egg=pkgName requirement not in requirements file:
installed: installed:
tomli: 1 toml: 1
pyproject.toml: | pyproject.toml: |
[build-system] [build-system]
requires = ["git+https://github.com/monty/spam.git@master#egg=spam", "foo"] requires = ["git+https://github.com/monty/spam.git@master#egg=spam", "foo"]
@ -125,7 +125,7 @@ Pip's egg=pkgName requirement not in requirements file:
URL without egg fragment as requirement: URL without egg fragment as requirement:
installed: installed:
tomli: 1 toml: 1
pyproject.toml: | pyproject.toml: |
[build-system] [build-system]
requires = ["git+https://github.com/pkg-dev/pkg.git@96dbe5e3", "foo"] requires = ["git+https://github.com/pkg-dev/pkg.git@96dbe5e3", "foo"]
@ -137,7 +137,7 @@ Build system dependencies in pyproject.toml with extras:
installed: installed:
setuptools: 50 setuptools: 50
wheel: 1 wheel: 1
tomli: 1 toml: 1
pyproject.toml: | pyproject.toml: |
[build-system] [build-system]
requires = [ requires = [
@ -186,7 +186,7 @@ Build system dependencies in pyproject.toml without extras:
installed: installed:
setuptools: 50 setuptools: 50
wheel: 1 wheel: 1
tomli: 1 toml: 1
pyproject.toml: | pyproject.toml: |
[build-system] [build-system]
requires = [ requires = [
@ -365,7 +365,6 @@ Run dependencies with extras and build wheel option:
setuptools: 50 setuptools: 50
wheel: 1 wheel: 1
pyyaml: 1 pyyaml: 1
pip: 20
include_runtime: true include_runtime: true
build_wheel: true build_wheel: true
extras: extras:
@ -376,7 +375,6 @@ Run dependencies with extras and build wheel option:
python3dist(wheel) python3dist(wheel)
python3dist(wheel) python3dist(wheel)
python3dist(setuptools) >= 40 python3dist(setuptools) >= 40
python3dist(pip) >= 19
python3dist(py) >= 1.5 python3dist(py) >= 1.5
python3dist(six) >= 1.10 python3dist(six) >= 1.10
python3dist(setuptools) python3dist(setuptools)
@ -391,7 +389,7 @@ Run dependencies with extras and build wheel option:
result: 0 result: 0
stderr_contains: "Reading metadata from {wheeldir}/pytest-6.6.6-py3-none-any.whl" stderr_contains: "Reading metadata from {wheeldir}/pytest-6.6.6-py3-none-any.whl"
tox dependencies: Tox dependencies:
installed: installed:
setuptools: 50 setuptools: 50
wheel: 1 wheel: 1
@ -435,7 +433,7 @@ tox dependencies:
python3dist(inst) python3dist(inst)
result: 0 result: 0
tox extras: Tox extras:
installed: installed:
setuptools: 50 setuptools: 50
wheel: 1 wheel: 1
@ -499,7 +497,7 @@ tox extras:
python3dist(extra-dep[extra_dep]) python3dist(extra-dep[extra_dep])
result: 0 result: 0
tox provision unsatisfied: Tox provision unsatisfied:
installed: installed:
setuptools: 50 setuptools: 50
wheel: 1 wheel: 1
@ -544,7 +542,7 @@ tox provision unsatisfied:
python3dist(tox) >= 3.999 python3dist(tox) >= 3.999
result: 0 result: 0
tox provision satisfied: Tox provision satisfied:
installed: installed:
setuptools: 50 setuptools: 50
wheel: 1 wheel: 1
@ -591,43 +589,6 @@ tox provision satisfied:
python3dist(inst) python3dist(inst)
result: 0 result: 0
tox provision no minversion:
installed:
setuptools: 50
wheel: 1
tox: 3.5.3
tox-current-env: 0.0.6
toxenv:
- py3
setup.py: |
from setuptools import setup
setup(
name='test',
version='0.1',
)
tox.ini: |
[tox]
requires =
setuptools > 40
wheel > 2
expected:
- | # tox 3
python3dist(setuptools) >= 40.8
python3dist(wheel)
python3dist(wheel)
python3dist(tox-current-env) >= 0.0.6
python3dist(setuptools) > 40.0
python3dist(wheel) > 2.0
- | # tox 4
python3dist(setuptools) >= 40.8
python3dist(wheel)
python3dist(wheel)
python3dist(tox-current-env) >= 0.0.6
python3dist(setuptools) > 40.0
python3dist(wheel) > 2.0
python3dist(tox)
result: 0
Default build system, unmet deps in requirements file: Default build system, unmet deps in requirements file:
installed: installed:
setuptools: 50 setuptools: 50
@ -693,7 +654,7 @@ With pyproject.toml, requirements file and with -N option:
installed: installed:
setuptools: 50 setuptools: 50
wheel: 1 wheel: 1
tomli: 1 toml: 1
lxml: 3.9 lxml: 3.9
ncclient: 1 ncclient: 1
cryptography: 2 cryptography: 2
@ -729,7 +690,7 @@ With pyproject.toml, requirements file and without -N option:
installed: installed:
setuptools: 50 setuptools: 50
wheel: 1 wheel: 1
tomli: 1 toml: 1
lxml: 3.9 lxml: 3.9
ncclient: 1 ncclient: 1
cryptography: 2 cryptography: 2
@ -840,7 +801,7 @@ Pre-releases are accepted:
installed: installed:
setuptools: 50 setuptools: 50
wheel: 1 wheel: 1
tomli: 1 toml: 1
cffi: 1.15.0rc2 cffi: 1.15.0rc2
pyproject.toml: | pyproject.toml: |
[build-system] [build-system]
@ -859,7 +820,7 @@ Pre-releases are accepted:
result: 0 result: 0
Stdout from wrapped subprocess does not appear in output: Wrapped subprocess prints to stdout from setup.py:
installed: installed:
setuptools: 50 setuptools: 50
wheel: 1 wheel: 1
@ -873,192 +834,5 @@ Stdout from wrapped subprocess does not appear in output:
python3dist(setuptools) >= 40.8 python3dist(setuptools) >= 40.8
python3dist(wheel) python3dist(wheel)
python3dist(wheel) python3dist(wheel)
result: 0 stderr_contains: "HOOK STDOUT: LEAK?"
pyproject.toml with runtime dependencies:
skipif: not SETUPTOOLS_60
installed:
setuptools: 50
wheel: 1
tomli: 1
pyproject.toml: |
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
[project]
name = "my_package"
version = "0.1"
dependencies = [
"foo",
'importlib-metadata; python_version<"3.8"',
]
expected: |
python3dist(setuptools)
python3dist(wheel)
python3dist(foo)
result: 0
pyproject.toml with runtime dependencies and partially selected extras:
skipif: not SETUPTOOLS_60
installed:
setuptools: 50
wheel: 1
tomli: 1
extras:
- tests
pyproject.toml: |
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
[project]
name = "my_package"
version = "0.1"
dependencies = [
"foo",
'importlib-metadata; python_version<"3.8"',
]
[project.optional-dependencies]
tests = ["pytest>=5", "pytest-mock"]
docs = ["sphinx", "python-docs-theme"]
expected: |
python3dist(setuptools)
python3dist(wheel)
python3dist(foo)
python3dist(pytest) >= 5
python3dist(pytest-mock)
result: 0
Self-referencing extras (sooner):
installed:
setuptools: 50
wheel: 1
tomli: 1
extras:
- dev # this is deliberately sooner in the alphabet than the referenced ones
pyproject.toml: |
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
setup.cfg: |
[metadata]
name = my_package
version = 0.1
[options]
install_requires =
foo
importlib-metadata; python_version<"3.8"
[options.extras_require]
tests = pytest>=5; pytest-mock
docs = sphinx; python-docs-theme
dev = my_package[docs,tests]
expected: |
python3dist(setuptools)
python3dist(wheel)
python3dist(foo)
python3dist(sphinx)
python3dist(python-docs-theme)
python3dist(pytest) >= 5
python3dist(pytest-mock)
result: 0
Self-referencing extras (later):
installed:
setuptools: 50
wheel: 1
tomli: 1
extras:
- xdev # this is deliberately later in the alphabet than the referenced ones
pyproject.toml: |
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
setup.cfg: |
[metadata]
name = my_package
version = 0.1
[options]
install_requires =
foo
importlib-metadata; python_version<"3.8"
[options.extras_require]
tests = pytest>=5; pytest-mock
docs = sphinx; python-docs-theme
xdev = my_package[docs,tests]
expected: |
python3dist(setuptools)
python3dist(wheel)
python3dist(foo)
python3dist(sphinx)
python3dist(python-docs-theme)
python3dist(pytest) >= 5
python3dist(pytest-mock)
result: 0
Self-referencing extras (maze):
installed:
setuptools: 50
wheel: 1
tomli: 1
extras:
- start
pyproject.toml: |
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
setup.cfg: |
[metadata]
name = my_package
version = 0.1
[options.extras_require]
start = my_package[left,right]; startdep
left = my_package[right,forward]; leftdep
right = my_package[left,forward]; rightdep
forward = my_package[backward]; forwarddep
backward = my_package[left,right]; backwarddep
never = my_package[forward]; neverdep
expected: |
python3dist(setuptools)
python3dist(wheel)
python3dist(backwarddep)
python3dist(forwarddep)
python3dist(leftdep)
python3dist(rightdep)
python3dist(startdep)
result: 0
config_settings_control:
include_runtime: false
config_settings:
pyproject.toml: |
[build-system]
build-backend = "test_backend"
backend-path = ["."]
test_backend.py: |
def get_requires_for_build_wheel(config_settings=None):
if not (config_settings is None or isinstance(config_settings, dict)):
raise TypeError
if config_settings and "test-config-setting" in config_settings:
return ["test-config-setting"]
return ["test-no-config-setting"]
expected: |
python3dist(test-no-config-setting)
result: 0
config_settings:
include_runtime: false
config_settings:
test-config-setting: ""
pyproject.toml: |
[build-system]
build-backend = "test_backend"
backend-path = ["."]
test_backend.py: |
def get_requires_for_build_wheel(config_settings=None):
if not (config_settings is None or isinstance(config_settings, dict)):
raise TypeError
if config_settings and "test-config-setting" in config_settings:
return ["test-config-setting"]
return ["test-no-config-setting"]
expected: |
python3dist(test-config-setting)
result: 0 result: 0

View File

@ -12,9 +12,6 @@ from importlib.metadata import Distribution
# From RPM's build/files.c strtokWithQuotes delim argument # From RPM's build/files.c strtokWithQuotes delim argument
RPM_FILES_DELIMETERS = ' \n\t' RPM_FILES_DELIMETERS = ' \n\t'
# See the comment in the macro that wraps this script
RPM_PERCENTAGES_COUNT = int(os.getenv('RPM_PERCENTAGES_COUNT', '2'))
# RPM hardcodes the lists of manpage extensions and directories, # RPM hardcodes the lists of manpage extensions and directories,
# so we have to maintain separate ones :( # so we have to maintain separate ones :(
# There is an issue for RPM to provide the lists as macros: # There is an issue for RPM to provide the lists as macros:
@ -345,7 +342,7 @@ def classify_paths(
} }
license_files = metadata.get_all('License-File') license_files = metadata.get_all('License-File')
license_directory = distinfo / 'licenses' # See PEP 639 "Root License Directory" license_directory = distinfo / 'licenses' # See PEP 369 "Root License Directory"
# setuptools was the first known build backend to implement License-File. # setuptools was the first known build backend to implement License-File.
# Unfortunately they don't put licenses to the license directory (yet): # Unfortunately they don't put licenses to the license directory (yet):
# https://github.com/pypa/setuptools/issues/3596 # https://github.com/pypa/setuptools/issues/3596
@ -444,13 +441,13 @@ def escape_rpm_path(path):
'"/usr/lib/python3.9/site-packages/setuptools/script (dev).tmpl"' '"/usr/lib/python3.9/site-packages/setuptools/script (dev).tmpl"'
>>> escape_rpm_path('/usr/share/data/100%valid.path') >>> escape_rpm_path('/usr/share/data/100%valid.path')
'/usr/share/data/100%%valid.path' '/usr/share/data/100%%%%%%%%valid.path'
>>> escape_rpm_path('/usr/share/data/100 % valid.path') >>> escape_rpm_path('/usr/share/data/100 % valid.path')
'"/usr/share/data/100 %% valid.path"' '"/usr/share/data/100 %%%%%%%% valid.path"'
>>> escape_rpm_path('/usr/share/data/1000 %% valid.path') >>> escape_rpm_path('/usr/share/data/1000 %% valid.path')
'"/usr/share/data/1000 %%%% valid.path"' '"/usr/share/data/1000 %%%%%%%%%%%%%%%% valid.path"'
>>> escape_rpm_path('/usr/share/data/spaces and "quotes"') >>> escape_rpm_path('/usr/share/data/spaces and "quotes"')
Traceback (most recent call last): Traceback (most recent call last):
@ -464,7 +461,10 @@ def escape_rpm_path(path):
""" """
orig_path = path = str(path) orig_path = path = str(path)
if "%" in path: if "%" in path:
path = path.replace("%", "%" * RPM_PERCENTAGES_COUNT) # Escaping by 8 %s has been verified in RPM 4.16 and 4.17, but probably not stable
# See this thread http://lists.rpm.org/pipermail/rpm-list/2021-June/002048.html
# On the CI, we build tests/escape_percentages.spec to verify this assumption
path = path.replace("%", "%" * 8)
if any(symbol in path for symbol in RPM_FILES_DELIMETERS): if any(symbol in path for symbol in RPM_FILES_DELIMETERS):
if '"' in path: if '"' in path:
# As far as we know, RPM cannot list such file individually # As far as we know, RPM cannot list such file individually
@ -673,7 +673,7 @@ def load_parsed_record(pyproject_record):
content = json.load(pyproject_record_file) content = json.load(pyproject_record_file)
if len(content) > 1: if len(content) > 1:
raise FileExistsError("%pyproject_install has found more than one *.dist-info/RECORD file. " raise FileExistsError("%pyproject install has found more than one *.dist-info/RECORD file. "
"Currently, %pyproject_save_files supports only one wheel → one file list mapping. " "Currently, %pyproject_save_files supports only one wheel → one file list mapping. "
"Feel free to open a bugzilla for pyproject-rpm-macros and describe your usecase.") "Feel free to open a bugzilla for pyproject-rpm-macros and describe your usecase.")
@ -693,15 +693,12 @@ def dist_metadata(buildroot, record_path):
return dist.metadata return dist.metadata
def pyproject_save_files_and_modules(buildroot, sitelib, sitearch, python_version, pyproject_record, prefix, assert_license, varargs): def pyproject_save_files_and_modules(buildroot, sitelib, sitearch, python_version, pyproject_record, prefix, varargs):
""" """
Takes arguments from the %{pyproject_save_files} macro Takes arguments from the %{pyproject_save_files} macro
Returns tuple: list of paths for the %files section and list of module names Returns tuple: list of paths for the %files section and list of module names
for the %check section for the %check section
Raises ValueError when assert_license is true and no License-File (PEP 639)
is found.
""" """
# On 32 bit architectures, sitelib equals to sitearch # On 32 bit architectures, sitelib equals to sitearch
# This saves us browsing one directory twice # This saves us browsing one directory twice
@ -713,15 +710,11 @@ def pyproject_save_files_and_modules(buildroot, sitelib, sitearch, python_versio
final_file_list = [] final_file_list = []
final_module_list = [] final_module_list = []
# we assume OK when not asserting
license_ok = not assert_license
for record_path, files in parsed_records.items(): for record_path, files in parsed_records.items():
metadata = dist_metadata(buildroot, record_path) metadata = dist_metadata(buildroot, record_path)
paths_dict = classify_paths( paths_dict = classify_paths(
record_path, files, metadata, sitedirs, python_version, prefix record_path, files, metadata, sitedirs, python_version, prefix
) )
license_ok = license_ok or bool(paths_dict["metadata"]["licenses"])
final_file_list.extend( final_file_list.extend(
generate_file_list(paths_dict, globs, include_auto) generate_file_list(paths_dict, globs, include_auto)
@ -730,15 +723,6 @@ def pyproject_save_files_and_modules(buildroot, sitelib, sitearch, python_versio
generate_module_list(paths_dict, globs) generate_module_list(paths_dict, globs)
) )
if not license_ok:
raise ValueError(
"No License-File (PEP 639) in upstream metadata found. "
"Adjust the upstream metadata "
"if the project's build backend supports PEP 639 "
"or use `%pyproject_save_files -L` "
"and include the %license file in %files manually."
)
return final_file_list, final_module_list return final_file_list, final_module_list
@ -750,7 +734,6 @@ def main(cli_args):
cli_args.python_version, cli_args.python_version,
cli_args.pyproject_record, cli_args.pyproject_record,
cli_args.prefix, cli_args.prefix,
cli_args.assert_license,
cli_args.varargs, cli_args.varargs,
) )
@ -764,7 +747,7 @@ def argparser():
prog="%pyproject_save_files", prog="%pyproject_save_files",
add_help=False, add_help=False,
# custom usage to add +auto # custom usage to add +auto
usage="%(prog)s [-l|-L] MODULE_GLOB [MODULE_GLOB ...] [+auto]", usage="%(prog)s MODULE_GLOB [MODULE_GLOB ...] [+auto]",
) )
parser.add_argument( parser.add_argument(
'--help', action='help', '--help', action='help',
@ -780,14 +763,6 @@ def argparser():
r.add_argument("--python-version", type=str, required=True, help=argparse.SUPPRESS) r.add_argument("--python-version", type=str, required=True, help=argparse.SUPPRESS)
r.add_argument("--pyproject-record", type=PosixPath, required=True, help=argparse.SUPPRESS) r.add_argument("--pyproject-record", type=PosixPath, required=True, help=argparse.SUPPRESS)
r.add_argument("--prefix", type=PosixPath, required=True, help=argparse.SUPPRESS) r.add_argument("--prefix", type=PosixPath, required=True, help=argparse.SUPPRESS)
parser.add_argument(
"-l", "--assert-license", action="store_true", default=False,
help="Fail when no License-File (PEP 639) is found.",
)
parser.add_argument(
"-L", "--no-assert-license", action="store_false", dest="assert_license",
help="Don't fail when no License-File (PEP 639) is found (the default).",
)
parser.add_argument( parser.add_argument(
"varargs", nargs="+", metavar="MODULE_GLOB", "varargs", nargs="+", metavar="MODULE_GLOB",
help="Shell-like glob matching top-level module names to save into %%{pyproject_files}", help="Shell-like glob matching top-level module names to save into %%{pyproject_files}",

View File

@ -1,46 +1,8 @@
import argparse
import sys import sys
import subprocess import subprocess
def parse_config_settings_args(config_settings): def build_wheel(*, wheeldir, stdout=None):
"""
Given a list of config `KEY=VALUE` formatted config settings,
return a dictionary that can be passed to PEP 517 hook functions.
"""
if not config_settings:
return config_settings
new_config_settings = {}
for arg in config_settings:
key, _, value = arg.partition('=')
if key in new_config_settings:
if not isinstance(new_config_settings[key], list):
# convert the existing value to a list
new_config_settings[key] = [new_config_settings[key]]
new_config_settings[key].append(value)
else:
new_config_settings[key] = value
return new_config_settings
def get_config_settings_args(config_settings):
"""
Given a dictionary of PEP 517 backend config_settings,
yield --config-settings args that can be passed to pip's CLI
"""
if not config_settings:
return
for key, values in config_settings.items():
if not isinstance(values, list):
values = [values]
for value in values:
if value == '':
yield f'--config-settings={key}'
else:
yield f'--config-settings={key}={value}'
def build_wheel(*, wheeldir, stdout=None, config_settings=None):
command = ( command = (
sys.executable, sys.executable,
'-m', 'pip', '-m', 'pip',
@ -53,26 +15,11 @@ def build_wheel(*, wheeldir, stdout=None, config_settings=None):
'--no-clean', '--no-clean',
'--progress-bar', 'off', '--progress-bar', 'off',
'--verbose', '--verbose',
*get_config_settings_args(config_settings),
'.', '.',
) )
cp = subprocess.run(command, stdout=stdout) cp = subprocess.run(command, stdout=stdout)
return cp.returncode return cp.returncode
def parse_args(argv=None):
parser = argparse.ArgumentParser(prog='%pyproject_wheel')
parser.add_argument('wheeldir', help=argparse.SUPPRESS)
parser.add_argument(
'-C',
dest='config_settings',
action='append',
help='Configuration settings to pass to the PEP 517 backend',
)
args = parser.parse_args(argv)
args.config_settings = parse_config_settings_args(args.config_settings)
return args
if __name__ == '__main__': if __name__ == '__main__':
sys.exit(build_wheel(**vars(parse_args()))) sys.exit(build_wheel(wheeldir=sys.argv[1]))

View File

@ -1,15 +1,11 @@
from pathlib import Path from pathlib import Path
import importlib.metadata import importlib.metadata
import packaging.version
import pytest import pytest
import setuptools
import yaml import yaml
from pyproject_buildrequires import generate_requires from pyproject_buildrequires import generate_requires
SETUPTOOLS_VERSION = packaging.version.parse(setuptools.__version__)
SETUPTOOLS_60 = SETUPTOOLS_VERSION >= packaging.version.parse('60')
testcases = {} testcases = {}
with Path(__file__).parent.joinpath('pyproject_buildrequires_testcases.yaml').open() as f: with Path(__file__).parent.joinpath('pyproject_buildrequires_testcases.yaml').open() as f:
@ -25,16 +21,12 @@ def test_data(case_name, capfd, tmp_path, monkeypatch):
monkeypatch.chdir(cwd) monkeypatch.chdir(cwd)
wheeldir = cwd.joinpath('wheeldir') wheeldir = cwd.joinpath('wheeldir')
wheeldir.mkdir() wheeldir.mkdir()
output = tmp_path.joinpath('output.txt')
if case.get('xfail'): if case.get('xfail'):
pytest.xfail(case.get('xfail')) pytest.xfail(case.get('xfail'))
if case.get('skipif') and eval(case.get('skipif')):
pytest.skip(case.get('skipif'))
for filename in case: for filename in case:
file_types = ('.toml', '.py', '.in', '.ini', '.txt', '.cfg') file_types = ('.toml', '.py', '.in', '.ini', '.txt')
if filename.endswith(file_types): if filename.endswith(file_types):
cwd.joinpath(filename).write_text(case[filename]) cwd.joinpath(filename).write_text(case[filename])
@ -62,8 +54,6 @@ def test_data(case_name, capfd, tmp_path, monkeypatch):
generate_extras=case.get('generate_extras', False), generate_extras=case.get('generate_extras', False),
requirement_files=requirement_files, requirement_files=requirement_files,
use_build_system=use_build_system, use_build_system=use_build_system,
output=output,
config_settings=case.get('config_settings'),
) )
except SystemExit as e: except SystemExit as e:
assert e.code == case['result'] assert e.code == case['result']
@ -79,15 +69,14 @@ def test_data(case_name, capfd, tmp_path, monkeypatch):
assert 'expected' in case or 'stderr_contains' in case assert 'expected' in case or 'stderr_contains' in case
out, err = capfd.readouterr() out, err = capfd.readouterr()
dependencies = output.read_text()
if 'expected' in case: if 'expected' in case:
expected = case['expected'] expected = case['expected']
if isinstance(expected, list): if isinstance(expected, list):
# at least one of them needs to match # at least one of them needs to match
assert dependencies in expected assert any(out == e for e in expected)
else: else:
assert dependencies == expected assert out == expected
# stderr_contains may be a string or list of strings # stderr_contains may be a string or list of strings
stderr_contains = case.get('stderr_contains') stderr_contains = case.get('stderr_contains')

View File

@ -2,10 +2,9 @@ Name: pyproject-rpm-macros
Summary: RPM macros for PEP 517 Python packages Summary: RPM macros for PEP 517 Python packages
License: MIT License: MIT
%bcond tests 1 # Disable tests on RHEL9 as to not pull in the test dependencies
# pytest-xdist and tox are not desired in RHEL # Specify --with tests to run the tests e.g. on EPEL
%bcond pytest_xdist %{undefined rhel} %bcond_with tests
%bcond tox_tests %{undefined rhel}
# The idea is to follow the spirit of semver # The idea is to follow the spirit of semver
# Given version X.Y.Z: # Given version X.Y.Z:
@ -13,7 +12,7 @@ License: MIT
# Increment Y and reset Z when new macros or features are added # Increment Y and reset Z when new macros or features are added
# Increment Z when this is a bugfix or a cosmetic change # Increment Z when this is a bugfix or a cosmetic change
# Dropping support for EOL Fedoras is *not* considered a breaking change # Dropping support for EOL Fedoras is *not* considered a breaking change
Version: 1.12.0 Version: 1.6.2
Release: 1%{?dist} Release: 1%{?dist}
# Macro files # Macro files
@ -50,33 +49,14 @@ BuildArch: noarch
%if %{with tests} %if %{with tests}
BuildRequires: python3dist(pytest) BuildRequires: python3dist(pytest)
%if %{with pytest_xdist}
BuildRequires: python3dist(pytest-xdist) BuildRequires: python3dist(pytest-xdist)
%endif
BuildRequires: python3dist(pyyaml) BuildRequires: python3dist(pyyaml)
BuildRequires: python3dist(packaging) BuildRequires: python3dist(packaging)
BuildRequires: python3dist(pip) BuildRequires: python3dist(pip)
BuildRequires: python3dist(setuptools) BuildRequires: python3dist(setuptools)
%if %{with tox_tests}
BuildRequires: python3dist(tox-current-env) >= 0.0.6 BuildRequires: python3dist(tox-current-env) >= 0.0.6
%endif
BuildRequires: python3dist(wheel) BuildRequires: python3dist(wheel)
BuildRequires: (python3dist(tomli) if python3 < 3.11) BuildRequires: (python3dist(toml) if python3-devel < 3.11)
# RHEL 9: We also run pytest with Python 3.11 and 3.12
BuildRequires: python3.11dist(pytest)
BuildRequires: python3.11dist(pyyaml)
BuildRequires: python3.11dist(packaging)
BuildRequires: python3.11dist(pip)
BuildRequires: python3.11dist(setuptools)
BuildRequires: python3.11dist(wheel)
BuildRequires: python3.12dist(pytest)
BuildRequires: python3.12dist(pyyaml)
BuildRequires: python3.12dist(packaging)
BuildRequires: python3.12dist(pip)
BuildRequires: python3.12dist(setuptools)
BuildRequires: python3.12dist(wheel)
%endif %endif
# We build on top of those: # We build on top of those:
@ -92,12 +72,6 @@ Requires: (pyproject-srpm-macros = %{?epoch:%{epoch}:}%{version}-%{release
Requires: /usr/bin/find Requires: /usr/bin/find
Requires: /usr/bin/sed Requires: /usr/bin/sed
# This package requires the %%generate_buildrequires functionality.
# It has been introduced in RPM 4.15 (4.14.90 is the alpha of 4.15).
# What we need is rpmlib(DynamicBuildRequires), but that is impossible to (Build)Require.
Requires: (rpm-build >= 4.14.90 if rpm-build)
BuildRequires: rpm-build >= 4.14.90
%description %description
These macros allow projects that follow the Python packaging specifications These macros allow projects that follow the Python packaging specifications
to be packaged as RPMs. to be packaged as RPMs.
@ -116,7 +90,6 @@ which only work with setup.py.
%package -n pyproject-srpm-macros %package -n pyproject-srpm-macros
Summary: Minimal implementation of %%pyproject_buildrequires Summary: Minimal implementation of %%pyproject_buildrequires
Requires: (pyproject-rpm-macros = %{?epoch:%{epoch}:}%{version}-%{release} if pyproject-rpm-macros) Requires: (pyproject-rpm-macros = %{?epoch:%{epoch}:}%{version}-%{release} if pyproject-rpm-macros)
Requires: (rpm-build >= 4.14.90 if rpm-build)
%description -n pyproject-srpm-macros %description -n pyproject-srpm-macros
This package contains a minimal implementation of %%pyproject_buildrequires. This package contains a minimal implementation of %%pyproject_buildrequires.
@ -131,9 +104,6 @@ takes precedence.
%setup -c -T %setup -c -T
cp -p %{sources} . cp -p %{sources} .
%generate_buildrequires
# nothing to do, this is here just to assert we have that functionality
%build %build
# nothing to do, sources are not buildable # nothing to do, sources are not buildable
@ -150,25 +120,10 @@ install -pm 644 pyproject_construct_toxenv.py %{buildroot}%{_rpmconfigdir}/redha
install -pm 644 pyproject_requirements_txt.py %{buildroot}%{_rpmconfigdir}/redhat/ install -pm 644 pyproject_requirements_txt.py %{buildroot}%{_rpmconfigdir}/redhat/
install -pm 644 pyproject_wheel.py %{buildroot}%{_rpmconfigdir}/redhat/ install -pm 644 pyproject_wheel.py %{buildroot}%{_rpmconfigdir}/redhat/
%check
# assert the two signatures of %%pyproject_buildrequires match exactly
signature1="$(grep '^%%pyproject_buildrequires' macros.pyproject | cut -d' ' -f1)"
signature2="$(grep '^%%pyproject_buildrequires' macros.aaa-pyproject-srpm | cut -d' ' -f1)"
test "$signature1" == "$signature2"
# but also assert we are not comparing empty strings
test "$signature1" != ""
%if %{with tests} %if %{with tests}
%check
export HOSTNAME="rpmbuild" # to speedup tox in network-less mock, see rhbz#1856356 export HOSTNAME="rpmbuild" # to speedup tox in network-less mock, see rhbz#1856356
%pytest -vv --doctest-modules %{?with_pytest_xdist:-n auto} %{!?with_tox_tests:-k "not tox"} %pytest -vv --doctest-modules -n auto
# RHEL 9 only:
%global __pytest pytest-3.11
%pytest -vv --doctest-modules -k "not tox"
# RHEL 9 only:
%global __pytest pytest-3.12
%pytest -vv --doctest-modules -k "not tox"
# brp-compress is provided as an argument to get the right directory macro expansion # brp-compress is provided as an argument to get the right directory macro expansion
%{python3} compare_mandata.py -f %{_rpmconfigdir}/brp-compress %{python3} compare_mandata.py -f %{_rpmconfigdir}/brp-compress
@ -194,50 +149,6 @@ export HOSTNAME="rpmbuild" # to speedup tox in network-less mock, see rhbz#1856
%changelog %changelog
* Fri Jan 26 2024 Miro Hrončok <miro@hroncok.cz> - 1.12.0-1
- Namespace pyproject-rpm-macros generated text files with %%{python3_pkgversion}
- That way, a single-spec can be used to build packages for multiple Python versions
- Fixes: rhbz#2209055
* Wed Sep 27 2023 Miro Hrončok <mhroncok@redhat.com> - 1.11.0-1
- Add the -l/-L flag to %%pyproject_save_files
- The -l flag can be used to assert at least 1 License-File was detected
- The -L flag explicitly disables this check (which remains the default)
- Prevent incorrect usage of %%pyproject_buildrequires -R with -x/-e/-t
- Fixes: rhbz#2244282
- Show a better error message when %%pyproject_install finds no wheel
- Fixes: rhbz#2242452
- Fix %%pyproject_buildrequires -w when the build backend is already installed and pip isn't
- Fixes: rhbz#2169855
* Wed Sep 13 2023 Python Maint <python-maint@redhat.com> - 1.10.0-1
- Add %%_pyproject_check_import_allow_no_modules for automated environments
- Fix handling of tox 4 provision without an explicit tox minversion
- Fixes: rhbz#2240590
* Wed May 31 2023 Maxwell G <maxwell@gtmx.me> - 1.9.0-1
- Allow passing config_settings to the build backend.
* Wed May 31 2023 Miro Hrončok <mhroncok@redhat.com> - 1.8.1-1
- On Python older than 3.11, use tomli instead of deprecated toml
- Fix literal %% handling in %%{pyproject_files} on RPM 4.19
* Tue May 23 2023 Miro Hrončok <mhroncok@redhat.com> - 1.8.0-2
- Rebuilt for ELN dependency changes
* Thu Apr 27 2023 Miro Hrončok <mhroncok@redhat.com> - 1.8.0-1
- %%pyproject_buildrequires: Add support for self-referential extras requirements
- Deprecate the provisional %%{pyproject_build_lib} macro
See https://lists.fedoraproject.org/archives/list/python-devel@lists.fedoraproject.org/thread/HMLOPAU3RZLXD4BOJHTIPKI3I4U6U7OE/
* Fri Mar 31 2023 Miro Hrončok <mhroncok@redhat.com> - 1.7.0-1
- %%pyproject_buildrequires: Redirect stdout to stderr via Shell
- Dependencies are recorded to a text file that is catted at the end
* Mon Feb 13 2023 Lumír Balhar <lbalhar@redhat.com> - 1.6.3-1
- Remove .dist-info directory at the end of %%pyproject_buildrequires
- An incomplete .dist-info directory in $PWD can confuse tests in %%check
* Wed Feb 08 2023 Lumír Balhar <lbalhar@redhat.com> - 1.6.2-1 * Wed Feb 08 2023 Lumír Balhar <lbalhar@redhat.com> - 1.6.2-1
- Improve detection of lang files - Improve detection of lang files
@ -254,8 +165,7 @@ export HOSTNAME="rpmbuild" # to speedup tox in network-less mock, see rhbz#1856
- Use %%py3_test_envvars in %%tox when available - Use %%py3_test_envvars in %%tox when available
* Mon Sep 19 2022 Python Maint <python-maint@redhat.com> - 1.4.0-1 * Mon Sep 19 2022 Python Maint <python-maint@redhat.com> - 1.4.0-1
- %%pyproject_save_files: Support License-Files installed into the *Root License Directory* from PEP 639 - %%pyproject_save_files: Support License-Files installed into the *Root License Directory* from PEP 369
- %%pyproject_check_import: Import only the modules whose top-level names - %%pyproject_check_import: Import only the modules whose top-level names
match any of the globs provided to %%pyproject_save_files match any of the globs provided to %%pyproject_save_files