diff --git a/README.md b/README.md index 7342728d557c602f51c6d278bba9f3dd9faaf356..970580b12c6b7e9fee2afa8660f37e707abdc9bc 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,508 @@ -Anolis OS -======================================= -# 代码仓库说明 -## 分支说明 ->进行代码开发工作时,请注意选择当前版本对应的分支 -* aX分支为对应大版本的主分支,如a8分支对应当前最新版本 -* aX.Y分支为对应小版本的维护分支,如a8.2分支对应8.2版本 -## 开发流程 -1. 首先fork目标分支到自己的namespace -2. 在自己的fork分支上做出修改 -3. 向对应的仓库中提交merge request,源分支为fork分支 +pyproject RPM macros +==================== + +These macros allow projects that follow the Python [packaging specifications] +to be packaged as RPMs. + +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/ + + +Usage +----- + +To use these macros, first BuildRequire the devel package for the Python you +are building against. In Fedora, that's `python3-devel`. + + BuildRequires: python3-devel + +The macros will be pulled in as a dependency on Fedora and EPEL 9+. +In other distributions you need to BuildRequire the macros as well: + + BuildRequires: python3-devel + BuildRequires: 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]. +This also adds run-time dependencies by default and +can add 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 `pyproject-wheeldir/` located in the root of the source tree. + + +Adding run-time and test-time dependencies +------------------------------------------ + +To run tests or import checks in the `%check` section, +the package's runtime dependencies need to also be included as build requirements. + +Hence, `%pyproject_buildrequires` also generates runtime dependencies by default. + +For this to work, the project's build system must support the [prepare-metadata-for-build-wheel hook]. +The popular buildsystems (setuptools, flit, poetry) do support it. + +This behavior can be disabled +(e.g. when the project's build system does not support it) +using the `-R` flag: + + %generate_buildrequires + %pyproject_buildrequires -R + +Alternatively, the runtime dependencies can be obtained by building the wheel and reading the metadata from the built wheel. +This can be enabled by using the `-w` flag. +Support for building wheels with `%pyproject_buildrequires -w` is **provisional** and the behavior might change. +Please subscribe to Fedora's [python-devel list] if you use the option. + + %generate_buildrequires + %pyproject_buildrequires -w + +When this is used, the wheel is going to be built at least twice, +becasue the `%generate_buildrequires` section runs repeatedly. +To avoid accidentally reusing a wheel leaking from a previous (different) build, +it cannot be reused between `%generate_buildrequires` rounds. +Contrarily to that, rebuilding the wheel again in the `%build` section is redundant +and the packager can omit the `%build` section entirely +to reuse the wheel built from the last round of `%generate_buildrequires`. +Be extra careful when attempting to modify the sources after `%pyproject_buildrequires`, +e.g. when running extra commands in the `%build` section: + + %build + cython src/wrong.pyx # this is too late with %%pyproject_buildrequires -w + %pyproject_wheel + +For projects that specify test requirements using an [`extra` +provide](https://packaging.python.org/specifications/core-metadata/#provides-extra-multiple-use), +these can be added using the `-x` flag. +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. + +If your package specifies some tox plugins in `tox.requires`, +such plugins will be BuildRequired as well. +Not all plugins are guaranteed to play well with [tox-current-env], +in worst case, patch/sed the requirement out from the tox configuration. + +Note that neither `-x` or `-t` can be used with `-R`, +because runtime dependencies are always required for testing. +You can only use those options if the build backend supports the [prepare-metadata-for-build-wheel hook], +or together with `-w`. + +[tox]: https://tox.readthedocs.io/ +[tox-current-env]: https://github.com/fedora-python/tox-current-env/ +[prepare-metadata-for-build-wheel hook]: https://www.python.org/dev/peps/pep-0517/#prepare-metadata-for-build-wheel + +Additionally to generated requirements you can supply multiple file names to `%pyproject_buildrequires` macro. +Dependencies will be loaded from them: + + %pyproject_buildrequires requirements/tests.in requirements/docs.in requirements/dev.in + +For packages not using build system you can use `-N` to entirely skip automatical +generation of requirements and install requirements only from manually specified files. +`-N` option implies `-R` and cannot be used in combination with other options mentioned above +(`-w`, `-e`, `-t`, `-x`). + +The `%pyproject_buildrequires` macro also accepts the `-r` flag for backward compatibility; +it means "include runtime dependencies" which has been the default since version 0-53. + + +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: + + - Sets environment variables via `%{py3_test_envvars}`, namely: + - 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 manually (for now). + + %files -n python3-requests -f %{pyproject_files} + %doc README.rst + +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 + %{_bindir}/downloader + +`%pyproject_save_files` can automatically mark license files with `%license` macro +and language (`*.mo`) files with `%lang` macro and appropriate language code. +Only license files declared via [PEP 639] `License-File` field are detected. +[PEP 639] is still a draft and can be changed in the future. + +Note that `%pyproject_save_files` uses data from the [RECORD file](https://www.python.org/dev/peps/pep-0627/). +If you wish to rename, remove or otherwise change the installed files of a package +*after* `%pyproject_install`, `%pyproject_save_files` might break. +If possible, remove/rename such files in `%prep`. +If not possible, avoid using `%pyproject_save_files` or edit/replace `%{pyproject_files}`. + + +Performing an import check on all importable modules +---------------------------------------------------- + +If the upstream test suite cannot be used during the package build +and you use `%pyproject_save_files`, +you can benefit from the `%pyproject_check_import` macro. +If `%pyproject_save_files` is not used, calling `%pyproject_check_import` will fail. + +When `%pyproject_save_files` is invoked, +it creates a list of all valid and public (i.e. not starting with `_`) +importable module names found in the package. +Each top-level module name matches at least one of the globs provided as an argument to `%pyproject_save_files`. +This list is then usable by `%pyproject_check_import` which performs an import check for each listed module. +When a module fails to import, the build fails. + +The modules are imported from both installed and buildroot's `%{python3_sitearch}` +and `%{python3_sitelib}`, not from the current directory. + +Use the macro in `%check`: + + %check + %pyproject_check_import + +By using the `-e` flag, you can exclude module names matching the given glob(s) from the import check +(put it in single quotes to prevent Shell from expanding it). +The flag can be used repeatedly. +For example, to exclude all submodules ending with `config` and all submodules starting with `test`, you can use: + + %pyproject_check_import -e '*.config' -e '*.test*' + +There must be at least one module left for the import check; +if, as a result of greedy excluding, no modules are left to check, the check fails. + +When the `-t` flag is used, only top-level modules are checked, +qualified module names with a dot (`.`) are excluded. +If the modules detected by `%pyproject_save_files` are `requests`, `requests.models`, and `requests.packages`, this will only perform an import of `requests`: + + %pyproject_check_import -t + +The modifying flags should only be used when there is a valid reason for not checking all available modules. +The reason should be documented in a comment. + +The `%pyproject_check_import` macro also accepts positional arguments with +additional qualified module names to check, useful for example if some modules are installed manually. +Note that filtering by `-t`/`-e` also applies to the positional arguments. + + +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. + + +PROVISIONAL: Importing just-built (extension) modules in %build +--------------------------------------------------------------- + +Sometimes, it is desired to be able to import the *just-built* extension modules +in the `%build` section, e.g. to build the documentation with Sphinx. + + %build + %pyproject_wheel + ... build the docs here ... + +With pure Python packages, it might be possible to set `PYTHONPATH=${PWD}` or `PYTHONPATH=${PWD}/src`. +However, it is a bit more complicated with extension modules. + +The location of just-built modules might differ depending on Python version, architecture, pip version, etc. +Hence, the macro `%{pyproject_build_lib}` exists to be used like this: + + %build + %pyproject_wheel + PYTHONPATH=%{pyproject_build_lib} ... build the docs here ... + +This macro is currently **provisional** and the behavior might change. +Please subscribe to Fedora's [python-devel list] if you use the macro. + +The `%{pyproject_build_lib}` macro expands to an Shell `$(...)` expression and does not work when put into single quotes (`'`). + +Depending on the pip version, the expanded value will differ: + +[python-devel list]: https://lists.fedoraproject.org/archives/list/python-devel@lists.fedoraproject.org/ + +### New pip 21.3+ with in-tree-build and setuptools 62.1+ (Fedora 37+) + +Always use the macro from the same directory where you called `%pyproject_wheel` from. +The value will expand to something like: + +* `/builddir/build/BUILD/%{name}-%{version}/build/lib.linux-x86_64-cpython-311` for wheels with extension modules +* `/builddir/build/BUILD/%{name}-%{version}/build/lib` for pure Python wheels + +If multiple wheels were built from the same directory, +some pure Python and some with extension modules, +the expanded value will be combined with `:`: + +* `/builddir/build/BUILD/%{name}-%{version}/build/lib.linux-x86_64-cypthon-311:/builddir/build/BUILD/%{name}-%{version}/build/lib` + +If multiple wheels were built from different directories, +the value will differ depending on the current directory. + + +### New pip 21.3+ with in-tree-build and older setuptools (Fedora 36) + +Always use the macro from the same directory where you called `%pyproject_wheel` from. +The value will expand to something like: + +* `/builddir/build/BUILD/%{name}-%{version}/build/lib.linux-x86_64-3.10` for wheels with extension modules +* `/builddir/build/BUILD/%{name}-%{version}/build/lib` for pure Python wheels + +If multiple wheels were built from the same directory, +some pure Python and some with extension modules, +the expanded value will be combined with `:`: + +* `/builddir/build/BUILD/%{name}-%{version}/build/lib.linux-x86_64-3.10:/builddir/build/BUILD/%{name}-%{version}/build/lib` + +If multiple wheels were built from different directories, +the value will differ depending on the current directory. + + +### Older pip with out-of-tree-build (Fedora 35 and EL 9) + +The value will expand to something like: + +* `/builddir/build/BUILD/%{name}-%{version}/.pyproject-builddir/pip-req-build-xxxxxxxx/build/lib.linux-x86_64-3.10` for wheels with extension modules +* `/builddir/build/BUILD/%{name}-%{version}/.pyproject-builddir/pip-req-build-xxxxxxxx/build/lib` for pure Python wheels + +Note that the exact value is **not stable** between builds +(the `xxxxxxxx` part is randomly generated, +neither you should consider the `.pyproject-builddir` directory to remain stable). + +If multiple wheels are built, +the expanded value will always be combined with `:` regardless of the current directory, e.g.: + +* `/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` + +**Note:** If you manage to build some wheels with in-tree-build and some with out-of-tree-build option, +the expanded value will contain all relevant directories. + + +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. + +When a dependency is specified via an URL or local path, for example as: + + https://github.com/ActiveState/appdirs/archive/8eacfa312d77aba28d483fbfb6f6fc54099622be.zip + /some/path/foo-1.2.3.tar.gz + git+https://github.com/sphinx-doc/sphinx.git@96dbe5e3 + +The `%pyproject_buildrequires` macro is unable to convert it to an appropriate RPM requirement and will fail. +If the URL contains the `packageName @` prefix as specified in [PEP 508], +the requirement will be generated without a version constraint: + + appdirs@https://github.com/ActiveState/appdirs/archive/8eacfa312d77aba28d483fbfb6f6fc54099622be.zip + foo@file:///some/path/foo-1.2.3.tar.gz + +Will be converted to: + + python3dist(appdirs) + python3dist(foo) + +Alternatively, when an URL requirement parsed from a text file +given as positional argument to `%pyproject_buildrequires` +contains the `#egg=packageName` fragment, +as documented in [pip's documentation]: + + git+https://github.com/sphinx-doc/sphinx.git@96dbe5e3#egg=sphinx + +The requirements will be converted to package names without versions, e.g.: + + python3dist(sphinx) + +However upstreams usually only use direct URLs for their requirements as workarounds, +so be prepared for problems. + +[PEP 508]: https://www.python.org/dev/peps/pep-0508/ +[PEP 517]: https://www.python.org/dev/peps/pep-0517/ +[PEP 518]: https://www.python.org/dev/peps/pep-0518/ +[PEP 639]: https://www.python.org/dev/peps/pep-0639/ +[pip's documentation]: https://pip.pypa.io/en/stable/cli/pip_install/#vcs-support + + +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 diff --git a/download b/download new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/macros.aaa-pyproject-srpm b/macros.aaa-pyproject-srpm new file mode 100644 index 0000000000000000000000000000000000000000..9dab5907db2a6fb16b09301cd376c8adf14436b7 --- /dev/null +++ b/macros.aaa-pyproject-srpm @@ -0,0 +1,7 @@ +# This file is called macros.aaa-pyproject-srpm +# to sort alphabetically before macros.pyproject. +# When this file is installed but macros.pyproject is not +# this macro will cause the package with the real macro to be installed. +# When macros.pyproject is installed, it overrides this macro. +# Note: This needs to maintain the same set of options as the real macro. +%pyproject_buildrequires(rRxtNwe:) echo 'pyproject-rpm-macros' && exit 0 diff --git a/macros.pyproject b/macros.pyproject index ca1ca682ae631dcf7f9d20525a4248a605199874..8cc59ae1136f17ea248703965d161e6849bac7c9 100644 --- a/macros.pyproject +++ b/macros.pyproject @@ -124,6 +124,7 @@ fi %toxenv %{default_toxenv} +# Note: Keep the options in sync with this macro from macros.aaa-pyproject-srpm %pyproject_buildrequires(rRxtNwe:) %{expand:\\\ %_set_pytest_addopts # The _auto_set_build_flags feature does not do this in %%generate_buildrequires section, @@ -144,7 +145,7 @@ fi %{-w:%{error:The -N and -w options are mutually exclusive}} } %{-e:%{expand:%global toxenv %(%{__python3} -s %{_rpmconfigdir}/redhat/pyproject_construct_toxenv.py %{?**})}} -echo 'pyproject-rpm-macros' # we already have this installed, but this way, it's repoqueryable +echo 'pyproject-rpm-macros' # first stdout line matches the implementation in macros.aaa-pyproject-srpm echo 'python%{python3_pkgversion}-devel' echo 'python%{python3_pkgversion}dist(pip) >= 19' echo 'python%{python3_pkgversion}dist(packaging)' @@ -176,10 +177,10 @@ fi %tox(e:) %{expand:\\\ TOX_TESTENV_PASSENV="${TOX_TESTENV_PASSENV:-*}" \\ -PYTHONDONTWRITEBYTECODE=1 \\ +%{?py3_test_envvars}%{?!py3_test_envvars:PYTHONDONTWRITEBYTECODE=1 \\ PATH="%{buildroot}%{_bindir}:$PATH" \\ PYTHONPATH="${PYTHONPATH:-%{buildroot}%{python3_sitearch}:%{buildroot}%{python3_sitelib}}" \\ -%{?__pytest_addopts:PYTEST_ADDOPTS="${PYTEST_ADDOPTS:-} %{__pytest_addopts}"} \\ +%{?__pytest_addopts:PYTEST_ADDOPTS="${PYTEST_ADDOPTS:-} %{__pytest_addopts}"}} \\ HOSTNAME="rpmbuild" \\ %{__python3} -m tox --current-env -q --recreate -e "%{-e:%{-e*}}%{!-e:%{toxenv}}" %{?*} } diff --git a/pyproject-rpm-macros.spec b/pyproject-rpm-macros.spec index b2a1ef017dc89e97927f9d3c6a5e870f6d8d0594..ed22fe899dd543ee686c7b71e8d00bf8ea31b90e 100644 --- a/pyproject-rpm-macros.spec +++ b/pyproject-rpm-macros.spec @@ -12,11 +12,12 @@ License: MIT # Increment Y and reset Z when new macros or features are added # Increment Z when this is a bugfix or a cosmetic change # Dropping support for EOL Fedoras is *not* considered a breaking change -Version: 1.3.3 +Version: 1.6.2 Release: 1%{?dist} # Macro files Source001: macros.pyproject +Source002: macros.aaa-pyproject-srpm # Implementation files Source101: pyproject_buildrequires.py @@ -48,6 +49,7 @@ BuildArch: noarch %if %{with tests} BuildRequires: python3dist(pytest) +BuildRequires: python3dist(pytest-xdist) BuildRequires: python3dist(pyyaml) BuildRequires: python3dist(packaging) BuildRequires: python3dist(pip) @@ -64,6 +66,7 @@ BuildRequires: python3-rpm-macros Requires: python-rpm-macros Requires: python-srpm-macros Requires: python3-rpm-macros +Requires: (pyproject-srpm-macros = %{?epoch:%{epoch}:}%{version}-%{release} if pyproject-srpm-macros) # We use the following tools outside of coreutils Requires: /usr/bin/find @@ -84,6 +87,17 @@ These macros replace %%py3_build and %%py3_install, which only work with setup.py. +%package -n pyproject-srpm-macros +Summary: Minimal implementation of %%pyproject_buildrequires +Requires: (pyproject-rpm-macros = %{?epoch:%{epoch}:}%{version}-%{release} if pyproject-rpm-macros) + +%description -n pyproject-srpm-macros +This package contains a minimal implementation of %%pyproject_buildrequires. +When used in %%generate_buildrequires, it will generate BuildRequires +for pyproject-rpm-macros. When both packages are installed, the full version +takes precedence. + + %prep # Not strictly necessary but allows working on file names instead # of source numbers in install section @@ -97,6 +111,7 @@ cp -p %{sources} . mkdir -p %{buildroot}%{_rpmmacrodir} mkdir -p %{buildroot}%{_rpmconfigdir}/redhat install -pm 644 macros.pyproject %{buildroot}%{_rpmmacrodir}/ +install -pm 644 macros.aaa-pyproject-srpm %{buildroot}%{_rpmmacrodir}/ install -pm 644 pyproject_buildrequires.py %{buildroot}%{_rpmconfigdir}/redhat/ install -pm 644 pyproject_convert.py %{buildroot}%{_rpmconfigdir}/redhat/ install -pm 644 pyproject_save_files.py %{buildroot}%{_rpmconfigdir}/redhat/ @@ -108,7 +123,7 @@ install -pm 644 pyproject_wheel.py %{buildroot}%{_rpmconfigdir}/redhat/ %if %{with tests} %check export HOSTNAME="rpmbuild" # to speedup tox in network-less mock, see rhbz#1856356 -%pytest -vv --doctest-modules +%pytest -vv --doctest-modules -n auto # brp-compress is provided as an argument to get the right directory macro expansion %{python3} compare_mandata.py -f %{_rpmconfigdir}/brp-compress @@ -128,7 +143,35 @@ export HOSTNAME="rpmbuild" # to speedup tox in network-less mock, see rhbz#1856 %doc README.md %license LICENSE +%files -n pyproject-srpm-macros +%{_rpmmacrodir}/macros.aaa-pyproject-srpm +%license LICENSE + + %changelog +* Wed Feb 08 2023 Lumír Balhar - 1.6.2-1 +- Improve detection of lang files + +* Fri Feb 03 2023 Miro Hrončok - 1.6.1-1 +- %%pyproject_buildrequires: Avoid leaking stdout from subprocesses + +* Fri Jan 20 2023 Miro Hrončok - 1.6.0-1 +- Add pyproject-srpm-macros with a minimal %%pyproject_buildrequires macro + +* Fri Jan 13 2023 Miro Hrončok - 1.5.1-1 +- Adjusts %%pyproject_buildrequires tests for tox 4 + +* Mon Nov 28 2022 Miro Hrončok - 1.5.0-1 +- Use %%py3_test_envvars in %%tox when available + +* Mon Sep 19 2022 Python Maint - 1.4.0-1 +- %%pyproject_save_files: Support License-Files installed into the *Root License Directory* from PEP 369 +- %%pyproject_check_import: Import only the modules whose top-level names + match any of the globs provided to %%pyproject_save_files + +* Tue Aug 30 2022 Otto Liljalaakso - 1.3.4-1 +- Fix typo in internal function name + * Tue Aug 09 2022 Karolina Surma - 1.3.3-1 - Don't fail %%pyproject_save_files '*' if no modules are detected diff --git a/pyproject_buildrequires.py b/pyproject_buildrequires.py index e604de554e0f807d05022e9552139bdb426e3d2a..323ab2a44ba1cb8ab97f199c06964d8d0a52ba5c 100644 --- a/pyproject_buildrequires.py +++ b/pyproject_buildrequires.py @@ -4,9 +4,9 @@ import os import sys import importlib.metadata import argparse +import tempfile import traceback import contextlib -from io import StringIO import json import subprocess import re @@ -48,11 +48,35 @@ from pyproject_convert import convert @contextlib.contextmanager def hook_call(): - captured_out = StringIO() - with contextlib.redirect_stdout(captured_out): + """Context manager that records all stdout content (on FD level) + and prints it to stderr at the end, with a 'HOOK STDOUT: ' prefix.""" + tmpfile = io.TextIOWrapper( + tempfile.TemporaryFile(buffering=0), + encoding='utf-8', + errors='replace', + write_through=True, + ) + + stdout_fd = 1 + stdout_fd_dup = os.dup(stdout_fd) + stdout_orig = sys.stdout + + # begin capture + sys.stdout = tmpfile + os.dup2(tmpfile.fileno(), stdout_fd) + + try: yield - for line in captured_out.getvalue().splitlines(): - print_err('HOOK STDOUT:', line) + finally: + # end capture + sys.stdout = stdout_orig + os.dup2(stdout_fd_dup, stdout_fd) + + tmpfile.seek(0) # rewind + for line in tmpfile: + print_err('HOOK STDOUT:', line, end='') + + tmpfile.close() def guess_reason_for_invalid_requirement(requirement_str): @@ -100,7 +124,7 @@ class Requirements: return [{'extra': e} for e in sorted(self.extras)] return [{'extra': ''}] - def evaluate_all_environamnets(self, requirement): + def evaluate_all_environments(self, requirement): for marker_env in self.marker_envs: if requirement.marker.evaluate(environment=marker_env): return True @@ -126,7 +150,7 @@ class Requirements: name = canonicalize_name(requirement.name) if (requirement.marker is not None and - not self.evaluate_all_environamnets(requirement)): + not self.evaluate_all_environments(requirement)): print_err(f'Ignoring alien requirement:', requirement_str) return @@ -424,29 +448,35 @@ def generate_requires( def main(argv): parser = argparse.ArgumentParser( - description='Generate BuildRequires for a Python project.' + description='Generate BuildRequires for a Python project.', + prog='%pyproject_buildrequires', + add_help=False, + ) + parser.add_argument( + '--help', action='help', + default=argparse.SUPPRESS, + help=argparse.SUPPRESS, ) parser.add_argument( '-r', '--runtime', action='store_true', default=True, - help='Generate run-time requirements (default, disable with -R)', + help=argparse.SUPPRESS, # Generate run-time requirements (backwards-compatibility only) ) parser.add_argument( - '-w', '--wheel', action='store_true', default=False, - help=('Generate run-time requirements by building the wheel ' - '(useful for build backends without the prepare_metadata_for_build_wheel hook)'), + '--generate-extras', action='store_true', + help=argparse.SUPPRESS, ) parser.add_argument( - '--wheeldir', metavar='PATH', default=None, - help='The directory with wheel, used when -w.', + '-p', '--python3_pkgversion', metavar='PYTHON3_PKGVERSION', + default="3", help=argparse.SUPPRESS, ) parser.add_argument( - '-R', '--no-runtime', action='store_false', dest='runtime', - help="Don't generate run-time requirements (implied by -N)", + '--wheeldir', metavar='PATH', default=None, + help=argparse.SUPPRESS, ) parser.add_argument( - '-e', '--toxenv', metavar='TOXENVS', action='append', - help=('specify tox environments (comma separated and/or repeated)' - '(implies --tox)'), + '-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( '-t', '--tox', action='store_true', @@ -454,25 +484,26 @@ def main(argv): '(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)', + '-e', '--toxenv', metavar='TOXENVS', action='append', + help=('specify tox environments (comma separated and/or repeated)' + '(implies --tox)'), ) parser.add_argument( - '--generate-extras', action='store_true', - help='Generate build requirements on Python Extras', + '-w', '--wheel', action='store_true', default=False, + help=('Generate run-time requirements by building the wheel ' + '(useful for build backends without the prepare_metadata_for_build_wheel hook)'), ) parser.add_argument( - '-p', '--python3_pkgversion', metavar='PYTHON3_PKGVERSION', - default="3", help=('Python version for pythonXdist()' - 'or pythonX.Ydist() requirements'), + '-R', '--no-runtime', action='store_false', dest='runtime', + help="Don't generate run-time requirements (implied by -N)", ) parser.add_argument( '-N', '--no-use-build-system', dest='use_build_system', action='store_false', help='Use -N to indicate that project does not use any build system', ) parser.add_argument( - 'requirement_files', nargs='*', type=argparse.FileType('r'), + 'requirement_files', nargs='*', type=argparse.FileType('r'), + metavar='REQUIREMENTS.TXT', help=('Add buildrequires from file'), ) diff --git a/pyproject_buildrequires_testcases.yaml b/pyproject_buildrequires_testcases.yaml index 50f9d4e2e2eae12340259a4850760b53bf1c8751..1a2e6bbad31ffd180b8ed488e607f60cb4da4623 100644 --- a/pyproject_buildrequires_testcases.yaml +++ b/pyproject_buildrequires_testcases.yaml @@ -413,14 +413,24 @@ Tox dependencies: toxdep2 commands = true - expected: | - python3dist(setuptools) >= 40.8 - python3dist(wheel) - python3dist(wheel) - python3dist(tox-current-env) >= 0.0.6 - python3dist(toxdep1) - python3dist(toxdep2) - python3dist(inst) + expected: + - | # tox 3 + python3dist(setuptools) >= 40.8 + python3dist(wheel) + python3dist(wheel) + python3dist(tox-current-env) >= 0.0.6 + python3dist(toxdep1) + python3dist(toxdep2) + python3dist(inst) + - | # tox 4 + python3dist(setuptools) >= 40.8 + python3dist(wheel) + python3dist(wheel) + python3dist(tox-current-env) >= 0.0.6 + python3dist(tox) + python3dist(toxdep1) + python3dist(toxdep2) + python3dist(inst) result: 0 Tox extras: @@ -455,20 +465,36 @@ Tox extras: extra1 commands = true - expected: | - python3dist(setuptools) >= 40.8 - python3dist(wheel) - python3dist(wheel) - python3dist(tox-current-env) >= 0.0.6 - python3dist(toxdep) - python3dist(inst) - python3dist(dep11) > 11.0 - python3dist(dep12) - python3dist(dep21) - python3dist(dep22) - python3dist(dep23) - python3dist(extra-dep) - python3dist(extra-dep[extra_dep]) + expected: + - | # tox 3 + python3dist(setuptools) >= 40.8 + python3dist(wheel) + python3dist(wheel) + python3dist(tox-current-env) >= 0.0.6 + python3dist(toxdep) + python3dist(inst) + python3dist(dep11) > 11.0 + python3dist(dep12) + python3dist(dep21) + python3dist(dep22) + python3dist(dep23) + python3dist(extra-dep) + python3dist(extra-dep[extra_dep]) + - | # tox 4 + python3dist(setuptools) >= 40.8 + python3dist(wheel) + python3dist(wheel) + python3dist(tox-current-env) >= 0.0.6 + python3dist(tox) + python3dist(toxdep) + python3dist(inst) + python3dist(dep11) > 11.0 + python3dist(dep12) + python3dist(dep21) + python3dist(dep22) + python3dist(dep23) + python3dist(extra-dep) + python3dist(extra-dep[extra_dep]) result: 0 Tox provision unsatisfied: @@ -496,14 +522,24 @@ Tox provision unsatisfied: deps = toxdep1 toxdep2 - expected: | - python3dist(setuptools) >= 40.8 - python3dist(wheel) - python3dist(wheel) - python3dist(tox-current-env) >= 0.0.6 - python3dist(tox) >= 3.999 - python3dist(setuptools) > 40.0 - python3dist(wheel) > 2.0 + expected: + - | # tox 3 + python3dist(setuptools) >= 40.8 + python3dist(wheel) + python3dist(wheel) + python3dist(tox-current-env) >= 0.0.6 + python3dist(tox) >= 3.999 + python3dist(setuptools) > 40.0 + python3dist(wheel) > 2.0 + - | # tox 4 + python3dist(setuptools) >= 40.8 + python3dist(wheel) + python3dist(wheel) + python3dist(tox-current-env) >= 0.0.6 + python3dist(tox) >= 3.999 + python3dist(setuptools) > 40.0 + python3dist(wheel) > 2.0 + python3dist(tox) >= 3.999 result: 0 Tox provision satisfied: @@ -530,16 +566,27 @@ Tox provision satisfied: deps = toxdep1 toxdep2 - expected: | - python3dist(setuptools) >= 40.8 - python3dist(wheel) - python3dist(wheel) - python3dist(tox-current-env) >= 0.0.6 - python3dist(tox) >= 3.5 - python3dist(setuptools) > 40.0 - python3dist(toxdep1) - python3dist(toxdep2) - python3dist(inst) + expected: + - | # tox 3 + python3dist(setuptools) >= 40.8 + python3dist(wheel) + python3dist(wheel) + python3dist(tox-current-env) >= 0.0.6 + python3dist(tox) >= 3.5 + python3dist(setuptools) > 40.0 + python3dist(toxdep1) + python3dist(toxdep2) + python3dist(inst) + - | # tox 4 + python3dist(setuptools) >= 40.8 + python3dist(wheel) + python3dist(wheel) + python3dist(tox-current-env) >= 0.0.6 + python3dist(setuptools) > 40.0 + python3dist(tox) >= 3.5 + python3dist(toxdep1) + python3dist(toxdep2) + python3dist(inst) result: 0 Default build system, unmet deps in requirements file: @@ -771,3 +818,21 @@ Pre-releases are accepted: python3dist(wheel) stderr_contains: "Requirement satisfied: cffi" result: 0 + + +Wrapped subprocess prints to stdout from setup.py: + installed: + setuptools: 50 + wheel: 1 + include_runtime: false + setup.py: | + import os + os.system('echo LEAK?') + from setuptools import setup + setup(name='test', version='0.1') + expected: | + python3dist(setuptools) >= 40.8 + python3dist(wheel) + python3dist(wheel) + stderr_contains: "HOOK STDOUT: LEAK?" + result: 0 diff --git a/pyproject_requirements_txt.py b/pyproject_requirements_txt.py index d2389e8100aed0d539089d8028cd13b599e9901b..5ff1f26f5c0f3cdfb1c90338be1ecb0830203cc3 100644 --- a/pyproject_requirements_txt.py +++ b/pyproject_requirements_txt.py @@ -98,6 +98,4 @@ def expand_env_vars(lines): return match['var'] return value for line in lines: - if match := ENV_VAR_RE.search(line): - var = match['var'] yield ENV_VAR_RE.sub(repl, line) diff --git a/pyproject_save_files.py b/pyproject_save_files.py index 18925f1c323806a50d2bb712268266237a3bae29..00d706db82498aec9d6cfd989c00a040329dbee6 100644 --- a/pyproject_save_files.py +++ b/pyproject_save_files.py @@ -154,8 +154,8 @@ def add_lang_to_module(paths, module_name, path): 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 + if parent.name == 'LC_MESSAGES': + lang_country_code = path.parents[i+1].name break else: return False @@ -286,6 +286,36 @@ def module_names_from_path(path): return {'.'.join(parts[:x+1]) for x in range(len(parts))} +def is_license_file(path, license_files, license_directories): + """ + Check if the given BuildrootPath path matches any of the "License-File" entries. + The path is considered matched when resolved from any of the license_directories + matches string-wise what is stored in any "License-File" entry (license_files). + + Examples: + >>> site_packages = BuildrootPath('/usr/lib/python3.12/site-packages') + >>> distinfo = site_packages / 'foo-1.0.dist-info' + >>> license_directories = [distinfo / 'licenses', distinfo] + >>> license_files = ['LICENSE.txt', 'AUTHORS.md'] + >>> is_license_file(distinfo / 'AUTHORS.md', license_files, license_directories) + True + >>> is_license_file(distinfo / 'licenses/LICENSE.txt', license_files, license_directories) + True + >>> # we don't match based on directory only + >>> is_license_file(distinfo / 'licenses/COPYING', license_files, license_directories) + False + >>> is_license_file(site_packages / 'foo/LICENSE.txt', license_files, license_directories) + False + """ + if not license_files or not license_directories: + return False + for license_dir in license_directories: + if (path.is_relative_to(license_dir) and + str(path.relative_to(license_dir)) in license_files): + return True + return False + + def classify_paths( record_path, parsed_record_content, metadata, sitedirs, python_version, prefix ): @@ -311,10 +341,17 @@ def classify_paths( "other": {"files": []}, # regular %file entries we could not parse :( } + license_files = metadata.get_all('License-File') + license_directory = distinfo / 'licenses' # See PEP 369 "Root License Directory" + # setuptools was the first known build backend to implement License-File. + # Unfortunately they don't put licenses to the license directory (yet): + # https://github.com/pypa/setuptools/issues/3596 + # Hence, we check licenses in both licenses and dist-info + license_directories = (license_directory, distinfo) + # 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. - license_files = metadata.get_all('License-File') for path in parsed_record_content: if path.suffix == ".pyc": # we handle bytecode separately @@ -325,7 +362,7 @@ def classify_paths( # RECORD and REQUESTED files are removed in %pyproject_install # See PEP 627 continue - if license_files and str(path.relative_to(distinfo)) in license_files: + if is_license_file(path, license_files, license_directories): paths["metadata"]["licenses"].append(path) else: paths["metadata"]["files"].append(path) @@ -499,6 +536,50 @@ def generate_file_list(paths_dict, module_globs, include_others=False): return sorted(files) +def generate_module_list(paths_dict, module_globs): + """ + This function takes the paths_dict created by the classify_paths() function and + reads the modules names from it. + It filters those whose top-level module names match any of the provided module_globs. + + Returns list with matching qualified module names. + + Examples: + + >>> generate_module_list({'module_names': {'foo', 'foo.bar', 'baz'}}, {'foo'}) + ['foo', 'foo.bar'] + + >>> generate_module_list({'module_names': {'foo', 'foo.bar', 'baz'}}, {'*foo'}) + ['foo', 'foo.bar'] + + >>> generate_module_list({'module_names': {'foo', 'foo.bar', 'baz'}}, {'foo', 'baz'}) + ['baz', 'foo', 'foo.bar'] + + >>> generate_module_list({'module_names': {'foo', 'foo.bar', 'baz'}}, {'*'}) + ['baz', 'foo', 'foo.bar'] + + >>> generate_module_list({'module_names': {'foo', 'foo.bar', 'baz'}}, {'bar'}) + [] + + Submodules aren't discovered: + + >>> generate_module_list({'module_names': {'foo', 'foo.bar', 'baz'}}, {'*bar'}) + [] + """ + + module_names = paths_dict['module_names'] + filtered_module_names = set() + + for glob in module_globs: + for name in module_names: + # Match the top-level part of the qualified name, eg. 'foo.bar.baz' -> 'foo' + top_level_name = name.split('.')[0] + if fnmatch.fnmatchcase(top_level_name, glob): + filtered_module_names.add(name) + + return sorted(filtered_module_names) + + def parse_varargs(varargs): """ Parse varargs from the %pyproject_save_files macro @@ -627,7 +708,7 @@ def pyproject_save_files_and_modules(buildroot, sitelib, sitearch, python_versio parsed_records = load_parsed_record(pyproject_record) final_file_list = [] - all_module_names = set() + final_module_list = [] for record_path, files in parsed_records.items(): metadata = dist_metadata(buildroot, record_path) @@ -638,12 +719,11 @@ def pyproject_save_files_and_modules(buildroot, sitelib, sitearch, python_versio final_file_list.extend( generate_file_list(paths_dict, globs, include_auto) ) - all_module_names.update(paths_dict["module_names"]) - - # Sort values, so they are always checked in the same order - all_module_names = sorted(all_module_names) + final_module_list.extend( + generate_module_list(paths_dict, globs) + ) - return final_file_list, all_module_names + return final_file_list, final_module_list def main(cli_args): @@ -662,17 +742,31 @@ def main(cli_args): def argparser(): - parser = argparse.ArgumentParser() + parser = argparse.ArgumentParser( + description="Create %{pyproject_files} for a Python project.", + prog="%pyproject_save_files", + add_help=False, + # custom usage to add +auto + usage="%(prog)s MODULE_GLOB [MODULE_GLOB ...] [+auto]", + ) + parser.add_argument( + '--help', action='help', + default=argparse.SUPPRESS, + help=argparse.SUPPRESS, + ) r = parser.add_argument_group("required arguments") - r.add_argument("--output-files", type=PosixPath, required=True) - r.add_argument("--output-modules", 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) - r.add_argument("--prefix", type=PosixPath, required=True) - parser.add_argument("varargs", nargs="+") + r.add_argument("--output-files", type=PosixPath, required=True, help=argparse.SUPPRESS) + r.add_argument("--output-modules", type=PosixPath, required=True, help=argparse.SUPPRESS) + r.add_argument("--buildroot", type=PosixPath, required=True, help=argparse.SUPPRESS) + r.add_argument("--sitelib", type=BuildrootPath, required=True, help=argparse.SUPPRESS) + r.add_argument("--sitearch", type=BuildrootPath, required=True, help=argparse.SUPPRESS) + r.add_argument("--python-version", type=str, required=True, help=argparse.SUPPRESS) + r.add_argument("--pyproject-record", type=PosixPath, required=True, help=argparse.SUPPRESS) + r.add_argument("--prefix", type=PosixPath, required=True, help=argparse.SUPPRESS) + parser.add_argument( + "varargs", nargs="+", metavar="MODULE_GLOB", + help="Shell-like glob matching top-level module names to save into %%{pyproject_files}", + ) return parser diff --git a/pyproject_save_files_test_data.yaml b/pyproject_save_files_test_data.yaml index c6d22911dec89a8d896ecce44405415fb40cfdcb..8f6775dee188f830802aae03727178c10b809259 100644 --- a/pyproject_save_files_test_data.yaml +++ b/pyproject_save_files_test_data.yaml @@ -230,6 +230,28 @@ classified: - /usr/lib/python3.7/site-packages/ipykernel-5.2.1.dist-info/COPYING.md - /usr/lib/python3.7/site-packages/ipykernel-5.2.1.dist-info/INSTALLER licenses: [] + lang: + ipykernel: + fr: + - /usr/lib/python3.7/site-packages/ipykernel/i18n/fr_FR/LC_MESSAGES/nbjs.mo + - /usr/lib/python3.7/site-packages/ipykernel/i18n/fr_FR/LC_MESSAGES/nbui.mo + - /usr/lib/python3.7/site-packages/ipykernel/i18n/fr_FR/LC_MESSAGES/notebook.mo + ja: + - /usr/lib/python3.7/site-packages/ipykernel/i18n/ja_JP/LC_MESSAGES/nbjs.mo + - /usr/lib/python3.7/site-packages/ipykernel/i18n/ja_JP/LC_MESSAGES/nbui.mo + - /usr/lib/python3.7/site-packages/ipykernel/i18n/ja_JP/LC_MESSAGES/notebook.mo + nl: + - /usr/lib/python3.7/site-packages/ipykernel/i18n/nl/LC_MESSAGES/nbjs.mo + - /usr/lib/python3.7/site-packages/ipykernel/i18n/nl/LC_MESSAGES/nbui.mo + - /usr/lib/python3.7/site-packages/ipykernel/i18n/nl/LC_MESSAGES/notebook.mo + ru: + - /usr/lib/python3.7/site-packages/ipykernel/i18n/ru_RU/LC_MESSAGES/nbjs.mo + - /usr/lib/python3.7/site-packages/ipykernel/i18n/ru_RU/LC_MESSAGES/nbui.mo + - /usr/lib/python3.7/site-packages/ipykernel/i18n/ru_RU/LC_MESSAGES/notebook.mo + zh: + - /usr/lib/python3.7/site-packages/ipykernel/i18n/zh_CN/LC_MESSAGES/nbjs.mo + - /usr/lib/python3.7/site-packages/ipykernel/i18n/zh_CN/LC_MESSAGES/nbui.mo + - /usr/lib/python3.7/site-packages/ipykernel/i18n/zh_CN/LC_MESSAGES/notebook.mo modules: ipykernel: - files: @@ -279,6 +301,8 @@ classified: - /usr/lib/python3.7/site-packages/ipykernel/gui/gtk3embed.py - /usr/lib/python3.7/site-packages/ipykernel/gui/gtkembed.py - /usr/lib/python3.7/site-packages/ipykernel/heartbeat.py + - /usr/lib/python3.7/site-packages/ipykernel/i18n/__init__.py + - /usr/lib/python3.7/site-packages/ipykernel/i18n/__pycache__/__init__.cpython-37{,.opt-?}.pyc - /usr/lib/python3.7/site-packages/ipykernel/inprocess/__init__.py - /usr/lib/python3.7/site-packages/ipykernel/inprocess/__pycache__/__init__.cpython-37{,.opt-?}.pyc - /usr/lib/python3.7/site-packages/ipykernel/inprocess/__pycache__/blocking.cpython-37{,.opt-?}.pyc @@ -362,6 +386,18 @@ classified: - /usr/lib/python3.7/site-packages/ipykernel/comm/__pycache__ - /usr/lib/python3.7/site-packages/ipykernel/gui - /usr/lib/python3.7/site-packages/ipykernel/gui/__pycache__ + - /usr/lib/python3.7/site-packages/ipykernel/i18n + - /usr/lib/python3.7/site-packages/ipykernel/i18n/__pycache__ + - /usr/lib/python3.7/site-packages/ipykernel/i18n/fr_FR + - /usr/lib/python3.7/site-packages/ipykernel/i18n/fr_FR/LC_MESSAGES + - /usr/lib/python3.7/site-packages/ipykernel/i18n/ja_JP + - /usr/lib/python3.7/site-packages/ipykernel/i18n/ja_JP/LC_MESSAGES + - /usr/lib/python3.7/site-packages/ipykernel/i18n/nl + - /usr/lib/python3.7/site-packages/ipykernel/i18n/nl/LC_MESSAGES + - /usr/lib/python3.7/site-packages/ipykernel/i18n/ru_RU + - /usr/lib/python3.7/site-packages/ipykernel/i18n/ru_RU/LC_MESSAGES + - /usr/lib/python3.7/site-packages/ipykernel/i18n/zh_CN + - /usr/lib/python3.7/site-packages/ipykernel/i18n/zh_CN/LC_MESSAGES - /usr/lib/python3.7/site-packages/ipykernel/inprocess - /usr/lib/python3.7/site-packages/ipykernel/inprocess/__pycache__ - /usr/lib/python3.7/site-packages/ipykernel/inprocess/tests @@ -7559,6 +7595,18 @@ dumped: - '%dir /usr/lib/python3.7/site-packages/ipykernel/comm/__pycache__' - '%dir /usr/lib/python3.7/site-packages/ipykernel/gui' - '%dir /usr/lib/python3.7/site-packages/ipykernel/gui/__pycache__' + - '%dir /usr/lib/python3.7/site-packages/ipykernel/i18n' + - '%dir /usr/lib/python3.7/site-packages/ipykernel/i18n/__pycache__' + - '%dir /usr/lib/python3.7/site-packages/ipykernel/i18n/fr_FR' + - '%dir /usr/lib/python3.7/site-packages/ipykernel/i18n/fr_FR/LC_MESSAGES' + - '%dir /usr/lib/python3.7/site-packages/ipykernel/i18n/ja_JP' + - '%dir /usr/lib/python3.7/site-packages/ipykernel/i18n/ja_JP/LC_MESSAGES' + - '%dir /usr/lib/python3.7/site-packages/ipykernel/i18n/nl' + - '%dir /usr/lib/python3.7/site-packages/ipykernel/i18n/nl/LC_MESSAGES' + - '%dir /usr/lib/python3.7/site-packages/ipykernel/i18n/ru_RU' + - '%dir /usr/lib/python3.7/site-packages/ipykernel/i18n/ru_RU/LC_MESSAGES' + - '%dir /usr/lib/python3.7/site-packages/ipykernel/i18n/zh_CN' + - '%dir /usr/lib/python3.7/site-packages/ipykernel/i18n/zh_CN/LC_MESSAGES' - '%dir /usr/lib/python3.7/site-packages/ipykernel/inprocess' - '%dir /usr/lib/python3.7/site-packages/ipykernel/inprocess/__pycache__' - '%dir /usr/lib/python3.7/site-packages/ipykernel/inprocess/tests' @@ -7568,6 +7616,21 @@ dumped: - '%dir /usr/lib/python3.7/site-packages/ipykernel/resources' - '%dir /usr/lib/python3.7/site-packages/ipykernel/tests' - '%dir /usr/lib/python3.7/site-packages/ipykernel/tests/__pycache__' + - '%lang(fr) /usr/lib/python3.7/site-packages/ipykernel/i18n/fr_FR/LC_MESSAGES/nbjs.mo' + - '%lang(fr) /usr/lib/python3.7/site-packages/ipykernel/i18n/fr_FR/LC_MESSAGES/nbui.mo' + - '%lang(fr) /usr/lib/python3.7/site-packages/ipykernel/i18n/fr_FR/LC_MESSAGES/notebook.mo' + - '%lang(ja) /usr/lib/python3.7/site-packages/ipykernel/i18n/ja_JP/LC_MESSAGES/nbjs.mo' + - '%lang(ja) /usr/lib/python3.7/site-packages/ipykernel/i18n/ja_JP/LC_MESSAGES/nbui.mo' + - '%lang(ja) /usr/lib/python3.7/site-packages/ipykernel/i18n/ja_JP/LC_MESSAGES/notebook.mo' + - '%lang(nl) /usr/lib/python3.7/site-packages/ipykernel/i18n/nl/LC_MESSAGES/nbjs.mo' + - '%lang(nl) /usr/lib/python3.7/site-packages/ipykernel/i18n/nl/LC_MESSAGES/nbui.mo' + - '%lang(nl) /usr/lib/python3.7/site-packages/ipykernel/i18n/nl/LC_MESSAGES/notebook.mo' + - '%lang(ru) /usr/lib/python3.7/site-packages/ipykernel/i18n/ru_RU/LC_MESSAGES/nbjs.mo' + - '%lang(ru) /usr/lib/python3.7/site-packages/ipykernel/i18n/ru_RU/LC_MESSAGES/nbui.mo' + - '%lang(ru) /usr/lib/python3.7/site-packages/ipykernel/i18n/ru_RU/LC_MESSAGES/notebook.mo' + - '%lang(zh) /usr/lib/python3.7/site-packages/ipykernel/i18n/zh_CN/LC_MESSAGES/nbjs.mo' + - '%lang(zh) /usr/lib/python3.7/site-packages/ipykernel/i18n/zh_CN/LC_MESSAGES/nbui.mo' + - '%lang(zh) /usr/lib/python3.7/site-packages/ipykernel/i18n/zh_CN/LC_MESSAGES/notebook.mo' - /usr/lib/python3.7/site-packages/ipykernel-5.2.1.dist-info/COPYING.md - /usr/lib/python3.7/site-packages/ipykernel-5.2.1.dist-info/INSTALLER - /usr/lib/python3.7/site-packages/ipykernel-5.2.1.dist-info/METADATA @@ -7619,6 +7682,8 @@ dumped: - /usr/lib/python3.7/site-packages/ipykernel/gui/gtk3embed.py - /usr/lib/python3.7/site-packages/ipykernel/gui/gtkembed.py - /usr/lib/python3.7/site-packages/ipykernel/heartbeat.py + - /usr/lib/python3.7/site-packages/ipykernel/i18n/__init__.py + - /usr/lib/python3.7/site-packages/ipykernel/i18n/__pycache__/__init__.cpython-37{,.opt-?}.pyc - /usr/lib/python3.7/site-packages/ipykernel/inprocess/__init__.py - /usr/lib/python3.7/site-packages/ipykernel/inprocess/__pycache__/__init__.cpython-37{,.opt-?}.pyc - /usr/lib/python3.7/site-packages/ipykernel/inprocess/__pycache__/blocking.cpython-37{,.opt-?}.pyc @@ -7713,6 +7778,7 @@ dumped: - ipykernel.gui.gtk3embed - ipykernel.gui.gtkembed - ipykernel.heartbeat + - ipykernel.i18n - ipykernel.inprocess - ipykernel.inprocess.blocking - ipykernel.inprocess.channels @@ -7755,7 +7821,6 @@ dumped: - ipykernel.tests.utils - ipykernel.trio_runner - ipykernel.zmqshell - - ipykernel_launcher - - zope - zope - - '%dir /usr/lib/python3.7/site-packages/zope' @@ -15484,8 +15549,8 @@ metadata: content: | Name: Django Version: 3.0.7 - License-File: licenses/LICENSE - License-File: licenses/LICENSE.python + License-File: LICENSE + License-File: LICENSE.python Whatever: False data records: @@ -15674,6 +15739,24 @@ records: ipykernel/gui/gtk3embed.py,sha256=mjUXqAzPxF956OcmWdWzvU2VLJoZ4ZyXrqCImJcn_Ug,3222 ipykernel/gui/gtkembed.py,sha256=yYp-Npg8jPrfXiN6mrzFy8L6JS7JeBOHz5WxTxSdvMA,3131 ipykernel/heartbeat.py,sha256=ZwIsWYgvjZQgFLjw6PrD9GJnN9XO1CzafUc89DEiPaA,4194 + ipykernel/i18n/__init__.py,sha256=0000000000000000000000000000000000000000000,10 + ipykernel/i18n/__pycache__/__init__.cpython-37.opt-1.pyc,, + ipykernel/i18n/__pycache__/__init__.cpython-37.pyc,, + ipykernel/i18n/fr_FR/LC_MESSAGES/nbjs.mo,sha256=0000000000000000000000000000000000000000000,10 + ipykernel/i18n/fr_FR/LC_MESSAGES/nbui.mo,sha256=0000000000000000000000000000000000000000000,10 + ipykernel/i18n/fr_FR/LC_MESSAGES/notebook.mo,sha256=0000000000000000000000000000000000000000000,10 + ipykernel/i18n/ja_JP/LC_MESSAGES/nbjs.mo,sha256=0000000000000000000000000000000000000000000,10 + ipykernel/i18n/ja_JP/LC_MESSAGES/nbui.mo,sha256=0000000000000000000000000000000000000000000,10 + ipykernel/i18n/ja_JP/LC_MESSAGES/notebook.mo,sha256=0000000000000000000000000000000000000000000,10 + ipykernel/i18n/nl/LC_MESSAGES/nbjs.mo,sha256=0000000000000000000000000000000000000000000,10 + ipykernel/i18n/nl/LC_MESSAGES/nbui.mo,sha256=0000000000000000000000000000000000000000000,10 + ipykernel/i18n/nl/LC_MESSAGES/notebook.mo,sha256=0000000000000000000000000000000000000000000,10 + ipykernel/i18n/ru_RU/LC_MESSAGES/nbjs.mo,sha256=0000000000000000000000000000000000000000000,10 + ipykernel/i18n/ru_RU/LC_MESSAGES/nbui.mo,sha256=0000000000000000000000000000000000000000000,10 + ipykernel/i18n/ru_RU/LC_MESSAGES/notebook.mo,sha256=0000000000000000000000000000000000000000000,10 + ipykernel/i18n/zh_CN/LC_MESSAGES/nbjs.mo,sha256=0000000000000000000000000000000000000000000,10 + ipykernel/i18n/zh_CN/LC_MESSAGES/nbui.mo,sha256=0000000000000000000000000000000000000000000,10 + ipykernel/i18n/zh_CN/LC_MESSAGES/notebook.mo,sha256=0000000000000000000000000000000000000000000,10 ipykernel/inprocess/__init__.py,sha256=UrsfQEevAq5OZ3au4Fn9bu_7c6b_QqroRIE7vE4PB_o,211 ipykernel/inprocess/__pycache__/__init__.cpython-37.pyc,, ipykernel/inprocess/__pycache__/blocking.cpython-37.pyc,, diff --git a/test_pyproject_buildrequires.py b/test_pyproject_buildrequires.py index f9b265063c33c891270c4f161f652e882c67b90c..74f3ae8837b079bf69920076d15414c1191011ff 100644 --- a/test_pyproject_buildrequires.py +++ b/test_pyproject_buildrequires.py @@ -71,7 +71,12 @@ def test_data(case_name, capfd, tmp_path, monkeypatch): out, err = capfd.readouterr() if 'expected' in case: - assert out == case['expected'] + expected = case['expected'] + if isinstance(expected, list): + # at least one of them needs to match + assert any(out == e for e in expected) + else: + assert out == expected # stderr_contains may be a string or list of strings stderr_contains = case.get('stderr_contains')