From 11c67be5370005189fb4af6d09c3a4939d6493c6 Mon Sep 17 00:00:00 2001 From: MSVSphere Packaging Team Date: Thu, 28 Mar 2024 18:08:26 +0300 Subject: [PATCH] import pyproject-rpm-macros-1.12.0-1.el9 --- SOURCES/README.md | 12 +++++ SOURCES/macros.pyproject | 35 +++++++++---- SOURCES/pyproject_buildrequires.py | 7 ++- .../pyproject_buildrequires_testcases.yaml | 39 ++++++++++++++ SOURCES/pyproject_save_files.py | 33 ++++++++++-- SPECS/pyproject-rpm-macros.spec | 51 +++++++++++++++++-- 6 files changed, 159 insertions(+), 18 deletions(-) diff --git a/SOURCES/README.md b/SOURCES/README.md index 256f3f5..48416a7 100644 --- a/SOURCES/README.md +++ b/SOURCES/README.md @@ -288,6 +288,12 @@ However, in Fedora packages, always list executables explicitly to avoid uninten and language (`*.mo`) files with `%lang` macro and appropriate language code. 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. +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/). If you wish to rename, remove or otherwise change the installed files of a package @@ -342,6 +348,12 @@ 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. 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 ----------------------------- diff --git a/SOURCES/macros.pyproject b/SOURCES/macros.pyproject index dfb3e2d..6ca9114 100644 --- a/SOURCES/macros.pyproject +++ b/SOURCES/macros.pyproject @@ -1,5 +1,9 @@ +# 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 -%_pyproject_wheeldir %{_builddir}%{?buildsubdir:/%{buildsubdir}}/pyproject-wheeldir +%_pyproject_wheeldir %{_builddir}%{?buildsubdir:/%{buildsubdir}}/pyproject-wheeldir%{_pyproject_files_pkgversion} # 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 @@ -8,12 +12,12 @@ # This will be used in debugsource package paths (applies to extension modules only) # NB: pytest collects tests from here if not hidden # https://docs.pytest.org/en/latest/reference.html#confval-norecursedirs -%_pyproject_builddir %{_builddir}%{?buildsubdir:/%{buildsubdir}}/.pyproject-builddir +%_pyproject_builddir %{_builddir}%{?buildsubdir:/%{buildsubdir}}/.pyproject-builddir%{_pyproject_files_pkgversion} # 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 # The used value is similar to the one used to define the default %%buildroot -%_pyproject_files_prefix %{name}-%{version}-%{release}.%{_arch} +%_pyproject_files_prefix %{name}-%{version}-%{release}.%{_arch}%{_pyproject_files_pkgversion} %pyproject_files %{_builddir}/%{_pyproject_files_prefix}-pyproject-files %_pyproject_modules %{_builddir}/%{_pyproject_files_prefix}-pyproject-modules @@ -65,6 +69,10 @@ echo $(IFS=:; echo "${pyproject_build_lib[*]}") %pyproject_install() %{expand:\\\ 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 if [ -d %{buildroot}%{_bindir} ]; then %py3_shebang_fix %{buildroot}%{_bindir}/* @@ -106,12 +114,10 @@ fi # Escaping an actual percentage sign in path by 8 signs has been verified in RPM 4.16 and 4.17. # See this thread http://lists.rpm.org/pipermail/rpm-list/2021-June/002048.html -# Since RPM 4.19, 2 signs are needed instead. +# 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. -# We should check RPM version here instead of Fedora/RHEL, but it's hard; -# see https://github.com/rpm-software-management/rpm/issues/2523 -%pyproject_save_files() %{expand:\\\ -%{expr:0%{?fedora} >= 39 || 0%{?rhel} >= 10 ? "RPM_PERCENTAGES_COUNT=2" : "RPM_PERCENTAGES_COUNT=8" } \\ +%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 \\ --output-files "%{pyproject_files}" \\ --output-modules "%{_pyproject_modules}" \\ @@ -121,7 +127,7 @@ fi --python-version "%{python3_version}" \\ --pyproject-record "%{_pyproject_record}" \\ --prefix "%{_prefix}" \\ - %{*} + %{**} } # -t - Process only top-level modules @@ -135,6 +141,14 @@ 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} %toxenv %{default_toxenv} @@ -150,6 +164,9 @@ fi %{?_package_note_flags:%_generate_package_note_file} %{-R: %{-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}} } %{-N: diff --git a/SOURCES/pyproject_buildrequires.py b/SOURCES/pyproject_buildrequires.py index 844fd38..e91111f 100644 --- a/SOURCES/pyproject_buildrequires.py +++ b/SOURCES/pyproject_buildrequires.py @@ -328,6 +328,11 @@ def generate_run_requirements_wheel(backend, requirements, wheeldir): # Reuse the wheel from the previous round of %pyproject_buildrequires (if it exists) wheel = find_built_wheel(wheeldir) 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 returncode = pyproject_wheel.build_wheel( wheeldir=wheeldir, @@ -386,7 +391,7 @@ def generate_tox_requirements(toxenv, requirements): provision_content = provision.read() if provision_content and r.returncode != 0: provision_requires = json.loads(provision_content) - if 'minversion' in provision_requires: + if provision_requires.get('minversion') is not None: requirements.add(f'tox >= {provision_requires["minversion"]}', source='tox provision (minversion)') if 'requires' in provision_requires: diff --git a/SOURCES/pyproject_buildrequires_testcases.yaml b/SOURCES/pyproject_buildrequires_testcases.yaml index aba6002..892e3c3 100644 --- a/SOURCES/pyproject_buildrequires_testcases.yaml +++ b/SOURCES/pyproject_buildrequires_testcases.yaml @@ -365,6 +365,7 @@ Run dependencies with extras and build wheel option: setuptools: 50 wheel: 1 pyyaml: 1 + pip: 20 include_runtime: true build_wheel: true extras: @@ -375,6 +376,7 @@ Run dependencies with extras and build wheel option: python3dist(wheel) python3dist(wheel) python3dist(setuptools) >= 40 + python3dist(pip) >= 19 python3dist(py) >= 1.5 python3dist(six) >= 1.10 python3dist(setuptools) @@ -589,6 +591,43 @@ tox provision satisfied: python3dist(inst) 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: installed: setuptools: 50 diff --git a/SOURCES/pyproject_save_files.py b/SOURCES/pyproject_save_files.py index 551a876..3944882 100644 --- a/SOURCES/pyproject_save_files.py +++ b/SOURCES/pyproject_save_files.py @@ -345,7 +345,7 @@ def classify_paths( } license_files = metadata.get_all('License-File') - license_directory = distinfo / 'licenses' # See PEP 369 "Root License Directory" + license_directory = distinfo / 'licenses' # See PEP 639 "Root License Directory" # setuptools was the first known build backend to implement License-File. # Unfortunately they don't put licenses to the license directory (yet): # https://github.com/pypa/setuptools/issues/3596 @@ -673,7 +673,7 @@ def load_parsed_record(pyproject_record): content = json.load(pyproject_record_file) 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. " "Feel free to open a bugzilla for pyproject-rpm-macros and describe your usecase.") @@ -693,12 +693,15 @@ def dist_metadata(buildroot, record_path): return dist.metadata -def pyproject_save_files_and_modules(buildroot, sitelib, sitearch, python_version, pyproject_record, prefix, varargs): +def pyproject_save_files_and_modules(buildroot, sitelib, sitearch, python_version, pyproject_record, prefix, assert_license, varargs): """ Takes arguments from the %{pyproject_save_files} macro Returns tuple: list of paths for the %files section and list of module names 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 # This saves us browsing one directory twice @@ -710,11 +713,15 @@ def pyproject_save_files_and_modules(buildroot, sitelib, sitearch, python_versio final_file_list = [] final_module_list = [] + # we assume OK when not asserting + license_ok = not assert_license + for record_path, files in parsed_records.items(): metadata = dist_metadata(buildroot, record_path) paths_dict = classify_paths( record_path, files, metadata, sitedirs, python_version, prefix ) + license_ok = license_ok or bool(paths_dict["metadata"]["licenses"]) final_file_list.extend( generate_file_list(paths_dict, globs, include_auto) @@ -723,6 +730,15 @@ def pyproject_save_files_and_modules(buildroot, sitelib, sitearch, python_versio 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 @@ -734,6 +750,7 @@ def main(cli_args): cli_args.python_version, cli_args.pyproject_record, cli_args.prefix, + cli_args.assert_license, cli_args.varargs, ) @@ -747,7 +764,7 @@ def argparser(): prog="%pyproject_save_files", add_help=False, # custom usage to add +auto - usage="%(prog)s MODULE_GLOB [MODULE_GLOB ...] [+auto]", + usage="%(prog)s [-l|-L] MODULE_GLOB [MODULE_GLOB ...] [+auto]", ) parser.add_argument( '--help', action='help', @@ -763,6 +780,14 @@ def argparser(): 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("--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( "varargs", nargs="+", metavar="MODULE_GLOB", help="Shell-like glob matching top-level module names to save into %%{pyproject_files}", diff --git a/SPECS/pyproject-rpm-macros.spec b/SPECS/pyproject-rpm-macros.spec index d0526f4..a81ff56 100644 --- a/SPECS/pyproject-rpm-macros.spec +++ b/SPECS/pyproject-rpm-macros.spec @@ -13,7 +13,7 @@ License: MIT # Increment Y and reset Z when new macros or features are added # Increment Z when this is a bugfix or a cosmetic change # Dropping support for EOL Fedoras is *not* considered a breaking change -Version: 1.9.0 +Version: 1.12.0 Release: 1%{?dist} # Macro files @@ -63,13 +63,20 @@ BuildRequires: python3dist(tox-current-env) >= 0.0.6 BuildRequires: python3dist(wheel) BuildRequires: (python3dist(tomli) if python3 < 3.11) -# RHEL 9: We also run pytest with Python 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 # We build on top of those: @@ -85,6 +92,12 @@ Requires: (pyproject-srpm-macros = %{?epoch:%{epoch}:}%{version}-%{release Requires: /usr/bin/find 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 These macros allow projects that follow the Python packaging specifications to be packaged as RPMs. @@ -103,6 +116,7 @@ which only work with setup.py. %package -n pyproject-srpm-macros Summary: Minimal implementation of %%pyproject_buildrequires 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 This package contains a minimal implementation of %%pyproject_buildrequires. @@ -117,6 +131,9 @@ takes precedence. %setup -c -T cp -p %{sources} . +%generate_buildrequires +# nothing to do, this is here just to assert we have that functionality + %build # nothing to do, sources are not buildable @@ -146,7 +163,11 @@ export HOSTNAME="rpmbuild" # to speedup tox in network-less mock, see rhbz#1856 %pytest -vv --doctest-modules %{?with_pytest_xdist:-n auto} %{!?with_tox_tests:-k "not tox"} # RHEL 9 only: -%global __pytest %{__pytest}-3.11 +%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 @@ -173,6 +194,27 @@ export HOSTNAME="rpmbuild" # to speedup tox in network-less mock, see rhbz#1856 %changelog +* Fri Jan 26 2024 Miro Hrončok - 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 - 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 - 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 - 1.9.0-1 - Allow passing config_settings to the build backend. @@ -215,7 +257,8 @@ export HOSTNAME="rpmbuild" # to speedup tox in network-less mock, see rhbz#1856 - Use %%py3_test_envvars in %%tox when available * Mon Sep 19 2022 Python Maint - 1.4.0-1 -- %%pyproject_save_files: Support License-Files installed into the *Root License Directory* from PEP 369 +- %%pyproject_save_files: Support License-Files installed into the *Root License Directory* from PEP 639 + - %%pyproject_check_import: Import only the modules whose top-level names match any of the globs provided to %%pyproject_save_files