162b0ca
pyproject RPM macros
162b0ca
====================
162b0ca
fb4066b
These macros allow projects that follow the Python [packaging specifications]
fb4066b
to be packaged as RPMs.
162b0ca
fb4066b
They are still *provisional*: we can make non-backwards-compatible changes to
fb4066b
the API.
fb4066b
Please subscribe to Fedora's [python-devel list] if you use the macros.
fb4066b
fb4066b
They work for:
fb4066b
fb4066b
* traditional Setuptools-based projects that use the `setup.py` file,
fb4066b
* newer Setuptools-based projects that have a `setup.cfg` file,
fb4066b
* general Python projects that use the [PEP 517] `pyproject.toml` file (which allows using any build system, such as setuptools, flit or poetry).
fb4066b
fb4066b
These macros replace `%py3_build` and `%py3_install`, which only work with `setup.py`.
fb4066b
fb4066b
[packaging specifications]: https://packaging.python.org/specifications/
fb4066b
[python-devel list]: https://lists.fedoraproject.org/archives/list/python-devel@lists.fedoraproject.org/
162b0ca
162b0ca
162b0ca
Usage
162b0ca
-----
162b0ca
fb4066b
To use these macros, first BuildRequire them:
162b0ca
162b0ca
    BuildRequires: pyproject-rpm-macros
162b0ca
fb4066b
Also BuildRequire the devel package for the Python you are building against.
fb4066b
In Fedora, that's `python3-devel`.
fb4066b
(In the future, we plan to make `python3-devel` itself require
fb4066b
`pyproject-rpm-macros`.)
162b0ca
fb4066b
Next, you need to generate more build dependencies (of your projects and
fb4066b
the macros themselves) by running `%pyproject_buildrequires` in the
fb4066b
`%generate_buildrequires` section:
fdf5116
fdf5116
    %generate_buildrequires
fdf5116
    %pyproject_buildrequires
fdf5116
fb4066b
This will add build dependencies according to [PEP 517] and [PEP 518].
fb4066b
To also add run-time and test-time dependencies, see the section below.
fb4066b
If you need more dependencies, such as non-Python libraries, BuildRequire
fb4066b
them manually.
fb4066b
fb4066b
Note that `%generate_buildrequires` may produce error messages `(exit 11)` in
fb4066b
the build log. This is expected behavior of BuildRequires generators; see
fb4066b
[the Fedora change] for details.
fb4066b
fb4066b
[the Fedora change]: https://fedoraproject.org/wiki/Changes/DynamicBuildRequires
fdf5116
fdf5116
Then, build a wheel in `%build` with `%pyproject_wheel`:
162b0ca
162b0ca
    %build
162b0ca
    %pyproject_wheel
162b0ca
fdf5116
And install the wheel in `%install` with `%pyproject_install`:
162b0ca
162b0ca
    %install
162b0ca
    %pyproject_install
162b0ca
29157c1
`%pyproject_install` installs all wheels in `pyproject-wheeldir/` located in the root of the source tree.
49a323e
162b0ca
03316d8
Adding run-time and test-time dependencies
03316d8
------------------------------------------
03316d8
03316d8
To run tests in the `%check` section, the package's runtime dependencies
03316d8
often need to also be included as build requirements.
fb4066b
This can be done using the `-r` flag:
03316d8
03316d8
    %generate_buildrequires
03316d8
    %pyproject_buildrequires -r
03316d8
fb4066b
For this to work, the project's build system must support the
fb4066b
[`prepare-metadata-for-build-wheel` hook](https://www.python.org/dev/peps/pep-0517/#prepare-metadata-for-build-wheel).
fb4066b
The popular buildsystems (setuptools, flit, poetry) do support it.
fb4066b
03316d8
For projects that specify test requirements using an [`extra`
03316d8
provide](https://packaging.python.org/specifications/core-metadata/#provides-extra-multiple-use),
03316d8
these can be added using the `-x` flag.
ea43eeb
Multiple extras can be supplied by repeating the flag or as a comma separated list.
03316d8
For example, if upstream suggests installing test dependencies with
03316d8
`pip install mypackage[testing]`, the test deps would be generated by:
03316d8
03316d8
    %generate_buildrequires
262f6d3
    %pyproject_buildrequires -x testing
03316d8
8a60635
For projects that specify test requirements in their [tox] configuration,
fda0a23
these can be added using the `-t` flag (default tox environment)
fda0a23
or the `-e` flag followed by the tox environment.
fda0a23
The default tox environment (such as `py37` assuming the Fedora's Python version is 3.7)
ec07317
is available in the `%{toxenv}` macro.
8a60635
For example, if upstream suggests running the tests on Python 3.7 with `tox -e py37`,
8a60635
the test deps would be generated by:
8a60635
8a60635
    %generate_buildrequires
fda0a23
    %pyproject_buildrequires -t
ec07317
ec07317
If upstream uses a custom derived environment, such as `py37-unit`, use:
ec07317
fda0a23
    %pyproject_buildrequires -e %{toxenv}-unit
ec07317
ec07317
Or specify more environments if needed:
ec07317
fda0a23
    %pyproject_buildrequires -e %{toxenv}-unit,%{toxenv}-integration
ec07317
fda0a23
The `-e` option redefines `%{toxenv}` for further reuse.
ec07317
Use `%{default_toxenv}` to get the default value.
8a60635
fda0a23
The `-t`/`-e` option uses [tox-current-env]'s `--print-deps-to-file` behind the scenes.
8a60635
7e1a8fd
If your package specifies some tox plugins in `tox.requires`,
7e1a8fd
such plugins will be BuildRequired as well.
7e1a8fd
Not all plugins are guaranteed to play well with [tox-current-env],
7e1a8fd
in worst case, patch/sed the requirement out from the tox configuration.
7e1a8fd
262f6d3
Note that both `-x` and `-t` imply `-r`,
262f6d3
because runtime dependencies are always required for testing.
262f6d3
8a60635
[tox]: https://tox.readthedocs.io/
8a60635
[tox-current-env]: https://github.com/fedora-python/tox-current-env/
03316d8
5729f18
Additionally to generated requirements you can supply multiple file names to `%pyproject_buildrequires` macro.
d6ad9a7
Dependencies will be loaded from them:
d6ad9a7
d6ad9a7
    %pyproject_buildrequires -r requirements/tests.in requirements/docs.in requirements/dev.in
d6ad9a7
d6ad9a7
For packages not using build system you can use `-N` to entirely skip automatical
d6ad9a7
generation of requirements and install requirements only from manually specified files.
d6ad9a7
`-N` option cannot be used in combination with other options mentioned above
d6ad9a7
(`-r`, `-e`, `-t`, `-x`).
ec07317
ec07317
Running tox based tests
ec07317
-----------------------
ec07317
ec07317
In case you want to run the tests as specified in [tox] configuration,
fb4066b
you must use `%pyproject_buildrequires` with `-t` or `-e` as explained above.
fb4066b
Then, use the `%tox` macro in `%check`:
ec07317
ec07317
    %check
ec07317
    %tox
ec07317
ec07317
The macro:
ec07317
ec07317
 - Always prepends `$PATH` with `%{buildroot}%{_bindir}`
ec07317
 - If not defined, sets `$PYTHONPATH` to `%{buildroot}%{python3_sitearch}:%{buildroot}%{python3_sitelib}`
ec07317
 - If not defined, sets `$TOX_TESTENV_PASSENV` to `*`
ec07317
 - Runs `tox` with `-q` (quiet), `--recreate` and `--current-env` (from [tox-current-env]) flags
6a44fe2
 - Implicitly uses the tox environment name stored in `%{toxenv}` - as overridden by `%pyproject_buildrequires -e`
ec07317
ec07317
By using the `-e` flag, you can use a different tox environment(s):
ec07317
ec07317
    %check
ec07317
    %tox
ec07317
    %if %{with integration_test}
11da7b7
    %tox -e %{default_toxenv}-integration
ec07317
    %endif
ec07317
ec07317
If you wish to provide custom `tox` flags or arguments, add them after `--`:
ec07317
ec07317
    %tox -- --flag-for-tox
ec07317
ec07317
If you wish to pass custom `posargs` to tox, use another `--`:
ec07317
ec07317
    %tox -- --flag-for-tox -- --flag-for-posargs
ec07317
ec07317
Or (note the two sequential `--`s):
ec07317
ec07317
    %tox -- -- --flag-for-posargs
ec07317
ec07317
2800b49
2800b49
Generating the %files section
2800b49
-----------------------------
2800b49
2800b49
To generate the list of files in the `%files` section, you can use `%pyproject_save_files` after the `%pyproject_install` macro.
2800b49
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}`.
2800b49
For example, if a package provides the modules `requests` and `_requests`, write:
2800b49
2800b49
    %install
2800b49
    %pyproject_install
2800b49
    %pyproject_save_files requests _requests
2800b49
2800b49
To add listed files to the `%files` section, use `%files -f %{pyproject_files}`.
5169e0e
Note that you still need to add any documentation manually (for now).
2800b49
2800b49
    %files -n python3-requests -f %{pyproject_files}
2800b49
    %doc README.rst
2800b49
2800b49
You can use globs in the module names if listing them explicitly would be too tedious:
2800b49
2800b49
    %install
2800b49
    %pyproject_install
99d9596
    %pyproject_save_files '*requests'
2800b49
09be4c1
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`).
2800b49
5809bbc
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.
2800b49
2800b49
    %install
2800b49
    %pyproject_install
99d9596
    %pyproject_save_files '*' +auto
2800b49
    
2800b49
    %files -n python3-requests -f %{pyproject_files}
2800b49
2800b49
However, in Fedora packages, always list executables explicitly to avoid unintended collisions with other packages or accidental missing executables:
2800b49
2800b49
    %install
2800b49
    %pyproject_install
2800b49
    %pyproject_save_files requests _requests
2800b49
    
2800b49
    %files -n python3-requests -f %{pyproject_files}
2800b49
    %doc README.rst
2800b49
    %{_bindir}/downloader
2800b49
5169e0e
`%pyproject_save_files` can automatically mark license files with `%license` macro
5169e0e
and  language (`*.mo`) files with `%lang` macro and appropriate language code.
5169e0e
Only license files declared via [PEP 639] `License-Field` field are detected.
5169e0e
[PEP 639] is still a draft and can be changed in the future.
da3d9bc
d8b6408
Note that `%pyproject_save_files` uses data from the [RECORD file](https://www.python.org/dev/peps/pep-0627/).
d8b6408
If you wish to rename, remove or otherwise change the installed files of a package
d8b6408
*after* `%pyproject_install`, `%pyproject_save_files` might break.
d8b6408
If possible, remove/rename such files in `%prep`.
d8b6408
If not possible, avoid using `%pyproject_save_files` or edit/replace `%{pyproject_files}`.
2800b49
c1baa53
c1baa53
Performing an import check on all importable modules
c1baa53
----------------------------------------------------
c1baa53
c1baa53
If the upstream test suite cannot be used during the package build
c1baa53
and you use `%pyproject_save_files`,
c1baa53
you can benefit from the `%pyproject_check_import` macro.
c1baa53
If `%pyproject_save_files` is not used, calling `%pyproject_check_import` will fail.
c1baa53
c1baa53
When `%pyproject_save_files` is invoked,
c1baa53
it creates a list of all valid and public (i.e. not starting with `_`)
c1baa53
importable module names found in the package.
c1baa53
This list is then usable by `%pyproject_check_import` which performs an import check for each listed module.
c1baa53
When a module fails to import, the build fails.
c1baa53
c1baa53
The modules are imported from both installed and buildroot's `%{python3_sitearch}`
c1baa53
and `%{python3_sitelib}`, not from the current directory.
c1baa53
c1baa53
Use the macro in `%check`:
c1baa53
c1baa53
    %check
c1baa53
    %pyproject_check_import
c1baa53
c1baa53
By using the `-e` flag, you can exclude module names matching the given glob(s) from the import check
c1baa53
(put it in single quotes to prevent Shell from expanding it).
c1baa53
The flag can be used repeatedly.
c1baa53
For example, to exclude all submodules ending with `config` and all submodules starting with `test`, you can use:
c1baa53
c1baa53
    %pyproject_check_import -e '*.config' -e '*.test*'
c1baa53
c1baa53
There must be at least one module left for the import check;
c1baa53
if, as a result of greedy excluding, no modules are left to check, the check fails.
c1baa53
c1baa53
When the `-t` flag is used, only top-level modules are checked,
c1baa53
qualified module names with a dot (`.`) are excluded.
c1baa53
If the modules detected by `%pyproject_save_files` are `requests`, `requests.models`, and `requests.packages`, this will only perform an import of `requests`:
c1baa53
c1baa53
    %pyproject_check_import -t
c1baa53
c1baa53
The modifying flags should only be used when there is a valid reason for not checking all available modules.
c1baa53
The reason should be documented in a comment.
c1baa53
c1baa53
The `%pyproject_check_import` macro also accepts positional arguments with
c1baa53
additional qualified module names to check, useful for example if some modules are installed manually.
c1baa53
Note that filtering by `-t`/`-e` also applies to the positional arguments.
c1baa53
c1baa53
d719a00
Generating Extras subpackages
d719a00
-----------------------------
d719a00
d719a00
The `%pyproject_extras_subpkg` macro generates simple subpackage(s)
d719a00
for Python extras.
d719a00
d719a00
The macro should be placed after the base package's `%description` to avoid
d719a00
issues in building the SRPM.
d719a00
d719a00
For example, if the `requests` project's metadata defines the extras
d719a00
`security` and `socks`, the following invocation will generate the subpackage
d719a00
`python3-requests+security` that provides `python3dist(requests[security])`,
d719a00
and a similar one for `socks`.
d719a00
d719a00
    %pyproject_extras_subpkg -n python3-requests security socks
d719a00
d719a00
The macro works like `%python_extras_subpkg`,
d719a00
except the `-i`/`-f`/`-F` arguments are optional and discouraged.
d719a00
A filelist written by `%pyproject_install` is used by default.
d719a00
For more information on `%python_extras_subpkg`, see the [Fedora change].
d719a00
d719a00
[Fedora change]: https://fedoraproject.org/wiki/Changes/PythonExtras
d719a00
d719a00
These arguments are still required:
d719a00
d719a00
* -n: name of the “base” package (e.g. python3-requests)
d719a00
* Positional arguments: the extra name(s).
d719a00
  Multiple subpackages are generated when multiple names are provided.
d719a00
d719a00
162b0ca
Limitations
162b0ca
-----------
162b0ca
dbb90f5
`%pyproject_install` changes shebang lines of every Python script in `%{buildroot}%{_bindir}` to `#!%{__python3} %{py3_shbang_opt}` (`#!/usr/bin/python3 -s`).
dbb90f5
Existing Python flags in shebangs are preserved.
dbb90f5
For example `#!/usr/bin/python3 -Ru` will be updated to `#!/usr/bin/python3 -sRu`.
dbb90f5
Sometimes, this can interfere with tests that run such scripts directly by name,
dbb90f5
because in tests we usually rely on `PYTHONPATH` (and `-s` ignores that).
dbb90f5
Would this behavior be undesired for any reason,
9bb7de7
undefine `%{py3_shbang_opt}` to turn it off.
fdf5116
50645e1
Some valid Python version specifiers are not supported.
50645e1
f8a3343
When a dependency is specified via an URL or local path, for example as:
f8a3343
f8a3343
    https://github.com/ActiveState/appdirs/archive/8eacfa312d77aba28d483fbfb6f6fc54099622be.zip
f8a3343
    /some/path/foo-1.2.3.tar.gz
f8a3343
    git+https://github.com/sphinx-doc/sphinx.git@96dbe5e3
f8a3343
f8a3343
The `%pyproject_buildrequires` macro is unable to convert it to an appropriate RPM requirement and will fail.
f8a3343
If the URL contains the `packageName @` prefix as specified in [PEP 508],
f8a3343
the requirement will be generated without a version constraint:
f8a3343
f8a3343
    appdirs@https://github.com/ActiveState/appdirs/archive/8eacfa312d77aba28d483fbfb6f6fc54099622be.zip
f8a3343
    foo@file:///some/path/foo-1.2.3.tar.gz
f8a3343
f8a3343
Will be converted to:
f8a3343
f8a3343
    python3dist(appdirs)
f8a3343
    python3dist(foo)
f8a3343
f8a3343
Alternatively, when an URL requirement parsed from a text file
f8a3343
given as positional argument to `%pyproject_buildrequires`
f8a3343
contains the `#egg=packageName` fragment,
f8a3343
as documented in [pip's documentation]:
f8a3343
f8a3343
    git+https://github.com/sphinx-doc/sphinx.git@96dbe5e3#egg=sphinx
f8a3343
f8a3343
The requirements will be converted to package names without versions, e.g.:
f8a3343
f8a3343
    python3dist(sphinx)
f8a3343
f8a3343
However upstreams usually only use direct URLs for their requirements as workarounds,
f8a3343
so be prepared for problems.
f8a3343
f8a3343
[PEP 508]: https://www.python.org/dev/peps/pep-0508/
fdf5116
[PEP 517]: https://www.python.org/dev/peps/pep-0517/
fdf5116
[PEP 518]: https://www.python.org/dev/peps/pep-0518/
5169e0e
[PEP 639]: https://www.python.org/dev/peps/pep-0639/
f8a3343
[pip's documentation]: https://pip.pypa.io/en/stable/cli/pip_install/#vcs-support
82a7579
82a7579
82a7579
Testing the macros
82a7579
------------------
82a7579
82a7579
This repository has two kinds of tests.
82a7579
First, there is RPM `%check` section, run when building the `python-rpm-macros`
82a7579
package.
82a7579
82a7579
Then there are CI tests.
82a7579
There is currently [no way to run Fedora CI tests locally][ci-rfe],
82a7579
but you can do what the tests do manually using mock.
82a7579
For each `$PKG.spec` in `tests/`:
82a7579
82a7579
  - clean your mock environment:
82a7579
82a7579
        mock -r fedora-rawhide-x86_64 clean
82a7579
82a7579
  - install the version of `python-rpm-macros` you're testing, e.g.:
82a7579
82a7579
        mock -r fedora-rawhide-x86_64 install .../python-rpm-macros-*.noarch.rpm
82a7579
82a7579
  - download the sources:
82a7579
82a7579
        spectool -g -R $PKG.spec
82a7579
82a7579
  - build a SRPM:
82a7579
82a7579
        rpmbuild -bs $PKG.spec
82a7579
82a7579
  - build in mock, using the path from the command above as `$SRPM`:
82a7579
82a7579
        mock -r fedora-rawhide-x86_64 -n -N $SRPM
82a7579
82a7579
[ci-rfe]: https://pagure.io/fedora-ci/general/issue/4