Blame SOURCES/README.md

838e4d
pyproject RPM macros
838e4d
====================
838e4d
838e4d
These macros allow projects that follow the Python [packaging specifications]
838e4d
to be packaged as RPMs.
838e4d
838e4d
They work for:
838e4d
838e4d
* traditional Setuptools-based projects that use the `setup.py` file,
838e4d
* newer Setuptools-based projects that have a `setup.cfg` file,
838e4d
* general Python projects that use the [PEP 517] `pyproject.toml` file (which allows using any build system, such as setuptools, flit or poetry).
838e4d
838e4d
These macros replace `%py3_build` and `%py3_install`, which only work with `setup.py`.
838e4d
838e4d
[packaging specifications]: https://packaging.python.org/specifications/
838e4d
838e4d
838e4d
Usage
838e4d
-----
838e4d
98862f
To use these macros, first BuildRequire the devel package for the Python you
98862f
are building against. In Fedora, that's `python3-devel`.
838e4d
98862f
    BuildRequires: python3-devel
838e4d
98862f
The macros will be pulled in as a dependency on Fedora and EPEL 9+.  
98862f
In other distributions you need to BuildRequire the macros as well:
98862f
98862f
    BuildRequires: python3-devel
98862f
    BuildRequires: pyproject-rpm-macros
838e4d
838e4d
Next, you need to generate more build dependencies (of your projects and
838e4d
the macros themselves) by running `%pyproject_buildrequires` in the
838e4d
`%generate_buildrequires` section:
838e4d
838e4d
    %generate_buildrequires
838e4d
    %pyproject_buildrequires
838e4d
838e4d
This will add build dependencies according to [PEP 517] and [PEP 518].
98862f
This also adds run-time dependencies by default and
98862f
can add test-time dependencies, see the section below.
838e4d
If you need more dependencies, such as non-Python libraries, BuildRequire
838e4d
them manually.
838e4d
838e4d
Note that `%generate_buildrequires` may produce error messages `(exit 11)` in
838e4d
the build log. This is expected behavior of BuildRequires generators; see
838e4d
[the Fedora change] for details.
838e4d
838e4d
[the Fedora change]: https://fedoraproject.org/wiki/Changes/DynamicBuildRequires
838e4d
838e4d
Then, build a wheel in `%build` with `%pyproject_wheel`:
838e4d
838e4d
    %build
838e4d
    %pyproject_wheel
838e4d
838e4d
And install the wheel in `%install` with `%pyproject_install`:
838e4d
838e4d
    %install
838e4d
    %pyproject_install
838e4d
838e4d
`%pyproject_install` installs all wheels in `pyproject-wheeldir/` located in the root of the source tree.
838e4d
838e4d
838e4d
Adding run-time and test-time dependencies
838e4d
------------------------------------------
838e4d
98862f
To run tests or import checks in the `%check` section,
98862f
the package's runtime dependencies need to also be included as build requirements.
838e4d
98862f
Hence, `%pyproject_buildrequires` also generates runtime dependencies by default.
838e4d
557ab5
For this to work, the project's build system must support the [prepare-metadata-for-build-wheel hook].
838e4d
The popular buildsystems (setuptools, flit, poetry) do support it.
838e4d
98862f
This behavior can be disabled
98862f
(e.g. when the project's build system does not support it)
98862f
using the `-R` flag:
98862f
98862f
    %generate_buildrequires
98862f
    %pyproject_buildrequires -R
98862f
557ab5
Alternatively, the runtime dependencies can be obtained by building the wheel and reading the metadata from the built wheel.
557ab5
This can be enabled by using the `-w` flag.
557ab5
Support for building wheels with `%pyproject_buildrequires -w` is **provisional** and the behavior might change.
557ab5
Please subscribe to Fedora's [python-devel list] if you use the option.
557ab5
557ab5
    %generate_buildrequires
557ab5
    %pyproject_buildrequires -w
557ab5
557ab5
When this is used, the wheel is going to be built at least twice,
557ab5
becasue the `%generate_buildrequires` section runs repeatedly.
557ab5
To avoid accidentally reusing a wheel leaking from a previous (different) build,
557ab5
it cannot be reused between `%generate_buildrequires` rounds.
557ab5
Contrarily to that, rebuilding the wheel again in the `%build` section is redundant
557ab5
and the packager can omit the `%build` section entirely
557ab5
to reuse the wheel built from the last round of `%generate_buildrequires`.
557ab5
Be extra careful when attempting to modify the sources after `%pyproject_buildrequires`,
557ab5
e.g. when running extra commands in the `%build` section:
557ab5
557ab5
    %build
557ab5
    cython src/wrong.pyx  # this is too late with %%pyproject_buildrequires -w
557ab5
    %pyproject_wheel
557ab5
838e4d
For projects that specify test requirements using an [`extra`
838e4d
provide](https://packaging.python.org/specifications/core-metadata/#provides-extra-multiple-use),
838e4d
these can be added using the `-x` flag.
838e4d
Multiple extras can be supplied by repeating the flag or as a comma separated list.
838e4d
For example, if upstream suggests installing test dependencies with
838e4d
`pip install mypackage[testing]`, the test deps would be generated by:
838e4d
838e4d
    %generate_buildrequires
838e4d
    %pyproject_buildrequires -x testing
838e4d
838e4d
For projects that specify test requirements in their [tox] configuration,
838e4d
these can be added using the `-t` flag (default tox environment)
838e4d
or the `-e` flag followed by the tox environment.
838e4d
The default tox environment (such as `py37` assuming the Fedora's Python version is 3.7)
838e4d
is available in the `%{toxenv}` macro.
838e4d
For example, if upstream suggests running the tests on Python 3.7 with `tox -e py37`,
838e4d
the test deps would be generated by:
838e4d
838e4d
    %generate_buildrequires
838e4d
    %pyproject_buildrequires -t
838e4d
838e4d
If upstream uses a custom derived environment, such as `py37-unit`, use:
838e4d
838e4d
    %pyproject_buildrequires -e %{toxenv}-unit
838e4d
838e4d
Or specify more environments if needed:
838e4d
838e4d
    %pyproject_buildrequires -e %{toxenv}-unit,%{toxenv}-integration
838e4d
838e4d
The `-e` option redefines `%{toxenv}` for further reuse.
838e4d
Use `%{default_toxenv}` to get the default value.
838e4d
838e4d
The `-t`/`-e` option uses [tox-current-env]'s `--print-deps-to-file` behind the scenes.
838e4d
838e4d
If your package specifies some tox plugins in `tox.requires`,
838e4d
such plugins will be BuildRequired as well.
838e4d
Not all plugins are guaranteed to play well with [tox-current-env],
838e4d
in worst case, patch/sed the requirement out from the tox configuration.
838e4d
838e4d
Note that both `-x` and `-t` imply `-r`,
838e4d
because runtime dependencies are always required for testing.
557ab5
You can only use those options if the build backend  supports the [prepare-metadata-for-build-wheel hook],
557ab5
or together with `-w`.
838e4d
838e4d
[tox]: https://tox.readthedocs.io/
838e4d
[tox-current-env]: https://github.com/fedora-python/tox-current-env/
557ab5
[prepare-metadata-for-build-wheel hook]: https://www.python.org/dev/peps/pep-0517/#prepare-metadata-for-build-wheel
838e4d
838e4d
Additionally to generated requirements you can supply multiple file names to `%pyproject_buildrequires` macro.
838e4d
Dependencies will be loaded from them:
838e4d
838e4d
    %pyproject_buildrequires -r requirements/tests.in requirements/docs.in requirements/dev.in
838e4d
838e4d
For packages not using build system you can use `-N` to entirely skip automatical
838e4d
generation of requirements and install requirements only from manually specified files.
838e4d
`-N` option cannot be used in combination with other options mentioned above
557ab5
(`-r`, `-w`, `-e`, `-t`, `-x`).
838e4d
838e4d
Running tox based tests
838e4d
-----------------------
838e4d
838e4d
In case you want to run the tests as specified in [tox] configuration,
838e4d
you must use `%pyproject_buildrequires` with `-t` or `-e` as explained above.
838e4d
Then, use the `%tox` macro in `%check`:
838e4d
838e4d
    %check
838e4d
    %tox
838e4d
838e4d
The macro:
838e4d
838e4d
 - Always prepends `$PATH` with `%{buildroot}%{_bindir}`
838e4d
 - If not defined, sets `$PYTHONPATH` to `%{buildroot}%{python3_sitearch}:%{buildroot}%{python3_sitelib}`
838e4d
 - If not defined, sets `$TOX_TESTENV_PASSENV` to `*`
838e4d
 - Runs `tox` with `-q` (quiet), `--recreate` and `--current-env` (from [tox-current-env]) flags
838e4d
 - Implicitly uses the tox environment name stored in `%{toxenv}` - as overridden by `%pyproject_buildrequires -e`
838e4d
838e4d
By using the `-e` flag, you can use a different tox environment(s):
838e4d
838e4d
    %check
838e4d
    %tox
838e4d
    %if %{with integration_test}
838e4d
    %tox -e %{default_toxenv}-integration
838e4d
    %endif
838e4d
838e4d
If you wish to provide custom `tox` flags or arguments, add them after `--`:
838e4d
838e4d
    %tox -- --flag-for-tox
838e4d
838e4d
If you wish to pass custom `posargs` to tox, use another `--`:
838e4d
838e4d
    %tox -- --flag-for-tox -- --flag-for-posargs
838e4d
838e4d
Or (note the two sequential `--`s):
838e4d
838e4d
    %tox -- -- --flag-for-posargs
838e4d
838e4d
838e4d
838e4d
Generating the %files section
838e4d
-----------------------------
838e4d
838e4d
To generate the list of files in the `%files` section, you can use `%pyproject_save_files` after the `%pyproject_install` macro.
838e4d
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}`.
838e4d
For example, if a package provides the modules `requests` and `_requests`, write:
838e4d
838e4d
    %install
838e4d
    %pyproject_install
838e4d
    %pyproject_save_files requests _requests
838e4d
838e4d
To add listed files to the `%files` section, use `%files -f %{pyproject_files}`.
838e4d
Note that you still need to add any documentation manually (for now).
838e4d
838e4d
    %files -n python3-requests -f %{pyproject_files}
838e4d
    %doc README.rst
838e4d
838e4d
You can use globs in the module names if listing them explicitly would be too tedious:
838e4d
838e4d
    %install
838e4d
    %pyproject_install
838e4d
    %pyproject_save_files '*requests'
838e4d
838e4d
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`).
838e4d
838e4d
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.
838e4d
838e4d
    %install
838e4d
    %pyproject_install
838e4d
    %pyproject_save_files '*' +auto
838e4d
    
838e4d
    %files -n python3-requests -f %{pyproject_files}
838e4d
838e4d
However, in Fedora packages, always list executables explicitly to avoid unintended collisions with other packages or accidental missing executables:
838e4d
838e4d
    %install
838e4d
    %pyproject_install
838e4d
    %pyproject_save_files requests _requests
838e4d
    
838e4d
    %files -n python3-requests -f %{pyproject_files}
838e4d
    %doc README.rst
838e4d
    %{_bindir}/downloader
838e4d
838e4d
`%pyproject_save_files` can automatically mark license files with `%license` macro
838e4d
and  language (`*.mo`) files with `%lang` macro and appropriate language code.
557ab5
Only license files declared via [PEP 639] `License-File` field are detected.
838e4d
[PEP 639] is still a draft and can be changed in the future.
838e4d
838e4d
Note that `%pyproject_save_files` uses data from the [RECORD file](https://www.python.org/dev/peps/pep-0627/).
838e4d
If you wish to rename, remove or otherwise change the installed files of a package
838e4d
*after* `%pyproject_install`, `%pyproject_save_files` might break.
838e4d
If possible, remove/rename such files in `%prep`.
838e4d
If not possible, avoid using `%pyproject_save_files` or edit/replace `%{pyproject_files}`.
838e4d
98862f
98862f
Performing an import check on all importable modules
98862f
----------------------------------------------------
98862f
98862f
If the upstream test suite cannot be used during the package build
98862f
and you use `%pyproject_save_files`,
98862f
you can benefit from the `%pyproject_check_import` macro.
98862f
If `%pyproject_save_files` is not used, calling `%pyproject_check_import` will fail.
98862f
98862f
When `%pyproject_save_files` is invoked,
98862f
it creates a list of all valid and public (i.e. not starting with `_`)
98862f
importable module names found in the package.
98862f
This list is then usable by `%pyproject_check_import` which performs an import check for each listed module.
98862f
When a module fails to import, the build fails.
98862f
98862f
The modules are imported from both installed and buildroot's `%{python3_sitearch}`
98862f
and `%{python3_sitelib}`, not from the current directory.
98862f
98862f
Use the macro in `%check`:
98862f
98862f
    %check
98862f
    %pyproject_check_import
98862f
98862f
By using the `-e` flag, you can exclude module names matching the given glob(s) from the import check
98862f
(put it in single quotes to prevent Shell from expanding it).
98862f
The flag can be used repeatedly.
98862f
For example, to exclude all submodules ending with `config` and all submodules starting with `test`, you can use:
98862f
98862f
    %pyproject_check_import -e '*.config' -e '*.test*'
98862f
98862f
There must be at least one module left for the import check;
98862f
if, as a result of greedy excluding, no modules are left to check, the check fails.
98862f
98862f
When the `-t` flag is used, only top-level modules are checked,
98862f
qualified module names with a dot (`.`) are excluded.
98862f
If the modules detected by `%pyproject_save_files` are `requests`, `requests.models`, and `requests.packages`, this will only perform an import of `requests`:
98862f
98862f
    %pyproject_check_import -t
98862f
98862f
The modifying flags should only be used when there is a valid reason for not checking all available modules.
98862f
The reason should be documented in a comment.
98862f
98862f
The `%pyproject_check_import` macro also accepts positional arguments with
98862f
additional qualified module names to check, useful for example if some modules are installed manually.
98862f
Note that filtering by `-t`/`-e` also applies to the positional arguments.
98862f
98862f
838e4d
Generating Extras subpackages
838e4d
-----------------------------
838e4d
838e4d
The `%pyproject_extras_subpkg` macro generates simple subpackage(s)
838e4d
for Python extras.
838e4d
838e4d
The macro should be placed after the base package's `%description` to avoid
838e4d
issues in building the SRPM.
838e4d
838e4d
For example, if the `requests` project's metadata defines the extras
838e4d
`security` and `socks`, the following invocation will generate the subpackage
838e4d
`python3-requests+security` that provides `python3dist(requests[security])`,
838e4d
and a similar one for `socks`.
838e4d
838e4d
    %pyproject_extras_subpkg -n python3-requests security socks
838e4d
838e4d
The macro works like `%python_extras_subpkg`,
838e4d
except the `-i`/`-f`/`-F` arguments are optional and discouraged.
838e4d
A filelist written by `%pyproject_install` is used by default.
838e4d
For more information on `%python_extras_subpkg`, see the [Fedora change].
838e4d
838e4d
[Fedora change]: https://fedoraproject.org/wiki/Changes/PythonExtras
838e4d
838e4d
These arguments are still required:
838e4d
838e4d
* -n: name of the “base” package (e.g. python3-requests)
838e4d
* Positional arguments: the extra name(s).
838e4d
  Multiple subpackages are generated when multiple names are provided.
838e4d
838e4d
98862f
PROVISIONAL: Importing just-built (extension) modules in %build
98862f
---------------------------------------------------------------
98862f
98862f
Sometimes, it is desired to be able to import the *just-built* extension modules
98862f
in the `%build` section, e.g. to build the documentation with Sphinx.
98862f
98862f
    %build
98862f
    %pyproject_wheel
98862f
    ... build the docs here ...
98862f
98862f
With pure Python packages, it might be possible to set `PYTHONPATH=${PWD}` or `PYTHONPATH=${PWD}/src`.
98862f
However, it is a bit more complicated with extension modules.
98862f
557ab5
The location of just-built modules might differ depending on Python version, architecture, pip version, etc.
98862f
Hence, the macro `%{pyproject_build_lib}` exists to be used like this:
98862f
98862f
    %build
98862f
    %pyproject_wheel
98862f
    PYTHONPATH=%{pyproject_build_lib} ... build the docs here ...
98862f
98862f
This macro is currently **provisional** and the behavior might change.
98862f
Please subscribe to Fedora's [python-devel list] if you use the macro.
98862f
98862f
The `%{pyproject_build_lib}` macro expands to an Shell `$(...)` expression and does not work when put into single quotes (`'`).
98862f
98862f
Depending on the pip version, the expanded value will differ:
98862f
98862f
[python-devel list]: https://lists.fedoraproject.org/archives/list/python-devel@lists.fedoraproject.org/
98862f
557ab5
### New pip 21.3+ with in-tree-build and setuptools 62.1+ (Fedora 37+)
557ab5
557ab5
Always use the macro from the same directory where you called `%pyproject_wheel` from.
557ab5
The value will expand to something like:
557ab5
557ab5
* `/builddir/build/BUILD/%{name}-%{version}/build/lib.linux-x86_64-cpython-311` for wheels with extension modules
557ab5
* `/builddir/build/BUILD/%{name}-%{version}/build/lib` for pure Python wheels
557ab5
557ab5
If multiple wheels were built from the same directory,
557ab5
some pure Python and some with extension modules,
557ab5
the expanded value will be combined with `:`:
557ab5
557ab5
* `/builddir/build/BUILD/%{name}-%{version}/build/lib.linux-x86_64-cypthon-311:/builddir/build/BUILD/%{name}-%{version}/build/lib`
557ab5
557ab5
If multiple wheels were built from different directories,
557ab5
the value will differ depending on the current directory.
557ab5
557ab5
557ab5
### New pip 21.3+ with in-tree-build and older setuptools (Fedora 36)
98862f
98862f
Always use the macro from the same directory where you called `%pyproject_wheel` from.
98862f
The value will expand to something like:
98862f
98862f
* `/builddir/build/BUILD/%{name}-%{version}/build/lib.linux-x86_64-3.10` for wheels with extension modules
98862f
* `/builddir/build/BUILD/%{name}-%{version}/build/lib` for pure Python wheels
98862f
98862f
If multiple wheels were built from the same directory,
98862f
some pure Python and some with extension modules,
98862f
the expanded value will be combined with `:`:
98862f
98862f
* `/builddir/build/BUILD/%{name}-%{version}/build/lib.linux-x86_64-3.10:/builddir/build/BUILD/%{name}-%{version}/build/lib`
98862f
98862f
If multiple wheels were built from different directories,
98862f
the value will differ depending on the current directory.
98862f
98862f
557ab5
### Older pip with out-of-tree-build (Fedora 35 and EL 9)
98862f
98862f
The value will expand to something like:
98862f
98862f
* `/builddir/build/BUILD/%{name}-%{version}/.pyproject-builddir/pip-req-build-xxxxxxxx/build/lib.linux-x86_64-3.10` for wheels with extension modules
98862f
* `/builddir/build/BUILD/%{name}-%{version}/.pyproject-builddir/pip-req-build-xxxxxxxx/build/lib` for pure Python wheels
98862f
98862f
Note that the exact value is **not stable** between builds
98862f
(the `xxxxxxxx` part is randomly generated,
98862f
neither you should consider the `.pyproject-builddir` directory to remain stable).
98862f
98862f
If multiple wheels are built,
98862f
the expanded value will always be combined with `:` regardless of the current directory, e.g.:
98862f
98862f
* `/builddir/build/BUILD/%{name}-%{version}/.pyproject-builddir/pip-req-build-xxxxxxxx/build/lib.linux-x86_64-3.10:/builddir/build/BUILD/%{name}-%{version}/.pyproject-builddir/pip-req-build-yyyyyyyy/build/lib.linux-x86_64-3.10:/builddir/build/BUILD/%{name}-%{version}/.pyproject-builddir/pip-req-build-zzzzzzzz/build/lib`
98862f
98862f
**Note:** If you manage to build some wheels with in-tree-build and some with out-of-tree-build option,
98862f
the expanded value will contain all relevant directories.
98862f
98862f
838e4d
Limitations
838e4d
-----------
838e4d
838e4d
`%pyproject_install` changes shebang lines of every Python script in `%{buildroot}%{_bindir}` to `#!%{__python3} %{py3_shbang_opt}` (`#!/usr/bin/python3 -s`).
838e4d
Existing Python flags in shebangs are preserved.
838e4d
For example `#!/usr/bin/python3 -Ru` will be updated to `#!/usr/bin/python3 -sRu`.
838e4d
Sometimes, this can interfere with tests that run such scripts directly by name,
838e4d
because in tests we usually rely on `PYTHONPATH` (and `-s` ignores that).
838e4d
Would this behavior be undesired for any reason,
838e4d
undefine `%{py3_shbang_opt}` to turn it off.
838e4d
838e4d
Some valid Python version specifiers are not supported.
838e4d
838e4d
When a dependency is specified via an URL or local path, for example as:
838e4d
838e4d
    https://github.com/ActiveState/appdirs/archive/8eacfa312d77aba28d483fbfb6f6fc54099622be.zip
838e4d
    /some/path/foo-1.2.3.tar.gz
838e4d
    git+https://github.com/sphinx-doc/sphinx.git@96dbe5e3
838e4d
838e4d
The `%pyproject_buildrequires` macro is unable to convert it to an appropriate RPM requirement and will fail.
838e4d
If the URL contains the `packageName @` prefix as specified in [PEP 508],
838e4d
the requirement will be generated without a version constraint:
838e4d
838e4d
    appdirs@https://github.com/ActiveState/appdirs/archive/8eacfa312d77aba28d483fbfb6f6fc54099622be.zip
838e4d
    foo@file:///some/path/foo-1.2.3.tar.gz
838e4d
838e4d
Will be converted to:
838e4d
838e4d
    python3dist(appdirs)
838e4d
    python3dist(foo)
838e4d
838e4d
Alternatively, when an URL requirement parsed from a text file
838e4d
given as positional argument to `%pyproject_buildrequires`
838e4d
contains the `#egg=packageName` fragment,
838e4d
as documented in [pip's documentation]:
838e4d
838e4d
    git+https://github.com/sphinx-doc/sphinx.git@96dbe5e3#egg=sphinx
838e4d
838e4d
The requirements will be converted to package names without versions, e.g.:
838e4d
838e4d
    python3dist(sphinx)
838e4d
838e4d
However upstreams usually only use direct URLs for their requirements as workarounds,
838e4d
so be prepared for problems.
838e4d
838e4d
[PEP 508]: https://www.python.org/dev/peps/pep-0508/
838e4d
[PEP 517]: https://www.python.org/dev/peps/pep-0517/
838e4d
[PEP 518]: https://www.python.org/dev/peps/pep-0518/
838e4d
[PEP 639]: https://www.python.org/dev/peps/pep-0639/
838e4d
[pip's documentation]: https://pip.pypa.io/en/stable/cli/pip_install/#vcs-support
838e4d
838e4d
838e4d
Testing the macros
838e4d
------------------
838e4d
838e4d
This repository has two kinds of tests.
838e4d
First, there is RPM `%check` section, run when building the `python-rpm-macros`
838e4d
package.
838e4d
838e4d
Then there are CI tests.
838e4d
There is currently [no way to run Fedora CI tests locally][ci-rfe],
838e4d
but you can do what the tests do manually using mock.
838e4d
For each `$PKG.spec` in `tests/`:
838e4d
838e4d
  - clean your mock environment:
838e4d
838e4d
        mock -r fedora-rawhide-x86_64 clean
838e4d
838e4d
  - install the version of `python-rpm-macros` you're testing, e.g.:
838e4d
838e4d
        mock -r fedora-rawhide-x86_64 install .../python-rpm-macros-*.noarch.rpm
838e4d
838e4d
  - download the sources:
838e4d
838e4d
        spectool -g -R $PKG.spec
838e4d
838e4d
  - build a SRPM:
838e4d
838e4d
        rpmbuild -bs $PKG.spec
838e4d
838e4d
  - build in mock, using the path from the command above as `$SRPM`:
838e4d
838e4d
        mock -r fedora-rawhide-x86_64 -n -N $SRPM
838e4d
838e4d
[ci-rfe]: https://pagure.io/fedora-ci/general/issue/4