diff --git a/SOURCES/README.md b/SOURCES/README.md index 888c144..f5aea49 100644 --- a/SOURCES/README.md +++ b/SOURCES/README.md @@ -69,8 +69,7 @@ the package's runtime dependencies need to also be included as build requirement Hence, `%pyproject_buildrequires` also generates runtime dependencies by default. -For this to work, the project's build system must support the -[`prepare-metadata-for-build-wheel` hook](https://www.python.org/dev/peps/pep-0517/#prepare-metadata-for-build-wheel). +For this to work, the project's build system must support the [prepare-metadata-for-build-wheel hook]. The popular buildsystems (setuptools, flit, poetry) do support it. This behavior can be disabled @@ -80,6 +79,28 @@ using the `-R` flag: %generate_buildrequires %pyproject_buildrequires -R +Alternatively, the runtime dependencies can be obtained by building the wheel and reading the metadata from the built wheel. +This can be enabled by using the `-w` flag. +Support for building wheels with `%pyproject_buildrequires -w` is **provisional** and the behavior might change. +Please subscribe to Fedora's [python-devel list] if you use the option. + + %generate_buildrequires + %pyproject_buildrequires -w + +When this is used, the wheel is going to be built at least twice, +becasue the `%generate_buildrequires` section runs repeatedly. +To avoid accidentally reusing a wheel leaking from a previous (different) build, +it cannot be reused between `%generate_buildrequires` rounds. +Contrarily to that, rebuilding the wheel again in the `%build` section is redundant +and the packager can omit the `%build` section entirely +to reuse the wheel built from the last round of `%generate_buildrequires`. +Be extra careful when attempting to modify the sources after `%pyproject_buildrequires`, +e.g. when running extra commands in the `%build` section: + + %build + cython src/wrong.pyx # this is too late with %%pyproject_buildrequires -w + %pyproject_wheel + For projects that specify test requirements using an [`extra` provide](https://packaging.python.org/specifications/core-metadata/#provides-extra-multiple-use), these can be added using the `-x` flag. @@ -121,9 +142,12 @@ in worst case, patch/sed the requirement out from the tox configuration. Note that both `-x` and `-t` imply `-r`, because runtime dependencies are always required for testing. +You can only use those options if the build backend supports the [prepare-metadata-for-build-wheel hook], +or together with `-w`. [tox]: https://tox.readthedocs.io/ [tox-current-env]: https://github.com/fedora-python/tox-current-env/ +[prepare-metadata-for-build-wheel hook]: https://www.python.org/dev/peps/pep-0517/#prepare-metadata-for-build-wheel Additionally to generated requirements you can supply multiple file names to `%pyproject_buildrequires` macro. Dependencies will be loaded from them: @@ -133,7 +157,7 @@ Dependencies will be loaded from them: For packages not using build system you can use `-N` to entirely skip automatical generation of requirements and install requirements only from manually specified files. `-N` option cannot be used in combination with other options mentioned above -(`-r`, `-e`, `-t`, `-x`). +(`-r`, `-w`, `-e`, `-t`, `-x`). Running tox based tests ----------------------- @@ -220,7 +244,7 @@ However, in Fedora packages, always list executables explicitly to avoid uninten `%pyproject_save_files` can automatically mark license files with `%license` macro and language (`*.mo`) files with `%lang` macro and appropriate language code. -Only license files declared via [PEP 639] `License-Field` 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. Note that `%pyproject_save_files` uses data from the [RECORD file](https://www.python.org/dev/peps/pep-0627/). @@ -319,7 +343,7 @@ in the `%build` section, e.g. to build the documentation with Sphinx. 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. +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 @@ -335,7 +359,25 @@ 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 (Fedora 36+) +### 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: @@ -353,7 +395,7 @@ 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 34, 35, and EL 9) +### Older pip with out-of-tree-build (Fedora 35 and EL 9) The value will expand to something like: diff --git a/SOURCES/macros.pyproject b/SOURCES/macros.pyproject index 09bbe2d..ca1ca68 100644 --- a/SOURCES/macros.pyproject +++ b/SOURCES/macros.pyproject @@ -1,4 +1,4 @@ -# This is a directory where wheels are stored and installed from, relative to PWD +# This is a directory where wheels are stored and installed from, absolute %_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 @@ -10,10 +10,15 @@ # https://docs.pytest.org/en/latest/reference.html#confval-norecursedirs %_pyproject_builddir %{_builddir}%{?buildsubdir:/%{buildsubdir}}/.pyproject-builddir -%pyproject_files %{_builddir}/pyproject-files -%_pyproject_modules %{_builddir}/pyproject-modules -%_pyproject_ghost_distinfo %{_builddir}/pyproject-ghost-distinfo -%_pyproject_record %{_builddir}/pyproject-record +# 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 %{_builddir}/%{_pyproject_files_prefix}-pyproject-files +%_pyproject_modules %{_builddir}/%{_pyproject_files_prefix}-pyproject-modules +%_pyproject_ghost_distinfo %{_builddir}/%{_pyproject_files_prefix}-pyproject-ghost-distinfo +%_pyproject_record %{_builddir}/%{_pyproject_files_prefix}-pyproject-record # Avoid leaking %%{_pyproject_builddir} to pytest collection # https://bugzilla.redhat.com/show_bug.cgi?id=1935212 @@ -24,13 +29,16 @@ %_set_pytest_addopts mkdir -p "%{_pyproject_builddir}" CFLAGS="${CFLAGS:-${RPM_OPT_FLAGS}}" LDFLAGS="${LDFLAGS:-${RPM_LD_FLAGS}}" TMPDIR="%{_pyproject_builddir}" \\\ -%{__python3} -m pip wheel --wheel-dir %{_pyproject_wheeldir} --no-deps --use-pep517 --no-build-isolation --disable-pip-version-check --no-clean --progress-bar off --verbose . +%{__python3} -Bs %{_rpmconfigdir}/redhat/pyproject_wheel.py %{_pyproject_wheeldir} } %pyproject_build_lib %{expand:\\\ $( pyproject_build_lib=() +if [ -d build/lib.%{python3_platform}-cpython-%{python3_version_nodots} ]; then + pyproject_build_lib+=( "${PWD}/build/lib.%{python3_platform}-cpython-%{python3_version_nodots}" ) +fi if [ -d build/lib.%{python3_platform}-%{python3_version} ]; then pyproject_build_lib+=( "${PWD}/build/lib.%{python3_platform}-%{python3_version}" ) fi @@ -49,7 +57,7 @@ echo $(IFS=:; echo "${pyproject_build_lib[*]}") %pyproject_install() %{expand:\\\ specifier=$(ls %{_pyproject_wheeldir}/*.whl | xargs basename --multiple | sed -E 's/([^-]+)-([^-]+)-.+\\\.whl/\\\1==\\\2/') -TMPDIR="%{_pyproject_builddir}" %{__python3} -m pip install --root %{buildroot} --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 %py3_shebang_fix %{buildroot}%{_bindir}/* rm -rfv %{buildroot}%{_bindir}/__pycache__ @@ -116,13 +124,24 @@ fi %toxenv %{default_toxenv} -%pyproject_buildrequires(rRxtNe:) %{expand:\\\ -%{-R:%{-r:%{error:The -R and -r options are mutually exclusive}}} +%pyproject_buildrequires(rRxtNwe:) %{expand:\\\ +%_set_pytest_addopts +# The _auto_set_build_flags feature does not do this in %%generate_buildrequires section, +# but we want to get an environment consistent with %%build: +%{?_auto_set_build_flags:%set_build_flags} +# The default flags expect the package note file to exist +# see https://bugzilla.redhat.com/show_bug.cgi?id=2097535 +%{?_package_note_flags:%_generate_package_note_file} +%{-R: +%{-r:%{error:The -R and -r options are mutually exclusive}} +%{-w:%{error:The -R and -w options are mutually exclusive}} +} %{-N: %{-r:%{error:The -N and -r options are mutually exclusive}} %{-x:%{error:The -N and -x options are mutually exclusive}} %{-e:%{error:The -N and -e options are mutually exclusive}} %{-t:%{error:The -N and -t options are mutually exclusive}} +%{-w:%{error:The -N and -w options are mutually exclusive}} } %{-e:%{expand:%global toxenv %(%{__python3} -s %{_rpmconfigdir}/redhat/pyproject_construct_toxenv.py %{?**})}} echo 'pyproject-rpm-macros' # we already have this installed, but this way, it's repoqueryable @@ -130,7 +149,13 @@ echo 'python%{python3_pkgversion}-devel' echo 'python%{python3_pkgversion}dist(pip) >= 19' echo 'python%{python3_pkgversion}dist(packaging)' %{!-N:if [ -f pyproject.toml ]; then - echo 'python%{python3_pkgversion}dist(toml)' + %["%{python3_pkgversion}" == "3" + ? "echo '(python%{python3_pkgversion}dist(toml) if python%{python3_pkgversion}-devel < 3.11)'" + : "%[v"%{python3_pkgversion}" < v"3.11" + ? "echo 'python%{python3_pkgversion}dist(toml)'" + : "true # will use tomllib, echo nothing" + ]" + ] elif [ -f setup.py ]; then # Note: If the default requirements change, also change them in the script! echo 'python%{python3_pkgversion}dist(setuptools) >= 40.8' @@ -142,7 +167,9 @@ fi} # setuptools assumes no pre-existing dist-info rm -rfv *.dist-info/ >&2 if [ -f %{__python3} ]; then - RPM_TOXENV="%{toxenv}" HOSTNAME="rpmbuild" %{__python3} -s %{_rpmconfigdir}/redhat/pyproject_buildrequires.py %{?!_python_no_extras_requires:--generate-extras} --python3_pkgversion %{python3_pkgversion} %{?**} + mkdir -p "%{_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} %{?**} fi } diff --git a/SOURCES/pyproject_buildrequires.py b/SOURCES/pyproject_buildrequires.py index 5f55b01..e604de5 100644 --- a/SOURCES/pyproject_buildrequires.py +++ b/SOURCES/pyproject_buildrequires.py @@ -1,3 +1,5 @@ +import glob +import io import os import sys import importlib.metadata @@ -11,6 +13,7 @@ import re import tempfile import email.parser import pathlib +import zipfile from pyproject_requirements_txt import convert_requirements_txt @@ -184,21 +187,32 @@ class Requirements: self.add(req_str, **kwargs) -def get_backend(requirements): +def toml_load(opened_binary_file): try: - f = open('pyproject.toml') - except FileNotFoundError: - pyproject_data = {} - else: + # tomllib is in the standard library since 3.11.0b1 + import tomllib as toml_module + load_from = opened_binary_file + except ImportError: try: - # lazy import toml here, not needed without pyproject.toml - import toml + # 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: print_err('Import error:', e) # already echoed by the %pyproject_buildrequires macro sys.exit(0) + return toml_module.load(load_from) + + +def get_backend(requirements): + try: + f = open('pyproject.toml', 'rb') + except FileNotFoundError: + pyproject_data = {} + else: with f: - pyproject_data = toml.load(f) + pyproject_data = toml_load(f) buildsystem_data = pyproject_data.get('build-system', {}) requirements.extend( @@ -253,21 +267,67 @@ def generate_build_requirements(backend, requirements): requirements.check(source='get_requires_for_build_wheel') -def generate_run_requirements(backend, requirements): +def requires_from_metadata_file(metadata_file): + message = email.parser.Parser().parse(metadata_file, headersonly=True) + return {k: message.get_all(k, ()) for k in ('Requires', 'Requires-Dist')} + + +def generate_run_requirements_hook(backend, requirements): hook_name = 'prepare_metadata_for_build_wheel' prepare_metadata = getattr(backend, hook_name, None) if not prepare_metadata: raise ValueError( - 'build backend cannot provide build metadata ' - + '(incl. runtime requirements) before build' + 'The build backend cannot provide build metadata ' + '(incl. runtime requirements) before build. ' + '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.' ) with hook_call(): dir_basename = prepare_metadata('.') - with open(dir_basename + '/METADATA') as f: - message = email.parser.Parser().parse(f, headersonly=True) - for key in 'Requires', 'Requires-Dist': - requires = message.get_all(key, ()) - requirements.extend(requires, source=f'wheel metadata: {key}') + with open(dir_basename + '/METADATA') as metadata_file: + for key, requires in requires_from_metadata_file(metadata_file).items(): + requirements.extend(requires, source=f'hook generated metadata: {key}') + + +def find_built_wheel(wheeldir): + wheels = glob.glob(os.path.join(wheeldir, '*.whl')) + if not wheels: + return None + if len(wheels) > 1: + raise RuntimeError('Found multiple wheels in %{_pyproject_wheeldir}, ' + 'this is not supported with %pyproject_buildrequires -w.') + return wheels[0] + + +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: + import pyproject_wheel + returncode = pyproject_wheel.build_wheel(wheeldir=wheeldir, stdout=sys.stderr) + if returncode != 0: + raise RuntimeError('Failed to build the wheel for %pyproject_buildrequires -w.') + wheel = find_built_wheel(wheeldir) + if not wheel: + raise RuntimeError('Cannot locate the built wheel for %pyproject_buildrequires -w.') + + print_err(f'Reading metadata from {wheel}') + with zipfile.ZipFile(wheel) as wheelfile: + for name in wheelfile.namelist(): + if name.count('/') == 1 and name.endswith('.dist-info/METADATA'): + with io.TextIOWrapper(wheelfile.open(name), encoding='utf-8') as metadata_file: + for key, requires in requires_from_metadata_file(metadata_file).items(): + requirements.extend(requires, source=f'built wheel metadata: {key}') + break + else: + raise RuntimeError('Could not find *.dist-info/METADATA in built wheel.') + + +def generate_run_requirements(backend, requirements, *, build_wheel, wheeldir): + if build_wheel: + generate_run_requirements_wheel(backend, requirements, wheeldir) + else: + generate_run_requirements_hook(backend, requirements) def generate_tox_requirements(toxenv, requirements): @@ -282,7 +342,7 @@ def generate_tox_requirements(toxenv, requirements): '--print-deps-to', deps.name, '--print-extras-to', extras.name, '--no-provision', provision.name, - '-qre', toxenv], + '-q', '-r', '-e', toxenv], check=False, encoding='utf-8', stdout=subprocess.PIPE, @@ -326,7 +386,7 @@ def python3dist(name, op=None, version=None, python3_pkgversion="3"): def generate_requires( - *, include_runtime=False, 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 generate_extras=False, python3_pkgversion="3", requirement_files=None, use_build_system=True ): @@ -357,7 +417,7 @@ def generate_requires( include_runtime = True generate_tox_requirements(toxenv, requirements) if include_runtime: - generate_run_requirements(backend, requirements) + generate_run_requirements(backend, requirements, build_wheel=build_wheel, wheeldir=wheeldir) except EndPass: return @@ -370,6 +430,15 @@ def main(argv): '-r', '--runtime', action='store_true', default=True, help='Generate run-time requirements (default, disable with -R)', ) + parser.add_argument( + '-w', '--wheel', action='store_true', default=False, + help=('Generate run-time requirements by building the wheel ' + '(useful for build backends without the prepare_metadata_for_build_wheel hook)'), + ) + parser.add_argument( + '--wheeldir', metavar='PATH', default=None, + help='The directory with wheel, used when -w.', + ) parser.add_argument( '-R', '--no-runtime', action='store_false', dest='runtime', help="Don't generate run-time requirements (implied by -N)", @@ -412,6 +481,10 @@ def main(argv): if not args.use_build_system: args.runtime = False + if args.wheel: + if not args.wheeldir: + raise ValueError('--wheeldir must be set when -w.') + if args.toxenv: args.tox = True @@ -427,6 +500,8 @@ def main(argv): try: generate_requires( include_runtime=args.runtime, + build_wheel=args.wheel, + wheeldir=args.wheeldir, toxenv=args.toxenv, extras=args.extras, generate_extras=args.generate_extras, diff --git a/SOURCES/pyproject_buildrequires_testcases.yaml b/SOURCES/pyproject_buildrequires_testcases.yaml index 5e4b5e5..50f9d4e 100644 --- a/SOURCES/pyproject_buildrequires_testcases.yaml +++ b/SOURCES/pyproject_buildrequires_testcases.yaml @@ -268,6 +268,8 @@ Run dependencies with extras (not selected): def main(): setup( + name = "pytest", + version = "6.6.6", setup_requires=["setuptools>=40.0"], # fmt: off extras_require={ @@ -358,6 +360,35 @@ Run dependencies with multiple extras: python3dist(dep1) result: 0 +Run dependencies with extras and build wheel option: + installed: + setuptools: 50 + wheel: 1 + pyyaml: 1 + include_runtime: true + build_wheel: true + extras: + - testing + setup.py: *pytest_setup_py + expected: | + python3dist(setuptools) >= 40.8 + python3dist(wheel) + python3dist(wheel) + python3dist(setuptools) >= 40 + python3dist(py) >= 1.5 + python3dist(six) >= 1.10 + python3dist(setuptools) + python3dist(attrs) >= 17.4 + python3dist(atomicwrites) >= 1 + python3dist(pluggy) >= 0.11 + python3dist(more-itertools) >= 4 + python3dist(argcomplete) + python3dist(hypothesis) >= 3.56 + python3dist(nose) + python3dist(requests) + result: 0 + stderr_contains: "Reading metadata from {wheeldir}/pytest-6.6.6-py3-none-any.whl" + Tox dependencies: installed: setuptools: 50 diff --git a/SOURCES/pyproject_save_files.py b/SOURCES/pyproject_save_files.py index 6cd53e7..18925f1 100644 --- a/SOURCES/pyproject_save_files.py +++ b/SOURCES/pyproject_save_files.py @@ -320,15 +320,20 @@ def classify_paths( # we handle bytecode separately continue - if path.parent == distinfo: - if path.name in ("RECORD", "REQUESTED"): + if distinfo in path.parents: + if path.parent == distinfo and path.name in ("RECORD", "REQUESTED"): # RECORD and REQUESTED files are removed in %pyproject_install # See PEP 627 continue - if license_files and path.name in license_files: + if license_files and str(path.relative_to(distinfo)) in license_files: paths["metadata"]["licenses"].append(path) else: paths["metadata"]["files"].append(path) + # nested directories within distinfo + index = path.parents.index(distinfo) + for parent in list(path.parents)[:index]: # no direct slice until Python 3.10 + if parent not in paths["metadata"]["dirs"]: + paths["metadata"]["dirs"].append(parent) continue for sitedir in sitedirs: @@ -480,6 +485,12 @@ def generate_file_list(paths_dict, module_globs, include_others=False): done_modules.add(name) done_globs.add(glob) + # Users using '*' don't care about the files in the package, so it's ok + # not to fail the build when no modules are detected + # There can be legitimate reasons to create a package without Python modules + if not modules and fnmatch.fnmatchcase("", glob): + done_globs.add(glob) + missed = module_globs - done_globs if missed: missed_text = ", ".join(sorted(missed)) diff --git a/SOURCES/pyproject_save_files_test_data.yaml b/SOURCES/pyproject_save_files_test_data.yaml index eeed5d3..c6d2291 100644 --- a/SOURCES/pyproject_save_files_test_data.yaml +++ b/SOURCES/pyproject_save_files_test_data.yaml @@ -409,10 +409,27 @@ classified: other: files: - /usr/lib/python3.7/site-packages/zope.event-4.4-py3.7-nspkg.pth + comic2pdf: + metadata: + dirs: + - /usr/lib/python3.7/site-packages/comic2pdf-3.1.0.dist-info + docs: [] + files: + - /usr/lib/python3.7/site-packages/comic2pdf-3.1.0.dist-info/METADATA + - /usr/lib/python3.7/site-packages/comic2pdf-3.1.0.dist-info/WHEEL + - /usr/lib/python3.7/site-packages/comic2pdf-3.1.0.dist-info/entry_points.txt + - /usr/lib/python3.7/site-packages/comic2pdf-3.1.0.dist-info/top_level.txt + - /usr/lib/python3.7/site-packages/comic2pdf-3.1.0.dist-info/zip-safe + licenses: [] + modules: [] + other: + files: + - /usr/bin/comic2pdf.py django: metadata: dirs: - /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info + - /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/licenses docs: [] files: - /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/AUTHORS @@ -422,8 +439,8 @@ classified: - /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/entry_points.txt - /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/top_level.txt licenses: - - /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/LICENSE - - /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/LICENSE.python + - /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/licenses/LICENSE + - /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/licenses/LICENSE.python lang: django: af: @@ -7763,9 +7780,20 @@ dumped: - zope.event - zope.event.classhandler - zope.event.tests +- - comic2pdf + - '*' + - - '%dir /usr/lib/python3.7/site-packages/comic2pdf-3.1.0.dist-info' + - /usr/bin/comic2pdf.py + - /usr/lib/python3.7/site-packages/comic2pdf-3.1.0.dist-info/METADATA + - /usr/lib/python3.7/site-packages/comic2pdf-3.1.0.dist-info/WHEEL + - /usr/lib/python3.7/site-packages/comic2pdf-3.1.0.dist-info/entry_points.txt + - /usr/lib/python3.7/site-packages/comic2pdf-3.1.0.dist-info/top_level.txt + - /usr/lib/python3.7/site-packages/comic2pdf-3.1.0.dist-info/zip-safe + - [] - - django - django - - '%dir /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info' + - '%dir /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/licenses' - '%dir /usr/lib/python3.7/site-packages/django' - '%dir /usr/lib/python3.7/site-packages/django/__pycache__' - '%dir /usr/lib/python3.7/site-packages/django/apps' @@ -11349,8 +11377,8 @@ dumped: - '%lang(zh) /usr/lib/python3.7/site-packages/django/contrib/sessions/locale/zh_Hant/LC_MESSAGES/django.mo' - '%lang(zh) /usr/lib/python3.7/site-packages/django/contrib/sites/locale/zh_Hans/LC_MESSAGES/django.mo' - '%lang(zh) /usr/lib/python3.7/site-packages/django/contrib/sites/locale/zh_Hant/LC_MESSAGES/django.mo' - - '%license /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/LICENSE' - - '%license /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/LICENSE.python' + - '%license /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/licenses/LICENSE' + - '%license /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/licenses/LICENSE.python' - /usr/bin/django-admin - /usr/bin/django-admin.py - /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/AUTHORS @@ -15456,8 +15484,8 @@ metadata: content: | Name: Django Version: 3.0.7 - License-File: LICENSE - License-File: LICENSE.python + License-File: licenses/LICENSE + License-File: licenses/LICENSE.python Whatever: False data records: @@ -15743,6 +15771,17 @@ records: zope/event/classhandler.py,sha256=CEx6issKWSia0Wruob_jIQI2EfYX45krokoTHyVsJFQ,1816 zope/event/tests.py,sha256=bvEzvOmPoQETMqYiqsR9EeVsC8Dzy-HOclfpQFVjDhE,1871 + comic2pdf: + path: /usr/lib/python3.7/site-packages/comic2pdf-3.1.0.dist-info/RECORD + content: | + ../../../bin/comic2pdf.py,sha256=ad0XbWxj2fzn_oYi1h-usY8jsxAvfpYA1aaify1Ym88,3266 + comic2pdf-3.1.0.dist-info/METADATA,sha256=qMVNbSPY02NdWfGex5yWNxoK1d96ereES-XoKxshVEA,3195 + comic2pdf-3.1.0.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92 + comic2pdf-3.1.0.dist-info/entry_points.txt,sha256=uORK0FJD-i46W74x2mNHfloSPS4QElN3-Y0vKQZ7svw,46 + comic2pdf-3.1.0.dist-info/top_level.txt,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1 + comic2pdf-3.1.0.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1 + comic2pdf-3.1.0.dist-info/RECORD,, + django: path: /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/RECORD content: | @@ -15751,8 +15790,8 @@ records: ../../../bin/django-admin.py,sha256=OOv0QKYqhDD2O4X3HQx3gFFQ-CC7hSLnWuzZnQXeiiA,115 Django-3.0.7.dist-info/AUTHORS,sha256=cV29hNQ1SpKhTmZuPff3LWHyQ7mHNBWP7_0JufEUHbs,36806 Django-3.0.7.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 - Django-3.0.7.dist-info/LICENSE,sha256=uEZBXRtRTpwd_xSiLeuQbXlLxUbKYSn5UKGM0JHipmk,1552 - Django-3.0.7.dist-info/LICENSE.python,sha256=Z-Pr3SuMPxOcaosqZSgr_NAjh2cFRcFyPZjP7nMeQrQ,13231 + Django-3.0.7.dist-info/licenses/LICENSE,sha256=uEZBXRtRTpwd_xSiLeuQbXlLxUbKYSn5UKGM0JHipmk,1552 + Django-3.0.7.dist-info/licenses/LICENSE.python,sha256=Z-Pr3SuMPxOcaosqZSgr_NAjh2cFRcFyPZjP7nMeQrQ,13231 Django-3.0.7.dist-info/METADATA,sha256=0ZU0N0E-CHKarXMLp4oOYf7EMUHR8eJ79f2yqw2NwoM,3574 Django-3.0.7.dist-info/RECORD,, Django-3.0.7.dist-info/WHEEL,sha256=g4nMs7d-Xl9-xC9XovUrsDHGXt-FT0E17Yqo92DEfvY,92 diff --git a/SOURCES/pyproject_wheel.py b/SOURCES/pyproject_wheel.py new file mode 100644 index 0000000..1936d9c --- /dev/null +++ b/SOURCES/pyproject_wheel.py @@ -0,0 +1,25 @@ +import sys +import subprocess + + +def build_wheel(*, wheeldir, stdout=None): + command = ( + sys.executable, + '-m', 'pip', + 'wheel', + '--wheel-dir', wheeldir, + '--no-deps', + '--use-pep517', + '--no-build-isolation', + '--disable-pip-version-check', + '--no-clean', + '--progress-bar', 'off', + '--verbose', + '.', + ) + cp = subprocess.run(command, stdout=stdout) + return cp.returncode + + +if __name__ == '__main__': + sys.exit(build_wheel(wheeldir=sys.argv[1])) diff --git a/SOURCES/test_pyproject_buildrequires.py b/SOURCES/test_pyproject_buildrequires.py index 15075c5..f9b2650 100644 --- a/SOURCES/test_pyproject_buildrequires.py +++ b/SOURCES/test_pyproject_buildrequires.py @@ -13,12 +13,14 @@ with Path(__file__).parent.joinpath('pyproject_buildrequires_testcases.yaml').op @pytest.mark.parametrize('case_name', testcases) -def test_data(case_name, capsys, tmp_path, monkeypatch): +def test_data(case_name, capfd, tmp_path, monkeypatch): case = testcases[case_name] cwd = tmp_path.joinpath('cwd') cwd.mkdir() monkeypatch.chdir(cwd) + wheeldir = cwd.joinpath('wheeldir') + wheeldir.mkdir() if case.get('xfail'): pytest.xfail(case.get('xfail')) @@ -45,6 +47,8 @@ def test_data(case_name, capsys, tmp_path, monkeypatch): generate_requires( get_installed_version=get_installed_version, include_runtime=case.get('include_runtime', use_build_system), + build_wheel=case.get('build_wheel', False), + wheeldir=str(wheeldir), extras=case.get('extras', []), toxenv=case.get('toxenv', None), generate_extras=case.get('generate_extras', False), @@ -64,7 +68,7 @@ def test_data(case_name, capsys, tmp_path, monkeypatch): # if we ever need to do that, we can remove the check or change it: assert 'expected' in case or 'stderr_contains' in case - out, err = capsys.readouterr() + out, err = capfd.readouterr() if 'expected' in case: assert out == case['expected'] @@ -75,7 +79,7 @@ def test_data(case_name, capsys, tmp_path, monkeypatch): if isinstance(stderr_contains, str): stderr_contains = [stderr_contains] for expected_substring in stderr_contains: - assert expected_substring in err + assert expected_substring.format(**locals()) in err finally: for req in requirement_files: req.close() diff --git a/SPECS/pyproject-rpm-macros.spec b/SPECS/pyproject-rpm-macros.spec index c69e573..b2a1ef0 100644 --- a/SPECS/pyproject-rpm-macros.spec +++ b/SPECS/pyproject-rpm-macros.spec @@ -12,7 +12,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.0.0~rc1 +Version: 1.3.3 Release: 1%{?dist} # Macro files @@ -25,6 +25,7 @@ Source103: pyproject_convert.py Source104: pyproject_preprocess_record.py Source105: pyproject_construct_toxenv.py Source106: pyproject_requirements_txt.py +Source107: pyproject_wheel.py # Tests Source201: test_pyproject_buildrequires.py @@ -46,24 +47,27 @@ URL: https://src.fedoraproject.org/rpms/pyproject-rpm-macros BuildArch: noarch %if %{with tests} -BuildRequires: python3dist(pytest) -BuildRequires: python3dist(pyyaml) -BuildRequires: python3dist(packaging) -BuildRequires: python3dist(pip) -BuildRequires: python3dist(setuptools) -BuildRequires: python3dist(toml) -BuildRequires: python3dist(tox-current-env) >= 0.0.6 -BuildRequires: python3dist(wheel) +BuildRequires: python3dist(pytest) +BuildRequires: python3dist(pyyaml) +BuildRequires: python3dist(packaging) +BuildRequires: python3dist(pip) +BuildRequires: python3dist(setuptools) +BuildRequires: python3dist(tox-current-env) >= 0.0.6 +BuildRequires: python3dist(wheel) +BuildRequires: (python3dist(toml) if python3-devel < 3.11) %endif # We build on top of those: -Requires: python-rpm-macros -Requires: python-srpm-macros -Requires: python3-rpm-macros +BuildRequires: python-rpm-macros +BuildRequires: python-srpm-macros +BuildRequires: python3-rpm-macros +Requires: python-rpm-macros +Requires: python-srpm-macros +Requires: python3-rpm-macros # We use the following tools outside of coreutils -Requires: /usr/bin/find -Requires: /usr/bin/sed +Requires: /usr/bin/find +Requires: /usr/bin/sed %description These macros allow projects that follow the Python packaging specifications @@ -92,18 +96,19 @@ cp -p %{sources} . %install mkdir -p %{buildroot}%{_rpmmacrodir} mkdir -p %{buildroot}%{_rpmconfigdir}/redhat -install -m 644 macros.pyproject %{buildroot}%{_rpmmacrodir}/ -install -m 644 pyproject_buildrequires.py %{buildroot}%{_rpmconfigdir}/redhat/ -install -m 644 pyproject_convert.py %{buildroot}%{_rpmconfigdir}/redhat/ -install -m 644 pyproject_save_files.py %{buildroot}%{_rpmconfigdir}/redhat/ -install -m 644 pyproject_preprocess_record.py %{buildroot}%{_rpmconfigdir}/redhat/ -install -m 644 pyproject_construct_toxenv.py %{buildroot}%{_rpmconfigdir}/redhat/ -install -m 644 pyproject_requirements_txt.py %{buildroot}%{_rpmconfigdir}/redhat/ +install -pm 644 macros.pyproject %{buildroot}%{_rpmmacrodir}/ +install -pm 644 pyproject_buildrequires.py %{buildroot}%{_rpmconfigdir}/redhat/ +install -pm 644 pyproject_convert.py %{buildroot}%{_rpmconfigdir}/redhat/ +install -pm 644 pyproject_save_files.py %{buildroot}%{_rpmconfigdir}/redhat/ +install -pm 644 pyproject_preprocess_record.py %{buildroot}%{_rpmconfigdir}/redhat/ +install -pm 644 pyproject_construct_toxenv.py %{buildroot}%{_rpmconfigdir}/redhat/ +install -pm 644 pyproject_requirements_txt.py %{buildroot}%{_rpmconfigdir}/redhat/ +install -pm 644 pyproject_wheel.py %{buildroot}%{_rpmconfigdir}/redhat/ %if %{with tests} %check export HOSTNAME="rpmbuild" # to speedup tox in network-less mock, see rhbz#1856356 -%{python3} -m pytest -vv --doctest-modules +%pytest -vv --doctest-modules # brp-compress is provided as an argument to get the right directory macro expansion %{python3} compare_mandata.py -f %{_rpmconfigdir}/brp-compress @@ -118,11 +123,44 @@ export HOSTNAME="rpmbuild" # to speedup tox in network-less mock, see rhbz#1856 %{_rpmconfigdir}/redhat/pyproject_preprocess_record.py %{_rpmconfigdir}/redhat/pyproject_construct_toxenv.py %{_rpmconfigdir}/redhat/pyproject_requirements_txt.py +%{_rpmconfigdir}/redhat/pyproject_wheel.py %doc README.md %license LICENSE %changelog +* Tue Aug 09 2022 Karolina Surma - 1.3.3-1 +- Don't fail %%pyproject_save_files '*' if no modules are detected + +* Wed Jun 15 2022 Benjamin A. Beasley - 1.3.2-1 +- Update %%pyproject_build_lib to support setuptools 62.1.0 and later +- %%pyproject_buildrequires: When extension modules are built, + support https://fedoraproject.org/wiki/Changes/Package_information_on_ELF_objects + +* Fri May 27 2022 Owen Taylor - 1.3.1-1 +- %%pyproject_install: pass %%{_prefix} explicitly to pip install + +* Thu May 12 2022 Miro Hrončok - 1.3.0-1 +- Use tomllib from the standard library on Python 3.11+ + +* Wed Apr 27 2022 Miro Hrončok - 1.2.0-1 +- %%pyproject_buildrequires: Add provisional -w flag for build backends without + prepare_metadata_for_build_wheel hook + When used, the wheel is built in %%pyproject_buildrequires + and information about runtime requires and extras is read from that wheel. + +* Tue Apr 12 2022 Miro Hrončok - 1.1.0-1 +- %%pyproject_save_files: Support nested directories in dist-info + +* Tue Mar 22 2022 Miro Hrončok - 1.0.1-1 +- Prefix paths of intermediate files (such as %%{pyproject_files}) with NVRA + +* Tue Mar 01 2022 Miro Hrončok - 1.0.0-1 +- Release final version 1.0.0 + +* Mon Feb 07 2022 Lumír Balhar - 1.0.0~rc2-1 +- Updated compatibility with tox4 + * Tue Jan 25 2022 Miro Hrončok - 1.0.0~rc1-1 - Release version 1.0.0, first release candidate