summaryrefslogtreecommitdiff
path: root/guix/build
diff options
context:
space:
mode:
authorEfraim Flashner <efraim@flashner.co.il>2023-01-30 11:33:18 +0200
committerEfraim Flashner <efraim@flashner.co.il>2023-01-30 12:39:40 +0200
commit4cf1acc7f3033b50b0bf19e02c9f522d522d338c (patch)
tree9fd64956ee60304c15387eb394cd649e49f01467 /guix/build
parentedb8c09addd186d9538d43b12af74d6c7aeea082 (diff)
parent595b53b74e3ef57a1c0c96108ba86d38a170a241 (diff)
Merge remote-tracking branch 'origin/master' into core-updates
Conflicts: doc/guix.texi gnu/local.mk gnu/packages/admin.scm gnu/packages/base.scm gnu/packages/chromium.scm gnu/packages/compression.scm gnu/packages/databases.scm gnu/packages/diffoscope.scm gnu/packages/freedesktop.scm gnu/packages/gnome.scm gnu/packages/gnupg.scm gnu/packages/guile.scm gnu/packages/inkscape.scm gnu/packages/llvm.scm gnu/packages/openldap.scm gnu/packages/pciutils.scm gnu/packages/ruby.scm gnu/packages/samba.scm gnu/packages/sqlite.scm gnu/packages/statistics.scm gnu/packages/syndication.scm gnu/packages/tex.scm gnu/packages/tls.scm gnu/packages/version-control.scm gnu/packages/xml.scm guix/build-system/copy.scm guix/scripts/home.scm
Diffstat (limited to 'guix/build')
-rw-r--r--guix/build/ant-build-system.scm3
-rw-r--r--guix/build/cargo-build-system.scm5
-rw-r--r--guix/build/clojure-build-system.scm3
-rw-r--r--guix/build/debug-link.scm12
-rw-r--r--guix/build/dub-build-system.scm3
-rw-r--r--guix/build/dune-build-system.scm4
-rw-r--r--guix/build/emacs-utils.scm13
-rw-r--r--guix/build/java-utils.scm3
-rw-r--r--guix/build/kconfig.scm181
-rw-r--r--guix/build/pyproject-build-system.scm381
-rw-r--r--guix/build/syscalls.scm9
11 files changed, 597 insertions, 20 deletions
diff --git a/guix/build/ant-build-system.scm b/guix/build/ant-build-system.scm
index fae1b47ec5..d29912bf59 100644
--- a/guix/build/ant-build-system.scm
+++ b/guix/build/ant-build-system.scm
@@ -19,7 +19,6 @@
(define-module (guix build ant-build-system)
#:use-module ((guix build gnu-build-system) #:prefix gnu:)
- #:use-module (guix build syscalls)
#:use-module (guix build utils)
#:use-module (sxml simple)
#:use-module (ice-9 match)
@@ -201,7 +200,7 @@ dependencies of this jar file."
repack them. This is necessary to ensure that archives are reproducible."
(define (repack-archive jar)
(format #t "repacking ~a\n" jar)
- (let* ((dir (mkdtemp! "jar-contents.XXXXXX"))
+ (let* ((dir (mkdtemp "jar-contents.XXXXXX"))
(manifest (string-append dir "/META-INF/MANIFEST.MF")))
(with-directory-excursion dir
(invoke "jar" "xf" jar))
diff --git a/guix/build/cargo-build-system.scm b/guix/build/cargo-build-system.scm
index 0a95672b00..41766228c2 100644
--- a/guix/build/cargo-build-system.scm
+++ b/guix/build/cargo-build-system.scm
@@ -2,7 +2,7 @@
;;; Copyright © 2016 David Craven <david@craven.ch>
;;; Copyright © 2017 Mathieu Othacehe <m.othacehe@gmail.com>
;;; Copyright © 2019 Ivan Petkov <ivanppetkov@gmail.com>
-;;; Copyright © 2019, 2020, 2021 Efraim Flashner <efraim@flashner.co.il>
+;;; Copyright © 2019-2022 Efraim Flashner <efraim@flashner.co.il>
;;; Copyright © 2020 Jakub Kądziołka <kuba@kadziolka.net>
;;; Copyright © 2020 Marius Bakke <marius@gnu.org>
;;;
@@ -135,7 +135,8 @@ Cargo.toml file present at its root."
;; so that we can generate any cargo checksums.
;; The --strip-components argument is needed to prevent creating
;; an extra directory within `crate-dir`.
- (invoke "tar" "xvf" path "-C" crate-dir "--strip-components" "1")))))
+ (format #t "Unpacking ~a~%" name)
+ (invoke "tar" "xf" path "-C" crate-dir "--strip-components" "1")))))
inputs)
;; Configure cargo to actually use this new directory.
diff --git a/guix/build/clojure-build-system.scm b/guix/build/clojure-build-system.scm
index cacbefb386..2cb153b6db 100644
--- a/guix/build/clojure-build-system.scm
+++ b/guix/build/clojure-build-system.scm
@@ -22,7 +22,6 @@
ant-build))
#:use-module (guix build clojure-utils)
#:use-module (guix build java-utils)
- #:use-module (guix build syscalls)
#:use-module (guix build utils)
#:use-module (ice-9 match)
#:use-module (ice-9 regex)
@@ -129,7 +128,7 @@ and repack them. This is necessary to ensure that archives are reproducible."
;; Note: .class files need to be strictly newer than source files,
;; otherwise the Clojure compiler will recompile sources.
(let* ((early-1980 315619200) ; 1980-01-02 UTC
- (dir (mkdtemp! "jar-contents.XXXXXX"))
+ (dir (mkdtemp "jar-contents.XXXXXX"))
(manifest (string-append dir "/META-INF/MANIFEST.MF")))
(with-directory-excursion dir
(invoke "jar" "xf" jar))
diff --git a/guix/build/debug-link.scm b/guix/build/debug-link.scm
index f3284f74c4..80941df2fc 100644
--- a/guix/build/debug-link.scm
+++ b/guix/build/debug-link.scm
@@ -1,5 +1,5 @@
;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2018 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2018, 2023 Ludovic Courtès <ludo@gnu.org>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -175,7 +175,15 @@ directories."
outputs))
(append-map (lambda (directory)
- (filter elf-file?
+ (filter (lambda (file)
+ (catch 'system-error
+ (lambda ()
+ (elf-file? file))
+ (lambda args
+ ;; FILE might be a dangling symlink.
+ (if (= ENOENT (system-error-errno args))
+ #f
+ (apply throw args)))))
(with-error-to-port (%make-void-port "w")
(lambda ()
(find-files directory)))))
diff --git a/guix/build/dub-build-system.scm b/guix/build/dub-build-system.scm
index 9ee0433ffd..c9bc2af3a5 100644
--- a/guix/build/dub-build-system.scm
+++ b/guix/build/dub-build-system.scm
@@ -20,7 +20,6 @@
(define-module (guix build dub-build-system)
#:use-module ((guix build gnu-build-system) #:prefix gnu:)
- #:use-module (guix build syscalls)
#:use-module (guix build utils)
#:use-module (ice-9 popen)
#:use-module (ice-9 rdelim)
@@ -52,7 +51,7 @@
to do this (instead of just using /gnu/store as the directory) because we want
to hide the libraries in subdirectories lib/dub/... instead of polluting the
user's profile root."
- (let* ((dir (mkdtemp! "/tmp/dub.XXXXXX"))
+ (let* ((dir (mkdtemp "/tmp/dub.XXXXXX"))
(vendor-dir (string-append dir "/vendor")))
(setenv "HOME" dir)
(mkdir vendor-dir)
diff --git a/guix/build/dune-build-system.scm b/guix/build/dune-build-system.scm
index e9ccc71057..f311cd37f1 100644
--- a/guix/build/dune-build-system.scm
+++ b/guix/build/dune-build-system.scm
@@ -42,13 +42,13 @@
build-flags)))
#t)
-(define* (check #:key (test-flags '()) (test-target "test") tests?
+(define* (check #:key (test-flags '()) tests?
(jbuild? #f) (package #f) (dune-release-flags '())
#:allow-other-keys)
"Test the given package."
(when tests?
(let ((program (if jbuild? "jbuilder" "dune")))
- (apply invoke program "runtest" test-target
+ (apply invoke program "runtest"
(append (if package (list "-p" package)
dune-release-flags)
test-flags))))
diff --git a/guix/build/emacs-utils.scm b/guix/build/emacs-utils.scm
index b2280ae70c..850b1f5f2a 100644
--- a/guix/build/emacs-utils.scm
+++ b/guix/build/emacs-utils.scm
@@ -75,10 +75,15 @@ true, evaluate using dynamic scoping."
(string-append "--visit=" file)
(string-append "--eval=" (expr->string expr))))
-(define (emacs-batch-disable-compilation file)
+(define* (emacs-batch-disable-compilation file #:key native?)
+ "Disable byte compilation for FILE.
+If NATIVE?, only disable native compilation."
(emacs-batch-edit-file file
- '(progn
- (add-file-local-variable 'no-byte-compile t)
+ `(progn
+ (add-file-local-variable ',(if native?
+ 'no-native-compile
+ 'no-byte-compile)
+ t)
(basic-save-buffer))))
(define-condition-type &emacs-batch-error &error
@@ -220,7 +225,7 @@ useful to avoid double quotes being added when the replacement is provided as
a string."
((_ file (variable replacement modifier ...) ...)
(emacs-substitute-sexps file
- ((string-append "(def[a-z]+[[:space:]\n]+" variable "\\>")
+ ((string-append "(def[a-z]+[[:space:]\n]+" variable "\\_>")
replacement
modifier ...)
...))))
diff --git a/guix/build/java-utils.scm b/guix/build/java-utils.scm
index 87c3ac43c9..6025c81667 100644
--- a/guix/build/java-utils.scm
+++ b/guix/build/java-utils.scm
@@ -21,7 +21,6 @@
(define-module (guix build java-utils)
#:use-module (guix build utils)
- #:use-module (guix build syscalls)
#:use-module (guix build maven pom)
#:use-module (guix build maven plugin)
#:use-module (ice-9 match)
@@ -83,7 +82,7 @@ fetched."
"Unpack the jar archive, add the pom file, and repack it. This is necessary
to ensure that maven can find dependencies."
(format #t "adding ~a to ~a\n" pom-file jar)
- (let* ((dir (mkdtemp! "jar-contents.XXXXXX"))
+ (let* ((dir (mkdtemp "jar-contents.XXXXXX"))
(manifest (string-append dir "/META-INF/MANIFEST.MF"))
(pom (get-pom pom-file))
(artifact (pom-artifactid pom))
diff --git a/guix/build/kconfig.scm b/guix/build/kconfig.scm
new file mode 100644
index 0000000000..0c9ef6baff
--- /dev/null
+++ b/guix/build/kconfig.scm
@@ -0,0 +1,181 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2020 Stefan <stefan-guix@vodafonemail.de>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix build kconfig)
+ #:use-module (ice-9 rdelim)
+ #:use-module (ice-9 regex)
+ #:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-26)
+ #:export (modify-defconfig
+ verify-config))
+
+;; Commentary:
+;;
+;; Builder-side code to modify configurations for the Kconfig build system as
+;; used by Linux and U-Boot.
+;;
+;; Code:
+
+(define (pair->config-string pair)
+ "Convert a PAIR back to a config-string."
+ (let* ((key (first pair))
+ (value (cdr pair)))
+ (if (string? key)
+ (if (string? value)
+ (string-append key "=" value)
+ (string-append "# " key " is not set"))
+ value)))
+
+(define (config-string->pair config-string)
+ "Parse a configuration string like \"CONFIG_EXAMPLE=m\" into a key-value pair.
+An error is thrown for invalid configurations.
+
+\"CONFIG_A=y\" -> '(\"CONFIG_A\" . \"y\")
+\"CONFIG_B=\\\"\\\"\" -> '(\"CONFIG_B\" . \"\\\"\\\"\")
+\"CONFIG_C=\" -> '(\"CONFIG_C\" . \"\")
+\"# CONFIG_E is not set\" -> '(\"CONFIG_E\" . #f)
+\"CONFIG_D\" -> '(\"CONFIG_D\" . #f)
+\"# Any comment\" -> '(#f . \"# Any comment\")
+\"\" -> '(#f . \"\")
+\"# CONFIG_E=y\" -> (error \"Invalid configuration\")
+\"CONFIG_E is not set\" -> (error \"Invalid configuration\")
+\"Anything else\" -> (error \"Invalid configuration\")"
+ (define config-regexp
+ (make-regexp
+ ;; (match:substring (string-match "=(.*)" "=") 1) returns "", but the
+ ;; pattern "=(.+)?" makes it return #f instead. From a "CONFIG_A=" we like
+ ;; to get "", which later emits "CONFIG_A=" again.
+ (string-append "^ *(#[\\t ]*)?(CONFIG_[a-zA-Z0-9_]+)([\\t ]*="
+ "[\\t ]*(.*)|([\\t ]+is[\\t ]+not[\\t ]+set))?$")))
+
+ (define config-comment-regexp
+ (make-regexp "^([\\t ]*(#.*)?)$"))
+
+ (let ((match (regexp-exec config-regexp (string-trim-right config-string))))
+ (if match
+ (let* ((comment (match:substring match 1))
+ (key (match:substring match 2))
+ (unset (match:substring match 5))
+ (value (and (not comment)
+ (not unset)
+ (match:substring match 4))))
+ (if (eq? (not comment) (not unset))
+ ;; The key is uncommented and set or commented and unset.
+ (cons key value)
+ ;; The key is set or unset ambigiously.
+ (error (format #f "invalid configuration, did you mean \"~a\"?"
+ (pair->config-string (cons key #f)))
+ config-string)))
+ ;; This is not a valid or ambigious config-string, but maybe a
+ ;; comment.
+ (if (regexp-exec config-comment-regexp config-string)
+ (cons #f config-string) ;keep valid comments
+ (error "Invalid configuration" config-string)))))
+
+(define (defconfig->alist defconfig)
+ "Convert the content of a DEFCONFIG (or .config) file into an alist."
+ (with-input-from-file defconfig
+ (lambda ()
+ (let loop ((alist '())
+ (line (read-line)))
+ (if (eof-object? line)
+ ;; Building the alist is done, now check for duplicates.
+ ;; Note: the filter invocation is used to remove comments.
+ (let loop ((keys (map first (filter first alist)))
+ (duplicates '()))
+ (if (null? keys)
+ ;; The search for duplicates is done.
+ ;; Return the alist or throw an error on duplicates.
+ (if (null? duplicates)
+ (reverse alist)
+ (error
+ (format #f "duplicate configurations in ~a" defconfig)
+ (reverse duplicates)))
+ ;; Continue the search for duplicates.
+ (loop (cdr keys)
+ (if (member (first keys) (cdr keys))
+ (cons (first keys) duplicates)
+ duplicates))))
+ ;; Build the alist.
+ (loop (cons (config-string->pair line) alist)
+ (read-line)))))))
+
+(define (modify-defconfig defconfig configs)
+ "This function can modify a given DEFCONFIG (or .config) file by adding,
+changing or removing the list of strings in CONFIGS. This allows customization
+of Kconfig based projects like the kernel Linux or the bootloader 'Das U-Boot'.
+
+These are examples for CONFIGS to add, change or remove configurations to/from
+DEFCONFIG:
+
+'(\"CONFIG_A=\\\"a\\\"\"
+ \"CONFIG_B=0\"
+ \"CONFIG_C=y\"
+ \"CONFIG_D=m\"
+ \"CONFIG_E=\"
+ \"# CONFIG_G is not set\"
+ ;; For convenience this abbrevation can be used for not set configurations.
+ \"CONFIG_F\")
+
+Instead of a list, CONFIGS can be a string with one configuration per line."
+ ;; Normalize CONFIGS to a list of configuration pairs.
+ (let* ((config-pairs (map config-string->pair
+ (append-map (cut string-split <> #\newline)
+ (if (string? configs)
+ (list configs)
+ configs))))
+ ;; Generate a blocklist from all valid keys in config-pairs.
+ (blocklist (delete #f (map first config-pairs)))
+ ;; Generate an alist from the defconfig without the keys in blocklist.
+ (filtered-defconfig-pairs (remove (lambda (pair)
+ (member (first pair) blocklist))
+ (defconfig->alist defconfig))))
+ (with-output-to-file defconfig
+ (lambda ()
+ (for-each (lambda (pair)
+ (display (pair->config-string pair))
+ (newline))
+ (append filtered-defconfig-pairs config-pairs))))))
+
+(define (verify-config config defconfig)
+ "Verify that the CONFIG file contains all configurations from the DEFCONFIG
+file. When the verification fails, raise an error with the mismatching keys
+and their values."
+ (let* ((config-pairs (defconfig->alist config))
+ (defconfig-pairs (defconfig->alist defconfig))
+ (mismatching-pairs
+ (remove (lambda (pair)
+ ;; Remove all configurations, whose values are #f and
+ ;; whose keys are not in config-pairs, as not in
+ ;; config-pairs means unset, ...
+ (and (not (cdr pair))
+ (not (assoc-ref config-pairs (first pair)))))
+ ;; ... from the defconfig-pairs different to config-pairs.
+ (lset-difference equal?
+ ;; Remove comments by filtering with first.
+ (filter first defconfig-pairs)
+ config-pairs))))
+ (unless (null? mismatching-pairs)
+ (error (format #f "Mismatching configurations in ~a and ~a"
+ config defconfig)
+ (map (lambda (mismatching-pair)
+ (let* ((key (first mismatching-pair))
+ (defconfig-value (cdr mismatching-pair))
+ (config-value (assoc-ref config-pairs key)))
+ (cons key (list (list config-value defconfig-value)))))
+ mismatching-pairs)))))
diff --git a/guix/build/pyproject-build-system.scm b/guix/build/pyproject-build-system.scm
new file mode 100644
index 0000000000..c69ccc9d64
--- /dev/null
+++ b/guix/build/pyproject-build-system.scm
@@ -0,0 +1,381 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2021 Lars-Dominik Braun <lars@6xq.net>
+;;; Copyright © 2022 Marius Bakke <marius@gnu.org>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix build pyproject-build-system)
+ #:use-module ((guix build python-build-system) #:prefix python:)
+ #:use-module (guix build utils)
+ #:use-module (guix build json)
+ #:use-module (ice-9 match)
+ #:use-module (ice-9 ftw)
+ #:use-module (ice-9 format)
+ #:use-module (ice-9 rdelim)
+ #:use-module (ice-9 regex)
+ #:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-26)
+ #:use-module (srfi srfi-34)
+ #:use-module (srfi srfi-35)
+ #:export (%standard-phases
+ add-installed-pythonpath
+ site-packages
+ python-version
+ pyproject-build))
+
+;;; Commentary:
+;;;
+;;; PEP 517-compatible build system for Python packages.
+;;;
+;;; PEP 517 mandates the use of a TOML file called pyproject.toml at the
+;;; project root, describing build and runtime dependencies, as well as the
+;;; build system, which can be different from setuptools. This module uses
+;;; that file to extract the build system used and call its wheel-building
+;;; entry point build_wheel (see 'build). setuptools’ wheel builder is
+;;; used as a fallback if either no pyproject.toml exists or it does not
+;;; declare a build-system. It supports config_settings through the
+;;; standard #:configure-flags argument.
+;;;
+;;; This wheel, which is just a ZIP file with a file structure defined
+;;; by PEP 427 (https://www.python.org/dev/peps/pep-0427/), is then unpacked
+;;; and its contents are moved to the appropriate locations in 'install.
+;;;
+;;; Then entry points, as defined by the PyPa Entry Point Specification
+;;; (https://packaging.python.org/specifications/entry-points/) are read
+;;; from a file called entry_points.txt in the package’s site-packages
+;;; subdirectory and scripts are written to bin/. These are not part of a
+;;; wheel and expected to be created by the installing utility.
+;;; TODO: Add support for PEP-621 entry points.
+;;;
+;;; Caveats:
+;;; - There is no support for in-tree build backends.
+;;;
+;;; Code:
+;;;
+
+;; Re-export these variables from python-build-system as many packages
+;; rely on these.
+(define python-version python:python-version)
+(define site-packages python:site-packages)
+(define add-installed-pythonpath python:add-installed-pythonpath)
+
+;; Base error type.
+(define-condition-type &python-build-error &error python-build-error?)
+
+;; Raised when 'check cannot find a valid test system in the inputs.
+(define-condition-type &test-system-not-found &python-build-error
+ test-system-not-found?)
+
+;; Raised when multiple wheels are created by 'build.
+(define-condition-type &cannot-extract-multiple-wheels &python-build-error
+ cannot-extract-multiple-wheels?)
+
+;; Raised, when no wheel has been built by the build system.
+(define-condition-type &no-wheels-built &python-build-error no-wheels-built?)
+
+(define* (build #:key outputs build-backend configure-flags #:allow-other-keys)
+ "Build a given Python package."
+
+ (define (pyproject.toml->build-backend file)
+ "Look up the build backend in a pyproject.toml file."
+ (call-with-input-file file
+ (lambda (in)
+ (let loop
+ ((line (read-line in 'concat)))
+ (if (eof-object? line) #f
+ (let ((m (string-match "build-backend = [\"'](.+)[\"']" line)))
+ (if m
+ (match:substring m 1)
+ (loop (read-line in 'concat)))))))))
+
+ (let* ((wheel-output (assoc-ref outputs "wheel"))
+ (wheel-dir (if wheel-output wheel-output "dist"))
+ ;; There is no easy way to get data from Guile into Python via
+ ;; s-expressions, but we have JSON serialization already, which Python
+ ;; also supports out-of-the-box.
+ (config-settings (call-with-output-string
+ (cut write-json configure-flags <>)))
+ ;; python-setuptools’ default backend supports setup.py *and*
+ ;; pyproject.toml. Allow overriding this automatic detection via
+ ;; build-backend.
+ (auto-build-backend (if (file-exists? "pyproject.toml")
+ (pyproject.toml->build-backend
+ "pyproject.toml")
+ #f))
+ ;; Use build system detection here and not in importer, because a) we
+ ;; have alot of legacy packages and b) the importer cannot update arbitrary
+ ;; fields in case a package switches its build system.
+ (use-build-backend (or build-backend
+ auto-build-backend
+ "setuptools.build_meta")))
+ (format #t
+ "Using '~a' to build wheels, auto-detected '~a', override '~a'.~%"
+ use-build-backend auto-build-backend build-backend)
+ (mkdir-p wheel-dir)
+ ;; Call the PEP 517 build function, which drops a .whl into wheel-dir.
+ (invoke "python" "-c"
+ "import sys, importlib, json
+config_settings = json.loads (sys.argv[3])
+builder = importlib.import_module(sys.argv[1])
+builder.build_wheel(sys.argv[2], config_settings=config_settings)"
+ use-build-backend
+ wheel-dir
+ config-settings)))
+
+(define* (check #:key tests? test-backend test-flags #:allow-other-keys)
+ "Run the test suite of a given Python package."
+ (if tests?
+ ;; Unfortunately with PEP 517 there is no common method to specify test
+ ;; systems. Guess test system based on inputs instead.
+ (let* ((pytest (which "pytest"))
+ (nosetests (which "nosetests"))
+ (nose2 (which "nose2"))
+ (have-setup-py (file-exists? "setup.py"))
+ (use-test-backend
+ (or test-backend
+ ;; Prefer pytest
+ (if pytest 'pytest #f)
+ (if nosetests 'nose #f)
+ (if nose2 'nose2 #f)
+ ;; But fall back to setup.py, which should work for most
+ ;; packages. XXX: would be nice not to depend on setup.py here?
+ ;; fails more often than not to find any tests at all. Maybe
+ ;; we can run `python -m unittest`?
+ (if have-setup-py 'setup.py #f))))
+ (format #t "Using ~a~%" use-test-backend)
+ (match use-test-backend
+ ('pytest
+ (apply invoke pytest "-vv" test-flags))
+ ('nose
+ (apply invoke nosetests "-v" test-flags))
+ ('nose2
+ (apply invoke nose2 "-v" "--pretty-assert" test-flags))
+ ('setup.py
+ (apply invoke "python" "setup.py"
+ (if (null? test-flags)
+ '("test" "-v")
+ test-flags)))
+ ;; The developer should explicitly disable tests in this case.
+ (else (raise (condition (&test-system-not-found))))))
+ (format #t "test suite not run~%")))
+
+(define* (install #:key inputs outputs #:allow-other-keys)
+ "Install a wheel file according to PEP 427"
+ ;; See https://www.python.org/dev/peps/pep-0427/#installing-a-wheel-distribution-1-0-py32-none-any-whl
+ (let ((site-dir (site-packages inputs outputs))
+ (python (assoc-ref inputs "python"))
+ (out (assoc-ref outputs "out")))
+ (define (extract file)
+ "Extract wheel (ZIP file) into site-packages directory"
+ ;; Use Python’s zipfile to avoid extra dependency
+ (invoke "python" "-m" "zipfile" "-e" file site-dir))
+
+ (define python-hashbang
+ (string-append "#!" python "/bin/python"))
+
+ (define* (merge-directories source destination
+ #:optional (post-move #f))
+ "Move all files in SOURCE into DESTINATION, merging the two directories."
+ (format #t "Merging directory ~a into ~a~%" source destination)
+ (for-each (lambda (file)
+ (format #t "~a/~a -> ~a/~a~%"
+ source file destination file)
+ (mkdir-p destination)
+ (rename-file (string-append source "/" file)
+ (string-append destination "/" file))
+ (when post-move
+ (post-move file)))
+ (scandir source
+ (negate (cut member <> '("." "..")))))
+ (rmdir source))
+
+ (define (expand-data-directory directory)
+ "Move files from all .data subdirectories to their respective\ndestinations."
+ ;; Python’s distutils.command.install defines this mapping from source to
+ ;; destination mapping.
+ (let ((source (string-append directory "/scripts"))
+ (destination (string-append out "/bin")))
+ (when (file-exists? source)
+ (merge-directories source destination
+ (lambda (f)
+ (let ((dest-path (string-append destination
+ "/" f)))
+ (chmod dest-path #o755)
+ ;; PEP 427 recommends that installers rewrite
+ ;; this odd shebang.
+ (substitute* dest-path
+ (("#!python")
+ python-hashbang)))))))
+ ;; Data can be contained in arbitrary directory structures. Most
+ ;; commonly it is used for share/.
+ (let ((source (string-append directory "/data"))
+ (destination out))
+ (when (file-exists? source)
+ (merge-directories source destination)))
+ (let* ((distribution (car (string-split (basename directory) #\-)))
+ (source (string-append directory "/headers"))
+ (destination (string-append out "/include/python"
+ (python-version python)
+ "/" distribution)))
+ (when (file-exists? source)
+ (merge-directories source destination))))
+
+ (define (list-directories base predicate)
+ ;; Cannot use find-files here, because it’s recursive.
+ (scandir base
+ (lambda (name)
+ (let ((stat (lstat (string-append base "/" name))))
+ (and (not (member name '("." "..")))
+ (eq? (stat:type stat) 'directory)
+ (predicate name stat))))))
+
+ (let* ((wheel-output (assoc-ref outputs "wheel"))
+ (wheel-dir (if wheel-output wheel-output "dist"))
+ (wheels (map (cut string-append wheel-dir "/" <>)
+ (scandir wheel-dir
+ (cut string-suffix? ".whl" <>)))))
+ (cond
+ ((> (length wheels) 1)
+ ;; This code does not support multiple wheels yet, because their
+ ;; outputs would have to be merged properly.
+ (raise (condition (&cannot-extract-multiple-wheels))))
+ ((= (length wheels) 0)
+ (raise (condition (&no-wheels-built)))))
+ (for-each extract wheels))
+ (let ((datadirs (map (cut string-append site-dir "/" <>)
+ (list-directories site-dir
+ (file-name-predicate "\\.data$")))))
+ (for-each (lambda (directory)
+ (expand-data-directory directory)
+ (rmdir directory)) datadirs))))
+
+(define* (compile-bytecode #:key inputs outputs #:allow-other-keys)
+ "Compile installed byte-code in site-packages."
+ (let* ((site-dir (site-packages inputs outputs))
+ (python (assoc-ref inputs "python"))
+ (major-minor (map string->number
+ (take (string-split (python-version python) #\.) 2)))
+ (<3.7? (match major-minor
+ ((major minor)
+ (or (< major 3)
+ (and (= major 3)
+ (< minor 7)))))))
+ (if <3.7?
+ ;; These versions don’t have the hash invalidation modes and do
+ ;; not produce reproducible bytecode files.
+ (format #t "Skipping bytecode compilation for Python version ~a < 3.7~%"
+ (python-version python))
+ (invoke "python" "-m" "compileall"
+ "--invalidation-mode=unchecked-hash" site-dir))))
+
+(define* (create-entrypoints #:key inputs outputs #:allow-other-keys)
+ "Implement Entry Points Specification
+(https://packaging.python.org/specifications/entry-points/) by PyPa,
+which creates runnable scripts in bin/ from entry point specification
+file entry_points.txt. This is necessary, because wheels do not contain
+these binaries and installers are expected to create them."
+
+ (define (entry-points.txt->entry-points file)
+ "Specialized parser for Python configfile-like files, in particular
+entry_points.txt. Returns a list of console_script and gui_scripts
+entry points."
+ (call-with-input-file file
+ (lambda (in)
+ (let loop ((line (read-line in))
+ (inside #f)
+ (result '()))
+ (if (eof-object? line)
+ result
+ (let* ((group-match (string-match "^\\[(.+)\\]$" line))
+ (group-name (if group-match
+ (match:substring group-match 1)
+ #f))
+ (next-inside (if (not group-name)
+ inside
+ (or (string=? group-name
+ "console_scripts")
+ (string=? group-name "gui_scripts"))))
+ (item-match (string-match
+ "^([^ =]+)\\s*=\\s*([^:]+):(.+)$" line)))
+ (if (and inside item-match)
+ (loop (read-line in)
+ next-inside
+ (cons (list (match:substring item-match 1)
+ (match:substring item-match 2)
+ (match:substring item-match 3))
+ result))
+ (loop (read-line in) next-inside result))))))))
+
+ (define (create-script path name module function)
+ "Create a Python script from an entry point’s NAME, MODULE and FUNCTION
+and return write it to PATH/NAME."
+ (let ((interpreter (which "python"))
+ (file-path (string-append path "/" name)))
+ (format #t "Creating entry point for '~a.~a' at '~a'.~%"
+ module function file-path)
+ (call-with-output-file file-path
+ (lambda (port)
+ ;; Technically the script could also include search-paths,
+ ;; but having a generic 'wrap phases also handles manually
+ ;; written entry point scripts.
+ (format port "#!~a
+# Auto-generated entry point script.
+import sys
+import ~a as mod
+sys.exit (mod.~a ())~%" interpreter module function)))
+ (chmod file-path #o755)))
+
+ (let* ((site-dir (site-packages inputs outputs))
+ (out (assoc-ref outputs "out"))
+ (bin-dir (string-append out "/bin"))
+ (entry-point-files (find-files site-dir "^entry_points.txt$")))
+ (mkdir-p bin-dir)
+ (for-each (lambda (f)
+ (for-each (lambda (ep)
+ (apply create-script
+ (cons bin-dir ep)))
+ (entry-points.txt->entry-points f)))
+ entry-point-files)))
+
+(define* (set-SOURCE-DATE-EPOCH* #:rest _)
+ "Set the 'SOURCE_DATE_EPOCH' environment variable. This is used by tools
+that incorporate timestamps as a way to tell them to use a fixed timestamp.
+See https://reproducible-builds.org/specs/source-date-epoch/."
+ ;; Use a post-1980 timestamp because the Zip format used in wheels do
+ ;; not support timestamps before 1980.
+ (setenv "SOURCE_DATE_EPOCH" "315619200"))
+
+(define %standard-phases
+ ;; The build phase only builds C extensions and copies the Python sources,
+ ;; while the install phase copies then byte-compiles the sources to the
+ ;; prefix directory. The check phase is moved after the installation phase
+ ;; to ease testing the built package.
+ (modify-phases python:%standard-phases
+ (replace 'set-SOURCE-DATE-EPOCH set-SOURCE-DATE-EPOCH*)
+ (replace 'build build)
+ (replace 'install install)
+ (delete 'check)
+ ;; Must be before tests, so they can use installed packages’ entry points.
+ (add-before 'wrap 'create-entrypoints create-entrypoints)
+ (add-after 'wrap 'check check)
+ (add-before 'check 'compile-bytecode compile-bytecode)))
+
+(define* (pyproject-build #:key inputs (phases %standard-phases)
+ #:allow-other-keys #:rest args)
+ "Build the given Python package, applying all of PHASES in order."
+ (apply python:python-build #:inputs inputs #:phases phases args))
+
+;;; pyproject-build-system.scm ends here
diff --git a/guix/build/syscalls.scm b/guix/build/syscalls.scm
index e081aaca44..0358960ff5 100644
--- a/guix/build/syscalls.scm
+++ b/guix/build/syscalls.scm
@@ -46,10 +46,12 @@
MS_NOEXEC
MS_REMOUNT
MS_NOATIME
+ MS_NODIRATIME
MS_STRICTATIME
MS_RELATIME
MS_BIND
MS_MOVE
+ MS_REC
MS_SHARED
MS_LAZYTIME
MNT_FORCE
@@ -542,8 +544,10 @@ the last argument of `mknod'."
(define MS_NOEXEC 8)
(define MS_REMOUNT 32)
(define MS_NOATIME 1024)
+(define MS_NODIRATIME 2048)
(define MS_BIND 4096)
(define MS_MOVE 8192)
+(define MS_REC 16384)
(define MS_SHARED 1048576)
(define MS_RELATIME 2097152)
(define MS_STRICTATIME 16777216)
@@ -645,7 +649,8 @@ the remaining unprocessed options."
("nodev" => MS_NODEV)
("noexec" => MS_NOEXEC)
("relatime" => MS_RELATIME)
- ("noatime" => MS_NOATIME)))))))
+ ("noatime" => MS_NOATIME)
+ ("nodiratime" => MS_NODIRATIME)))))))
(define (mount-flags mount)
"Return the mount flags of MOUNT, a <mount> record, as an inclusive or of
@@ -878,7 +883,7 @@ fdatasync(2) on the underlying file descriptor."
(ST_NODEV => MS_NODEV)
(ST_NOEXEC => MS_NOEXEC)
(ST_NOATIME => MS_NOATIME)
- (ST_NODIRATIME => 0) ;FIXME
+ (ST_NODIRATIME => MS_NODIRATIME)
(ST_RELATIME => MS_RELATIME))))
(define-c-struct %statfs ;<bits/statfs.h>