diff options
| author | Lars-Dominik Braun <lars@6xq.net> | 2022-04-23 11:36:55 +0200 |
|---|---|---|
| committer | Marius Bakke <marius@gnu.org> | 2022-10-27 19:43:06 +0200 |
| commit | 400a7a4c80efbde1905ae98a298bbb5882d46a0d (patch) | |
| tree | 151375e9dab1ace78459c2e1e852991e9bdb6acb | |
| parent | b4e2effb30bdcbab00dbe1af3e2b9d4ad446897e (diff) | |
build-system: Add pyproject-build-system.
This is an experimental build system based on python-build-system
that implements PEP 517-compliant builds.
* doc/guix.texi (Build Systems): Add pyproject-build-system section.
* doc/contributing.texi (Python Modules): Mention pyproject.toml and the
PYTHON-TOOLCHAIN package, as well as differences to python-build-system.
* guix/build-system/pyproject.scm,
guix/build/pyproject-build-system.scm,
gnu/packages/aux-files/python/sanity-check-next.py,
gnu/packages/python-commencement.scm: New files.
* Makefile.am (MODULES): Register the new build systems.
* gnu/local.mk (GNU_SYSTEM_MODULES): Add python-commencement.scm.
* gnu/packages/python.scm (python-sans-pip, python-sans-pip-wrapper): New
variables.
Co-authored-by: Marius Bakke <marius@gnu.org>
| -rw-r--r-- | Makefile.am | 2 | ||||
| -rw-r--r-- | doc/contributing.texi | 36 | ||||
| -rw-r--r-- | doc/guix.texi | 29 | ||||
| -rw-r--r-- | gnu/packages/aux-files/python/sanity-check-next.py | 99 | ||||
| -rw-r--r-- | gnu/packages/python.scm | 35 | ||||
| -rw-r--r-- | guix/build-system/pyproject.scm | 147 | ||||
| -rw-r--r-- | guix/build/pyproject-build-system.scm | 393 |
7 files changed, 732 insertions, 9 deletions
diff --git a/Makefile.am b/Makefile.am index 22dcc43f99d..6ccb790c118 100644 --- a/Makefile.am +++ b/Makefile.am | |||
| @@ -166,6 +166,7 @@ MODULES = \ | |||
| 166 | guix/build-system/maven.scm \ | 166 | guix/build-system/maven.scm \ |
| 167 | guix/build-system/node.scm \ | 167 | guix/build-system/node.scm \ |
| 168 | guix/build-system/perl.scm \ | 168 | guix/build-system/perl.scm \ |
| 169 | guix/build-system/pyproject.scm \ | ||
| 169 | guix/build-system/python.scm \ | 170 | guix/build-system/python.scm \ |
| 170 | guix/build-system/renpy.scm \ | 171 | guix/build-system/renpy.scm \ |
| 171 | guix/build-system/ocaml.scm \ | 172 | guix/build-system/ocaml.scm \ |
| @@ -222,6 +223,7 @@ MODULES = \ | |||
| 222 | guix/build/minetest-build-system.scm \ | 223 | guix/build/minetest-build-system.scm \ |
| 223 | guix/build/node-build-system.scm \ | 224 | guix/build/node-build-system.scm \ |
| 224 | guix/build/perl-build-system.scm \ | 225 | guix/build/perl-build-system.scm \ |
| 226 | guix/build/pyproject-build-system.scm \ | ||
| 225 | guix/build/python-build-system.scm \ | 227 | guix/build/python-build-system.scm \ |
| 226 | guix/build/ocaml-build-system.scm \ | 228 | guix/build/ocaml-build-system.scm \ |
| 227 | guix/build/qt-build-system.scm \ | 229 | guix/build/qt-build-system.scm \ |
diff --git a/doc/contributing.texi b/doc/contributing.texi index 4b1eed1cb11..c3221d23e4a 100644 --- a/doc/contributing.texi +++ b/doc/contributing.texi | |||
| @@ -786,12 +786,29 @@ for instance, the module python-dateutil is packaged under the names | |||
| 786 | starts with @code{py} (e.g.@: @code{pytz}), we keep it and prefix it as | 786 | starts with @code{py} (e.g.@: @code{pytz}), we keep it and prefix it as |
| 787 | described above. | 787 | described above. |
| 788 | 788 | ||
| 789 | @quotation Note | ||
| 790 | Currently there are two different build systems for Python packages in Guix: | ||
| 791 | @var{python-build-system} and @var{pyproject-build-system}. For the | ||
| 792 | longest time, Python packages were built from an informally specified | ||
| 793 | @file{setup.py} file. That worked amazingly well, considering Python's | ||
| 794 | success, but was difficult to build tooling around. As a result, a host | ||
| 795 | of alternative build systems emerged and the community eventually settled on a | ||
| 796 | @url{https://peps.python.org/pep-0517/, formal standard} for specifying build | ||
| 797 | requirements. @var{pyproject-build-system} is Guix's implementation of this | ||
| 798 | standard. It is considered ``experimental'' in that it does not yet support | ||
| 799 | all the various PEP-517 @emph{build backends}, but you are encouraged to try | ||
| 800 | it for new Python packages and report any problems. It will eventually be | ||
| 801 | deprecated and merged into @var{python-build-system}. | ||
| 802 | @end quotation | ||
| 803 | |||
| 789 | @subsubsection Specifying Dependencies | 804 | @subsubsection Specifying Dependencies |
| 790 | @cindex inputs, for Python packages | 805 | @cindex inputs, for Python packages |
| 791 | 806 | ||
| 792 | Dependency information for Python packages is usually available in the | 807 | Dependency information for Python packages is usually available in the |
| 793 | package source tree, with varying degrees of accuracy: in the | 808 | package source tree, with varying degrees of accuracy: in the |
| 794 | @file{setup.py} file, in @file{requirements.txt}, or in @file{tox.ini}. | 809 | @file{pyproject.toml} file, the @file{setup.py} file, in |
| 810 | @file{requirements.txt}, or in @file{tox.ini} (the latter mostly for | ||
| 811 | test dependencies). | ||
| 795 | 812 | ||
| 796 | Your mission, when writing a recipe for a Python package, is to map | 813 | Your mission, when writing a recipe for a Python package, is to map |
| 797 | these dependencies to the appropriate type of ``input'' (@pxref{package | 814 | these dependencies to the appropriate type of ``input'' (@pxref{package |
| @@ -802,10 +819,12 @@ following check list to determine which dependency goes where. | |||
| 802 | @itemize | 819 | @itemize |
| 803 | 820 | ||
| 804 | @item | 821 | @item |
| 805 | We currently package Python 2 with @code{setuptools} and @code{pip} | 822 | We currently package Python with @code{setuptools} and @code{pip} |
| 806 | installed like Python 3.4 has per default. Thus you don't need to | 823 | installed per default. This is about to change, and users are encouraged |
| 807 | specify either of these as an input. @command{guix lint} will warn you | 824 | to use @code{python-toolchain} if they want a build environment for Python. |
| 808 | if you do. | 825 | |
| 826 | @command{guix lint} will warn if @code{setuptools} or @code{pip} are | ||
| 827 | added as native-inputs because they are generally not necessary. | ||
| 809 | 828 | ||
| 810 | @item | 829 | @item |
| 811 | Python dependencies required at run time go into | 830 | Python dependencies required at run time go into |
| @@ -814,9 +833,10 @@ Python dependencies required at run time go into | |||
| 814 | @file{requirements.txt} file. | 833 | @file{requirements.txt} file. |
| 815 | 834 | ||
| 816 | @item | 835 | @item |
| 817 | Python packages required only at build time---e.g., those listed with | 836 | Python packages required only at build time---e.g., those listed under |
| 818 | the @code{setup_requires} keyword in @file{setup.py}---or only for | 837 | @code{build-system.requires} in @file{pyproject.toml} or with the |
| 819 | testing---e.g., those in @code{tests_require}---go into | 838 | @code{setup_requires} keyword in @file{setup.py}---or dependencies only |
| 839 | for testing---e.g., those in @code{tests_require} or @file{tox.ini}---go into | ||
| 820 | @code{native-inputs}. The rationale is that (1) they do not need to be | 840 | @code{native-inputs}. The rationale is that (1) they do not need to be |
| 821 | propagated because they are not needed at run time, and (2) in a | 841 | propagated because they are not needed at run time, and (2) in a |
| 822 | cross-compilation context, it's the ``native'' input that we'd want. | 842 | cross-compilation context, it's the ``native'' input that we'd want. |
diff --git a/doc/guix.texi b/doc/guix.texi index 2f7ab61aecd..3bfb89bc33a 100644 --- a/doc/guix.texi +++ b/doc/guix.texi | |||
| @@ -9313,7 +9313,36 @@ instead of the default @code{"out"} output. This is useful for packages that | |||
| 9313 | include a Python package as only a part of the software, and thus want to | 9313 | include a Python package as only a part of the software, and thus want to |
| 9314 | combine the phases of @code{python-build-system} with another build system. | 9314 | combine the phases of @code{python-build-system} with another build system. |
| 9315 | Python bindings are a common usecase. | 9315 | Python bindings are a common usecase. |
| 9316 | @end defvr | ||
| 9317 | |||
| 9318 | @defvr {Scheme Variable} pyproject-build-system | ||
| 9319 | This is a variable exported by @code{guix build-system pyproject}. It | ||
| 9320 | is based on @var{python-build-system}, and adds support for | ||
| 9321 | @file{pyproject.toml} and @url{https://peps.python.org/pep-0517/, PEP 517}. | ||
| 9322 | It also supports a variety of build backends and test frameworks. | ||
| 9323 | |||
| 9324 | The API is slightly different from @var{python-build-system}: | ||
| 9325 | @itemize | ||
| 9326 | @item | ||
| 9327 | @code{#:use-setuptools?} and @code{#:test-target} is removed. | ||
| 9328 | @item | ||
| 9329 | @code{#:build-backend} is added. It defaults to @code{#false} and will try | ||
| 9330 | to guess the appropriate backend based on @file{pyproject.toml}. | ||
| 9331 | @item | ||
| 9332 | @code{#:test-backend} is added. It defaults to @code{#false} and will guess | ||
| 9333 | an appropriate test backend based on what is available in package inputs. | ||
| 9334 | @item | ||
| 9335 | @code{#:test-flags} is added. The default is @code{#false}, and varies based | ||
| 9336 | on the detected @code{#:test-backend}. | ||
| 9337 | @end itemize | ||
| 9338 | |||
| 9339 | It is considered ``experimental'' in that the implementation details are | ||
| 9340 | not set in stone yet, however users are encouraged to try it for new | ||
| 9341 | Python projects (even those using @file{setup.py}). The API is subject to | ||
| 9342 | change, but any breaking changes in the Guix channel will be dealt with. | ||
| 9316 | 9343 | ||
| 9344 | Eventually this build system will be deprecated and merged back into | ||
| 9345 | @var{python-build-system}, probably some time in 2024. | ||
| 9317 | @end defvr | 9346 | @end defvr |
| 9318 | 9347 | ||
| 9319 | @defvr {Scheme Variable} perl-build-system | 9348 | @defvr {Scheme Variable} perl-build-system |
diff --git a/gnu/packages/aux-files/python/sanity-check-next.py b/gnu/packages/aux-files/python/sanity-check-next.py new file mode 100644 index 00000000000..a206b51d0a0 --- /dev/null +++ b/gnu/packages/aux-files/python/sanity-check-next.py | |||
| @@ -0,0 +1,99 @@ | |||
| 1 | # -*- coding: utf-8 -*- | ||
| 2 | # GNU Guix --- Functional package management for GNU | ||
| 3 | # Copyright © 2021, 2022 Lars-Dominik Braun <lars@6xq.net> | ||
| 4 | # | ||
| 5 | # This file is part of GNU Guix. | ||
| 6 | # | ||
| 7 | # GNU Guix is free software; you can redistribute it and/or modify it | ||
| 8 | # under the terms of the GNU General Public License as published by | ||
| 9 | # the Free Software Foundation; either version 3 of the License, or (at | ||
| 10 | # your option) any later version. | ||
| 11 | # | ||
| 12 | # GNU Guix is distributed in the hope that it will be useful, but | ||
| 13 | # WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 15 | # GNU General Public License for more details. | ||
| 16 | # | ||
| 17 | # You should have received a copy of the GNU General Public License | ||
| 18 | # along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. | ||
| 19 | |||
| 20 | # This version adds a small change to accommodate missing python-setuptools. | ||
| 21 | # TODO: Merge with sanity-check.py in the next core-updates cycle. | ||
| 22 | |||
| 23 | from __future__ import print_function # Python 2 support. | ||
| 24 | import importlib | ||
| 25 | import sys | ||
| 26 | import traceback | ||
| 27 | try: | ||
| 28 | import pkg_resources | ||
| 29 | except ImportError: | ||
| 30 | print('Warning: Skipping, because python-setuptools are not available.') | ||
| 31 | sys.exit(0) | ||
| 32 | |||
| 33 | try: | ||
| 34 | from importlib.machinery import PathFinder | ||
| 35 | except ImportError: | ||
| 36 | PathFinder = None | ||
| 37 | |||
| 38 | ret = 0 | ||
| 39 | |||
| 40 | # Only check site-packages installed by this package, but not dependencies | ||
| 41 | # (which pkg_resources.working_set would include). Path supplied via argv. | ||
| 42 | ws = pkg_resources.find_distributions(sys.argv[1]) | ||
| 43 | |||
| 44 | for dist in ws: | ||
| 45 | print('validating', repr(dist.project_name), dist.location) | ||
| 46 | try: | ||
| 47 | print('...checking requirements: ', end='') | ||
| 48 | req = str(dist.as_requirement()) | ||
| 49 | # dist.activate() is not enough to actually check requirements, we | ||
| 50 | # have to .require() it. | ||
| 51 | pkg_resources.require(req) | ||
| 52 | print('OK') | ||
| 53 | except Exception as e: | ||
| 54 | print('ERROR:', req, repr(e)) | ||
| 55 | ret = 1 | ||
| 56 | continue | ||
| 57 | |||
| 58 | # Try to load top level modules. This should not have any side-effects. | ||
| 59 | try: | ||
| 60 | metalines = dist.get_metadata_lines('top_level.txt') | ||
| 61 | except (KeyError, EnvironmentError): | ||
| 62 | # distutils (i.e. #:use-setuptools? #f) will not install any metadata. | ||
| 63 | # This file is also missing for packages built using a PEP 517 builder | ||
| 64 | # such as poetry. | ||
| 65 | print('WARNING: cannot determine top-level modules') | ||
| 66 | continue | ||
| 67 | for name in metalines: | ||
| 68 | # Only available on Python 3. | ||
| 69 | if PathFinder and PathFinder.find_spec(name) is None: | ||
| 70 | # Ignore unavailable modules, often C modules, which were not | ||
| 71 | # installed at the top-level. Cannot use ModuleNotFoundError, | ||
| 72 | # because it is raised by failed imports too. | ||
| 73 | continue | ||
| 74 | try: | ||
| 75 | print('...trying to load module', name, end=': ') | ||
| 76 | importlib.import_module(name) | ||
| 77 | print('OK') | ||
| 78 | except Exception: | ||
| 79 | print('ERROR:') | ||
| 80 | traceback.print_exc(file=sys.stdout) | ||
| 81 | ret = 1 | ||
| 82 | |||
| 83 | # Try to load entry points of console scripts too, making sure they | ||
| 84 | # work. They should be removed if they don't. Other groups may not be | ||
| 85 | # safe, as they can depend on optional packages. | ||
| 86 | for group, v in dist.get_entry_map().items(): | ||
| 87 | if group not in {'console_scripts', 'gui_scripts'}: | ||
| 88 | continue | ||
| 89 | for name, ep in v.items(): | ||
| 90 | try: | ||
| 91 | print('...trying to load endpoint', group, name, end=': ') | ||
| 92 | ep.load() | ||
| 93 | print('OK') | ||
| 94 | except Exception: | ||
| 95 | print('ERROR:') | ||
| 96 | traceback.print_exc(file=sys.stdout) | ||
| 97 | ret = 1 | ||
| 98 | |||
| 99 | sys.exit(ret) | ||
diff --git a/gnu/packages/python.scm b/gnu/packages/python.scm index 00c3eb7774e..0c164d7991d 100644 --- a/gnu/packages/python.scm +++ b/gnu/packages/python.scm | |||
| @@ -26,7 +26,7 @@ | |||
| 26 | ;;; Copyright © 2016, 2017 Nikita <nikita@n0.is> | 26 | ;;; Copyright © 2016, 2017 Nikita <nikita@n0.is> |
| 27 | ;;; Copyright © 2016 Dylan Jeffers <sapientech@sapientech@openmailbox.org> | 27 | ;;; Copyright © 2016 Dylan Jeffers <sapientech@sapientech@openmailbox.org> |
| 28 | ;;; Copyright © 2016 David Craven <david@craven.ch> | 28 | ;;; Copyright © 2016 David Craven <david@craven.ch> |
| 29 | ;;; Copyright © 2016, 2017, 2018, 2019, 2020, 2021 Marius Bakke <marius@gnu.org> | 29 | ;;; Copyright © 2016-2022 Marius Bakke <marius@gnu.org> |
| 30 | ;;; Copyright © 2016, 2017 Stefan Reichör <stefan@xsteve.at> | 30 | ;;; Copyright © 2016, 2017 Stefan Reichör <stefan@xsteve.at> |
| 31 | ;;; Copyright © 2016, 2017 Alex Vong <alexvong1995@gmail.com> | 31 | ;;; Copyright © 2016, 2017 Alex Vong <alexvong1995@gmail.com> |
| 32 | ;;; Copyright © 2016, 2017, 2018 Arun Isaac <arunisaac@systemreboot.net> | 32 | ;;; Copyright © 2016, 2017, 2018 Arun Isaac <arunisaac@systemreboot.net> |
| @@ -60,6 +60,7 @@ | |||
| 60 | ;;; Copyright © 2020, 2021 Greg Hogan <code@greghogan.com> | 60 | ;;; Copyright © 2020, 2021 Greg Hogan <code@greghogan.com> |
| 61 | ;;; Copyright © 2022 Philip McGrath <philip@philipmcgrath.com> | 61 | ;;; Copyright © 2022 Philip McGrath <philip@philipmcgrath.com> |
| 62 | ;;; Copyright © 2022 jgart <jgart@dismail.de> | 62 | ;;; Copyright © 2022 jgart <jgart@dismail.de> |
| 63 | ;;; Copyright © 2021 Lars-Dominik Braun <lars@6xq.net> | ||
| 63 | ;;; | 64 | ;;; |
| 64 | ;;; This file is part of GNU Guix. | 65 | ;;; This file is part of GNU Guix. |
| 65 | ;;; | 66 | ;;; |
| @@ -87,6 +88,7 @@ | |||
| 87 | #:use-module (gnu packages hurd) | 88 | #:use-module (gnu packages hurd) |
| 88 | #:use-module (gnu packages libffi) | 89 | #:use-module (gnu packages libffi) |
| 89 | #:use-module (gnu packages pkg-config) | 90 | #:use-module (gnu packages pkg-config) |
| 91 | #:use-module (gnu packages python-build) | ||
| 90 | #:use-module (gnu packages readline) | 92 | #:use-module (gnu packages readline) |
| 91 | #:use-module (gnu packages sqlite) | 93 | #:use-module (gnu packages sqlite) |
| 92 | #:use-module (gnu packages tcl) | 94 | #:use-module (gnu packages tcl) |
| @@ -674,6 +676,37 @@ and the unversioned commands available."))) | |||
| 674 | (define-public python-wrapper (wrap-python3 python)) | 676 | (define-public python-wrapper (wrap-python3 python)) |
| 675 | (define-public python-minimal-wrapper (wrap-python3 python-minimal)) | 677 | (define-public python-minimal-wrapper (wrap-python3 python-minimal)) |
| 676 | 678 | ||
| 679 | ;; The Python used in pyproject-build-system. | ||
| 680 | (define-public python-sans-pip | ||
| 681 | (hidden-package | ||
| 682 | (package/inherit python | ||
| 683 | (arguments | ||
| 684 | (substitute-keyword-arguments (package-arguments python) | ||
| 685 | ((#:configure-flags flags #~()) | ||
| 686 | #~(append '("--with-ensurepip=no") | ||
| 687 | (delete "--with-ensurepip=install" #$flags)))))))) | ||
| 688 | |||
| 689 | (define-public python-sans-pip-wrapper | ||
| 690 | (wrap-python3 python-sans-pip)) | ||
| 691 | |||
| 692 | (define-public python-toolchain | ||
| 693 | (let ((base (package/inherit python-sans-pip-wrapper))) | ||
| 694 | (package | ||
| 695 | (inherit base) | ||
| 696 | (properties '()) | ||
| 697 | (name "python-toolchain") | ||
| 698 | (propagated-inputs | ||
| 699 | (modify-inputs (package-propagated-inputs base) | ||
| 700 | (append python-pip | ||
| 701 | python-pypa-build | ||
| 702 | python-setuptools | ||
| 703 | python-wheel))) | ||
| 704 | (synopsis "Python toolchain") | ||
| 705 | (description | ||
| 706 | "Python toolchain including Python itself, setuptools and pip. | ||
| 707 | Use this package if you need a minimal Python toolchain instead of just | ||
| 708 | the interpreter.")))) | ||
| 709 | |||
| 677 | (define-public micropython | 710 | (define-public micropython |
| 678 | (package | 711 | (package |
| 679 | (name "micropython") | 712 | (name "micropython") |
diff --git a/guix/build-system/pyproject.scm b/guix/build-system/pyproject.scm new file mode 100644 index 00000000000..1e365d4f218 --- /dev/null +++ b/guix/build-system/pyproject.scm | |||
| @@ -0,0 +1,147 @@ | |||
| 1 | ;;; GNU Guix --- Functional package management for GNU | ||
| 2 | ;;; Copyright © 2021 Lars-Dominik Braun <lars@6xq.net> | ||
| 3 | ;;; Copyright © 2022 Marius Bakke <marius@gnu.org> | ||
| 4 | ;;; | ||
| 5 | ;;; This file is part of GNU Guix. | ||
| 6 | ;;; | ||
| 7 | ;;; GNU Guix is free software; you can redistribute it and/or modify it | ||
| 8 | ;;; under the terms of the GNU General Public License as published by | ||
| 9 | ;;; the Free Software Foundation; either version 3 of the License, or (at | ||
| 10 | ;;; your option) any later version. | ||
| 11 | ;;; | ||
| 12 | ;;; GNU Guix is distributed in the hope that it will be useful, but | ||
| 13 | ;;; WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 14 | ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 15 | ;;; GNU General Public License for more details. | ||
| 16 | ;;; | ||
| 17 | ;;; You should have received a copy of the GNU General Public License | ||
| 18 | ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. | ||
| 19 | |||
| 20 | (define-module (guix build-system pyproject) | ||
| 21 | #:use-module ((gnu packages) #:select (search-auxiliary-file)) | ||
| 22 | #:use-module (guix gexp) | ||
| 23 | #:use-module (guix store) | ||
| 24 | #:use-module (guix utils) | ||
| 25 | #:use-module (guix memoization) | ||
| 26 | #:use-module (guix gexp) | ||
| 27 | #:use-module (guix monads) | ||
| 28 | #:use-module (guix packages) | ||
| 29 | #:use-module (guix derivations) | ||
| 30 | #:use-module (guix search-paths) | ||
| 31 | #:use-module (guix build-system) | ||
| 32 | #:use-module (guix build-system gnu) | ||
| 33 | #:use-module (guix build-system python) | ||
| 34 | #:use-module (ice-9 match) | ||
| 35 | #:use-module (srfi srfi-1) | ||
| 36 | #:use-module (srfi srfi-26) | ||
| 37 | #:export (%pyproject-build-system-modules | ||
| 38 | default-python | ||
| 39 | pyproject-build | ||
| 40 | pyproject-build-system)) | ||
| 41 | |||
| 42 | ;; Commentary: | ||
| 43 | ;; | ||
| 44 | ;; Standard build procedure for Python packages using 'pyproject.toml'. | ||
| 45 | ;; This is implemented as an extension of 'python-build-system'. | ||
| 46 | ;; | ||
| 47 | ;; Code: | ||
| 48 | |||
| 49 | (define %pyproject-build-system-modules | ||
| 50 | ;; Build-side modules imported by default. | ||
| 51 | `((guix build pyproject-build-system) | ||
| 52 | (guix build json) | ||
| 53 | ,@%python-build-system-modules)) | ||
| 54 | |||
| 55 | (define (default-python) | ||
| 56 | "Return the default Python package." | ||
| 57 | ;; Lazily resolve the binding to avoid a circular dependency. | ||
| 58 | (let ((python (resolve-interface '(gnu packages python)))) | ||
| 59 | (module-ref python 'python-toolchain))) | ||
| 60 | |||
| 61 | (define sanity-check.py | ||
| 62 | ;; TODO: Merge with sanity-check.py in the next rebuild cycle. | ||
| 63 | (search-auxiliary-file "python/sanity-check-next.py")) | ||
| 64 | |||
| 65 | (define* (lower name | ||
| 66 | #:key source inputs native-inputs outputs system target | ||
| 67 | (python (default-python)) | ||
| 68 | #:allow-other-keys | ||
| 69 | #:rest arguments) | ||
| 70 | "Return a bag for NAME." | ||
| 71 | (define private-keywords | ||
| 72 | '(#:target #:python #:inputs #:native-inputs)) | ||
| 73 | |||
| 74 | (and (not target) ;XXX: no cross-compilation | ||
| 75 | (bag | ||
| 76 | (name name) | ||
| 77 | (system system) | ||
| 78 | (host-inputs `(,@(if source | ||
| 79 | `(("source" ,source)) | ||
| 80 | '()) | ||
| 81 | ,@inputs | ||
| 82 | |||
| 83 | ;; Keep the standard inputs of 'gnu-build-system'. | ||
| 84 | ,@(standard-packages))) | ||
| 85 | (build-inputs `(("python" ,python) | ||
| 86 | ("sanity-check.py" ,(local-file sanity-check.py)) | ||
| 87 | ,@native-inputs)) | ||
| 88 | (outputs (append outputs '(wheel))) | ||
| 89 | (build pyproject-build) | ||
| 90 | (arguments (strip-keyword-arguments private-keywords arguments))))) | ||
| 91 | |||
| 92 | (define* (pyproject-build name inputs | ||
| 93 | #:key source | ||
| 94 | (tests? #t) | ||
| 95 | (configure-flags ''()) | ||
| 96 | (build-backend #f) | ||
| 97 | (test-backend #f) | ||
| 98 | (test-flags #f) | ||
| 99 | (phases '%standard-phases) | ||
| 100 | (outputs '("out" "wheel")) | ||
| 101 | (search-paths '()) | ||
| 102 | (system (%current-system)) | ||
| 103 | (guile #f) | ||
| 104 | (imported-modules %pyproject-build-system-modules) | ||
| 105 | (modules '((guix build pyproject-build-system) | ||
| 106 | (guix build utils)))) | ||
| 107 | "Build SOURCE using PYTHON, and with INPUTS." | ||
| 108 | (define build | ||
| 109 | (with-imported-modules imported-modules | ||
| 110 | #~(begin | ||
| 111 | (use-modules #$@(sexp->gexp modules)) | ||
| 112 | |||
| 113 | #$(with-build-variables inputs outputs | ||
| 114 | #~(pyproject-build | ||
| 115 | #:name #$name | ||
| 116 | #:source #+source | ||
| 117 | #:configure-flags #$configure-flags | ||
| 118 | #:system #$system | ||
| 119 | #:build-backend #$build-backend | ||
| 120 | #:test-backend #$test-backend | ||
| 121 | #:test-flags #$test-flags | ||
| 122 | #:tests? #$tests? | ||
| 123 | #:phases #$(if (pair? phases) | ||
| 124 | (sexp->gexp phases) | ||
| 125 | phases) | ||
| 126 | #:outputs %outputs | ||
| 127 | #:search-paths '#$(sexp->gexp | ||
| 128 | (map search-path-specification->sexp | ||
| 129 | search-paths)) | ||
| 130 | #:inputs %build-inputs))))) | ||
| 131 | |||
| 132 | |||
| 133 | (mlet %store-monad ((guile (package->derivation (or guile (default-guile)) | ||
| 134 | system #:graft? #f))) | ||
| 135 | (gexp->derivation name build | ||
| 136 | #:system system | ||
| 137 | #:graft? #f ;consistent with 'gnu-build' | ||
| 138 | #:target #f | ||
| 139 | #:guile-for-build guile))) | ||
| 140 | |||
| 141 | (define pyproject-build-system | ||
| 142 | (build-system | ||
| 143 | (name 'pyproject) | ||
| 144 | (description "The PEP517-compliant Python build system") | ||
| 145 | (lower lower))) | ||
| 146 | |||
| 147 | ;;; pyproject.scm ends here | ||
diff --git a/guix/build/pyproject-build-system.scm b/guix/build/pyproject-build-system.scm new file mode 100644 index 00000000000..d247fd77a81 --- /dev/null +++ b/guix/build/pyproject-build-system.scm | |||
| @@ -0,0 +1,393 @@ | |||
| 1 | ;;; GNU Guix --- Functional package management for GNU | ||
| 2 | ;;; Copyright © 2021 Lars-Dominik Braun <lars@6xq.net> | ||
| 3 | ;;; Copyright © 2022 Marius Bakke <marius@gnu.org> | ||
| 4 | ;;; | ||
| 5 | ;;; This file is part of GNU Guix. | ||
| 6 | ;;; | ||
| 7 | ;;; GNU Guix is free software; you can redistribute it and/or modify it | ||
| 8 | ;;; under the terms of the GNU General Public License as published by | ||
| 9 | ;;; the Free Software Foundation; either version 3 of the License, or (at | ||
| 10 | ;;; your option) any later version. | ||
| 11 | ;;; | ||
| 12 | ;;; GNU Guix is distributed in the hope that it will be useful, but | ||
| 13 | ;;; WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 14 | ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 15 | ;;; GNU General Public License for more details. | ||
| 16 | ;;; | ||
| 17 | ;;; You should have received a copy of the GNU General Public License | ||
| 18 | ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. | ||
| 19 | |||
| 20 | (define-module (guix build pyproject-build-system) | ||
| 21 | #:use-module ((guix build python-build-system) #:prefix python:) | ||
| 22 | #:use-module (guix build utils) | ||
| 23 | #:use-module (guix build json) | ||
| 24 | #:use-module (ice-9 match) | ||
| 25 | #:use-module (ice-9 ftw) | ||
| 26 | #:use-module (ice-9 format) | ||
| 27 | #:use-module (ice-9 rdelim) | ||
| 28 | #:use-module (ice-9 regex) | ||
| 29 | #:use-module (srfi srfi-1) | ||
| 30 | #:use-module (srfi srfi-26) | ||
| 31 | #:use-module (srfi srfi-34) | ||
| 32 | #:use-module (srfi srfi-35) | ||
| 33 | #:export (%standard-phases | ||
| 34 | add-installed-pythonpath | ||
| 35 | site-packages | ||
| 36 | python-version | ||
| 37 | pyproject-build)) | ||
| 38 | |||
| 39 | ;;; Commentary: | ||
| 40 | ;;; | ||
| 41 | ;;; PEP 517-compatible build system for Python packages. | ||
| 42 | ;;; | ||
| 43 | ;;; PEP 517 mandates the use of a TOML file called pyproject.toml at the | ||
| 44 | ;;; project root, describing build and runtime dependencies, as well as the | ||
| 45 | ;;; build system, which can be different from setuptools. This module uses | ||
| 46 | ;;; that file to extract the build system used and call its wheel-building | ||
| 47 | ;;; entry point build_wheel (see 'build). setuptools’ wheel builder is | ||
| 48 | ;;; used as a fallback if either no pyproject.toml exists or it does not | ||
| 49 | ;;; declare a build-system. It supports config_settings through the | ||
| 50 | ;;; standard #:configure-flags argument. | ||
| 51 | ;;; | ||
| 52 | ;;; This wheel, which is just a ZIP file with a file structure defined | ||
| 53 | ;;; by PEP 427 (https://www.python.org/dev/peps/pep-0427/), is then unpacked | ||
| 54 | ;;; and its contents are moved to the appropriate locations in 'install. | ||
| 55 | ;;; | ||
| 56 | ;;; Then entry points, as defined by the PyPa Entry Point Specification | ||
| 57 | ;;; (https://packaging.python.org/specifications/entry-points/) are read | ||
| 58 | ;;; from a file called entry_points.txt in the package’s site-packages | ||
| 59 | ;;; subdirectory and scripts are written to bin/. These are not part of a | ||
| 60 | ;;; wheel and expected to be created by the installing utility. | ||
| 61 | ;;; TODO: Add support for PEP-621 entry points. | ||
| 62 | ;;; | ||
| 63 | ;;; Caveats: | ||
| 64 | ;;; - There is no support for in-tree build backends. | ||
| 65 | ;;; | ||
| 66 | ;;; Code: | ||
| 67 | ;;; | ||
| 68 | |||
| 69 | ;; Re-export these variables from python-build-system as many packages | ||
| 70 | ;; rely on these. | ||
| 71 | (define python-version python:python-version) | ||
| 72 | (define site-packages python:site-packages) | ||
| 73 | (define add-installed-pythonpath python:add-installed-pythonpath) | ||
| 74 | |||
| 75 | ;; Base error type. | ||
| 76 | (define-condition-type &python-build-error &error python-build-error?) | ||
| 77 | |||
| 78 | ;; Raised when 'check cannot find a valid test system in the inputs. | ||
| 79 | (define-condition-type &test-system-not-found &python-build-error | ||
| 80 | test-system-not-found?) | ||
| 81 | |||
| 82 | ;; Raised when multiple wheels are created by 'build. | ||
| 83 | (define-condition-type &cannot-extract-multiple-wheels &python-build-error | ||
| 84 | cannot-extract-multiple-wheels?) | ||
| 85 | |||
| 86 | ;; Raised, when no wheel has been built by the build system. | ||
| 87 | (define-condition-type &no-wheels-built &python-build-error no-wheels-built?) | ||
| 88 | |||
| 89 | (define* (build #:key outputs build-backend configure-flags #:allow-other-keys) | ||
| 90 | "Build a given Python package." | ||
| 91 | |||
| 92 | (define (pyproject.toml->build-backend file) | ||
| 93 | "Look up the build backend in a pyproject.toml file." | ||
| 94 | (call-with-input-file file | ||
| 95 | (lambda (in) | ||
| 96 | (let loop | ||
| 97 | ((line (read-line in | ||
| 98 | 'concat))) | ||
| 99 | (if (eof-object? line) #f | ||
| 100 | (let ((m (string-match "build-backend = [\"'](.+)[\"']" line))) | ||
| 101 | (if m | ||
| 102 | (match:substring m 1) | ||
| 103 | (loop (read-line in | ||
| 104 | 'concat))))))))) | ||
| 105 | |||
| 106 | (let* ((wheel-output (assoc-ref outputs "wheel")) | ||
| 107 | (wheel-dir (if wheel-output wheel-output "dist")) | ||
| 108 | ;; There is no easy way to get data from Guile into Python via | ||
| 109 | ;; s-expressions, but we have JSON serialization already, which Python | ||
| 110 | ;; also supports out-of-the-box. | ||
| 111 | (config-settings (call-with-output-string (cut write-json | ||
| 112 | configure-flags <>))) | ||
| 113 | ;; python-setuptools’ default backend supports setup.py *and* | ||
| 114 | ;; pyproject.toml. Allow overriding this automatic detection via | ||
| 115 | ;; build-backend. | ||
| 116 | (auto-build-backend (if (file-exists? "pyproject.toml") | ||
| 117 | (pyproject.toml->build-backend | ||
| 118 | "pyproject.toml") #f)) | ||
| 119 | ;; Use build system detection here and not in importer, because a) we | ||
| 120 | ;; have alot of legacy packages and b) the importer cannot update arbitrary | ||
| 121 | ;; fields in case a package switches its build system. | ||
| 122 | (use-build-backend (or build-backend auto-build-backend | ||
| 123 | "setuptools.build_meta"))) | ||
| 124 | (format #t | ||
| 125 | "Using '~a' to build wheels, auto-detected '~a', override '~a'.~%" | ||
| 126 | use-build-backend auto-build-backend build-backend) | ||
| 127 | (mkdir-p wheel-dir) | ||
| 128 | ;; Call the PEP 517 build function, which drops a .whl into wheel-dir. | ||
| 129 | (invoke "python" | ||
| 130 | "-c" | ||
| 131 | "import sys, importlib, json\nconfig_settings = json.loads (sys.argv[3])\nbuilder = importlib.import_module(sys.argv[1])\nbuilder.build_wheel(sys.argv[2], config_settings=config_settings)" | ||
| 132 | use-build-backend | ||
| 133 | wheel-dir | ||
| 134 | config-settings))) | ||
| 135 | |||
| 136 | (define* (check #:key inputs | ||
| 137 | outputs | ||
| 138 | tests? | ||
| 139 | test-backend | ||
| 140 | test-flags | ||
| 141 | #:allow-other-keys) | ||
| 142 | "Run the test suite of a given Python package." | ||
| 143 | (if tests? | ||
| 144 | ;; Unfortunately with PEP 517 there is no common method to specify test | ||
| 145 | ;; systems. Guess test system based on inputs instead. | ||
| 146 | (let* ((pytest (which "pytest")) | ||
| 147 | (nosetests (which "nosetests")) | ||
| 148 | (nose2 (which "nose2")) | ||
| 149 | (have-setup-py (file-exists? "setup.py")) | ||
| 150 | (use-test-backend | ||
| 151 | (or test-backend | ||
| 152 | ;; Prefer pytest | ||
| 153 | (if pytest 'pytest #f) | ||
| 154 | (if nosetests 'nose #f) | ||
| 155 | (if nose2 'nose2 #f) | ||
| 156 | ;; But fall back to setup.py, which should work for most | ||
| 157 | ;; packages. XXX: would be nice not to depend on setup.py here? | ||
| 158 | ;; fails more often than not to find any tests at all. Maybe | ||
| 159 | ;; we can run `python -m unittest`? | ||
| 160 | (if have-setup-py 'setup.py #f)))) | ||
| 161 | (format #t "Using ~a~%" use-test-backend) | ||
| 162 | (match use-test-backend | ||
| 163 | ('pytest | ||
| 164 | (apply invoke (cons pytest (or test-flags '("-vv"))))) | ||
| 165 | ('nose | ||
| 166 | (apply invoke (cons nosetests (or test-flags '("-v"))))) | ||
| 167 | ('nose2 | ||
| 168 | (apply invoke (cons nose2 (or test-flags '("-v" "--pretty-assert"))))) | ||
| 169 | ('setup.py | ||
| 170 | (apply invoke (append '("python" "setup.py") | ||
| 171 | (or test-flags '("test" "-v"))))) | ||
| 172 | ;; The developer should explicitly disable tests in this case. | ||
| 173 | (else (raise (condition (&test-system-not-found)))))) | ||
| 174 | (format #t "test suite not run~%"))) | ||
| 175 | |||
| 176 | (define* (install #:key inputs outputs #:allow-other-keys) | ||
| 177 | "Install a wheel file according to PEP 427" | ||
| 178 | ;; See https://www.python.org/dev/peps/pep-0427/#installing-a-wheel-distribution-1-0-py32-none-any-whl | ||
| 179 | (let ((site-dir (site-packages inputs outputs)) | ||
| 180 | (python (assoc-ref inputs "python")) | ||
| 181 | (out (assoc-ref outputs "out"))) | ||
| 182 | (define (extract file) | ||
| 183 | "Extract wheel (ZIP file) into site-packages directory" | ||
| 184 | ;; Use Python’s zipfile to avoid extra dependency | ||
| 185 | (invoke "python" | ||
| 186 | "-m" | ||
| 187 | "zipfile" | ||
| 188 | "-e" | ||
| 189 | file | ||
| 190 | site-dir)) | ||
| 191 | |||
| 192 | (define python-hashbang | ||
| 193 | (string-append "#!" python "/bin/python")) | ||
| 194 | |||
| 195 | (define* (merge-directories source destination | ||
| 196 | #:optional (post-move #f)) | ||
| 197 | "Move all files in SOURCE into DESTINATION, merging the two directories." | ||
| 198 | (format #t "Merging directory ~a into ~a~%" source destination) | ||
| 199 | (for-each (lambda (file) | ||
| 200 | (format #t | ||
| 201 | "~a/~a -> ~a/~a~%" | ||
| 202 | source | ||
| 203 | file | ||
| 204 | destination | ||
| 205 | file) | ||
| 206 | (mkdir-p destination) | ||
| 207 | (rename-file (string-append source "/" file) | ||
| 208 | (string-append destination "/" file)) | ||
| 209 | (when post-move | ||
| 210 | (post-move file))) | ||
| 211 | (scandir source | ||
| 212 | (negate (cut member <> | ||
| 213 | '("." ".."))))) | ||
| 214 | (rmdir source)) | ||
| 215 | |||
| 216 | (define (expand-data-directory directory) | ||
| 217 | "Move files from all .data subdirectories to their respective\ndestinations." | ||
| 218 | ;; Python’s distutils.command.install defines this mapping from source to | ||
| 219 | ;; destination mapping. | ||
| 220 | (let ((source (string-append directory "/scripts")) | ||
| 221 | (destination (string-append out "/bin"))) | ||
| 222 | (when (file-exists? source) | ||
| 223 | (merge-directories source destination | ||
| 224 | (lambda (f) | ||
| 225 | (let ((dest-path (string-append destination "/" | ||
| 226 | f))) | ||
| 227 | (chmod dest-path #o755) | ||
| 228 | (substitute* dest-path | ||
| 229 | (("#!python") | ||
| 230 | python-hashbang))))))) | ||
| 231 | ;; Data can be contained in arbitrary directory structures. Most | ||
| 232 | ;; commonly it is used for share/. | ||
| 233 | (let ((source (string-append directory "/data")) | ||
| 234 | (destination out)) | ||
| 235 | (when (file-exists? source) | ||
| 236 | (merge-directories source destination))) | ||
| 237 | (let* ((distribution (car (string-split (basename directory) #\-))) | ||
| 238 | (source (string-append directory "/headers")) | ||
| 239 | (destination (string-append out "/include/python" | ||
| 240 | (python-version python) "/" | ||
| 241 | distribution))) | ||
| 242 | (when (file-exists? source) | ||
| 243 | (merge-directories source destination)))) | ||
| 244 | |||
| 245 | (define (list-directories base predicate) | ||
| 246 | ;; Cannot use find-files here, because it’s recursive. | ||
| 247 | (scandir base | ||
| 248 | (lambda (name) | ||
| 249 | (let ((stat (lstat (string-append base "/" name)))) | ||
| 250 | (and (not (member name | ||
| 251 | '("." ".."))) | ||
| 252 | (eq? (stat:type stat) | ||
| 253 | 'directory) | ||
| 254 | (predicate name stat)))))) | ||
| 255 | |||
| 256 | (let* ((wheel-output (assoc-ref outputs "wheel")) | ||
| 257 | (wheel-dir (if wheel-output wheel-output "dist")) | ||
| 258 | (wheels (map (cut string-append wheel-dir "/" <>) | ||
| 259 | (scandir wheel-dir | ||
| 260 | (cut string-suffix? ".whl" <>))))) | ||
| 261 | (cond | ||
| 262 | ((> (length wheels) 1) | ||
| 263 | ;This code does not support multiple wheels | ||
| 264 | ;; yet, because their outputs would have to be | ||
| 265 | ;; merged properly. | ||
| 266 | (raise (condition (&cannot-extract-multiple-wheels)))) | ||
| 267 | ((= (length wheels) 0) | ||
| 268 | (raise (condition (&no-wheels-built))))) | ||
| 269 | (for-each extract wheels)) | ||
| 270 | (let ((datadirs (map (cut string-append site-dir "/" <>) | ||
| 271 | (list-directories site-dir | ||
| 272 | (file-name-predicate "\\.data$"))))) | ||
| 273 | (for-each (lambda (directory) | ||
| 274 | (expand-data-directory directory) | ||
| 275 | (rmdir directory)) datadirs)))) | ||
| 276 | |||
| 277 | (define* (compile-bytecode #:key inputs outputs #:allow-other-keys) | ||
| 278 | "Compile installed byte-code in site-packages." | ||
| 279 | (let* ((site-dir (site-packages inputs outputs)) | ||
| 280 | (python (assoc-ref inputs "python")) | ||
| 281 | (major-minor (map string->number | ||
| 282 | (take (string-split (python-version python) #\.) 2))) | ||
| 283 | (<3.7? (match major-minor | ||
| 284 | ((major minor) | ||
| 285 | (or (< major 3) | ||
| 286 | (and (= major 3) | ||
| 287 | (< minor 7))))))) | ||
| 288 | (if <3.7? | ||
| 289 | ;; These versions don’t have the hash invalidation modes and do | ||
| 290 | ;; not produce reproducible bytecode files. | ||
| 291 | (format #t "Skipping bytecode compilation for Python version ~a < 3.7~%" | ||
| 292 | (python-version python)) | ||
| 293 | (invoke "python" "-m" "compileall" | ||
| 294 | "--invalidation-mode=unchecked-hash" site-dir)))) | ||
| 295 | |||
| 296 | (define* (create-entrypoints #:key inputs outputs (configure-flags '()) #:allow-other-keys) | ||
| 297 | "Implement Entry Points Specification | ||
| 298 | (https://packaging.python.org/specifications/entry-points/) by PyPa, | ||
| 299 | which creates runnable scripts in bin/ from entry point specification | ||
| 300 | file entry_points.txt. This is necessary, because wheels do not contain | ||
| 301 | these binaries and installers are expected to create them." | ||
| 302 | |||
| 303 | (define (entry-points.txt->entry-points file) | ||
| 304 | "Specialized parser for Python configfile-like files, in particular | ||
| 305 | entry_points.txt. Returns a list of console_script and gui_scripts | ||
| 306 | entry points." | ||
| 307 | (call-with-input-file file | ||
| 308 | (lambda (in) | ||
| 309 | (let loop ((line (read-line in)) | ||
| 310 | (inside #f) | ||
| 311 | (result '())) | ||
| 312 | (if (eof-object? line) | ||
| 313 | result | ||
| 314 | (let* ((group-match (string-match "^\\[(.+)\\]$" line)) | ||
| 315 | (group-name (if group-match | ||
| 316 | (match:substring group-match 1) | ||
| 317 | #f)) | ||
| 318 | (next-inside (if (not group-name) | ||
| 319 | inside | ||
| 320 | (or (string=? group-name | ||
| 321 | "console_scripts") | ||
| 322 | (string=? group-name "gui_scripts")))) | ||
| 323 | (item-match (string-match | ||
| 324 | "^([^ =]+)\\s*=\\s*([^:]+):(.+)$" line))) | ||
| 325 | (if (and inside item-match) | ||
| 326 | (loop (read-line in) | ||
| 327 | next-inside | ||
| 328 | (cons (list (match:substring item-match 1) | ||
| 329 | (match:substring item-match 2) | ||
| 330 | (match:substring item-match 3)) | ||
| 331 | result)) | ||
| 332 | (loop (read-line in) next-inside result)))))))) | ||
| 333 | |||
| 334 | (define (create-script path name module function) | ||
| 335 | "Create a Python script from an entry point’s NAME, MODULE and | ||
| 336 | FUNCTION and return write it to PATH/NAME." | ||
| 337 | (let ((interpreter (which "python")) | ||
| 338 | (file-path (string-append path "/" name))) | ||
| 339 | (format #t "Creating entry point for '~a.~a' at '~a'.~%" | ||
| 340 | module function file-path) | ||
| 341 | (call-with-output-file file-path | ||
| 342 | (lambda (port) | ||
| 343 | ;; Technically the script could also include search-paths, | ||
| 344 | ;; but having a generic 'wrap phases also handles manually | ||
| 345 | ;; written entry point scripts. | ||
| 346 | (format port "#!~a | ||
| 347 | # Auto-generated entry point script. | ||
| 348 | import sys | ||
| 349 | import ~a as mod | ||
| 350 | sys.exit (mod.~a ())~%" interpreter module function))) | ||
| 351 | (chmod file-path #o755))) | ||
| 352 | |||
| 353 | (let* ((site-dir (site-packages inputs outputs)) | ||
| 354 | (out (assoc-ref outputs "out")) | ||
| 355 | (bin-dir (string-append out "/bin")) | ||
| 356 | (entry-point-files (find-files site-dir "^entry_points.txt$"))) | ||
| 357 | (mkdir-p bin-dir) | ||
| 358 | (for-each (lambda (f) | ||
| 359 | (for-each (lambda (ep) | ||
| 360 | (apply create-script | ||
| 361 | (cons bin-dir ep))) | ||
| 362 | (entry-points.txt->entry-points f))) | ||
| 363 | entry-point-files))) | ||
| 364 | |||
| 365 | (define* (set-SOURCE-DATE-EPOCH* #:rest _) | ||
| 366 | "Set the 'SOURCE_DATE_EPOCH' environment variable. This is used by tools | ||
| 367 | that incorporate timestamps as a way to tell them to use a fixed timestamp. | ||
| 368 | See https://reproducible-builds.org/specs/source-date-epoch/." | ||
| 369 | ;; Use a post-1980 timestamp because the Zip format used in wheels do | ||
| 370 | ;; not support timestamps before 1980. | ||
| 371 | (setenv "SOURCE_DATE_EPOCH" "315619200")) | ||
| 372 | |||
| 373 | (define %standard-phases | ||
| 374 | ;; The build phase only builds C extensions and copies the Python sources, | ||
| 375 | ;; while the install phase copies then byte-compiles the sources to the | ||
| 376 | ;; prefix directory. The check phase is moved after the installation phase | ||
| 377 | ;; to ease testing the built package. | ||
| 378 | (modify-phases python:%standard-phases | ||
| 379 | (replace 'set-SOURCE-DATE-EPOCH set-SOURCE-DATE-EPOCH*) | ||
| 380 | (replace 'build build) | ||
| 381 | (replace 'install install) | ||
| 382 | (delete 'check) | ||
| 383 | ;; Must be before tests, so they can use installed packages’ entry points. | ||
| 384 | (add-before 'wrap 'create-entrypoints create-entrypoints) | ||
| 385 | (add-after 'wrap 'check check) | ||
| 386 | (add-before 'check 'compile-bytecode compile-bytecode))) | ||
| 387 | |||
| 388 | (define* (pyproject-build #:key inputs (phases %standard-phases) | ||
| 389 | #:allow-other-keys #:rest args) | ||
| 390 | "Build the given Python package, applying all of PHASES in order." | ||
| 391 | (apply python:python-build #:inputs inputs #:phases phases args)) | ||
| 392 | |||
| 393 | ;;; pyproject-build-system.scm ends here | ||
