RHEL 9.0.0 Alpha bootstrap
The content of this branch was automatically imported from Fedora ELN with the following as its source: https://src.fedoraproject.org/rpms/pyproject-rpm-macros#6a44fe2d7a819cdcb55159d19c6878419871b41f
This commit is contained in:
parent
50a250ddcf
commit
89b6866906
1
.gitignore
vendored
1
.gitignore
vendored
@ -0,0 +1 @@
|
||||
__pycache__/
|
7
LICENSE
Normal file
7
LICENSE
Normal file
@ -0,0 +1,7 @@
|
||||
Copyright 2019 pyproject-rpm-macros contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
296
README.md
Normal file
296
README.md
Normal file
@ -0,0 +1,296 @@
|
||||
pyproject RPM macros
|
||||
====================
|
||||
|
||||
These macros allow projects that follow the Python [packaging specifications]
|
||||
to be packaged as RPMs.
|
||||
|
||||
They are still *provisional*: we can make non-backwards-compatible changes to
|
||||
the API.
|
||||
Please subscribe to Fedora's [python-devel list] if you use the macros.
|
||||
|
||||
They work for:
|
||||
|
||||
* traditional Setuptools-based projects that use the `setup.py` file,
|
||||
* newer Setuptools-based projects that have a `setup.cfg` file,
|
||||
* general Python projects that use the [PEP 517] `pyproject.toml` file (which allows using any build system, such as setuptools, flit or poetry).
|
||||
|
||||
These macros replace `%py3_build` and `%py3_install`, which only work with `setup.py`.
|
||||
|
||||
[packaging specifications]: https://packaging.python.org/specifications/
|
||||
[python-devel list]: https://lists.fedoraproject.org/archives/list/python-devel@lists.fedoraproject.org/
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
To use these macros, first BuildRequire them:
|
||||
|
||||
BuildRequires: pyproject-rpm-macros
|
||||
|
||||
Also BuildRequire the devel package for the Python you are building against.
|
||||
In Fedora, that's `python3-devel`.
|
||||
(In the future, we plan to make `python3-devel` itself require
|
||||
`pyproject-rpm-macros`.)
|
||||
|
||||
Next, you need to generate more build dependencies (of your projects and
|
||||
the macros themselves) by running `%pyproject_buildrequires` in the
|
||||
`%generate_buildrequires` section:
|
||||
|
||||
%generate_buildrequires
|
||||
%pyproject_buildrequires
|
||||
|
||||
This will add build dependencies according to [PEP 517] and [PEP 518].
|
||||
To also add run-time and test-time dependencies, see the section below.
|
||||
If you need more dependencies, such as non-Python libraries, BuildRequire
|
||||
them manually.
|
||||
|
||||
Note that `%generate_buildrequires` may produce error messages `(exit 11)` in
|
||||
the build log. This is expected behavior of BuildRequires generators; see
|
||||
[the Fedora change] for details.
|
||||
|
||||
[the Fedora change]: https://fedoraproject.org/wiki/Changes/DynamicBuildRequires
|
||||
|
||||
Then, build a wheel in `%build` with `%pyproject_wheel`:
|
||||
|
||||
%build
|
||||
%pyproject_wheel
|
||||
|
||||
And install the wheel in `%install` with `%pyproject_install`:
|
||||
|
||||
%install
|
||||
%pyproject_install
|
||||
|
||||
`%pyproject_install` installs all wheels in `$PWD/pyproject-wheeldir/`.
|
||||
|
||||
|
||||
Adding run-time and test-time dependencies
|
||||
------------------------------------------
|
||||
|
||||
To run tests in the `%check` section, the package's runtime dependencies
|
||||
often need to also be included as build requirements.
|
||||
This can be done using the `-r` flag:
|
||||
|
||||
%generate_buildrequires
|
||||
%pyproject_buildrequires -r
|
||||
|
||||
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).
|
||||
The popular buildsystems (setuptools, flit, poetry) do support it.
|
||||
|
||||
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.
|
||||
Multiple extras can be supplied by repeating the flag or as a comma separated list.
|
||||
For example, if upstream suggests installing test dependencies with
|
||||
`pip install mypackage[testing]`, the test deps would be generated by:
|
||||
|
||||
%generate_buildrequires
|
||||
%pyproject_buildrequires -x testing
|
||||
|
||||
For projects that specify test requirements in their [tox] configuration,
|
||||
these can be added using the `-t` flag (default tox environment)
|
||||
or the `-e` flag followed by the tox environment.
|
||||
The default tox environment (such as `py37` assuming the Fedora's Python version is 3.7)
|
||||
is available in the `%{toxenv}` macro.
|
||||
For example, if upstream suggests running the tests on Python 3.7 with `tox -e py37`,
|
||||
the test deps would be generated by:
|
||||
|
||||
%generate_buildrequires
|
||||
%pyproject_buildrequires -t
|
||||
|
||||
If upstream uses a custom derived environment, such as `py37-unit`, use:
|
||||
|
||||
%pyproject_buildrequires -e %{toxenv}-unit
|
||||
|
||||
Or specify more environments if needed:
|
||||
|
||||
%pyproject_buildrequires -e %{toxenv}-unit,%{toxenv}-integration
|
||||
|
||||
The `-e` option redefines `%{toxenv}` for further reuse.
|
||||
Use `%{default_toxenv}` to get the default value.
|
||||
|
||||
The `-t`/`-e` option uses [tox-current-env]'s `--print-deps-to-file` behind the scenes.
|
||||
|
||||
Note that both `-x` and `-t` imply `-r`,
|
||||
because runtime dependencies are always required for testing.
|
||||
|
||||
[tox]: https://tox.readthedocs.io/
|
||||
[tox-current-env]: https://github.com/fedora-python/tox-current-env/
|
||||
|
||||
|
||||
Running tox based tests
|
||||
-----------------------
|
||||
|
||||
In case you want to run the tests as specified in [tox] configuration,
|
||||
you must use `%pyproject_buildrequires` with `-t` or `-e` as explained above.
|
||||
Then, use the `%tox` macro in `%check`:
|
||||
|
||||
%check
|
||||
%tox
|
||||
|
||||
The macro:
|
||||
|
||||
- Always prepends `$PATH` with `%{buildroot}%{_bindir}`
|
||||
- If not defined, sets `$PYTHONPATH` to `%{buildroot}%{python3_sitearch}:%{buildroot}%{python3_sitelib}`
|
||||
- If not defined, sets `$TOX_TESTENV_PASSENV` to `*`
|
||||
- Runs `tox` with `-q` (quiet), `--recreate` and `--current-env` (from [tox-current-env]) flags
|
||||
- Implicitly uses the tox environment name stored in `%{toxenv}` - as overridden by `%pyproject_buildrequires -e`
|
||||
|
||||
By using the `-e` flag, you can use a different tox environment(s):
|
||||
|
||||
%check
|
||||
%tox
|
||||
%if %{with integration_test}
|
||||
%tox -e %{default_toxenv}-integration
|
||||
%endif
|
||||
|
||||
If you wish to provide custom `tox` flags or arguments, add them after `--`:
|
||||
|
||||
%tox -- --flag-for-tox
|
||||
|
||||
If you wish to pass custom `posargs` to tox, use another `--`:
|
||||
|
||||
%tox -- --flag-for-tox -- --flag-for-posargs
|
||||
|
||||
Or (note the two sequential `--`s):
|
||||
|
||||
%tox -- -- --flag-for-posargs
|
||||
|
||||
|
||||
|
||||
Generating the %files section
|
||||
-----------------------------
|
||||
|
||||
To generate the list of files in the `%files` section, you can use `%pyproject_save_files` after the `%pyproject_install` macro.
|
||||
It takes toplevel module names (i.e. the names used with `import` in Python) and stores paths for those modules and metadata for the package (dist-info directory) to a file stored at `%{pyproject_files}`.
|
||||
For example, if a package provides the modules `requests` and `_requests`, write:
|
||||
|
||||
%install
|
||||
%pyproject_install
|
||||
%pyproject_save_files requests _requests
|
||||
|
||||
To add listed files to the `%files` section, use `%files -f %{pyproject_files}`.
|
||||
Note that you still need to add any documentation and license manually (for now).
|
||||
|
||||
%files -n python3-requests -f %{pyproject_files}
|
||||
%doc README.rst
|
||||
%license LICENSE
|
||||
|
||||
You can use globs in the module names if listing them explicitly would be too tedious:
|
||||
|
||||
%install
|
||||
%pyproject_install
|
||||
%pyproject_save_files '*requests'
|
||||
|
||||
In fully automated environments, you can use the `*` glob to include all modules (put it in single quotes to prevent Shell from expanding it). In Fedora however, you should always use a more specific glob to avoid accidentally packaging unwanted files (for example, a top level module named `test`).
|
||||
|
||||
Speaking about automated environments, some files cannot be classified with `%pyproject_save_files`, but it is possible to list all unclassified files by adding a special `+auto` argument.
|
||||
|
||||
%install
|
||||
%pyproject_install
|
||||
%pyproject_save_files '*' +auto
|
||||
|
||||
%files -n python3-requests -f %{pyproject_files}
|
||||
|
||||
However, in Fedora packages, always list executables explicitly to avoid unintended collisions with other packages or accidental missing executables:
|
||||
|
||||
%install
|
||||
%pyproject_install
|
||||
%pyproject_save_files requests _requests
|
||||
|
||||
%files -n python3-requests -f %{pyproject_files}
|
||||
%doc README.rst
|
||||
%license LICENSE
|
||||
%{_bindir}/downloader
|
||||
|
||||
`%pyproject_save_files` also automatically recognizes language (`*.mo`) files and marks them with `%lang` macro and appropriate language code.
|
||||
Note that RPM might warn about such files listed twice:
|
||||
|
||||
warning: File listed twice: /usr/lib/python3.9/site-packages/django/conf/locale/af/LC_MESSAGES/django.mo
|
||||
|
||||
The warning is harmless.
|
||||
|
||||
|
||||
Generating Extras subpackages
|
||||
-----------------------------
|
||||
|
||||
The `%pyproject_extras_subpkg` macro generates simple subpackage(s)
|
||||
for Python extras.
|
||||
|
||||
The macro should be placed after the base package's `%description` to avoid
|
||||
issues in building the SRPM.
|
||||
|
||||
For example, if the `requests` project's metadata defines the extras
|
||||
`security` and `socks`, the following invocation will generate the subpackage
|
||||
`python3-requests+security` that provides `python3dist(requests[security])`,
|
||||
and a similar one for `socks`.
|
||||
|
||||
%pyproject_extras_subpkg -n python3-requests security socks
|
||||
|
||||
The macro works like `%python_extras_subpkg`,
|
||||
except the `-i`/`-f`/`-F` arguments are optional and discouraged.
|
||||
A filelist written by `%pyproject_install` is used by default.
|
||||
For more information on `%python_extras_subpkg`, see the [Fedora change].
|
||||
|
||||
[Fedora change]: https://fedoraproject.org/wiki/Changes/PythonExtras
|
||||
|
||||
These arguments are still required:
|
||||
|
||||
* -n: name of the “base” package (e.g. python3-requests)
|
||||
* Positional arguments: the extra name(s).
|
||||
Multiple subpackages are generated when multiple names are provided.
|
||||
|
||||
The macro does nothing on Fedora 32 and lower, as automation around
|
||||
extras was only added in f33.
|
||||
|
||||
|
||||
Limitations
|
||||
-----------
|
||||
|
||||
`%pyproject_install` changes shebang lines of every Python script in `%{buildroot}%{_bindir}` to `#!%{__python3} %{py3_shbang_opt}` (`#!/usr/bin/python3 -s`).
|
||||
Existing Python flags in shebangs are preserved.
|
||||
For example `#!/usr/bin/python3 -Ru` will be updated to `#!/usr/bin/python3 -sRu`.
|
||||
Sometimes, this can interfere with tests that run such scripts directly by name,
|
||||
because in tests we usually rely on `PYTHONPATH` (and `-s` ignores that).
|
||||
Would this behavior be undesired for any reason,
|
||||
undefine `%{py3_shbang_opt}` to turn it off.
|
||||
|
||||
Some valid Python version specifiers are not supported.
|
||||
|
||||
[PEP 517]: https://www.python.org/dev/peps/pep-0517/
|
||||
[PEP 518]: https://www.python.org/dev/peps/pep-0518/
|
||||
|
||||
|
||||
Testing the macros
|
||||
------------------
|
||||
|
||||
This repository has two kinds of tests.
|
||||
First, there is RPM `%check` section, run when building the `python-rpm-macros`
|
||||
package.
|
||||
|
||||
Then there are CI tests.
|
||||
There is currently [no way to run Fedora CI tests locally][ci-rfe],
|
||||
but you can do what the tests do manually using mock.
|
||||
For each `$PKG.spec` in `tests/`:
|
||||
|
||||
- clean your mock environment:
|
||||
|
||||
mock -r fedora-rawhide-x86_64 clean
|
||||
|
||||
- install the version of `python-rpm-macros` you're testing, e.g.:
|
||||
|
||||
mock -r fedora-rawhide-x86_64 install .../python-rpm-macros-*.noarch.rpm
|
||||
|
||||
- download the sources:
|
||||
|
||||
spectool -g -R $PKG.spec
|
||||
|
||||
- build a SRPM:
|
||||
|
||||
rpmbuild -bs $PKG.spec
|
||||
|
||||
- build in mock, using the path from the command above as `$SRPM`:
|
||||
|
||||
mock -r fedora-rawhide-x86_64 -n -N $SRPM
|
||||
|
||||
[ci-rfe]: https://pagure.io/fedora-ci/general/issue/4
|
112
macros.pyproject
Normal file
112
macros.pyproject
Normal file
@ -0,0 +1,112 @@
|
||||
# This is a directory where wheels are stored and installed from, relative to PWD
|
||||
%_pyproject_wheeldir pyproject-wheeldir
|
||||
|
||||
# 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
|
||||
# See https://github.com/pypa/pip/issues/7555#issuecomment-595180864
|
||||
#
|
||||
# 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 .pyproject-builddir
|
||||
|
||||
%pyproject_files %{_builddir}/pyproject-files
|
||||
%pyproject_ghost_distinfo %{_builddir}/pyproject-ghost-distinfo
|
||||
%pyproject_record %{_builddir}/pyproject-record
|
||||
|
||||
%pyproject_wheel() %{expand:\\\
|
||||
export TMPDIR="${PWD}/%{_pyproject_builddir}"
|
||||
mkdir -p "${TMPDIR}"
|
||||
CFLAGS="${CFLAGS:-${RPM_OPT_FLAGS}}" LDFLAGS="${LDFLAGS:-${RPM_LD_FLAGS}}" \\\
|
||||
%{__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 .
|
||||
}
|
||||
|
||||
|
||||
%pyproject_install() %{expand:\\\
|
||||
specifier=$(ls %{_pyproject_wheeldir}/*.whl | xargs basename --multiple | sed -E 's/([^-]+)-([^-]+)-.+\\\.whl/\\\1==\\\2/')
|
||||
export TMPDIR="${PWD}/%{_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
|
||||
if [ -d %{buildroot}%{_bindir} ]; then
|
||||
%py3_shebang_fix %{buildroot}%{_bindir}/*
|
||||
rm -rfv %{buildroot}%{_bindir}/__pycache__
|
||||
fi
|
||||
rm -f %{pyproject_ghost_distinfo}
|
||||
site_dirs=()
|
||||
# Process %%{python3_sitelib} if exists
|
||||
if [ -d %{buildroot}%{python3_sitelib} ]; then
|
||||
site_dirs+=( "%{python3_sitelib}" )
|
||||
fi
|
||||
# Process %%{python3_sitearch} if exists and does not equal to %%{python3_sitelib}
|
||||
if [ %{buildroot}%{python3_sitearch} != %{buildroot}%{python3_sitelib} ] && [ -d %{buildroot}%{python3_sitearch} ]; then
|
||||
site_dirs+=( "%{python3_sitearch}" )
|
||||
fi
|
||||
# Process all *.dist-info dirs in sitelib/sitearch
|
||||
for site_dir in ${site_dirs[@]}; do
|
||||
for distinfo in %{buildroot}$site_dir/*.dist-info; do
|
||||
echo "%ghost ${distinfo#%{buildroot}}" >> %{pyproject_ghost_distinfo}
|
||||
sed -i 's/pip/rpm/' ${distinfo}/INSTALLER
|
||||
PYTHONPATH=%{_rpmconfigdir}/redhat \\
|
||||
%{__python3} -B %{_rpmconfigdir}/redhat/pyproject_preprocess_record.py \\
|
||||
--buildroot %{buildroot} --record ${distinfo}/RECORD --output %{pyproject_record}
|
||||
rm -fv ${distinfo}/RECORD
|
||||
rm -fv ${distinfo}/REQUESTED
|
||||
done
|
||||
done
|
||||
lines=$(wc -l %{pyproject_ghost_distinfo} | cut -f1 -d" ")
|
||||
if [ $lines -ne 1 ]; then
|
||||
echo -e "\\n\\nWARNING: %%%%pyproject_extras_subpkg won't work without explicit -i or -F, found $lines dist-info directories.\\n\\n" >/dev/stderr
|
||||
rm %{pyproject_ghost_distinfo} # any attempt to use this will fail
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
# Note: the three times nested questionmarked -i -f -F pattern means: If none of those options was used -- in that case, we inject our own -f
|
||||
%pyproject_extras_subpkg(n:i:f:F) %{expand:%{?python_extras_subpkg:%{python_extras_subpkg%{?!-i:%{?!-f:%{?!-F: -f %{pyproject_ghost_distinfo}}}} %**}}}
|
||||
|
||||
|
||||
%pyproject_save_files() %{expand:\\\
|
||||
%{__python3} %{_rpmconfigdir}/redhat/pyproject_save_files.py \\
|
||||
--output "%{pyproject_files}" \\
|
||||
--buildroot "%{buildroot}" \\
|
||||
--sitelib "%{python3_sitelib}" \\
|
||||
--sitearch "%{python3_sitearch}" \\
|
||||
--python-version "%{python3_version}" \\
|
||||
--pyproject-record "%{pyproject_record}" \\
|
||||
%{*}
|
||||
}
|
||||
|
||||
|
||||
%default_toxenv py%{python3_version_nodots}
|
||||
%toxenv %{default_toxenv}
|
||||
|
||||
|
||||
%pyproject_buildrequires(rxte:) %{expand:\\\
|
||||
%{-e:%{expand:%global toxenv %{-e*}}}
|
||||
echo 'python%{python3_pkgversion}-devel'
|
||||
echo 'python%{python3_pkgversion}dist(pip) >= 19'
|
||||
echo 'python%{python3_pkgversion}dist(packaging)'
|
||||
# The first part is for cases when %%{python3_version_nodots} is not yet available
|
||||
if [ ! -z "%{?python3_version_nodots}" ] && [ %{python3_version_nodots} -lt 38 ]; then
|
||||
echo 'python%{python3_pkgversion}dist(importlib-metadata)'
|
||||
fi
|
||||
# Check if we can generate dependencies on Python extras
|
||||
if [ "%{py_dist_name []}" == "[]" ]; then
|
||||
extras_flag=%{?!_python_no_extras_requires:--generate-extras}
|
||||
else
|
||||
extras_flag=
|
||||
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 $extras_flag --python3_pkgversion %{python3_pkgversion} %{?**}
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
%tox(e:) %{expand:\\\
|
||||
TOX_TESTENV_PASSENV="${TOX_TESTENV_PASSENV:-*}" \\
|
||||
PATH="%{buildroot}%{_bindir}:$PATH" \\
|
||||
PYTHONPATH="${PYTHONPATH:-%{buildroot}%{python3_sitearch}:%{buildroot}%{python3_sitelib}}" \\
|
||||
HOSTNAME="rpmbuild" \\
|
||||
%{__python3} -m tox --current-env -q --recreate -e "%{-e:%{-e*}}%{!-e:%{toxenv}}" %{?*}
|
||||
}
|
8
pyproject-rpm-macros.rpmlintrc
Normal file
8
pyproject-rpm-macros.rpmlintrc
Normal file
@ -0,0 +1,8 @@
|
||||
# this dependency is on purpose
|
||||
addFilter(r'devel-dependency python3-devel')
|
||||
|
||||
# RPM macros, this is expected
|
||||
addFilter(r'only-non-binary-in-usr-lib')
|
||||
|
||||
# spelling errors
|
||||
addFilter(r'spelling-error .* en_US (toml|setuptools) ')
|
206
pyproject-rpm-macros.spec
Normal file
206
pyproject-rpm-macros.spec
Normal file
@ -0,0 +1,206 @@
|
||||
Name: pyproject-rpm-macros
|
||||
Summary: RPM macros for PEP 517 Python packages
|
||||
License: MIT
|
||||
|
||||
%bcond_without tests
|
||||
|
||||
# Keep the version at zero and increment only release
|
||||
Version: 0
|
||||
Release: 31%{?dist}
|
||||
|
||||
# Macro files
|
||||
Source001: macros.pyproject
|
||||
|
||||
# Implementation files
|
||||
Source101: pyproject_buildrequires.py
|
||||
Source102: pyproject_save_files.py
|
||||
Source103: pyproject_convert.py
|
||||
Source104: pyproject_preprocess_record.py
|
||||
|
||||
# Tests
|
||||
Source201: test_pyproject_buildrequires.py
|
||||
Source202: test_pyproject_save_files.py
|
||||
|
||||
# Test data
|
||||
Source301: pyproject_buildrequires_testcases.yaml
|
||||
Source302: pyproject_save_files_test_data.yaml
|
||||
Source303: test_RECORD
|
||||
|
||||
# Metadata
|
||||
Source901: README.md
|
||||
Source902: LICENSE
|
||||
|
||||
URL: https://src.fedoraproject.org/rpms/pyproject-rpm-macros
|
||||
|
||||
BuildArch: noarch
|
||||
|
||||
%if %{with tests}
|
||||
BuildRequires: python3dist(pytest)
|
||||
BuildRequires: python3dist(pyyaml)
|
||||
BuildRequires: python3dist(packaging)
|
||||
%if 0%{fedora} < 32
|
||||
# The %%if should not be needed, it works around:
|
||||
# https://github.com/rpm-software-management/mock/issues/336
|
||||
BuildRequires: (python3dist(importlib-metadata) if python3 < 3.8)
|
||||
%endif
|
||||
BuildRequires: python3dist(pip)
|
||||
BuildRequires: python3dist(setuptools)
|
||||
BuildRequires: python3dist(toml)
|
||||
BuildRequires: python3dist(tox-current-env) >= 0.0.3
|
||||
BuildRequires: python3dist(wheel)
|
||||
%endif
|
||||
|
||||
|
||||
%description
|
||||
This is a provisional implementation of pyproject RPM macros for Fedora 30+.
|
||||
These macros are useful for packaging Python projects that use the PEP 517
|
||||
pyproject.toml file, which specifies the package's build dependencies
|
||||
(including the build system, such as setuptools, flit or poetry).
|
||||
|
||||
|
||||
%prep
|
||||
# Not strictly necessary but allows working on file names instead
|
||||
# of source numbers in install section
|
||||
%setup -c -T
|
||||
cp -p %{sources} .
|
||||
|
||||
%build
|
||||
# nothing to do, sources are not buildable
|
||||
|
||||
%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/
|
||||
|
||||
%if %{with tests}
|
||||
%check
|
||||
export HOSTNAME="rpmbuild" # to speedup tox in network-less mock, see rhbz#1856356
|
||||
%{python3} -m pytest -vv --doctest-modules
|
||||
%endif
|
||||
|
||||
|
||||
%files
|
||||
%{_rpmmacrodir}/macros.pyproject
|
||||
%{_rpmconfigdir}/redhat/pyproject_buildrequires.py
|
||||
%{_rpmconfigdir}/redhat/pyproject_convert.py
|
||||
%{_rpmconfigdir}/redhat/pyproject_save_files.py
|
||||
%{_rpmconfigdir}/redhat/pyproject_preprocess_record.py
|
||||
|
||||
%doc README.md
|
||||
%license LICENSE
|
||||
|
||||
%changelog
|
||||
* Mon Oct 05 2020 Miro Hrončok <mhroncok@redhat.com> - 0-31
|
||||
- Support PEP 517 list based backend-path
|
||||
|
||||
* Tue Sep 29 2020 Lumír Balhar <lbalhar@redhat.com> - 0-30
|
||||
- Process RECORD files in %%pyproject_install and remove them
|
||||
- Support the extras configuration option of tox in %%pyproject_buildrequires -t
|
||||
- Support multiple -x options for %%pyproject_buildrequires
|
||||
- Fixes: rhbz#1877977
|
||||
- Fixes: rhbz#1877978
|
||||
|
||||
* Wed Sep 23 2020 Miro Hrončok <mhroncok@redhat.com> - 0-29
|
||||
- Check the requirements after installing "requires_for_build_wheel"
|
||||
- If not checked, installing runtime requirements might fail
|
||||
|
||||
* Tue Sep 08 2020 Gordon Messmer <gordon.messmer@gmail.com> - 0-28
|
||||
- Support more Python version specifiers in generated BuildRequires
|
||||
- This adds support for the '~=' operator and wildcards
|
||||
|
||||
* Fri Sep 04 2020 Miro Hrončok <miro@hroncok.cz> - 0-27
|
||||
- Make code in $PWD importable from %%pyproject_buildrequires
|
||||
- Only require toml for projects with pyproject.toml
|
||||
- Remove a no longer useful warning for unrecognized files in %%pyproject_save_files
|
||||
|
||||
* Mon Aug 24 2020 Tomas Hrnciar <thrnciar@redhat.com> - 0-26
|
||||
- Implement automatic detection of %%lang files in %%pyproject_save_files
|
||||
and mark them with %%lang in filelist
|
||||
|
||||
* Fri Aug 14 2020 Miro Hrončok <mhroncok@redhat.com> - 0-25
|
||||
- Handle Python Extras in %%pyproject_buildrequires on Fedora 33+
|
||||
|
||||
* Tue Aug 11 2020 Miro Hrončok <mhroncok@redhat.com> - 0-24
|
||||
- Allow multiple, comma-separated extras in %%pyproject_buildrequires -x
|
||||
|
||||
* Mon Aug 10 2020 Lumír Balhar <lbalhar@redhat.com> - 0-23
|
||||
- Make macros more universal for alternative Python stacks
|
||||
|
||||
* Thu Aug 06 2020 Tomas Hrnciar <thrnciar@redhat.com> - 0-22
|
||||
- Change %%pyproject_save_files +bindir argument to +auto
|
||||
to list all unclassified files in filelist
|
||||
|
||||
* Tue Aug 04 2020 Miro Hrončok <mhroncok@redhat.com> - 0-21
|
||||
- Actually implement %%pyproject_extras_subpkg
|
||||
|
||||
* Wed Jul 29 2020 Miro Hrončok <mhroncok@redhat.com> - 0-20
|
||||
- Implement %%pyproject_extras_subpkg
|
||||
|
||||
* Tue Jul 28 2020 Fedora Release Engineering <releng@fedoraproject.org> - 0-19
|
||||
- Rebuilt for https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild
|
||||
|
||||
* Thu Jul 16 2020 Miro Hrončok <mhroncok@redhat.com> - 0-18
|
||||
- %%pyproject_buildrequires -x (extras requires for tests) now implies -r
|
||||
(runtime requires) instead of erroring without it for better UX.
|
||||
|
||||
* Wed Jul 15 2020 Miro Hrončok <mhroncok@redhat.com> - 0-17
|
||||
- Set HOSTNAME to prevent tox 3.17+ from a DNS query
|
||||
- Fixes rhbz#1856356
|
||||
|
||||
* Fri Jun 19 2020 Miro Hrončok <mhroncok@redhat.com> - 0-16
|
||||
- Switch from upstream deprecated pytoml to toml
|
||||
|
||||
* Thu May 07 2020 Tomas Hrnciar <thrnciar@redhat.com> - 0-15
|
||||
- Adapt %%pyproject_install not to create a PEP 610 direct_url.json file
|
||||
|
||||
* Wed Apr 15 2020 Patrik Kopkan <pkopkan@redhat.com> - 0-14
|
||||
- Add %%pyproject_save_file macro for generating file section
|
||||
- Handle extracting debuginfo from extension modules (#1806625)
|
||||
|
||||
* Mon Mar 02 2020 Miro Hrončok <mhroncok@redhat.com> - 0-13
|
||||
- Tox dependency generator: Handle deps read in from a text file (#1808601)
|
||||
|
||||
* Wed Feb 05 2020 Miro Hrončok <mhroncok@redhat.com> - 0-12
|
||||
- Fallback to setuptools.build_meta:__legacy__ backend instead of setuptools.build_meta
|
||||
- Properly handle backends with colon
|
||||
- Preserve existing flags in shebangs of Python files in /usr/bin
|
||||
|
||||
* Thu Jan 30 2020 Fedora Release Engineering <releng@fedoraproject.org> - 0-11
|
||||
- Rebuilt for https://fedoraproject.org/wiki/Fedora_32_Mass_Rebuild
|
||||
|
||||
* Fri Nov 15 2019 Patrik Kopkan <pkopkan@redhat.com> - 0-10
|
||||
- Install wheel in '$PWD/pyproject-macros-wheeldir' to have more explicit path from which we install.
|
||||
- The path can be changed by redefining %%_pyproject_wheeldir.
|
||||
|
||||
* Wed Nov 13 2019 Anna Khaitovich <akhaitov@redhat.com> - 0-9
|
||||
- Remove stray __pycache__ directory from /usr/bin when running %%pyproject_install
|
||||
|
||||
* Fri Oct 25 2019 Miro Hrončok <mhroncok@redhat.com> - 0-8
|
||||
- When tox fails, print tox output before failing
|
||||
|
||||
* Tue Oct 08 2019 Miro Hrončok <mhroncok@redhat.com> - 0-7
|
||||
- Move a verbose line of %%pyproject_buildrequires from stdout to stderr
|
||||
|
||||
* Fri Jul 26 2019 Petr Viktorin <pviktori@redhat.com> - 0-6
|
||||
- Use importlib_metadata rather than pip freeze
|
||||
|
||||
* Fri Jul 26 2019 Miro Hrončok <mhroncok@redhat.com> - 0-5
|
||||
- Allow to fetch test dependencies from tox
|
||||
- Add %%tox macro to invoke tests
|
||||
|
||||
* Fri Jul 26 2019 Fedora Release Engineering <releng@fedoraproject.org> - 0-4
|
||||
- Rebuilt for https://fedoraproject.org/wiki/Fedora_31_Mass_Rebuild
|
||||
|
||||
* Tue Jul 02 2019 Miro Hrončok <mhroncok@redhat.com> - 0-3
|
||||
- Add %%pyproject_buildrequires
|
||||
|
||||
* Tue Jul 02 2019 Miro Hrončok <mhroncok@redhat.com> - 0-2
|
||||
- Fix shell syntax errors in %%pyproject_install
|
||||
- Drop PATH warning in %%pyproject_install
|
||||
|
||||
* Fri Jun 28 2019 Patrik Kopkan <pkopkan@redhat.com> - 0-1
|
||||
- created package
|
370
pyproject_buildrequires.py
Normal file
370
pyproject_buildrequires.py
Normal file
@ -0,0 +1,370 @@
|
||||
import os
|
||||
import sys
|
||||
import importlib
|
||||
import argparse
|
||||
import functools
|
||||
import traceback
|
||||
import contextlib
|
||||
from io import StringIO
|
||||
import subprocess
|
||||
import re
|
||||
import tempfile
|
||||
import email.parser
|
||||
|
||||
print_err = functools.partial(print, file=sys.stderr)
|
||||
|
||||
# Some valid Python version specifiers are not supported.
|
||||
# Whitelist characters we can handle.
|
||||
VERSION_RE = re.compile('[a-zA-Z0-9.-]+')
|
||||
|
||||
|
||||
class EndPass(Exception):
|
||||
"""End current pass of generating requirements"""
|
||||
|
||||
|
||||
try:
|
||||
from packaging.requirements import Requirement, InvalidRequirement
|
||||
from packaging.utils import canonicalize_name, canonicalize_version
|
||||
try:
|
||||
import importlib.metadata as importlib_metadata
|
||||
except ImportError:
|
||||
import importlib_metadata
|
||||
except ImportError as e:
|
||||
print_err('Import error:', e)
|
||||
# already echoed by the %pyproject_buildrequires macro
|
||||
sys.exit(0)
|
||||
|
||||
# uses packaging, needs to be imported after packaging is verified to be present
|
||||
from pyproject_convert import convert
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def hook_call():
|
||||
captured_out = StringIO()
|
||||
with contextlib.redirect_stdout(captured_out):
|
||||
yield
|
||||
for line in captured_out.getvalue().splitlines():
|
||||
print_err('HOOK STDOUT:', line)
|
||||
|
||||
|
||||
class Requirements:
|
||||
"""Requirement printer"""
|
||||
def __init__(self, get_installed_version, extras=None,
|
||||
generate_extras=False, python3_pkgversion='3'):
|
||||
self.get_installed_version = get_installed_version
|
||||
self.extras = set()
|
||||
|
||||
if extras:
|
||||
for extra in extras:
|
||||
self.add_extras(*extra.split(','))
|
||||
|
||||
self.missing_requirements = False
|
||||
|
||||
self.generate_extras = generate_extras
|
||||
self.python3_pkgversion = python3_pkgversion
|
||||
|
||||
def add_extras(self, *extras):
|
||||
self.extras |= set(e.strip() for e in extras)
|
||||
|
||||
@property
|
||||
def marker_envs(self):
|
||||
if self.extras:
|
||||
return [{'extra': e} for e in sorted(self.extras)]
|
||||
return [{'extra': ''}]
|
||||
|
||||
def evaluate_all_environamnets(self, requirement):
|
||||
for marker_env in self.marker_envs:
|
||||
if requirement.marker.evaluate(environment=marker_env):
|
||||
return True
|
||||
return False
|
||||
|
||||
def add(self, requirement_str, *, source=None):
|
||||
"""Output a Python-style requirement string as RPM dep"""
|
||||
print_err(f'Handling {requirement_str} from {source}')
|
||||
|
||||
try:
|
||||
requirement = Requirement(requirement_str)
|
||||
except InvalidRequirement as e:
|
||||
print_err(
|
||||
f'WARNING: Skipping invalid requirement: {requirement_str}\n'
|
||||
+ f' {e}',
|
||||
)
|
||||
return
|
||||
|
||||
name = canonicalize_name(requirement.name)
|
||||
if (requirement.marker is not None and
|
||||
not self.evaluate_all_environamnets(requirement)):
|
||||
print_err(f'Ignoring alien requirement:', requirement_str)
|
||||
return
|
||||
|
||||
try:
|
||||
# TODO: check if requirements with extras are satisfied
|
||||
installed = self.get_installed_version(requirement.name)
|
||||
except importlib_metadata.PackageNotFoundError:
|
||||
print_err(f'Requirement not satisfied: {requirement_str}')
|
||||
installed = None
|
||||
if installed and installed in requirement.specifier:
|
||||
print_err(f'Requirement satisfied: {requirement_str}')
|
||||
print_err(f' (installed: {requirement.name} {installed})')
|
||||
if requirement.extras:
|
||||
print_err(f' (extras are currently not checked)')
|
||||
else:
|
||||
self.missing_requirements = True
|
||||
|
||||
if self.generate_extras:
|
||||
extra_names = [f'{name}[{extra}]' for extra in sorted(requirement.extras)]
|
||||
else:
|
||||
extra_names = []
|
||||
|
||||
for name in [name] + extra_names:
|
||||
together = []
|
||||
for specifier in sorted(
|
||||
requirement.specifier,
|
||||
key=lambda s: (s.operator, s.version),
|
||||
):
|
||||
version = canonicalize_version(specifier.version)
|
||||
if not VERSION_RE.fullmatch(str(specifier.version)):
|
||||
raise ValueError(
|
||||
f'Unknown character in version: {specifier.version}. '
|
||||
+ '(This is probably a bug in pyproject-rpm-macros.)',
|
||||
)
|
||||
together.append(convert(python3dist(name, python3_pkgversion=self.python3_pkgversion),
|
||||
specifier.operator, version))
|
||||
if len(together) == 0:
|
||||
print(python3dist(name,
|
||||
python3_pkgversion=self.python3_pkgversion))
|
||||
elif len(together) == 1:
|
||||
print(together[0])
|
||||
else:
|
||||
print(f"({' with '.join(together)})")
|
||||
|
||||
def check(self, *, source=None):
|
||||
"""End current pass if any unsatisfied dependencies were output"""
|
||||
if self.missing_requirements:
|
||||
print_err(f'Exiting dependency generation pass: {source}')
|
||||
raise EndPass(source)
|
||||
|
||||
def extend(self, requirement_strs, *, source=None):
|
||||
"""add() several requirements"""
|
||||
for req_str in requirement_strs:
|
||||
self.add(req_str, source=source)
|
||||
|
||||
|
||||
def get_backend(requirements):
|
||||
try:
|
||||
f = open('pyproject.toml')
|
||||
except FileNotFoundError:
|
||||
pyproject_data = {}
|
||||
else:
|
||||
# lazy import toml here, not needed without pyproject.toml
|
||||
requirements.add('toml', source='parsing pyproject.toml')
|
||||
requirements.check(source='parsing pyproject.toml')
|
||||
import toml
|
||||
with f:
|
||||
pyproject_data = toml.load(f)
|
||||
|
||||
buildsystem_data = pyproject_data.get('build-system', {})
|
||||
requirements.extend(
|
||||
buildsystem_data.get('requires', ()),
|
||||
source='build-system.requires',
|
||||
)
|
||||
|
||||
backend_name = buildsystem_data.get('build-backend')
|
||||
if not backend_name:
|
||||
# https://www.python.org/dev/peps/pep-0517/:
|
||||
# If the pyproject.toml file is absent, or the build-backend key is
|
||||
# missing, the source tree is not using this specification, and tools
|
||||
# should revert to the legacy behaviour of running setup.py
|
||||
# (either directly, or by implicitly invoking the [following] backend).
|
||||
backend_name = 'setuptools.build_meta:__legacy__'
|
||||
|
||||
requirements.add('setuptools >= 40.8', source='default build backend')
|
||||
requirements.add('wheel', source='default build backend')
|
||||
|
||||
requirements.check(source='build backend')
|
||||
|
||||
backend_path = buildsystem_data.get('backend-path')
|
||||
if backend_path:
|
||||
# PEP 517 example shows the path as a list, but some projects don't follow that
|
||||
if isinstance(backend_path, str):
|
||||
backend_path = [backend_path]
|
||||
sys.path = backend_path + sys.path
|
||||
|
||||
module_name, _, object_name = backend_name.partition(":")
|
||||
backend_module = importlib.import_module(module_name)
|
||||
|
||||
if object_name:
|
||||
return getattr(backend_module, object_name)
|
||||
|
||||
return backend_module
|
||||
|
||||
|
||||
def generate_build_requirements(backend, requirements):
|
||||
get_requires = getattr(backend, 'get_requires_for_build_wheel', None)
|
||||
if get_requires:
|
||||
with hook_call():
|
||||
new_reqs = get_requires()
|
||||
requirements.extend(new_reqs, source='get_requires_for_build_wheel')
|
||||
requirements.check(source='get_requires_for_build_wheel')
|
||||
|
||||
|
||||
def generate_run_requirements(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 buld'
|
||||
)
|
||||
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}')
|
||||
|
||||
|
||||
def parse_tox_requires_lines(lines):
|
||||
packages = []
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
if line.startswith('-r'):
|
||||
path = line[2:]
|
||||
with open(path) as f:
|
||||
packages.extend(parse_tox_requires_lines(f.read().splitlines()))
|
||||
elif line.startswith('-'):
|
||||
print_err(
|
||||
f'WARNING: Skipping dependency line: {line}\n'
|
||||
+ f' tox deps options other than -r are not supported (yet).',
|
||||
)
|
||||
elif line:
|
||||
packages.append(line)
|
||||
return packages
|
||||
|
||||
|
||||
def generate_tox_requirements(toxenv, requirements):
|
||||
requirements.add('tox-current-env >= 0.0.3', source='tox itself')
|
||||
requirements.check(source='tox itself')
|
||||
with tempfile.NamedTemporaryFile('r') as deps, tempfile.NamedTemporaryFile('r') as extras:
|
||||
r = subprocess.run(
|
||||
[sys.executable, '-m', 'tox',
|
||||
'--print-deps-to', deps.name,
|
||||
'--print-extras-to', extras.name,
|
||||
'-qre', toxenv],
|
||||
check=False,
|
||||
encoding='utf-8',
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
)
|
||||
if r.stdout:
|
||||
print_err(r.stdout, end='')
|
||||
r.check_returncode()
|
||||
|
||||
deplines = deps.read().splitlines()
|
||||
packages = parse_tox_requires_lines(deplines)
|
||||
requirements.add_extras(*extras.read().splitlines())
|
||||
requirements.extend(packages,
|
||||
source=f'tox --print-deps-only: {toxenv}')
|
||||
|
||||
|
||||
def python3dist(name, op=None, version=None, python3_pkgversion="3"):
|
||||
prefix = f"python{python3_pkgversion}dist"
|
||||
|
||||
if op is None:
|
||||
if version is not None:
|
||||
raise AssertionError('op and version go together')
|
||||
return f'{prefix}({name})'
|
||||
else:
|
||||
return f'{prefix}({name}) {op} {version}'
|
||||
|
||||
|
||||
def generate_requires(
|
||||
*, include_runtime=False, toxenv=None, extras=None,
|
||||
get_installed_version=importlib_metadata.version, # for dep injection
|
||||
generate_extras=False, python3_pkgversion="3",
|
||||
):
|
||||
"""Generate the BuildRequires for the project in the current directory
|
||||
|
||||
This is the main Python entry point.
|
||||
"""
|
||||
requirements = Requirements(
|
||||
get_installed_version, extras=extras or [],
|
||||
generate_extras=generate_extras,
|
||||
python3_pkgversion=python3_pkgversion
|
||||
)
|
||||
|
||||
try:
|
||||
backend = get_backend(requirements)
|
||||
generate_build_requirements(backend, requirements)
|
||||
if toxenv is not None:
|
||||
include_runtime = True
|
||||
generate_tox_requirements(toxenv, requirements)
|
||||
if include_runtime:
|
||||
generate_run_requirements(backend, requirements)
|
||||
except EndPass:
|
||||
return
|
||||
|
||||
|
||||
def main(argv):
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Generate BuildRequires for a Python project.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'-r', '--runtime', action='store_true',
|
||||
help='Generate run-time requirements',
|
||||
)
|
||||
parser.add_argument(
|
||||
'-e', '--toxenv', metavar='TOXENVS', default=None,
|
||||
help=('specify tox environments'
|
||||
'(implies --tox)'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'-t', '--tox', action='store_true',
|
||||
help=('generate test tequirements from tox environment '
|
||||
'(implies --runtime)'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'-x', '--extras', metavar='EXTRAS', action='append',
|
||||
help='comma separated list of "extras" for runtime requirements '
|
||||
'(e.g. -x testing,feature-x) (implies --runtime, can be repeated)',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--generate-extras', action='store_true',
|
||||
help='Generate build requirements on Python Extras',
|
||||
)
|
||||
parser.add_argument(
|
||||
'-p', '--python3_pkgversion', metavar='PYTHON3_PKGVERSION',
|
||||
default="3", help=('Python version for pythonXdist()'
|
||||
'or pythonX.Ydist() requirements'),
|
||||
)
|
||||
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
if args.toxenv:
|
||||
args.tox = True
|
||||
|
||||
if args.tox:
|
||||
args.runtime = True
|
||||
args.toxenv = (args.toxenv or os.getenv('RPM_TOXENV') or
|
||||
f'py{sys.version_info.major}{sys.version_info.minor}')
|
||||
|
||||
if args.extras:
|
||||
args.runtime = True
|
||||
|
||||
try:
|
||||
generate_requires(
|
||||
include_runtime=args.runtime,
|
||||
toxenv=args.toxenv,
|
||||
extras=args.extras,
|
||||
generate_extras=args.generate_extras,
|
||||
python3_pkgversion=args.python3_pkgversion,
|
||||
)
|
||||
except Exception:
|
||||
# Log the traceback explicitly (it's useful debug info)
|
||||
traceback.print_exc()
|
||||
exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv[1:])
|
366
pyproject_buildrequires_testcases.yaml
Normal file
366
pyproject_buildrequires_testcases.yaml
Normal file
@ -0,0 +1,366 @@
|
||||
No pyproject.toml, nothing installed:
|
||||
installed:
|
||||
# empty
|
||||
expected: |
|
||||
python3dist(setuptools) >= 40.8
|
||||
python3dist(wheel)
|
||||
result: 0
|
||||
|
||||
Nothing installed yet:
|
||||
installed:
|
||||
# empty
|
||||
pyproject.toml: |
|
||||
# empty
|
||||
expected: |
|
||||
python3dist(toml)
|
||||
result: 0
|
||||
|
||||
Insufficient version of setuptools:
|
||||
installed:
|
||||
setuptools: 5
|
||||
wheel: 1
|
||||
toml: 1
|
||||
pyproject.toml: |
|
||||
# empty
|
||||
expected: |
|
||||
python3dist(toml)
|
||||
python3dist(setuptools) >= 40.8
|
||||
python3dist(wheel)
|
||||
result: 0
|
||||
|
||||
No pyproject.toml, empty setup.py:
|
||||
installed:
|
||||
setuptools: 50
|
||||
wheel: 1
|
||||
setup.py: |
|
||||
expected: |
|
||||
python3dist(setuptools) >= 40.8
|
||||
python3dist(wheel)
|
||||
python3dist(wheel)
|
||||
result: 0
|
||||
|
||||
Default build system, empty setup.py:
|
||||
installed:
|
||||
setuptools: 50
|
||||
wheel: 1
|
||||
toml: 1
|
||||
pyproject.toml: |
|
||||
# empty
|
||||
setup.py: |
|
||||
expected: |
|
||||
python3dist(toml)
|
||||
python3dist(setuptools) >= 40.8
|
||||
python3dist(wheel)
|
||||
python3dist(wheel)
|
||||
result: 0
|
||||
|
||||
Erroring setup.py:
|
||||
installed:
|
||||
setuptools: 50
|
||||
wheel: 1
|
||||
setup.py: |
|
||||
exit(77)
|
||||
result: 77
|
||||
|
||||
Bad character in version:
|
||||
installed:
|
||||
toml: 1
|
||||
pyproject.toml: |
|
||||
[build-system]
|
||||
requires = ["pkg == 0.$.^.*"]
|
||||
except: ValueError
|
||||
|
||||
Build system dependencies in pyproject.toml with extras:
|
||||
generate_extras: true
|
||||
installed:
|
||||
setuptools: 50
|
||||
wheel: 1
|
||||
toml: 1
|
||||
pyproject.toml: |
|
||||
[build-system]
|
||||
requires = [
|
||||
"foo",
|
||||
"bar[baz] > 5",
|
||||
"ne!=1",
|
||||
"ge>=1.2",
|
||||
"le <= 1.2.3",
|
||||
"lt < 1.2.3.4 ",
|
||||
" gt > 1.2.3.4.5",
|
||||
"multi[extras1,extras2] == 6.0",
|
||||
"combo >2, <5, != 3.0.0",
|
||||
"invalid!!ignored",
|
||||
"py2 ; python_version < '2.7'",
|
||||
"py3 ; python_version > '3.0'",
|
||||
]
|
||||
expected: |
|
||||
python3dist(toml)
|
||||
python3dist(foo)
|
||||
python3dist(bar) > 5
|
||||
python3dist(bar[baz]) > 5
|
||||
(python3dist(ne) < 1 or python3dist(ne) > 1)
|
||||
python3dist(ge) >= 1.2
|
||||
python3dist(le) <= 1.2.3
|
||||
python3dist(lt) < 1.2.3.4
|
||||
python3dist(gt) > 1.2.3.4.5
|
||||
python3dist(multi) = 6
|
||||
python3dist(multi[extras1]) = 6
|
||||
python3dist(multi[extras2]) = 6
|
||||
((python3dist(combo) < 3 or python3dist(combo) > 3) with python3dist(combo) < 5 with python3dist(combo) > 2)
|
||||
python3dist(py3)
|
||||
python3dist(setuptools) >= 40.8
|
||||
python3dist(wheel)
|
||||
result: 0
|
||||
|
||||
Build system dependencies in pyproject.toml without extras:
|
||||
generate_extras: false
|
||||
installed:
|
||||
setuptools: 50
|
||||
wheel: 1
|
||||
toml: 1
|
||||
pyproject.toml: |
|
||||
[build-system]
|
||||
requires = [
|
||||
"bar[baz] > 5",
|
||||
"multi[extras1,extras2] == 6.0",
|
||||
]
|
||||
expected: |
|
||||
python3dist(toml)
|
||||
python3dist(bar) > 5
|
||||
python3dist(multi) = 6
|
||||
python3dist(setuptools) >= 40.8
|
||||
python3dist(wheel)
|
||||
result: 0
|
||||
|
||||
Default build system, build dependencies in setup.py:
|
||||
installed:
|
||||
setuptools: 50
|
||||
wheel: 1
|
||||
setup.py: |
|
||||
from setuptools import setup
|
||||
setup(
|
||||
name='test',
|
||||
version='0.1',
|
||||
setup_requires=['foo', 'bar!=2', 'baz~=1.1.1'],
|
||||
install_requires=['inst'],
|
||||
)
|
||||
expected: |
|
||||
python3dist(setuptools) >= 40.8
|
||||
python3dist(wheel)
|
||||
python3dist(wheel)
|
||||
python3dist(foo)
|
||||
(python3dist(bar) < 2 or python3dist(bar) > 2)
|
||||
(python3dist(baz) >= 1.1.1 with python3dist(baz) < 1.2)
|
||||
result: 0
|
||||
|
||||
Default build system, run dependencies in setup.py:
|
||||
installed:
|
||||
setuptools: 50
|
||||
wheel: 1
|
||||
pyyaml: 1
|
||||
include_runtime: true
|
||||
setup.py: |
|
||||
from setuptools import setup
|
||||
setup(
|
||||
name='test',
|
||||
version='0.1',
|
||||
setup_requires=['pyyaml'], # nb. setuptools will try to install this
|
||||
install_requires=['inst > 1', 'inst2 < 3'],
|
||||
)
|
||||
expected: |
|
||||
python3dist(setuptools) >= 40.8
|
||||
python3dist(wheel)
|
||||
python3dist(wheel)
|
||||
python3dist(pyyaml)
|
||||
python3dist(inst) > 1
|
||||
python3dist(inst2) < 3
|
||||
result: 0
|
||||
|
||||
Run dependencies with extras (not selected):
|
||||
installed:
|
||||
setuptools: 50
|
||||
wheel: 1
|
||||
pyyaml: 1
|
||||
include_runtime: true
|
||||
setup.py: &pytest_setup_py |
|
||||
# slightly abriged copy of pytest's setup.py
|
||||
from setuptools import setup
|
||||
|
||||
INSTALL_REQUIRES = [
|
||||
"py>=1.5.0",
|
||||
"six>=1.10.0",
|
||||
"setuptools",
|
||||
"attrs>=17.4.0",
|
||||
'more-itertools>=4.0.0,<6.0.0;python_version<="2.7"',
|
||||
'more-itertools>=4.0.0;python_version>"2.7"',
|
||||
"atomicwrites>=1.0",
|
||||
'funcsigs>=1.0;python_version<"3.0"',
|
||||
'pathlib2>=2.2.0;python_version<"3.6"',
|
||||
'colorama;sys_platform=="win32"',
|
||||
"pluggy>=0.11",
|
||||
]
|
||||
|
||||
def main():
|
||||
setup(
|
||||
setup_requires=["setuptools>=40.0"],
|
||||
# fmt: off
|
||||
extras_require={
|
||||
"testing": [
|
||||
"argcomplete",
|
||||
"hypothesis>=3.56",
|
||||
"nose",
|
||||
"requests",
|
||||
"mock;python_version=='2.7'",
|
||||
],
|
||||
},
|
||||
# fmt: on
|
||||
install_requires=INSTALL_REQUIRES,
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
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
|
||||
result: 0
|
||||
|
||||
Run dependencies with extras (selected):
|
||||
installed:
|
||||
setuptools: 50
|
||||
wheel: 1
|
||||
pyyaml: 1
|
||||
include_runtime: 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
|
||||
|
||||
Run dependencies with multiple extras:
|
||||
installed:
|
||||
setuptools: 50
|
||||
wheel: 1
|
||||
pyyaml: 1
|
||||
include_runtime: true
|
||||
extras:
|
||||
- testing,more-testing
|
||||
- even-more-testing , cool-feature
|
||||
setup.py: |
|
||||
from setuptools import setup
|
||||
setup(
|
||||
extras_require={
|
||||
'testing': ['dep1'],
|
||||
'more-testing': ['dep2'],
|
||||
'even-more-testing': ['dep3'],
|
||||
'cool-feature': ['dep4'],
|
||||
},
|
||||
)
|
||||
expected: |
|
||||
python3dist(setuptools) >= 40.8
|
||||
python3dist(wheel)
|
||||
python3dist(wheel)
|
||||
python3dist(dep4)
|
||||
python3dist(dep3)
|
||||
python3dist(dep2)
|
||||
python3dist(dep1)
|
||||
result: 0
|
||||
|
||||
Tox dependencies:
|
||||
installed:
|
||||
setuptools: 50
|
||||
wheel: 1
|
||||
tox: 3.5.3
|
||||
tox-current-env: 0.0.3
|
||||
toxenv: py3
|
||||
setup.py: |
|
||||
from setuptools import setup
|
||||
setup(
|
||||
name='test',
|
||||
version='0.1',
|
||||
install_requires=['inst'],
|
||||
)
|
||||
tox.ini: |
|
||||
[tox]
|
||||
envlist = py36,py37,py38
|
||||
[testenv]
|
||||
deps =
|
||||
toxdep1
|
||||
toxdep2
|
||||
commands =
|
||||
true
|
||||
expected: |
|
||||
python3dist(setuptools) >= 40.8
|
||||
python3dist(wheel)
|
||||
python3dist(wheel)
|
||||
python3dist(tox-current-env) >= 0.0.3
|
||||
python3dist(toxdep1)
|
||||
python3dist(toxdep2)
|
||||
python3dist(inst)
|
||||
result: 0
|
||||
|
||||
Tox extras:
|
||||
installed:
|
||||
setuptools: 50
|
||||
wheel: 1
|
||||
tox: 3.5.3
|
||||
tox-current-env: 0.0.3
|
||||
toxenv: py3
|
||||
setup.py: |
|
||||
from setuptools import setup
|
||||
setup(
|
||||
name='test',
|
||||
version='0.1',
|
||||
install_requires=['inst'],
|
||||
extras_require={
|
||||
'extra1': ['dep11 > 11', 'dep12'],
|
||||
'extra2': ['dep21', 'dep22', 'dep23'],
|
||||
'nope': ['nopedep'],
|
||||
}
|
||||
)
|
||||
tox.ini: |
|
||||
[tox]
|
||||
envlist = py36,py37,py38
|
||||
[testenv]
|
||||
deps =
|
||||
toxdep
|
||||
extras =
|
||||
extra2
|
||||
extra1
|
||||
commands =
|
||||
true
|
||||
expected: |
|
||||
python3dist(setuptools) >= 40.8
|
||||
python3dist(wheel)
|
||||
python3dist(wheel)
|
||||
python3dist(tox-current-env) >= 0.0.3
|
||||
python3dist(toxdep)
|
||||
python3dist(inst)
|
||||
python3dist(dep11) > 11
|
||||
python3dist(dep12)
|
||||
python3dist(dep21)
|
||||
python3dist(dep22)
|
||||
python3dist(dep23)
|
||||
result: 0
|
142
pyproject_convert.py
Normal file
142
pyproject_convert.py
Normal file
@ -0,0 +1,142 @@
|
||||
# Copyright 2019 Gordon Messmer <gordon.messmer@gmail.com>
|
||||
#
|
||||
# Upstream: https://github.com/gordonmessmer/pyreq2rpm
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
from packaging.requirements import Requirement
|
||||
from packaging.version import parse as parse_version
|
||||
|
||||
class RpmVersion():
|
||||
def __init__(self, version_id):
|
||||
version = parse_version(version_id)
|
||||
if isinstance(version._version, str):
|
||||
self.version = version._version
|
||||
else:
|
||||
self.epoch = version._version.epoch
|
||||
self.version = list(version._version.release)
|
||||
self.pre = version._version.pre
|
||||
self.dev = version._version.dev
|
||||
self.post = version._version.post
|
||||
|
||||
def increment(self):
|
||||
self.version[-1] += 1
|
||||
self.pre = None
|
||||
self.dev = None
|
||||
self.post = None
|
||||
return self
|
||||
|
||||
def __str__(self):
|
||||
if isinstance(self.version, str):
|
||||
return self.version
|
||||
if self.epoch:
|
||||
rpm_epoch = str(self.epoch) + ':'
|
||||
else:
|
||||
rpm_epoch = ''
|
||||
while len(self.version) > 1 and self.version[-1] == 0:
|
||||
self.version.pop()
|
||||
rpm_version = '.'.join(str(x) for x in self.version)
|
||||
if self.pre:
|
||||
rpm_suffix = '~{}'.format(''.join(str(x) for x in self.pre))
|
||||
elif self.dev:
|
||||
rpm_suffix = '~~{}'.format(''.join(str(x) for x in self.dev))
|
||||
elif self.post:
|
||||
rpm_suffix = '^post{}'.format(self.post[1])
|
||||
else:
|
||||
rpm_suffix = ''
|
||||
return '{}{}{}'.format(rpm_epoch, rpm_version, rpm_suffix)
|
||||
|
||||
def convert_compatible(name, operator, version_id):
|
||||
if version_id.endswith('.*'):
|
||||
return 'Invalid version'
|
||||
version = RpmVersion(version_id)
|
||||
if len(version.version) == 1:
|
||||
return 'Invalid version'
|
||||
upper_version = RpmVersion(version_id)
|
||||
upper_version.version.pop()
|
||||
upper_version.increment()
|
||||
return '({} >= {} with {} < {})'.format(
|
||||
name, version, name, upper_version)
|
||||
|
||||
def convert_equal(name, operator, version_id):
|
||||
if version_id.endswith('.*'):
|
||||
version_id = version_id[:-2] + '.0'
|
||||
return convert_compatible(name, '~=', version_id)
|
||||
version = RpmVersion(version_id)
|
||||
return '{} = {}'.format(name, version)
|
||||
|
||||
def convert_arbitrary_equal(name, operator, version_id):
|
||||
if version_id.endswith('.*'):
|
||||
return 'Invalid version'
|
||||
version = RpmVersion(version_id)
|
||||
return '{} = {}'.format(name, version)
|
||||
|
||||
def convert_not_equal(name, operator, version_id):
|
||||
if version_id.endswith('.*'):
|
||||
version_id = version_id[:-2]
|
||||
version = RpmVersion(version_id)
|
||||
lower_version = RpmVersion(version_id).increment()
|
||||
else:
|
||||
version = RpmVersion(version_id)
|
||||
lower_version = version
|
||||
return '({} < {} or {} > {})'.format(
|
||||
name, version, name, lower_version)
|
||||
|
||||
def convert_ordered(name, operator, version_id):
|
||||
if version_id.endswith('.*'):
|
||||
# PEP 440 does not define semantics for prefix matching
|
||||
# with ordered comparisons
|
||||
version_id = version_id[:-2]
|
||||
version = RpmVersion(version_id)
|
||||
if operator == '>':
|
||||
# distutils will allow a prefix match with '>'
|
||||
operator = '>='
|
||||
if operator == '<=':
|
||||
# distutils will not allow a prefix match with '<='
|
||||
operator = '<'
|
||||
else:
|
||||
version = RpmVersion(version_id)
|
||||
return '{} {} {}'.format(name, operator, version)
|
||||
|
||||
OPERATORS = {'~=': convert_compatible,
|
||||
'==': convert_equal,
|
||||
'===': convert_arbitrary_equal,
|
||||
'!=': convert_not_equal,
|
||||
'<=': convert_ordered,
|
||||
'<': convert_ordered,
|
||||
'>=': convert_ordered,
|
||||
'>': convert_ordered}
|
||||
|
||||
def convert(name, operator, version_id):
|
||||
return OPERATORS[operator](name, operator, version_id)
|
||||
|
||||
def convert_requirement(req):
|
||||
parsed_req = Requirement.parse(req)
|
||||
reqs = []
|
||||
for spec in parsed_req.specs:
|
||||
reqs.append(convert(parsed_req.project_name, spec[0], spec[1]))
|
||||
if len(reqs) == 0:
|
||||
return parsed_req.project_name
|
||||
if len(reqs) == 1:
|
||||
return reqs[0]
|
||||
else:
|
||||
reqs.sort()
|
||||
return '({})'.format(' with '.join(reqs))
|
85
pyproject_preprocess_record.py
Normal file
85
pyproject_preprocess_record.py
Normal file
@ -0,0 +1,85 @@
|
||||
import argparse
|
||||
import csv
|
||||
import json
|
||||
import os
|
||||
from pathlib import PosixPath
|
||||
|
||||
from pyproject_save_files import BuildrootPath
|
||||
|
||||
|
||||
def read_record(record_path):
|
||||
"""
|
||||
A generator yielding individual RECORD triplets.
|
||||
|
||||
https://www.python.org/dev/peps/pep-0376/#record
|
||||
|
||||
The triplet is str-path, hash, size -- the last two optional.
|
||||
We will later care only for the paths anyway.
|
||||
|
||||
Example:
|
||||
|
||||
>>> g = read_record(PosixPath('./test_RECORD'))
|
||||
>>> next(g)
|
||||
['../../../bin/__pycache__/tldr.cpython-....pyc', '', '']
|
||||
>>> next(g)
|
||||
['../../../bin/tldr', 'sha256=...', '12766']
|
||||
>>> next(g)
|
||||
['../../../bin/tldr.py', 'sha256=...', '12766']
|
||||
"""
|
||||
with open(record_path, newline="", encoding="utf-8") as f:
|
||||
yield from csv.reader(
|
||||
f, delimiter=",", quotechar='"', lineterminator=os.linesep
|
||||
)
|
||||
|
||||
|
||||
def parse_record(record_path, record_content):
|
||||
"""
|
||||
Returns a list with BuildrootPaths parsed from record_content
|
||||
|
||||
params:
|
||||
record_path: RECORD BuildrootPath
|
||||
record_content: list of RECORD triplets
|
||||
first item is a str-path relative to directory where dist-info directory is
|
||||
(it can also be absolute according to the standard, but not from pip)
|
||||
|
||||
Examples:
|
||||
>>> parse_record(BuildrootPath('/usr/lib/python3.7/site-packages/requests-2.22.0.dist-info/RECORD'),
|
||||
... [('requests/sessions.py', 'sha256=xxx', '666')])
|
||||
['/usr/lib/python3.7/site-packages/requests/sessions.py']
|
||||
|
||||
>>> parse_record(BuildrootPath('/usr/lib/python3.7/site-packages/tldr-0.5.dist-info/RECORD'),
|
||||
... [('../../../bin/tldr', 'sha256=yyy', '777')])
|
||||
['/usr/bin/tldr']
|
||||
"""
|
||||
sitedir = record_path.parent.parent # trough the dist-info directory
|
||||
# / with absolute right operand will remove the left operand
|
||||
# any .. parts are resolved via normpath
|
||||
return [str((sitedir / row[0]).normpath()) for row in record_content]
|
||||
|
||||
|
||||
def save_parsed_record(record_path, parsed_record, output_file):
|
||||
content = {}
|
||||
if output_file.is_file():
|
||||
content = json.loads(output_file.read_text())
|
||||
content[str(record_path)] = parsed_record
|
||||
output_file.write_text(json.dumps(content))
|
||||
|
||||
|
||||
def main(cli_args):
|
||||
record_path = BuildrootPath.from_real(cli_args.record, root=cli_args.buildroot)
|
||||
parsed_record = parse_record(record_path, read_record(cli_args.record))
|
||||
save_parsed_record(record_path, parsed_record, cli_args.output)
|
||||
|
||||
|
||||
def argparser():
|
||||
parser = argparse.ArgumentParser()
|
||||
r = parser.add_argument_group("required arguments")
|
||||
r.add_argument("--buildroot", type=PosixPath, required=True)
|
||||
r.add_argument("--record", type=PosixPath, required=True)
|
||||
r.add_argument("--output", type=PosixPath, required=True)
|
||||
return parser
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
cli_args = argparser().parse_args()
|
||||
main(cli_args)
|
392
pyproject_save_files.py
Normal file
392
pyproject_save_files.py
Normal file
@ -0,0 +1,392 @@
|
||||
import argparse
|
||||
import fnmatch
|
||||
import json
|
||||
import os
|
||||
|
||||
from collections import defaultdict
|
||||
from pathlib import PosixPath, PurePosixPath
|
||||
|
||||
|
||||
class BuildrootPath(PurePosixPath):
|
||||
"""
|
||||
This path represents a path in a buildroot.
|
||||
When absolute, it is "relative" to a buildroot.
|
||||
|
||||
E.g. /usr/lib means %{buildroot}/usr/lib
|
||||
The object carries no buildroot information.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def from_real(realpath, *, root):
|
||||
"""
|
||||
For a given real disk path, return a BuildrootPath in the given root.
|
||||
|
||||
For example::
|
||||
|
||||
>>> BuildrootPath.from_real(PosixPath('/tmp/buildroot/foo'), root=PosixPath('/tmp/buildroot'))
|
||||
BuildrootPath('/foo')
|
||||
"""
|
||||
return BuildrootPath("/") / realpath.relative_to(root)
|
||||
|
||||
def to_real(self, root):
|
||||
"""
|
||||
Return a real PosixPath in the given root
|
||||
|
||||
For example::
|
||||
|
||||
>>> BuildrootPath('/foo').to_real(PosixPath('/tmp/buildroot'))
|
||||
PosixPath('/tmp/buildroot/foo')
|
||||
"""
|
||||
return root / self.relative_to("/")
|
||||
|
||||
def normpath(self):
|
||||
"""
|
||||
Normalize all the potential /../ parts of the path without touching real files.
|
||||
|
||||
PurePaths don't have .resolve().
|
||||
Paths have .resolve() but it touches real files.
|
||||
This is an alternative. It assumes there are no symbolic links.
|
||||
|
||||
Example:
|
||||
|
||||
>>> BuildrootPath('/usr/lib/python/../pypy').normpath()
|
||||
BuildrootPath('/usr/lib/pypy')
|
||||
"""
|
||||
return type(self)(os.path.normpath(self))
|
||||
|
||||
|
||||
def pycached(script, python_version):
|
||||
"""
|
||||
For a script BuildrootPath, return a list with that path and its bytecode glob.
|
||||
Like the %pycached macro.
|
||||
|
||||
The glob is represented as a BuildrootPath.
|
||||
|
||||
Examples:
|
||||
|
||||
>>> pycached(BuildrootPath('/whatever/bar.py'), '3.8')
|
||||
[BuildrootPath('/whatever/bar.py'), BuildrootPath('/whatever/__pycache__/bar.cpython-38{,.opt-?}.pyc')]
|
||||
|
||||
>>> pycached(BuildrootPath('/opt/python3.10/foo.py'), '3.10')
|
||||
[BuildrootPath('/opt/python3.10/foo.py'), BuildrootPath('/opt/python3.10/__pycache__/foo.cpython-310{,.opt-?}.pyc')]
|
||||
"""
|
||||
assert script.suffix == ".py"
|
||||
pyver = "".join(python_version.split(".")[:2])
|
||||
pycname = f"{script.stem}.cpython-{pyver}{{,.opt-?}}.pyc"
|
||||
pyc = script.parent / "__pycache__" / pycname
|
||||
return [script, pyc]
|
||||
|
||||
|
||||
def add_file_to_module(paths, module_name, module_type, *files):
|
||||
"""
|
||||
Helper procedure, adds given files to the module_name of a given module_type
|
||||
"""
|
||||
for module in paths["modules"][module_name]:
|
||||
if module["type"] == module_type:
|
||||
if files[0] not in module["files"]:
|
||||
module["files"].extend(files)
|
||||
break
|
||||
else:
|
||||
paths["modules"][module_name].append(
|
||||
{"type": module_type, "files": list(files)}
|
||||
)
|
||||
|
||||
|
||||
def add_lang_to_module(paths, module_name, path):
|
||||
"""
|
||||
Helper procedure, divides lang files by language and adds them to the module_name
|
||||
|
||||
Returns True if the language code detection was successful
|
||||
"""
|
||||
for i, parent in enumerate(path.parents):
|
||||
if i > 0 and parent.name == 'locale':
|
||||
lang_country_code = path.parents[i-1].name
|
||||
break
|
||||
else:
|
||||
return False
|
||||
# convert potential en_US to plain en
|
||||
lang_code = lang_country_code.partition('_')[0]
|
||||
if module_name not in paths["lang"]:
|
||||
paths["lang"].update({module_name: defaultdict(list)})
|
||||
paths["lang"][module_name][lang_code].append(path)
|
||||
return True
|
||||
|
||||
|
||||
def classify_paths(
|
||||
record_path, parsed_record_content, sitedirs, python_version
|
||||
):
|
||||
"""
|
||||
For each BuildrootPath in parsed_record_content classify it to a dict structure
|
||||
that allows to filter the files for the %files section easier.
|
||||
|
||||
For the dict structure, look at the beginning of this function's code.
|
||||
|
||||
Each "module" is a dict with "type" ("package", "script", "extension") and "files".
|
||||
"""
|
||||
distinfo = record_path.parent
|
||||
paths = {
|
||||
"metadata": {
|
||||
"files": [], # regular %file entries with dist-info content
|
||||
"dirs": [distinfo], # %dir %file entries with dist-info directory
|
||||
"docs": [], # to be used once there is upstream way to recognize READMEs
|
||||
"licenses": [], # to be used once there is upstream way to recognize LICENSEs
|
||||
},
|
||||
"lang": {}, # %lang entries: [module_name or None][language_code] lists of .mo files
|
||||
"modules": defaultdict(list), # each importable module (directory, .py, .so)
|
||||
"other": {"files": []}, # regular %file entries we could not parse :(
|
||||
}
|
||||
|
||||
# In RECORDs generated by pip, there are no directories, only files.
|
||||
# The example RECORD from PEP 376 does not contain directories either.
|
||||
# Hence, we'll only assume files, but TODO get it officially documented.
|
||||
for path in parsed_record_content:
|
||||
if path.suffix == ".pyc":
|
||||
# we handle bytecode separately
|
||||
continue
|
||||
|
||||
if path.parent == distinfo:
|
||||
if path.name in ("RECORD", "REQUESTED"):
|
||||
# RECORD and REQUESTED files are removed in %pyproject_install
|
||||
# See PEP 627
|
||||
continue
|
||||
# TODO is this a license/documentation?
|
||||
paths["metadata"]["files"].append(path)
|
||||
continue
|
||||
|
||||
for sitedir in sitedirs:
|
||||
if sitedir in path.parents:
|
||||
if path.parent == sitedir:
|
||||
if path.suffix == ".so":
|
||||
# extension modules can have 2 suffixes
|
||||
name = BuildrootPath(path.stem).stem
|
||||
add_file_to_module(paths, name, "extension", path)
|
||||
elif path.suffix == ".py":
|
||||
name = path.stem
|
||||
add_file_to_module(
|
||||
paths, name, "script", *pycached(path, python_version)
|
||||
)
|
||||
else:
|
||||
paths["other"]["files"].append(path)
|
||||
else:
|
||||
# this file is inside a dir, we classify that dir
|
||||
index = path.parents.index(sitedir)
|
||||
module_dir = path.parents[index - 1]
|
||||
add_file_to_module(paths, module_dir.name, "package", module_dir)
|
||||
if path.suffix == ".mo":
|
||||
add_lang_to_module(paths, module_dir.name, path)
|
||||
break
|
||||
else:
|
||||
if path.suffix == ".mo":
|
||||
add_lang_to_module(paths, None, path) or paths["other"]["files"].append(path)
|
||||
else:
|
||||
paths["other"]["files"].append(path)
|
||||
|
||||
return paths
|
||||
|
||||
|
||||
def generate_file_list(paths_dict, module_globs, include_others=False):
|
||||
"""
|
||||
This function takes the classified paths_dict and turns it into lines
|
||||
for the %files section. Returns list with text lines, no Path objects.
|
||||
|
||||
Only includes files from modules that match module_globs, metadata and
|
||||
optionaly all other files.
|
||||
|
||||
It asserts that all globs match at least one module, raises ValueError otherwise.
|
||||
Multiple globs matching identical module(s) are OK.
|
||||
"""
|
||||
files = set()
|
||||
|
||||
if include_others:
|
||||
files.update(f"{p}" for p in paths_dict["other"]["files"])
|
||||
try:
|
||||
for lang_code in paths_dict["lang"][None]:
|
||||
files.update(f"%lang({lang_code}) {path}" for path in paths_dict["lang"][None][lang_code])
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
files.update(f"{p}" for p in paths_dict["metadata"]["files"])
|
||||
for macro in "dir", "doc", "license":
|
||||
files.update(f"%{macro} {p}" for p in paths_dict["metadata"][f"{macro}s"])
|
||||
|
||||
modules = paths_dict["modules"]
|
||||
done_modules = set()
|
||||
done_globs = set()
|
||||
|
||||
for glob in module_globs:
|
||||
for name in modules:
|
||||
if fnmatch.fnmatchcase(name, glob):
|
||||
if name not in done_modules:
|
||||
try:
|
||||
for lang_code in paths_dict["lang"][name]:
|
||||
files.update(f"%lang({lang_code}) {path}" for path in paths_dict["lang"][name][lang_code])
|
||||
except KeyError:
|
||||
pass
|
||||
for module in modules[name]:
|
||||
if module["type"] == "package":
|
||||
files.update(f"{p}/" for p in module["files"])
|
||||
else:
|
||||
files.update(f"{p}" for p in module["files"])
|
||||
done_modules.add(name)
|
||||
done_globs.add(glob)
|
||||
|
||||
missed = module_globs - done_globs
|
||||
if missed:
|
||||
missed_text = ", ".join(sorted(missed))
|
||||
raise ValueError(f"Globs did not match any module: {missed_text}")
|
||||
|
||||
return sorted(files)
|
||||
|
||||
|
||||
def parse_varargs(varargs):
|
||||
"""
|
||||
Parse varargs from the %pyproject_save_files macro
|
||||
|
||||
Arguments starting with + are treated as a flags, everything else is a glob
|
||||
|
||||
Returns as set of globs, boolean flag whether to include all the other files
|
||||
|
||||
Raises ValueError for unknown flags and globs with dots (namespace packages).
|
||||
|
||||
Good examples:
|
||||
|
||||
>>> parse_varargs(['*'])
|
||||
({'*'}, False)
|
||||
|
||||
>>> mods, auto = parse_varargs(['requests*', 'kerberos', '+auto'])
|
||||
>>> auto
|
||||
True
|
||||
>>> sorted(mods)
|
||||
['kerberos', 'requests*']
|
||||
|
||||
>>> mods, auto = parse_varargs(['tldr', 'tensorf*'])
|
||||
>>> auto
|
||||
False
|
||||
>>> sorted(mods)
|
||||
['tensorf*', 'tldr']
|
||||
|
||||
>>> parse_varargs(['+auto'])
|
||||
(set(), True)
|
||||
|
||||
Bad examples:
|
||||
|
||||
>>> parse_varargs(['+kinkdir'])
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Invalid argument: +kinkdir
|
||||
|
||||
>>> parse_varargs(['good', '+bad', '*ugly*'])
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Invalid argument: +bad
|
||||
|
||||
>>> parse_varargs(['+bad', 'my.bad'])
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Invalid argument: +bad
|
||||
|
||||
>>> parse_varargs(['mod', 'mod.*'])
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Attempted to use a namespaced package with dot in the glob: mod.*. ...
|
||||
|
||||
>>> parse_varargs(['my.bad', '+bad'])
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Attempted to use a namespaced package with dot in the glob: my.bad. ...
|
||||
"""
|
||||
include_auto = False
|
||||
globs = set()
|
||||
|
||||
for arg in varargs:
|
||||
if arg.startswith("+"):
|
||||
if arg == "+auto":
|
||||
include_auto = True
|
||||
else:
|
||||
raise ValueError(f"Invalid argument: {arg}")
|
||||
elif "." in arg:
|
||||
top, *_ = arg.partition(".")
|
||||
msg = (
|
||||
f"Attempted to use a namespaced package with dot in the glob: {arg}. "
|
||||
f"That is not (yet) supported. Use {top} instead and/or file a Bugzilla explaining your use case."
|
||||
)
|
||||
raise ValueError(msg)
|
||||
else:
|
||||
globs.add(arg)
|
||||
|
||||
return globs, include_auto
|
||||
|
||||
|
||||
def load_parsed_record(pyproject_record):
|
||||
parsed_record = {}
|
||||
with open(pyproject_record) as pyproject_record_file:
|
||||
content = json.load(pyproject_record_file)
|
||||
|
||||
if len(content) > 1:
|
||||
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.")
|
||||
|
||||
# Redefine strings stored in JSON to BuildRootPaths
|
||||
for record_path, files in content.items():
|
||||
parsed_record[BuildrootPath(record_path)] = [BuildrootPath(f) for f in files]
|
||||
|
||||
return parsed_record
|
||||
|
||||
|
||||
def pyproject_save_files(buildroot, sitelib, sitearch, python_version, pyproject_record, varargs):
|
||||
"""
|
||||
Takes arguments from the %{pyproject_save_files} macro
|
||||
|
||||
Returns list of paths for the %files section
|
||||
"""
|
||||
# On 32 bit architectures, sitelib equals to sitearch
|
||||
# This saves us browsing one directory twice
|
||||
sitedirs = sorted({sitelib, sitearch})
|
||||
|
||||
globs, include_auto = parse_varargs(varargs)
|
||||
parsed_records = load_parsed_record(pyproject_record)
|
||||
|
||||
final_file_list = []
|
||||
|
||||
for record_path, files in parsed_records.items():
|
||||
paths_dict = classify_paths(
|
||||
record_path, files, sitedirs, python_version
|
||||
)
|
||||
|
||||
final_file_list.extend(
|
||||
generate_file_list(paths_dict, globs, include_auto)
|
||||
)
|
||||
|
||||
return final_file_list
|
||||
|
||||
|
||||
def main(cli_args):
|
||||
file_section = pyproject_save_files(
|
||||
cli_args.buildroot,
|
||||
cli_args.sitelib,
|
||||
cli_args.sitearch,
|
||||
cli_args.python_version,
|
||||
cli_args.pyproject_record,
|
||||
cli_args.varargs,
|
||||
)
|
||||
|
||||
cli_args.output.write_text("\n".join(file_section) + "\n", encoding="utf-8")
|
||||
|
||||
|
||||
def argparser():
|
||||
parser = argparse.ArgumentParser()
|
||||
r = parser.add_argument_group("required arguments")
|
||||
r.add_argument("--output", type=PosixPath, required=True)
|
||||
r.add_argument("--buildroot", type=PosixPath, required=True)
|
||||
r.add_argument("--sitelib", type=BuildrootPath, required=True)
|
||||
r.add_argument("--sitearch", type=BuildrootPath, required=True)
|
||||
r.add_argument("--python-version", type=str, required=True)
|
||||
r.add_argument("--pyproject-record", type=PosixPath, required=True)
|
||||
parser.add_argument("varargs", nargs="+")
|
||||
return parser
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
cli_args = argparser().parse_args()
|
||||
main(cli_args)
|
7388
pyproject_save_files_test_data.yaml
Normal file
7388
pyproject_save_files_test_data.yaml
Normal file
File diff suppressed because it is too large
Load Diff
11
test_RECORD
Normal file
11
test_RECORD
Normal file
@ -0,0 +1,11 @@
|
||||
../../../bin/__pycache__/tldr.cpython-37.pyc,,
|
||||
../../../bin/tldr,sha256=6MUiLCWhldmV8OelT2dvPgS7q5GFwuhvd6th0Bb-LH4,12766
|
||||
../../../bin/tldr.py,sha256=6MUiLCWhldmV8OelT2dvPgS7q5GFwuhvd6th0Bb-LH4,12766
|
||||
__pycache__/tldr.cpython-37.pyc,,
|
||||
tldr-0.5.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
tldr-0.5.dist-info/LICENSE,sha256=q7quAfjDWCYKC_WRk_uaP6d2wwVpOpVjUSkv8l6H7xI,1075
|
||||
tldr-0.5.dist-info/METADATA,sha256=AN5nYUVxo_zkVaMGKu34YDWWif84oA6uxKmTab213vM,3850
|
||||
tldr-0.5.dist-info/RECORD,,
|
||||
tldr-0.5.dist-info/WHEEL,sha256=S8S5VL-stOTSZDYxHyf0KP7eds0J72qrK0Evu3TfyAY,92
|
||||
tldr-0.5.dist-info/top_level.txt,sha256=xHSI9WD6Y-_hONbi2b_9RIn9oiO7RBGHU3A8geJq3mI,5
|
||||
tldr.py,sha256=aJlA3tIz4QYYy8e7DZUhPyLCqTwnfFjA7Nubwm9bPe0,12779
|
60
test_pyproject_buildrequires.py
Normal file
60
test_pyproject_buildrequires.py
Normal file
@ -0,0 +1,60 @@
|
||||
from pathlib import Path
|
||||
import io
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from pyproject_buildrequires import generate_requires
|
||||
|
||||
try:
|
||||
import importlib.metadata as importlib_metadata
|
||||
except ImportError:
|
||||
import importlib_metadata
|
||||
|
||||
testcases = {}
|
||||
with Path(__file__).parent.joinpath('pyproject_buildrequires_testcases.yaml').open() as f:
|
||||
testcases = yaml.safe_load(f)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('case_name', testcases)
|
||||
def test_data(case_name, capsys, tmp_path, monkeypatch):
|
||||
case = testcases[case_name]
|
||||
|
||||
cwd = tmp_path.joinpath('cwd')
|
||||
cwd.mkdir()
|
||||
monkeypatch.chdir(cwd)
|
||||
|
||||
if case.get('xfail'):
|
||||
pytest.xfail(case.get('xfail'))
|
||||
|
||||
for filename in 'pyproject.toml', 'setup.py', 'tox.ini':
|
||||
if filename in case:
|
||||
cwd.joinpath(filename).write_text(case[filename])
|
||||
|
||||
def get_installed_version(dist_name):
|
||||
try:
|
||||
return str(case['installed'][dist_name])
|
||||
except (KeyError, TypeError):
|
||||
raise importlib_metadata.PackageNotFoundError(
|
||||
f'info not found for {dist_name}'
|
||||
)
|
||||
|
||||
try:
|
||||
generate_requires(
|
||||
get_installed_version=get_installed_version,
|
||||
include_runtime=case.get('include_runtime', False),
|
||||
extras=case.get('extras', []),
|
||||
toxenv=case.get('toxenv', None),
|
||||
generate_extras=case.get('generate_extras', False),
|
||||
)
|
||||
except SystemExit as e:
|
||||
assert e.code == case['result']
|
||||
except Exception as e:
|
||||
if 'except' not in case:
|
||||
raise
|
||||
assert type(e).__name__ == case['except']
|
||||
else:
|
||||
assert 0 == case['result']
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert captured.out == case['expected']
|
205
test_pyproject_save_files.py
Executable file
205
test_pyproject_save_files.py
Executable file
@ -0,0 +1,205 @@
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from pathlib import Path
|
||||
from pprint import pprint
|
||||
|
||||
from pyproject_preprocess_record import parse_record, read_record, save_parsed_record
|
||||
|
||||
from pyproject_save_files import argparser, generate_file_list, BuildrootPath
|
||||
from pyproject_save_files import main as save_files_main
|
||||
|
||||
DIR = Path(__file__).parent
|
||||
BINDIR = BuildrootPath("/usr/bin")
|
||||
DATADIR = BuildrootPath("/usr/share")
|
||||
SITELIB = BuildrootPath("/usr/lib/python3.7/site-packages")
|
||||
SITEARCH = BuildrootPath("/usr/lib64/python3.7/site-packages")
|
||||
|
||||
yaml_file = DIR / "pyproject_save_files_test_data.yaml"
|
||||
yaml_data = yaml.safe_load(yaml_file.read_text())
|
||||
EXPECTED_DICT = yaml_data["classified"]
|
||||
EXPECTED_FILES = yaml_data["dumped"]
|
||||
TEST_RECORDS = yaml_data["records"]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tldr_root(tmp_path):
|
||||
prepare_pyproject_record(tmp_path, package="tldr")
|
||||
return tmp_path
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def pyproject_record(tmp_path):
|
||||
return tmp_path / "pyproject-record"
|
||||
|
||||
|
||||
def prepare_pyproject_record(tmp_path, package=None, content=None):
|
||||
"""
|
||||
Creates RECORD from test data and then uses
|
||||
functions from pyproject_process_record to convert
|
||||
it to pyproject-record file which is then
|
||||
further processed by functions from pyproject_save_files.
|
||||
"""
|
||||
record_file = tmp_path / "RECORD"
|
||||
pyproject_record = tmp_path / "pyproject-record"
|
||||
|
||||
if package is not None:
|
||||
# Get test data and write dist-info/RECORD file
|
||||
record_path = BuildrootPath(TEST_RECORDS[package]["path"])
|
||||
record_file.write_text(TEST_RECORDS[package]["content"])
|
||||
# Parse RECORD file
|
||||
parsed_record = parse_record(record_path, read_record(record_file))
|
||||
# Save JSON content to pyproject-record
|
||||
save_parsed_record(record_path, parsed_record, pyproject_record)
|
||||
elif content is not None:
|
||||
save_parsed_record(*content, output_file=pyproject_record)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def output(tmp_path):
|
||||
return tmp_path / "pyproject_files"
|
||||
|
||||
|
||||
def test_parse_record_tldr():
|
||||
record_path = BuildrootPath(TEST_RECORDS["tldr"]["path"])
|
||||
record_content = read_record(DIR / "test_RECORD")
|
||||
output = list(parse_record(record_path, record_content))
|
||||
pprint(output)
|
||||
expected = [
|
||||
str(BINDIR / "__pycache__/tldr.cpython-37.pyc"),
|
||||
str(BINDIR / "tldr"),
|
||||
str(BINDIR / "tldr.py"),
|
||||
str(SITELIB / "__pycache__/tldr.cpython-37.pyc"),
|
||||
str(SITELIB / "tldr-0.5.dist-info/INSTALLER"),
|
||||
str(SITELIB / "tldr-0.5.dist-info/LICENSE"),
|
||||
str(SITELIB / "tldr-0.5.dist-info/METADATA"),
|
||||
str(SITELIB / "tldr-0.5.dist-info/RECORD"),
|
||||
str(SITELIB / "tldr-0.5.dist-info/WHEEL"),
|
||||
str(SITELIB / "tldr-0.5.dist-info/top_level.txt"),
|
||||
str(SITELIB / "tldr.py"),
|
||||
]
|
||||
assert output == expected
|
||||
|
||||
|
||||
def test_parse_record_tensorflow():
|
||||
long = "tensorflow_core/include/tensorflow/core/common_runtime/base_collective_executor.h"
|
||||
record_path = SITEARCH / "tensorflow-2.1.0.dist-info/RECORD"
|
||||
record_content = [
|
||||
["../../../bin/toco_from_protos", "sha256=hello", "289"],
|
||||
[f"../../../lib/python3.7/site-packages/{long}", "sha256=darkness", "1024"],
|
||||
["tensorflow-2.1.0.dist-info/METADATA", "sha256=friend", "2859"],
|
||||
]
|
||||
output = list(parse_record(record_path, record_content))
|
||||
pprint(output)
|
||||
expected = [
|
||||
str(BINDIR / "toco_from_protos"),
|
||||
str(SITELIB / long),
|
||||
str(SITEARCH / "tensorflow-2.1.0.dist-info/METADATA"),
|
||||
]
|
||||
assert output == expected
|
||||
|
||||
|
||||
def remove_others(expected):
|
||||
return [p for p in expected if not (p.startswith(str(BINDIR)) or p.endswith(".pth") or p.rpartition(' ')[-1].startswith(str(DATADIR)))]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("include_auto", (True, False))
|
||||
@pytest.mark.parametrize("package, glob, expected", EXPECTED_FILES)
|
||||
def test_generate_file_list(package, glob, expected, include_auto):
|
||||
paths_dict = EXPECTED_DICT[package]
|
||||
modules_glob = {glob}
|
||||
if not include_auto:
|
||||
expected = remove_others(expected)
|
||||
tested = generate_file_list(paths_dict, modules_glob, include_auto)
|
||||
|
||||
assert tested == expected
|
||||
|
||||
|
||||
def test_generate_file_list_unused_glob():
|
||||
paths_dict = EXPECTED_DICT["kerberos"]
|
||||
modules_glob = {"kerberos", "unused_glob1", "unused_glob2", "kerb*"}
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
generate_file_list(paths_dict, modules_glob, True)
|
||||
|
||||
assert "unused_glob1, unused_glob2" in str(excinfo.value)
|
||||
assert "kerb" not in str(excinfo.value)
|
||||
|
||||
|
||||
def default_options(output, mock_root, pyproject_record):
|
||||
return [
|
||||
"--output",
|
||||
str(output),
|
||||
"--buildroot",
|
||||
str(mock_root),
|
||||
"--sitelib",
|
||||
str(SITELIB),
|
||||
"--sitearch",
|
||||
str(SITEARCH),
|
||||
"--python-version",
|
||||
"3.7", # test data are for 3.7,
|
||||
"--pyproject-record",
|
||||
str(pyproject_record)
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("include_auto", (True, False))
|
||||
@pytest.mark.parametrize("package, glob, expected", EXPECTED_FILES)
|
||||
def test_cli(tmp_path, package, glob, expected, include_auto, pyproject_record):
|
||||
prepare_pyproject_record(tmp_path, package)
|
||||
output = tmp_path / "files"
|
||||
globs = [glob, "+auto"] if include_auto else [glob]
|
||||
cli_args = argparser().parse_args([*default_options(output, tmp_path, pyproject_record), *globs])
|
||||
save_files_main(cli_args)
|
||||
|
||||
if not include_auto:
|
||||
expected = remove_others(expected)
|
||||
tested = output.read_text()
|
||||
assert tested == "\n".join(expected) + "\n"
|
||||
|
||||
|
||||
def test_cli_no_pyproject_record(tmp_path, pyproject_record):
|
||||
output = tmp_path / "files"
|
||||
cli_args = argparser().parse_args([*default_options(output, tmp_path, pyproject_record), "tldr*"])
|
||||
|
||||
with pytest.raises(FileNotFoundError):
|
||||
save_files_main(cli_args)
|
||||
|
||||
|
||||
def test_cli_too_many_RECORDS(tldr_root, output, pyproject_record):
|
||||
# Two calls to simulate how %pyproject_install process more than one RECORD file
|
||||
prepare_pyproject_record(tldr_root,
|
||||
content=("foo/bar/dist-info/RECORD", []))
|
||||
prepare_pyproject_record(tldr_root,
|
||||
content=("foo/baz/dist-info/RECORD", []))
|
||||
cli_args = argparser().parse_args([*default_options(output, tldr_root, pyproject_record), "tldr*"])
|
||||
|
||||
with pytest.raises(FileExistsError):
|
||||
save_files_main(cli_args)
|
||||
|
||||
|
||||
def test_cli_bad_argument(tldr_root, output, pyproject_record):
|
||||
cli_args = argparser().parse_args(
|
||||
[*default_options(output, tldr_root, pyproject_record), "tldr*", "+foodir"]
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
save_files_main(cli_args)
|
||||
|
||||
|
||||
def test_cli_bad_option(tldr_root, output, pyproject_record):
|
||||
prepare_pyproject_record(tldr_root.parent, content=("RECORD1", []))
|
||||
cli_args = argparser().parse_args(
|
||||
[*default_options(output, tldr_root, pyproject_record), "tldr*", "you_cannot_have_this"]
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
save_files_main(cli_args)
|
||||
|
||||
|
||||
def test_cli_bad_namespace(tldr_root, output, pyproject_record):
|
||||
cli_args = argparser().parse_args(
|
||||
[*default_options(output, tldr_root, pyproject_record), "tldr.didntread"]
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
save_files_main(cli_args)
|
49
tests/mocktest.sh
Executable file
49
tests/mocktest.sh
Executable file
@ -0,0 +1,49 @@
|
||||
#!/usr/bin/bash -eux
|
||||
. /etc/os-release
|
||||
fedora=$VERSION_ID
|
||||
|
||||
config="/tmp/fedora-${fedora}-x86_64-ci.cfg"
|
||||
|
||||
# create mock config if not present
|
||||
# this makes sure tested version of pyproject-rpm-macros is available
|
||||
# TODO: check if it has precedence if the release was not bumped in tested PR
|
||||
if [ ! -f $config ]; then
|
||||
original="/etc/mock/fedora-${fedora}-x86_64.cfg"
|
||||
cp $original $config
|
||||
|
||||
echo -e '\n\nconfig_opts[f"{config_opts.package_manager}.conf"] += """' >> $config
|
||||
|
||||
# The zuul CI has zuul-build.repo
|
||||
# The Jenkins CI has test-<pkgname>.repo
|
||||
# We run this code from various packages, so we support any <pkgname>
|
||||
if [ -f /etc/yum.repos.d/zuul-build.repo ]; then
|
||||
cat /etc/yum.repos.d/zuul-build.repo >> $config
|
||||
else
|
||||
cat /etc/yum.repos.d/test-*.repo >> $config
|
||||
fi
|
||||
echo -e '\n"""\n' >> $config
|
||||
fi
|
||||
|
||||
# prepare the rpmbuild folders, make sure nothing relevant is there
|
||||
mkdir -p ~/rpmbuild/{SOURCES,SRPMS}
|
||||
rm -f ~/rpmbuild/SRPMS/${1}-*.src.rpm
|
||||
|
||||
# download the sources and create SRPM
|
||||
spectool -g -R ${1}.spec
|
||||
rpmbuild -bs ${1}.spec
|
||||
|
||||
# build the SRPM in mock
|
||||
res=0
|
||||
mock -r $config --enablerepo=local init
|
||||
mock -r $config --enablerepo=local ~/rpmbuild/SRPMS/${1}-*.src.rpm || res=$?
|
||||
|
||||
# move the results to the artifacts directory, so we can examine them
|
||||
artifacts=${TEST_ARTIFACTS:-/tmp/artifacts}
|
||||
pushd /var/lib/mock/fedora-*-x86_64/result
|
||||
mv *.rpm ${artifacts}/ || :
|
||||
for log in *.log; do
|
||||
mv ${log} ${artifacts}/${1}-${log}
|
||||
done
|
||||
popd
|
||||
|
||||
exit $res
|
52
tests/printrun.spec
Normal file
52
tests/printrun.spec
Normal file
@ -0,0 +1,52 @@
|
||||
Name: printrun
|
||||
Version: 2.0.0~rc6
|
||||
%global upstream_version 2.0.0rc6
|
||||
Release: 0%{?dist}
|
||||
Summary: RepRap printer interface and tools
|
||||
License: GPLv3+ and FSFAP
|
||||
URL: https://github.com/kliment/Printrun
|
||||
Source0: https://github.com/kliment/Printrun/archive/%{name}-%{upstream_version}.tar.gz
|
||||
|
||||
# fix locale location
|
||||
Patch0: https://github.com/kliment/Printrun/pull/1101.patch
|
||||
|
||||
BuildRequires: pyproject-rpm-macros
|
||||
BuildRequires: python3-devel
|
||||
BuildRequires: gcc
|
||||
|
||||
%description
|
||||
This package contains lang files outside of printrun module.
|
||||
Building this tests that lang files are marked with %%lang in filelist.
|
||||
|
||||
|
||||
%prep
|
||||
%autosetup -p1 -n Printrun-printrun-%{upstream_version}
|
||||
|
||||
|
||||
%generate_buildrequires
|
||||
%pyproject_buildrequires
|
||||
|
||||
|
||||
%build
|
||||
%pyproject_wheel
|
||||
|
||||
|
||||
%install
|
||||
%pyproject_install
|
||||
%pyproject_save_files printrun +auto
|
||||
|
||||
|
||||
%check
|
||||
# Internal check if generated lang entries are same as
|
||||
# the ones generated using %%find_lang
|
||||
%find_lang pronterface
|
||||
%find_lang plater
|
||||
|
||||
grep '^%%lang' %{pyproject_files} | sort > tested.lang
|
||||
sort pronterface.lang plater.lang > expected.lang
|
||||
diff tested.lang expected.lang
|
||||
|
||||
|
||||
%files -f %{pyproject_files}
|
||||
%doc README*
|
||||
%license COPYING
|
51
tests/python-clikit.spec
Normal file
51
tests/python-clikit.spec
Normal file
@ -0,0 +1,51 @@
|
||||
%global pypi_name clikit
|
||||
Name: python-%{pypi_name}
|
||||
Version: 0.3.1
|
||||
Release: 1%{?dist}
|
||||
Summary: Builds beautiful and testable command line interfaces
|
||||
|
||||
License: MIT
|
||||
URL: https://github.com/sdispater/clikit
|
||||
Source0: %{pypi_source}
|
||||
|
||||
BuildArch: noarch
|
||||
BuildRequires: pyproject-rpm-macros
|
||||
|
||||
%description
|
||||
Tests building with the poetry build backend.
|
||||
|
||||
|
||||
%package -n python3-%{pypi_name}
|
||||
Summary: %{summary}
|
||||
|
||||
%description -n python3-%{pypi_name}
|
||||
%{summary}.
|
||||
|
||||
|
||||
%prep
|
||||
%autosetup -p1 -n %{pypi_name}-%{version}
|
||||
|
||||
|
||||
%generate_buildrequires
|
||||
%pyproject_buildrequires
|
||||
|
||||
|
||||
%build
|
||||
%pyproject_wheel
|
||||
|
||||
|
||||
%install
|
||||
%pyproject_install
|
||||
|
||||
%check
|
||||
# Internal check that the RECORD and REQUESTED files are
|
||||
# always removed in %%pyproject_wheel
|
||||
test ! $(find %{buildroot}%{python3_sitelib}/ | grep -E "\.dist-info/RECORD$")
|
||||
test ! $(find %{buildroot}%{python3_sitelib}/ | grep -E "\.dist-info/REQUESTED$")
|
||||
|
||||
|
||||
%files -n python3-%{pypi_name}
|
||||
%doc README.md
|
||||
%license LICENSE
|
||||
%{python3_sitelib}/%{pypi_name}/
|
||||
%{python3_sitelib}/%{pypi_name}-%{version}.dist-info/
|
50
tests/python-distroinfo.spec
Normal file
50
tests/python-distroinfo.spec
Normal file
@ -0,0 +1,50 @@
|
||||
Name: python-distroinfo
|
||||
Version: 0.3.2
|
||||
Release: 0%{?dist}
|
||||
Summary: Parsing and querying distribution metadata stored in text/YAML files
|
||||
License: ASL 2.0
|
||||
URL: https://github.com/softwarefactory-project/distroinfo
|
||||
Source0: %{pypi_source distroinfo}
|
||||
BuildArch: noarch
|
||||
|
||||
BuildRequires: pyproject-rpm-macros
|
||||
BuildRequires: python3-devel
|
||||
BuildRequires: python3-pytest
|
||||
BuildRequires: git-core
|
||||
|
||||
%description
|
||||
This package uses setuptools and pbr.
|
||||
It has setup_requires and tests that %%pyproject_buildrequires correctly
|
||||
handles that including runtime requirements.
|
||||
|
||||
%package -n python3-distroinfo
|
||||
Summary: %{summary}
|
||||
|
||||
%description -n python3-distroinfo
|
||||
...
|
||||
|
||||
|
||||
%prep
|
||||
%autosetup -p1 -n distroinfo-%{version}
|
||||
|
||||
|
||||
%generate_buildrequires
|
||||
%pyproject_buildrequires -r
|
||||
|
||||
|
||||
%build
|
||||
%pyproject_wheel
|
||||
|
||||
|
||||
%install
|
||||
%pyproject_install
|
||||
%pyproject_save_files distroinfo
|
||||
|
||||
|
||||
%check
|
||||
%pytest
|
||||
|
||||
|
||||
%files -n python3-distroinfo -f %{pyproject_files}
|
||||
%doc README.rst AUTHORS
|
||||
%license LICENSE
|
67
tests/python-django.spec
Normal file
67
tests/python-django.spec
Normal file
@ -0,0 +1,67 @@
|
||||
Name: python-django
|
||||
Version: 3.0.7
|
||||
Release: 0%{?dist}
|
||||
Summary: A high-level Python Web framework
|
||||
License: BSD
|
||||
URL: https://www.djangoproject.com/
|
||||
Source0: %{pypi_source Django}
|
||||
BuildArch: noarch
|
||||
|
||||
BuildRequires: pyproject-rpm-macros
|
||||
BuildRequires: python3-devel
|
||||
|
||||
%description
|
||||
This package contains lang files.
|
||||
Building this tests that lang files are marked with %%lang in filelist.
|
||||
|
||||
|
||||
%package -n python3-django
|
||||
Summary: %{summary}
|
||||
|
||||
%description -n python3-django
|
||||
...
|
||||
|
||||
|
||||
%prep
|
||||
%autosetup -p1 -n Django-%{version}
|
||||
%py3_shebang_fix django/conf/project_template/manage.py-tpl django/bin/django-admin.py
|
||||
|
||||
%if 0%{?fedora} < 32 && 0%{?rhel} < 9
|
||||
# Python RPM dependency generator doesn't support ~= yet
|
||||
# https://bugzilla.redhat.com/show_bug.cgi?id=1758141
|
||||
sed -i 's/asgiref ~= /asgiref >= /' setup.py
|
||||
%endif
|
||||
|
||||
|
||||
%generate_buildrequires
|
||||
%pyproject_buildrequires
|
||||
|
||||
|
||||
%build
|
||||
%pyproject_wheel
|
||||
|
||||
|
||||
%install
|
||||
%pyproject_install
|
||||
%pyproject_save_files django
|
||||
|
||||
# remove .po files
|
||||
find %{buildroot} -name "*.po" | xargs rm -f
|
||||
|
||||
|
||||
%check
|
||||
# Internal check if generated lang entries are same as
|
||||
# the ones generated using %%find_lang
|
||||
%find_lang django
|
||||
%find_lang djangojs
|
||||
|
||||
grep '^%%lang' %{pyproject_files} | sort > tested.lang
|
||||
sort django.lang djangojs.lang > expected.lang
|
||||
diff tested.lang expected.lang
|
||||
|
||||
|
||||
%files -n python3-django -f %{pyproject_files}
|
||||
%doc README.rst
|
||||
%license LICENSE
|
||||
%{_bindir}/django-admin
|
||||
%{_bindir}/django-admin.py
|
73
tests/python-dns-lexicon.spec
Normal file
73
tests/python-dns-lexicon.spec
Normal file
@ -0,0 +1,73 @@
|
||||
Name: python-dns-lexicon
|
||||
Version: 3.4.0
|
||||
Release: 0%{?dist}
|
||||
Summary: Manipulate DNS records on various DNS providers in a standardized/agnostic way
|
||||
License: MIT
|
||||
URL: https://github.com/AnalogJ/lexicon
|
||||
Source0: %{url}/archive/v%{version}/lexicon-%{version}.tar.gz
|
||||
BuildArch: noarch
|
||||
|
||||
BuildRequires: pyproject-rpm-macros
|
||||
BuildRequires: python3-devel
|
||||
|
||||
%description
|
||||
This package has extras specified in tox configuration,
|
||||
we test that the extras are installed when -e is used.
|
||||
This package also uses a custom toxenv and creates several extras subpackages.
|
||||
|
||||
|
||||
%package -n python3-dns-lexicon
|
||||
Summary: %{summary}
|
||||
|
||||
%description -n python3-dns-lexicon
|
||||
...
|
||||
|
||||
|
||||
%pyproject_extras_subpackage -n python3-dns-lexicon plesk route53
|
||||
|
||||
|
||||
%prep
|
||||
%autosetup -n lexicon-%{version}
|
||||
# The tox configuration lists a [dev] extra, but that installs nothing (is missing).
|
||||
# The test requirements are only specified via poetry.dev-dependencies.
|
||||
# Here we amend the data a bit so we can test more things, adding the tests deps to the dev extra:
|
||||
sed -i \
|
||||
's/\[tool.poetry.extras\]/'\
|
||||
'pytest = {version = ">3", optional = true}\n'\
|
||||
'vcrpy = {version = ">1", optional = true}\n\n'\
|
||||
'[tool.poetry.extras]\n'\
|
||||
'dev = ["pytest", "vcrpy"]/' pyproject.toml
|
||||
|
||||
|
||||
%generate_buildrequires
|
||||
%if 0%{?fedora} >= 33 || 0%{?rhel} >= 9
|
||||
# We use the "light" toxenv because the default one installs the [full] extra and we don't have all the deps.
|
||||
# Note that [full] contains [plesk] and [route53] but we specify them manually instead:
|
||||
%pyproject_buildrequires -e light -x plesk -x route53
|
||||
%else
|
||||
# older Fedoras don't have the required runtime dependencies, so we don't test it there
|
||||
%pyproject_buildrequires
|
||||
%endif
|
||||
|
||||
|
||||
%build
|
||||
%pyproject_wheel
|
||||
|
||||
|
||||
%install
|
||||
%pyproject_install
|
||||
%pyproject_save_files lexicon
|
||||
|
||||
|
||||
%if 0%{?fedora} >= 33 || 0%{?rhel} >= 9
|
||||
%check
|
||||
# we cannot use %%tox here, because the configured commands call poetry directly :/
|
||||
# we use %%pytest instead, running a subset of tests not to waste CI time
|
||||
%pytest -k "test_route53 or test_plesk"
|
||||
%endif
|
||||
|
||||
|
||||
%files -n python3-dns-lexicon -f %{pyproject_files}
|
||||
%license LICENSE
|
||||
%doc README.rst
|
||||
%{_bindir}/lexicon
|
51
tests/python-entrypoints.spec
Normal file
51
tests/python-entrypoints.spec
Normal file
@ -0,0 +1,51 @@
|
||||
%global pypi_name entrypoints
|
||||
Name: python-%{pypi_name}
|
||||
Version: 0.3
|
||||
Release: 0%{?dist}
|
||||
Summary: Discover and load entry points from installed packages
|
||||
License: MIT
|
||||
URL: https://entrypoints.readthedocs.io/
|
||||
Source0: %{pypi_source}
|
||||
|
||||
BuildArch: noarch
|
||||
BuildRequires: pyproject-rpm-macros
|
||||
|
||||
%description
|
||||
This package contains one .py module
|
||||
Building this tests the flit build backend.
|
||||
|
||||
|
||||
%package -n python3-%{pypi_name}
|
||||
Summary: %{summary}
|
||||
|
||||
%description -n python3-%{pypi_name}
|
||||
%{summary}.
|
||||
|
||||
|
||||
%prep
|
||||
%autosetup -p1 -n %{pypi_name}-%{version}
|
||||
|
||||
|
||||
%generate_buildrequires
|
||||
%pyproject_buildrequires
|
||||
|
||||
|
||||
%build
|
||||
%pyproject_wheel
|
||||
|
||||
|
||||
%install
|
||||
%pyproject_install
|
||||
%pyproject_save_files entrypoints
|
||||
|
||||
|
||||
%check
|
||||
# Internal check: Top level __pycache__ is never owned
|
||||
grep -vE '/__pycache__$' %{pyproject_files}
|
||||
grep -vE '/__pycache__/$' %{pyproject_files}
|
||||
grep -F '/__pycache__/' %{pyproject_files}
|
||||
|
||||
|
||||
%files -n python3-%{pypi_name} -f %{pyproject_files}
|
||||
%doc README.rst
|
||||
%license LICENSE
|
47
tests/python-flit-core.spec
Normal file
47
tests/python-flit-core.spec
Normal file
@ -0,0 +1,47 @@
|
||||
Name: python-flit-core
|
||||
Version: 3.0.0
|
||||
Release: 0%{?dist}
|
||||
Summary: Distribution-building parts of Flit
|
||||
|
||||
License: BSD
|
||||
URL: https://pypi.org/project/flit-core/
|
||||
Source0: %{pypi_source flit_core}
|
||||
|
||||
BuildArch: noarch
|
||||
BuildRequires: python3-devel
|
||||
BuildRequires: pyproject-rpm-macros
|
||||
|
||||
%description
|
||||
Test a build with pyproject.toml backend-path = .
|
||||
flit-core builds with flit-core.
|
||||
|
||||
|
||||
%package -n python3-flit-core
|
||||
Summary: %{summary}
|
||||
|
||||
%description -n python3-flit-core
|
||||
...
|
||||
|
||||
|
||||
%prep
|
||||
%autosetup -p1 -n flit_core-%{version}
|
||||
|
||||
|
||||
%generate_buildrequires
|
||||
%pyproject_buildrequires
|
||||
|
||||
|
||||
%build
|
||||
%if 0%{?fedora} < 33 && 0%{?rhel} < 9
|
||||
# the old pip version cannot handle backend-path properly, let's help it:
|
||||
export PYTHONPATH=$PWD
|
||||
%endif
|
||||
%pyproject_wheel
|
||||
|
||||
|
||||
%install
|
||||
%pyproject_install
|
||||
%pyproject_save_files flit_core
|
||||
|
||||
|
||||
%files -n python3-flit-core -f %{pyproject_files}
|
66
tests/python-httpbin.spec
Normal file
66
tests/python-httpbin.spec
Normal file
@ -0,0 +1,66 @@
|
||||
Name: python-httpbin
|
||||
Version: 0.7.0
|
||||
Release: 0%{?dist}
|
||||
Summary: HTTP Request & Response Service, written in Python + Flask
|
||||
License: MIT
|
||||
URL: https://github.com/Runscope/httpbin
|
||||
Source0: %{url}/archive/v%{version}/httpbin-%{version}.tar.gz
|
||||
BuildArch: noarch
|
||||
|
||||
BuildRequires: python3-devel
|
||||
BuildRequires: pyproject-rpm-macros
|
||||
|
||||
%description
|
||||
This package buildrequires a package with extra: raven[flask].
|
||||
|
||||
|
||||
%package -n python3-httpbin
|
||||
Summary: %{summary}
|
||||
|
||||
%if 0%{?fedora} < 33 && 0%{?rhel} < 9
|
||||
# Old Fedoras don't understand Python extras yet
|
||||
# This package needs raven[flask]
|
||||
# So we add the transitive dependencies manually:
|
||||
BuildRequires: %{py3_dist blinker flask}
|
||||
Requires: %{py3_dist blinker flask}
|
||||
%endif
|
||||
|
||||
%description -n python3-httpbin
|
||||
%{summary}.
|
||||
|
||||
|
||||
%prep
|
||||
%autosetup -n httpbin-%{version}
|
||||
|
||||
# brotlipy wrapper is not packaged, httpbin works fine with brotli
|
||||
sed -i s/brotlipy/brotli/ setup.py
|
||||
|
||||
# update test_httpbin.py to reflect new behavior of werkzeug
|
||||
sed -i /Content-Length/d test_httpbin.py
|
||||
|
||||
|
||||
%generate_buildrequires
|
||||
%pyproject_buildrequires -t
|
||||
|
||||
|
||||
%build
|
||||
%pyproject_wheel
|
||||
|
||||
|
||||
%install
|
||||
%pyproject_install
|
||||
%pyproject_save_files httpbin
|
||||
|
||||
|
||||
%check
|
||||
%tox
|
||||
|
||||
# Internal check for our macros
|
||||
# The runtime dependencies contain raven[flask], we assert we got them.
|
||||
# The %%tox above also dies without it, but this makes it more explicit
|
||||
%{python3} -c 'import blinker, flask' # transitive deps
|
||||
|
||||
|
||||
%files -n python3-httpbin -f %{pyproject_files}
|
||||
%doc README*
|
||||
%license LICENSE*
|
41
tests/python-ipykernel.spec
Normal file
41
tests/python-ipykernel.spec
Normal file
@ -0,0 +1,41 @@
|
||||
Name: python-ipykernel
|
||||
Version: 5.2.1
|
||||
Release: 0%{?dist}
|
||||
Summary: IPython Kernel for Jupyter
|
||||
License: BSD
|
||||
URL: https://github.com/ipython/ipykernel
|
||||
Source0: https://github.com/ipython/ipykernel/archive/v%{version}/ipykernel-%{version}.tar.gz
|
||||
|
||||
BuildArch: noarch
|
||||
|
||||
BuildRequires: pyproject-rpm-macros
|
||||
BuildRequires: python3-devel
|
||||
|
||||
%description
|
||||
This package contains data files.
|
||||
Building this tests that data files are not listed when +auto is not used
|
||||
with %%pyproject_save_files.
|
||||
|
||||
%package -n python3-ipykernel
|
||||
Summary: %{summary}
|
||||
|
||||
%description -n python3-ipykernel
|
||||
...
|
||||
|
||||
%prep
|
||||
%autosetup -p1 -n ipykernel-%{version}
|
||||
|
||||
%generate_buildrequires
|
||||
%pyproject_buildrequires
|
||||
|
||||
%build
|
||||
%pyproject_wheel
|
||||
|
||||
%install
|
||||
%pyproject_install
|
||||
%pyproject_save_files 'ipykernel*' +auto
|
||||
|
||||
%files -n python3-ipykernel -f %{pyproject_files}
|
||||
%license COPYING.md
|
||||
%doc README.md
|
||||
|
55
tests/python-isort.spec
Normal file
55
tests/python-isort.spec
Normal file
@ -0,0 +1,55 @@
|
||||
%global modname isort
|
||||
|
||||
Name: python-%{modname}
|
||||
Version: 4.3.21
|
||||
Release: 7%{?dist}
|
||||
Summary: Python utility / library to sort Python imports
|
||||
|
||||
License: MIT
|
||||
URL: https://github.com/timothycrosley/%{modname}
|
||||
Source0: %{url}/archive/%{version}-2/%{modname}-%{version}-2.tar.gz
|
||||
BuildArch: noarch
|
||||
BuildRequires: pyproject-rpm-macros
|
||||
|
||||
%description
|
||||
This package contains executables.
|
||||
Building this tests that executables are not listed when +auto is not used
|
||||
with %%pyproject_save_files.
|
||||
|
||||
%package -n python3-%{modname}
|
||||
Summary: %{summary}
|
||||
|
||||
%description -n python3-%{modname}
|
||||
%{summary}.
|
||||
|
||||
|
||||
%prep
|
||||
%autosetup -n %{modname}-%{version}-2
|
||||
|
||||
|
||||
%generate_buildrequires
|
||||
%pyproject_buildrequires -r
|
||||
|
||||
|
||||
%build
|
||||
%pyproject_wheel
|
||||
|
||||
|
||||
%install
|
||||
%pyproject_install
|
||||
%pyproject_save_files isort
|
||||
|
||||
|
||||
%check
|
||||
# Internal check if the instalation outputs expected result
|
||||
test -d %{buildroot}%{python3_sitelib}/%{modname}/
|
||||
test -d %{buildroot}%{python3_sitelib}/%{modname}-%{version}.dist-info/
|
||||
|
||||
# Internal check that executables are not present when +auto was not used with %%pyproject_save_files
|
||||
grep -vF %{buildroot}%{_bindir}/%{modname} %{pyproject_files}
|
||||
|
||||
|
||||
%files -n python3-%{modname} -f %{pyproject_files}
|
||||
%doc README.rst *.md
|
||||
%license LICENSE
|
||||
%{_bindir}/%{modname}
|
81
tests/python-ldap.spec
Normal file
81
tests/python-ldap.spec
Normal file
@ -0,0 +1,81 @@
|
||||
Name: python-ldap
|
||||
Version: 3.3.0
|
||||
Release: 0%{?dist}
|
||||
License: Python
|
||||
Summary: An object-oriented API to access LDAP directory servers
|
||||
Source0: %{pypi_source}
|
||||
|
||||
BuildRequires: pyproject-rpm-macros
|
||||
|
||||
BuildRequires: cyrus-sasl-devel
|
||||
BuildRequires: gcc
|
||||
BuildRequires: openldap-clients
|
||||
BuildRequires: openldap-devel
|
||||
BuildRequires: openldap-servers
|
||||
BuildRequires: openssl-devel
|
||||
|
||||
|
||||
%description
|
||||
This package contains extension modules. Does not contain pyproject.toml.
|
||||
Has multiple files and directories.
|
||||
Building this tests:
|
||||
- the proper files are installed in the proper places
|
||||
- module glob in %%pyproject_save_files (some modules are included, some not)
|
||||
- combined manual and generated Buildrequires
|
||||
|
||||
|
||||
%package -n python3-ldap
|
||||
Summary: %{summary}
|
||||
|
||||
%description -n python3-ldap
|
||||
%{summary}
|
||||
|
||||
|
||||
%prep
|
||||
%autosetup
|
||||
|
||||
|
||||
%generate_buildrequires
|
||||
%pyproject_buildrequires -t
|
||||
|
||||
|
||||
%build
|
||||
%pyproject_wheel
|
||||
|
||||
|
||||
%install
|
||||
%pyproject_install
|
||||
# We can pass multiple globs
|
||||
%pyproject_save_files 'ldap*' '*ldap'
|
||||
|
||||
|
||||
%check
|
||||
%tox
|
||||
|
||||
# Internal check if the instalation outputs expected files
|
||||
test -d %{buildroot}%{python3_sitearch}/__pycache__/
|
||||
test -d %{buildroot}%{python3_sitearch}/python_ldap-%{version}.dist-info/
|
||||
test -d %{buildroot}%{python3_sitearch}/ldap/
|
||||
test -f %{buildroot}%{python3_sitearch}/ldapurl.py
|
||||
test -f %{buildroot}%{python3_sitearch}/ldif.py
|
||||
test -d %{buildroot}%{python3_sitearch}/slapdtest/
|
||||
test -f %{buildroot}%{python3_sitearch}/_ldap.cpython-*.so
|
||||
|
||||
# Internal check: Unmatched modules are not supposed to be listed in %%{pyproject_files}
|
||||
# We'll list them explicitly
|
||||
grep -vF %{python3_sitearch}/ldif.py %{pyproject_files}
|
||||
grep -vF %{python3_sitearch}/__pycache__/ldif.cpython-%{python3_version_nodots}.pyc %{pyproject_files}
|
||||
grep -vF %{python3_sitearch}/__pycache__/ldif.cpython-%{python3_version_nodots}.opt-1.pyc %{pyproject_files}
|
||||
grep -vF %{python3_sitearch}/slapdtest/ %{pyproject_files}
|
||||
|
||||
# Internal check: Top level __pycache__ is never owned
|
||||
grep -vE '/__pycache__$' %{pyproject_files}
|
||||
grep -vE '/__pycache__/$' %{pyproject_files}
|
||||
|
||||
|
||||
%files -n python3-ldap -f %{pyproject_files}
|
||||
%license LICENCE
|
||||
%doc CHANGES README TODO Demo
|
||||
# Explicitly listed files can be combined with automation
|
||||
%pycached %{python3_sitearch}/ldif.py
|
||||
%{python3_sitearch}/slapdtest/
|
58
tests/python-mistune.spec
Normal file
58
tests/python-mistune.spec
Normal file
@ -0,0 +1,58 @@
|
||||
Name: python-mistune
|
||||
Version: 0.8.3
|
||||
Release: 11%{?dist}
|
||||
Summary: Markdown parser for Python
|
||||
|
||||
License: BSD
|
||||
URL: https://github.com/lepture/mistune
|
||||
Source0: %{url}/archive/v%{version}.tar.gz
|
||||
|
||||
BuildRequires: gcc
|
||||
BuildRequires: pyproject-rpm-macros
|
||||
|
||||
# optional dependency, listed explicitly to have the extension module:
|
||||
BuildRequires: python3-Cython
|
||||
|
||||
%description
|
||||
This package contains an extension module. Does not contain pyproject.toml.
|
||||
Has a script (.py) and extension (.so) with identical name.
|
||||
Building this tests:
|
||||
- installing both a script and an extension with the same name
|
||||
- default build backend without pyproject.toml
|
||||
|
||||
|
||||
%package -n python3-mistune
|
||||
Summary: %summary
|
||||
|
||||
%description -n python3-mistune
|
||||
%{summary}
|
||||
|
||||
|
||||
%prep
|
||||
%autosetup -n mistune-%{version}
|
||||
|
||||
|
||||
%generate_buildrequires
|
||||
%pyproject_buildrequires
|
||||
|
||||
|
||||
%build
|
||||
%pyproject_wheel
|
||||
|
||||
|
||||
%install
|
||||
%pyproject_install
|
||||
%pyproject_save_files mistune
|
||||
|
||||
|
||||
%check
|
||||
# Internal check for our macros
|
||||
# making sure that pyproject_install outputs these files so that we can test behaviour of %%pyproject_save_files
|
||||
# when a package has multiple files with the same name (here script and extension)
|
||||
test -f %{buildroot}%{python3_sitearch}/mistune.py
|
||||
test -f %{buildroot}%{python3_sitearch}/mistune.cpython-*.so
|
||||
|
||||
|
||||
%files -n python3-mistune -f %{pyproject_files}
|
||||
%doc README.rst
|
||||
%license LICENSE
|
52
tests/python-openqa_client.spec
Normal file
52
tests/python-openqa_client.spec
Normal file
@ -0,0 +1,52 @@
|
||||
%global pypi_name openqa_client
|
||||
Name: python-%{pypi_name}
|
||||
Version: 4.0.0
|
||||
Release: 1%{?dist}
|
||||
Summary: Python client library for openQA API
|
||||
|
||||
License: GPLv2+
|
||||
URL: https://github.com/os-autoinst/openQA-python-client
|
||||
Source0: %{pypi_source}
|
||||
|
||||
BuildArch: noarch
|
||||
BuildRequires: pyproject-rpm-macros
|
||||
|
||||
%description
|
||||
This package uses tox.ini file with recursive deps (via the -r option).
|
||||
|
||||
|
||||
%package -n python3-%{pypi_name}
|
||||
Summary: %{summary}
|
||||
|
||||
%description -n python3-%{pypi_name}
|
||||
%{summary}.
|
||||
|
||||
|
||||
%prep
|
||||
%autosetup -p1 -n %{pypi_name}-%{version}
|
||||
# setuptools-git is needed to build the source distribution, but not
|
||||
# for packaging, which *starts* from the source distribution
|
||||
# we sed it out to save ourselves a dependency, but that is not strictly required
|
||||
sed -i -e 's., "setuptools-git"..g' pyproject.toml
|
||||
|
||||
|
||||
%generate_buildrequires
|
||||
%pyproject_buildrequires -t
|
||||
|
||||
|
||||
%build
|
||||
%pyproject_wheel
|
||||
|
||||
|
||||
%install
|
||||
%pyproject_install
|
||||
%pyproject_save_files %{pypi_name}
|
||||
|
||||
|
||||
%check
|
||||
%tox
|
||||
|
||||
|
||||
%files -n python3-%{pypi_name} -f %{pyproject_files}
|
||||
%doc README.*
|
||||
%license COPYING
|
54
tests/python-pluggy.spec
Normal file
54
tests/python-pluggy.spec
Normal file
@ -0,0 +1,54 @@
|
||||
%global pypi_name pluggy
|
||||
Name: python-%{pypi_name}
|
||||
Version: 0.13.0
|
||||
Release: 1%{?dist}
|
||||
Summary: The plugin manager stripped of pytest specific details
|
||||
|
||||
License: MIT
|
||||
URL: https://github.com/pytest-dev/pluggy
|
||||
Source0: %{pypi_source}
|
||||
|
||||
BuildArch: noarch
|
||||
BuildRequires: pyproject-rpm-macros
|
||||
|
||||
%description
|
||||
A pure Python library. The package contains tox.ini. Does not contain executables.
|
||||
Building this tests:
|
||||
- generating runtime and testing dependencies
|
||||
- running tests with %%tox
|
||||
- the %%pyproject_save_files +auto option works without actual executables
|
||||
- pyproject.toml with the setuptools backend and setuptools-scm
|
||||
|
||||
|
||||
%package -n python3-%{pypi_name}
|
||||
Summary: %{summary}
|
||||
|
||||
%description -n python3-%{pypi_name}
|
||||
%{summary}.
|
||||
|
||||
|
||||
%prep
|
||||
%autosetup -p1 -n %{pypi_name}-%{version}
|
||||
|
||||
|
||||
%generate_buildrequires
|
||||
%pyproject_buildrequires -t
|
||||
|
||||
|
||||
%build
|
||||
%pyproject_wheel
|
||||
|
||||
|
||||
%install
|
||||
%pyproject_install
|
||||
# There are no executables, but we are allowed to pass +auto anyway
|
||||
%pyproject_save_files pluggy +auto
|
||||
|
||||
|
||||
%check
|
||||
%tox
|
||||
|
||||
|
||||
%files -n python3-%{pypi_name} -f %{pyproject_files}
|
||||
%doc README.rst
|
||||
%license LICENSE
|
49
tests/python-poetry-core.spec
Normal file
49
tests/python-poetry-core.spec
Normal file
@ -0,0 +1,49 @@
|
||||
Name: python-poetry-core
|
||||
Version: 1.0.0
|
||||
Release: 0%{?dist}
|
||||
Summary: Poetry PEP 517 Build Backend
|
||||
|
||||
License: MIT
|
||||
URL: https://pypi.org/project/poetry-core/
|
||||
Source0: %{pypi_source poetry-core}
|
||||
|
||||
BuildArch: noarch
|
||||
BuildRequires: python3-devel
|
||||
BuildRequires: pyproject-rpm-macros
|
||||
|
||||
%description
|
||||
Test a build with pyproject.toml backend-path = [.]
|
||||
poetry-core builds with poetry-core.
|
||||
|
||||
|
||||
%package -n python3-poetry-core
|
||||
Summary: %{summary}
|
||||
|
||||
%description -n python3-poetry-core
|
||||
...
|
||||
|
||||
|
||||
%prep
|
||||
%autosetup -p1 -n poetry-core-%{version}
|
||||
|
||||
|
||||
%generate_buildrequires
|
||||
%pyproject_buildrequires
|
||||
|
||||
|
||||
%build
|
||||
%if 0%{?fedora} < 33 && 0%{?rhel} < 9
|
||||
# the old pip version cannot handle backend-path properly, let's help it:
|
||||
export PYTHONPATH=$PWD
|
||||
%endif
|
||||
%pyproject_wheel
|
||||
|
||||
|
||||
%install
|
||||
%pyproject_install
|
||||
%pyproject_save_files poetry
|
||||
|
||||
|
||||
%files -n python3-poetry-core -f %{pyproject_files}
|
||||
%doc README.md
|
||||
%license LICENSE
|
53
tests/python-pytest.spec
Normal file
53
tests/python-pytest.spec
Normal file
@ -0,0 +1,53 @@
|
||||
%global pypi_name pytest
|
||||
Name: python-%{pypi_name}
|
||||
Version: 4.4.2
|
||||
Release: 0%{?dist}
|
||||
Summary: Simple powerful testing with Python
|
||||
License: MIT
|
||||
URL: https://pytest.org
|
||||
Source0: %{pypi_source}
|
||||
|
||||
BuildArch: noarch
|
||||
BuildRequires: pyproject-rpm-macros
|
||||
|
||||
%description
|
||||
This is a pure Python package with executables. It has a test suite in tox.ini
|
||||
and test dependencies specified via the [test] extra.
|
||||
Building this tests:
|
||||
- generating runtime and test dependencies by both tox.ini and extras
|
||||
- pyproject.toml with the setuptools backend and setuptools-scm
|
||||
- passing arguments into %%tox
|
||||
|
||||
%package -n python3-%{pypi_name}
|
||||
Summary: %{summary}
|
||||
|
||||
%description -n python3-%{pypi_name}
|
||||
%{summary}.
|
||||
|
||||
|
||||
%prep
|
||||
%autosetup -p1 -n %{pypi_name}-%{version}
|
||||
|
||||
|
||||
%generate_buildrequires
|
||||
%pyproject_buildrequires -x testing -t
|
||||
|
||||
%build
|
||||
%pyproject_wheel
|
||||
|
||||
|
||||
%install
|
||||
%pyproject_install
|
||||
%pyproject_save_files '*pytest' +auto
|
||||
|
||||
|
||||
%check
|
||||
# Only run one test (which uses a test-only dependency, hypothesis)
|
||||
# See how to pass options trough the macro to tox, trough tox to pytest
|
||||
%tox -- -- -k metafunc
|
||||
|
||||
|
||||
%files -n python3-%{pypi_name} -f %{pyproject_files}
|
||||
%doc README.rst
|
||||
%doc CHANGELOG.rst
|
||||
%license LICENSE
|
56
tests/python-requests.spec
Normal file
56
tests/python-requests.spec
Normal file
@ -0,0 +1,56 @@
|
||||
Name: python-requests
|
||||
Version: 2.24.0
|
||||
Release: 0%{?dist}
|
||||
Summary: Requests is an elegant and simple HTTP library for Python
|
||||
|
||||
License: ASL 2.0
|
||||
URL: https://requests.readthedocs.io/
|
||||
Source0: %{pypi_source requests}
|
||||
BuildArch: noarch
|
||||
|
||||
BuildRequires: python3-devel
|
||||
BuildRequires: pyproject-rpm-macros
|
||||
|
||||
%description
|
||||
This package uses multiple extras in %%pyproject_extras_subpkg and in
|
||||
%%pyproject_buildrequires.
|
||||
This test is mostly obsoleted by python-dns-lexicon.spec on Fedora 33+,
|
||||
but we keep it around until Fedora 32 EOL.
|
||||
|
||||
|
||||
%package -n python3-requests
|
||||
Summary: %{summary}
|
||||
|
||||
%description -n python3-requests
|
||||
%{summary}.
|
||||
|
||||
|
||||
%pyproject_extras_subpkg -n python3-requests security socks
|
||||
|
||||
|
||||
%prep
|
||||
%autosetup -n requests-%{version}
|
||||
|
||||
|
||||
%generate_buildrequires
|
||||
%pyproject_buildrequires -x security,socks
|
||||
|
||||
|
||||
%build
|
||||
%pyproject_wheel
|
||||
|
||||
|
||||
%install
|
||||
%pyproject_install
|
||||
%pyproject_save_files requests
|
||||
|
||||
|
||||
%check
|
||||
# Internal check for our macros
|
||||
# making sure that %%pyproject_buildrequires pulled in deps for both extras
|
||||
%{python3} -c 'import cryptography, socks'
|
||||
|
||||
|
||||
%files -n python3-requests -f %{pyproject_files}
|
||||
%doc README.*
|
||||
%license LICENSE
|
55
tests/python-setuptools_scm.spec
Normal file
55
tests/python-setuptools_scm.spec
Normal file
@ -0,0 +1,55 @@
|
||||
Name: python-setuptools_scm
|
||||
Version: 3.5.0
|
||||
Release: 0%{?dist}
|
||||
Summary: The blessed package to manage your versions by SCM tags
|
||||
License: MIT
|
||||
URL: https://github.com/pypa/setuptools_scm/
|
||||
Source0: %{pypi_source setuptools_scm}
|
||||
|
||||
BuildArch: noarch
|
||||
BuildRequires: python3-devel
|
||||
BuildRequires: pyproject-rpm-macros
|
||||
|
||||
%description
|
||||
Here we test that %%pyproject_extras_subpkg works and generates
|
||||
setuptools_scm[toml] extra subpackage.
|
||||
|
||||
Note that it only works on Fedora 33+.
|
||||
|
||||
%package -n python3-setuptools_scm
|
||||
Summary: %{summary}
|
||||
|
||||
%description -n python3-setuptools_scm
|
||||
...
|
||||
|
||||
%pyproject_extras_subpkg -n python3-setuptools_scm toml
|
||||
|
||||
|
||||
%prep
|
||||
%autosetup -p1 -n setuptools_scm-%{version}
|
||||
|
||||
|
||||
%generate_buildrequires
|
||||
%pyproject_buildrequires
|
||||
|
||||
|
||||
%build
|
||||
%pyproject_wheel
|
||||
|
||||
|
||||
%install
|
||||
%pyproject_install
|
||||
%pyproject_save_files setuptools_scm
|
||||
|
||||
|
||||
%check
|
||||
# Internal check for our macros
|
||||
# making sure that %%{pyproject_ghost_distinfo} has the right content
|
||||
test -f %{pyproject_ghost_distinfo}
|
||||
test "$(cat %{pyproject_ghost_distinfo})" == "%ghost %{python3_sitelib}/setuptools_scm-%{version}.dist-info"
|
||||
|
||||
|
||||
%files -n python3-setuptools_scm -f %{pyproject_files}
|
||||
%doc README.rst
|
||||
%doc CHANGELOG.rst
|
||||
%license LICENSE
|
46
tests/python-zope-event.spec
Normal file
46
tests/python-zope-event.spec
Normal file
@ -0,0 +1,46 @@
|
||||
Name: python-zope-event
|
||||
Version: 4.2.0
|
||||
Release: 0%{?dist}
|
||||
Summary: Zope Event Publication
|
||||
License: ZPLv2.1
|
||||
URL: https://pypi.python.org/pypi/zope.event/
|
||||
Source0: %{pypi_source zope.event}
|
||||
BuildArch: noarch
|
||||
|
||||
BuildRequires: pyproject-rpm-macros
|
||||
BuildRequires: python3-devel
|
||||
|
||||
%description
|
||||
This package contains .pth files.
|
||||
Building this tests that .pth files are not listed when +auto is not used
|
||||
with %%pyproject_save_files.
|
||||
|
||||
%package -n python3-zope-event
|
||||
Summary: %{summary}
|
||||
|
||||
%description -n python3-zope-event
|
||||
...
|
||||
|
||||
%prep
|
||||
%setup -q -n zope.event-%{version}
|
||||
|
||||
%generate_buildrequires
|
||||
%pyproject_buildrequires
|
||||
|
||||
%build
|
||||
%pyproject_wheel
|
||||
|
||||
%install
|
||||
%pyproject_install
|
||||
%pyproject_save_files zope +auto
|
||||
|
||||
%check
|
||||
# Internal check that the RECORD and REQUESTED files are
|
||||
# always removed in %%pyproject_wheel
|
||||
test ! $(find %{buildroot}%{python3_sitelib}/ | grep -E "\.dist-info/RECORD$")
|
||||
test ! $(find %{buildroot}%{python3_sitelib}/ | grep -E "\.dist-info/REQUESTED$")
|
||||
|
||||
%files -n python3-zope-event -f %{pyproject_files}
|
||||
%doc README.rst
|
||||
%license LICENSE.txt
|
||||
|
79
tests/tests.yml
Normal file
79
tests/tests.yml
Normal file
@ -0,0 +1,79 @@
|
||||
---
|
||||
- hosts: localhost
|
||||
tags:
|
||||
- classic
|
||||
tasks:
|
||||
- dnf:
|
||||
name: "*"
|
||||
state: latest
|
||||
|
||||
- hosts: localhost
|
||||
roles:
|
||||
- role: standard-test-basic
|
||||
tags:
|
||||
- classic
|
||||
tests:
|
||||
- pytest:
|
||||
dir: .
|
||||
run: ./mocktest.sh python-pytest
|
||||
- entrypoints:
|
||||
dir: .
|
||||
run: ./mocktest.sh python-entrypoints
|
||||
- pluggy:
|
||||
dir: .
|
||||
run: ./mocktest.sh python-pluggy
|
||||
- clikit:
|
||||
dir: .
|
||||
run: ./mocktest.sh python-clikit
|
||||
- distroinfo:
|
||||
dir: .
|
||||
run: ./mocktest.sh python-distroinfo
|
||||
- tldr:
|
||||
dir: .
|
||||
run: ./mocktest.sh tldr
|
||||
- openqa_client:
|
||||
dir: .
|
||||
run: ./mocktest.sh python-openqa_client
|
||||
- httpbin:
|
||||
dir: .
|
||||
run: ./mocktest.sh python-httpbin
|
||||
- ldap:
|
||||
dir: .
|
||||
run: ./mocktest.sh python-ldap
|
||||
- isort:
|
||||
dir: .
|
||||
run: ./mocktest.sh python-isort
|
||||
- mistune:
|
||||
dir: .
|
||||
run: ./mocktest.sh python-mistune
|
||||
- setuptools_scm:
|
||||
dir: .
|
||||
run: ./mocktest.sh python-setuptools_scm
|
||||
- requests:
|
||||
dir: .
|
||||
run: ./mocktest.sh python-requests
|
||||
- ipykernel:
|
||||
dir: .
|
||||
run: ./mocktest.sh python-ipykernel
|
||||
- zope:
|
||||
dir: .
|
||||
run: ./mocktest.sh python-zope-event
|
||||
- django:
|
||||
dir: .
|
||||
run: ./mocktest.sh python-django
|
||||
- printrun:
|
||||
dir: .
|
||||
run: ./mocktest.sh printrun
|
||||
- dns_lexicon:
|
||||
dir: .
|
||||
run: ./mocktest.sh python-dns-lexicon
|
||||
- flit_core:
|
||||
dir: .
|
||||
run: ./mocktest.sh python-flit-core
|
||||
- poetry_core:
|
||||
dir: .
|
||||
run: ./mocktest.sh python-poetry-core
|
||||
required_packages:
|
||||
- mock
|
||||
- rpmdevtools
|
||||
- rpm-build
|
45
tests/tldr.spec
Normal file
45
tests/tldr.spec
Normal file
@ -0,0 +1,45 @@
|
||||
Name: tldr
|
||||
Version: 0.4.4
|
||||
Release: 1%{?dist}
|
||||
Summary: Simplified and community-driven man pages
|
||||
|
||||
License: MIT
|
||||
URL: https://github.com/tldr-pages/tldr-python-client
|
||||
Source0: %{pypi_source}
|
||||
|
||||
BuildArch: noarch
|
||||
BuildRequires: pyproject-rpm-macros
|
||||
|
||||
%description
|
||||
A Python package containing executables.
|
||||
Building this tests:
|
||||
- there are no bytecompiled files in %%{_bindir}
|
||||
- the executable's shebang is adjusted properly
|
||||
- file direct_url.json isn't created
|
||||
|
||||
%prep
|
||||
%autosetup -n %{name}-%{version}
|
||||
|
||||
%generate_buildrequires
|
||||
%pyproject_buildrequires
|
||||
|
||||
%build
|
||||
%pyproject_wheel
|
||||
|
||||
%install
|
||||
%pyproject_install
|
||||
%pyproject_save_files tldr +auto
|
||||
|
||||
%check
|
||||
# Internal check for our macros: tests we don't ship __pycache__ in bindir
|
||||
test ! -d %{buildroot}%{_bindir}/__pycache__
|
||||
|
||||
# Internal check for our macros: tests we have a proper shebang line
|
||||
head -n1 %{buildroot}%{_bindir}/%{name}.py | grep -E '#!\s*%{python3}\s+%{py3_shbang_opts}\s*$'
|
||||
|
||||
# Internal check for our macros: tests that direct_url.json file wasn't created
|
||||
test ! -e %{buildroot}%{python3_sitelib}/*.dist-info/direct_url.json
|
||||
|
||||
%files -f %pyproject_files
|
||||
%license LICENSE
|
||||
%doc README.md
|
Loading…
Reference in New Issue
Block a user