diff --git a/README.md b/README.md index fc307ab..ede594e 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,6 @@ This is a provisional implementation of pyproject RPM macros for Fedora 30+. These macros are useful for packaging Python projects that use the [PEP 517] `pyproject.toml` file, which specifies the package's build dependencies (including the build system, such as setuptools, flit or poetry). -[PEP 517]: https://www.python.org/dev/peps/pep-0517/ - Usage ----- @@ -17,12 +15,20 @@ If your upstream sources include `pyproject.toml` and you want to use these macr This will bring in python3-devel, so you don't need to require python3-devel explicitly. -Then, build a wheel in %build: +In order to get automatic build dependencies on Fedora 31+, run `%pyproject_buildrequires` in the `%generate_buildrequires` section: + + %generate_buildrequires + %pyproject_buildrequires + +Only build dependencies according to [PEP 517] and [PEP 518] will be added. +All other build dependencies (such as non-Python libraries or test dependencies) still need to be specified manually. + +Then, build a wheel in `%build` with `%pyproject_wheel`: %build %pyproject_wheel -And install the wheel in %install: +And install the wheel in `%install` with `%pyproject_install`: %install %pyproject_install @@ -33,8 +39,12 @@ Limitations `%pyproject_install` currently installs all wheels in `$PWD`. We are working on a more explicit solution. -This macro changes shebang lines of every Python script in `%{buildroot}%{_bindir}` to `#! %{__python3} %{py3_shbang_opt}` (`#! /usr/bin/python -s`). -We plan to preserve exisiting Python flags in shebangs, but the work is not yet finished. +This macro changes shebang lines of every Python script in `%{buildroot}%{_bindir}` to `#! %{__python3} %{py3_shbang_opt}` (`#! /usr/bin/python3 -s`). +We plan to preserve existing Python flags in shebangs, but the work is not yet finished. + +The PEPs don't (yet) define a way to specify test dependencies and test runners. +That means you still need to handle test dependencies and `%check` on your own. -Currently, the macros do not automatically generate BuildRequires. We are working on that as well. +[PEP 517]: https://www.python.org/dev/peps/pep-0517/ +[PEP 518]: https://www.python.org/dev/peps/pep-0518/ diff --git a/macros.pyproject b/macros.pyproject index 1665d42..ffc1916 100644 --- a/macros.pyproject +++ b/macros.pyproject @@ -1,18 +1,28 @@ %pyproject_wheel() %{expand:\\\ - CFLAGS="${CFLAGS:-${RPM_OPT_FLAGS}}" LDFLAGS="${LDFLAGS:-${RPM_LD_FLAGS}}" \\\ - %{__python3} -m pip wheel --no-deps --use-pep517 --no-build-isolation --disable-pip-version-check --progress-bar off --verbose . +CFLAGS="${CFLAGS:-${RPM_OPT_FLAGS}}" LDFLAGS="${LDFLAGS:-${RPM_LD_FLAGS}}" \\\ +%{__python3} -m pip wheel --no-deps --use-pep517 --no-build-isolation --disable-pip-version-check --progress-bar off --verbose . } %pyproject_install() %{expand:\\\ %{__python3} -m pip install --root %{buildroot} --strip-file-prefix %{buildroot} --no-deps --disable-pip-version-check --progress-bar off --verbose --ignore-installed --no-warn-script-location ./*.whl -if [ -e %{buildroot}%{_bindir} ]; then +if [ -d %{buildroot}%{_bindir} ]; then pathfix.py -pni "%{__python3} %{py3_shbang_opts}" %{buildroot}%{_bindir}/* fi -if [ -e %{buildroot}%{python3_sitelib}]; then - sed -i -e 's/pip/rpm/g' %{buildroot}%{python3_sitelib}/*.dist-info/INSTALLER +if [ -d %{buildroot}%{python3_sitelib} ]; then + sed -i 's/pip/rpm/' %{buildroot}%{python3_sitelib}/*.dist-info/INSTALLER fi -if [ -e %{buildroot}{python3_sitearch}]; then - sed -i -e 's/pip/rpm/g' %{buildroot}%{python3_sitearch}/*.dist-info/INSTALLER +if [ -d %{buildroot}%{python3_sitearch} ]; then + sed -i 's/pip/rpm/' %{buildroot}%{python3_sitearch}/*.dist-info/INSTALLER +fi +} + +%pyproject_buildrequires() %{expand:\\\ +echo 'python3-devel' +echo 'python3dist(packaging)' +echo 'python3dist(pip) >= 19' +echo 'python3dist(pytoml)' +if [ -f %{__python3} ]; then + %{__python3} -I %{_rpmconfigdir}/redhat/pyproject_buildrequires.py fi } diff --git a/pyproject-rpm-macros.spec b/pyproject-rpm-macros.spec index c919461..06811ac 100644 --- a/pyproject-rpm-macros.spec +++ b/pyproject-rpm-macros.spec @@ -1,24 +1,35 @@ Name: pyproject-rpm-macros -Version: 0 -Release: 2%{?dist} +Summary: RPM macros for PEP 517 Python packages License: MIT + +# Keep the version at zero and increment only release +Version: 0 +Release: 3%{?dist} + Source0: macros.pyproject -Source1: README.md -Source2: LICENSE +Source1: pyproject_buildrequires.py + +Source8: README.md +Source9: LICENSE + URL: https://src.fedoraproject.org/rpms/pyproject-rpm-macros + BuildArch: noarch -Summary: RPM macros for PEP 517 Python packages +# We keep them here for now to avoid one loop of %%generate_buildrequires +# And to allow the other macros without %%pyproject_buildrequires (e.g. on Fedora 30) +# But those are also always in the output of %%generate_buildrequires +# in order to be removable in the future Requires: python3-pip >= 19 Requires: python3-devel - %description This is a provisional implementation of pyproject RPM macros for Fedora 30+. These macros are useful for packaging Python projects that use the PEP 517 pyproject.toml file, which specifies the package's build dependencies (including the build system, such as setuptools, flit or poetry). + %prep # Not strictly necessary but allows working on file names instead # of source numbers in install section @@ -29,16 +40,22 @@ cp -p %{sources} . # nothing to do, sources are not buildable %install -mkdir -p %{buildroot}/%{_rpmmacrodir} -install -m 644 macros.pyproject %{buildroot}/%{_rpmmacrodir}/ +mkdir -p %{buildroot}%{_rpmmacrodir} +mkdir -p %{buildroot}%{_rpmconfigdir}/redhat +install -m 644 macros.pyproject %{buildroot}%{_rpmmacrodir}/ +install -m 644 pyproject_buildrequires.py %{buildroot}%{_rpmconfigdir}/redhat/ %files %{_rpmmacrodir}/macros.pyproject +%{_rpmconfigdir}/redhat/pyproject_buildrequires.py %doc README.md %license LICENSE %changelog +* Tue Jul 02 2019 Miro Hrončok - 0-3 +- Add %%pyproject_buildrequires + * Tue Jul 02 2019 Miro Hrončok - 0-2 - Fix shell syntax errors in %%pyproject_install - Drop PATH warning in %%pyproject_install diff --git a/pyproject_buildrequires.py b/pyproject_buildrequires.py new file mode 100644 index 0000000..b643f9a --- /dev/null +++ b/pyproject_buildrequires.py @@ -0,0 +1,89 @@ +import sys + +try: + import pytoml + from packaging.requirements import Requirement, InvalidRequirement + from packaging.utils import canonicalize_name, canonicalize_version +except ImportError: + # already echoed by the %pyproject_buildrequires macro + sys.exit(0) + + +try: + with open("pyproject.toml") as f: + pyproject_data = pytoml.load(f) +except FileNotFoundError: + pyproject_data = {} +except Exception as e: + sys.exit(e) +else: + import importlib + + try: + backend = importlib.import_module( + pyproject_data["build-system"]["build-backend"] + ) + except KeyError: + try: + import setuptools.build_meta + except ImportError: + print("python3dist(setuptools) >= 40.8") + print("python3dist(wheel)") + sys.exit(0) + + backend = setuptools.build_meta + except ImportError: + backend = None + + +requirements = set() +rpm_requirements = set() + + +def add_requirement(requirement): + try: + requirements.add(Requirement(requirement)) + except InvalidRequirement as e: + print( + f"WARNING: Skipping invalid requirement: {requirement}\n {e}", + file=sys.stderr, + ) + + +if "requires" in pyproject_data.get("build-system", {}): + try: + for requirement in pyproject_data["build-system"]["requires"]: + add_requirement(requirement) + except Exception as e: + sys.exit(e) + + +if hasattr(backend, "get_requires_for_build_wheel"): + try: + for requirement in backend.get_requires_for_build_wheel(): + add_requirement(requirement) + except Exception as e: + sys.exit(e) + +for requirement in requirements: + name = canonicalize_name(requirement.name) + if requirement.marker is not None and not requirement.marker.evaluate(): + continue + together = [] + for specifier in requirement.specifier: + version = canonicalize_version(specifier.version) + if specifier.operator == "!=": + together.append( + f"(python3dist({name}) < {version} or python3dist({name}) >= {version}.0)" + ) + else: + together.append(f"python3dist({name}) {specifier.operator} {version}") + if len(together) == 0: + rpm_requirements.add(f"python3dist({name})") + if len(together) == 1: + rpm_requirements.add(together[0]) + elif len(together) > 1: + rpm_requirements.add(f"({' and '.join(together)})") + + +print(*sorted(rpm_requirements), sep="\n") diff --git a/tests/python-entrypoints.spec b/tests/python-entrypoints.spec new file mode 100644 index 0000000..ff52bb7 --- /dev/null +++ b/tests/python-entrypoints.spec @@ -0,0 +1,46 @@ +%global pypi_name entrypoints +Name: python-%{pypi_name} +Version: 0.3 +Release: 0%{?dist} +Summary: Discover and load entry points from installed packages +License: MIT +URL: https://entrypoints.readthedocs.io/ +Source0: %{pypi_source} + +BuildArch: noarch +BuildRequires: pyproject-rpm-macros + +%description +Discover and load entry points from installed packages. + + +%package -n python3-%{pypi_name} +Summary: %{summary} +%{?python_provide:%python_provide python3-%{pypi_name}} + +%description -n python3-%{pypi_name} +Discover and load entry points from installed packages. + + +%prep +%autosetup -p1 -n %{pypi_name}-%{version} + + +%generate_buildrequires +%pyproject_buildrequires + + +%build +%pyproject_wheel + + +%install +%pyproject_install + + +%files -n python3-%{pypi_name} +%doc README.rst +%license LICENSE +%{python3_sitelib}/entrypoints-*.dist-info/ +%{python3_sitelib}/entrypoints.py +%{python3_sitelib}/__pycache__/entrypoints.* diff --git a/tests/python-pytest.spec b/tests/python-pytest.spec index 0e7b22f..beb9b8e 100644 --- a/tests/python-pytest.spec +++ b/tests/python-pytest.spec @@ -1,6 +1,6 @@ %global pypi_name pytest Name: python-%{pypi_name} -Version: 5.0.0 +Version: 4.4.2 Release: 0%{?dist} Summary: Simple powerful testing with Python License: MIT