[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
06/131: build/python: Add a sanity check phase.
From: |
guix-commits |
Subject: |
06/131: build/python: Add a sanity check phase. |
Date: |
Fri, 29 Jan 2021 09:21:42 -0500 (EST) |
apteryx pushed a commit to branch cu/farewell-to-pythonpath
in repository guix.
commit 642fdd697bdf6dd95822dc86b3c38573e7fb7d07
Author: Lars-Dominik Braun <lars@6xq.net>
AuthorDate: Sun Jan 3 10:30:29 2021 +0100
build/python: Add a sanity check phase.
Add a new phase validating the usability of installed Python packages.
* gnu/packages/aux-files/python/sanity-check.py: New file.
* Makefile.am (AUX_FILES): Register it.
* guix/build-system/python.scm (sanity-check.py): New variable.
(lower): Add the script as an implicit input.
* guix/build/python-build-system.scm: Remove trailing #t.
(sanity-check): New phase.
(%standard-phases): Use it.
* tests/builders.scm: (make-python-dummy)
(dummy-ok, dummy-dummy-nosetuptools, dummy-fail-requirements)
(dummy-fail-import, dummy-fail-console-script): New variables.
("python-build-system: dummy-ok")
("python-build-system: dummy-dummy-nosetuptools")
("python-build-system: dummy-fail-requirements")
("python-build-system: dummy-fail-import")
("python-build-system: dummy-fail-console-script"): Add tests.
---
Makefile.am | 1 +
gnu/packages/aux-files/python/sanity-check.py | 91 +++++++++++++++++++++++++++
guix/build-system/python.scm | 8 +++
guix/build/python-build-system.scm | 26 +++++---
tests/builders.scm | 86 ++++++++++++++++++++++++-
5 files changed, 199 insertions(+), 13 deletions(-)
diff --git a/Makefile.am b/Makefile.am
index 7ef8d4d..e60c5f4 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -380,6 +380,7 @@ AUX_FILES = \
gnu/packages/aux-files/linux-libre/4.4-i686.conf \
gnu/packages/aux-files/linux-libre/4.4-x86_64.conf \
gnu/packages/aux-files/pack-audit.c \
+ gnu/packages/aux-files/python/sanity-check.py \
gnu/packages/aux-files/python/sitecustomize.py \
gnu/packages/aux-files/run-in-namespace.c
diff --git a/gnu/packages/aux-files/python/sanity-check.py
b/gnu/packages/aux-files/python/sanity-check.py
new file mode 100644
index 0000000..355e55b
--- /dev/null
+++ b/gnu/packages/aux-files/python/sanity-check.py
@@ -0,0 +1,91 @@
+# -*- coding: utf-8 -*-
+# GNU Guix --- Functional package management for GNU
+# Copyright © 2021 Lars-Dominik Braun <lars@6xq.net>
+#
+# 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/>.
+
+from __future__ import print_function # Python 2 support.
+import importlib
+import pkg_resources
+import sys
+import traceback
+
+try:
+ from importlib.machinery import PathFinder
+except ImportError:
+ PathFinder = None
+
+ret = 0
+
+# Only check site-packages installed by this package, but not dependencies
+# (which pkg_resources.working_set would include). Path supplied via argv.
+ws = pkg_resources.find_distributions(sys.argv[1])
+
+for dist in ws:
+ print('validating', repr(dist.project_name), dist.location)
+ try:
+ print('...checking requirements: ', end='')
+ req = str(dist.as_requirement())
+ # dist.activate() is not enough to actually check requirements, we
+ # have to .require() it.
+ pkg_resources.require(req)
+ print('OK')
+ except Exception as e:
+ print('ERROR:', req, e)
+ ret = 1
+ continue
+
+ # Try to load top level modules. This should not have any side-effects.
+ try:
+ metalines = dist.get_metadata_lines('top_level.txt')
+ except KeyError:
+ # distutils (i.e. #:use-setuptools? #f) will not install any metadata.
+ print('WARNING: cannot determine top-level modules')
+ continue
+ for name in metalines:
+ # Only available on Python 3.
+ if PathFinder and PathFinder.find_spec(name) is None:
+ # Ignore unavailable modules, often C modules, which were not
+ # installed at the top-level. Cannot use ModuleNotFoundError,
+ # because it is raised by failed imports too.
+ continue
+ try:
+ print('...trying to load module', name, end=': ')
+ importlib.import_module(name)
+ print('OK')
+ except Exception:
+ print('ERROR:')
+ traceback.print_exc(file=sys.stdout)
+ ret = 1
+ continue
+
+ # Try to load entry points of console scripts too, making sure they
+ # work. They should be removed if they don't. Other groups may not be
+ # safe, as they can depend on optional packages.
+ for group, v in dist.get_entry_map().items():
+ if group not in {'console_scripts', 'gui_scripts'}:
+ continue
+ for name, ep in v.items():
+ try:
+ print('...trying to load endpoint', group, name, end=': ')
+ ep.load()
+ print('OK')
+ except Exception:
+ print('ERROR:')
+ traceback.print_exc(file=sys.stdout)
+ ret = 1
+
+sys.exit(ret)
diff --git a/guix/build-system/python.scm b/guix/build-system/python.scm
index e39c065..2bb6fa8 100644
--- a/guix/build-system/python.scm
+++ b/guix/build-system/python.scm
@@ -2,6 +2,7 @@
;;; Copyright © 2013, 2014, 2015, 2016, 2017 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2013 Andreas Enge <andreas@enge.fr>
;;; Copyright © 2013 Nikita Karetnikov <nikita@karetnikov.org>
+;;; Copyright © 2021 Lars-Dominik Braun <lars@6xq.net>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -19,6 +20,8 @@
;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
(define-module (guix build-system python)
+ #:use-module ((gnu packages) #:select (search-auxiliary-file))
+ #:use-module (guix gexp)
#:use-module (guix store)
#:use-module (guix utils)
#:use-module (guix memoization)
@@ -70,6 +73,10 @@ extension, such as '.tar.gz'."
(let ((python (resolve-interface '(gnu packages python))))
(module-ref python 'python-2)))
+(define sanity-check.py
+ ;; The script used to validate the installation of a Python package.
+ (search-auxiliary-file "python/sanity-check.py"))
+
(define* (package-with-explicit-python python old-prefix new-prefix
#:key variant-property)
"Return a procedure of one argument, P. The procedure creates a package with
@@ -156,6 +163,7 @@ pre-defined variants."
;; Keep the standard inputs of 'gnu-build-system'.
,@(standard-packages)))
(build-inputs `(("python" ,python)
+ ("sanity-check.py" ,(local-file sanity-check.py))
,@native-inputs))
(outputs outputs)
(build python-build)
diff --git a/guix/build/python-build-system.scm
b/guix/build/python-build-system.scm
index 0f2402d..1fc97d1 100644
--- a/guix/build/python-build-system.scm
+++ b/guix/build/python-build-system.scm
@@ -9,6 +9,7 @@
;;; Copyright © 2019, 2020, 2021 Maxim Cournoyer <maxim.cournoyer@gmail.com>
;;; Copyright © 2020 Jakub Kądziołka <kuba@kadziolka.net>
;;; Copyright © 2020 Efraim Flashner <efraim@flashner.co.il>
+;;; Copyright © 2021 Lars-Dominik Braun <lars@6xq.net>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -132,6 +133,15 @@
(apply invoke "python" "./setup.py" command params)))
(error "no setup.py found")))
+(define* (sanity-check #:key tests? inputs outputs #:allow-other-keys)
+ "Ensure packages depending on this package via setuptools work properly,
+their advertised endpoints work and their top level modules are importable
+without errors."
+ (let ((sanity-check.py (assoc-ref inputs "sanity-check.py")))
+ ;; Make sure the working directory is empty (i.e. no Python modules in it)
+ (with-directory-excursion "/tmp"
+ (invoke "python" sanity-check.py (site-packages inputs outputs)))))
+
(define* (build #:key use-setuptools? #:allow-other-keys)
"Build a given Python package."
(call-setuppy "build" '() use-setuptools?)
@@ -209,8 +219,7 @@ running checks after installing the package."
;; '--invalidation-mode' option, do not generate any.
(unless <3.7?
(invoke "python" "-m" "compileall" "--invalidation-mode=unchecked-hash"
- out))
- #t))
+ out))))
(define* (wrap #:key inputs outputs #:allow-other-keys)
(define (list-of-files dir)
@@ -244,8 +253,7 @@ installed with setuptools."
(easy-install-pth (string-append site-packages "/easy-install.pth"))
(new-pth (string-append site-packages "/" name ".pth")))
(when (file-exists? easy-install-pth)
- (rename-file easy-install-pth new-pth))
- #t))
+ (rename-file easy-install-pth new-pth))))
(define* (ensure-no-mtimes-pre-1980 #:rest _)
"Ensure that there are no mtimes before 1980-01-02 in the source tree."
@@ -257,8 +265,7 @@ installed with setuptools."
(ftw "." (lambda (file stat flag)
(unless (<= early-1980 (stat:mtime stat))
(utime file early-1980 early-1980))
- #t))
- #t))
+ #t))))
(define* (enable-bytecode-determinism #:rest _)
"Improve determinism of pyc files."
@@ -266,8 +273,7 @@ installed with setuptools."
(setenv "PYTHONHASHSEED" "0")
;; Prevent Python from creating .pyc files when loading modules (such as
;; when running a test suite).
- (setenv "PYTHONDONTWRITEBYTECODE" "1")
- #t)
+ (setenv "PYTHONDONTWRITEBYTECODE" "1"))
(define* (ensure-no-cythonized-files #:rest _)
"Check the source code for @code{.c} files which may have been pre-generated
@@ -278,8 +284,7 @@ by Cython."
(string-append (string-drop-right file 3) "c")))
(when (file-exists? generated-file)
(format #t "Possible Cythonized file found: ~a~%" generated-file))))
- (find-files "." "\\.pyx$"))
- #t)
+ (find-files "." "\\.pyx$")))
(define %standard-phases
;; The build phase only builds C extensions and copies the Python sources,
@@ -301,6 +306,7 @@ by Cython."
(add-after 'install 'wrap wrap)
(add-before 'check 'add-install-to-pythonpath add-install-to-pythonpath)
(add-before 'check 'add-install-to-path add-install-to-path)
+ (add-after 'check 'sanity-check sanity-check)
(add-before 'strip 'rename-pth-file rename-pth-file)))
(define* (python-build #:key inputs (phases %standard-phases)
diff --git a/tests/builders.scm b/tests/builders.scm
index 6245475..2143c07 100644
--- a/tests/builders.scm
+++ b/tests/builders.scm
@@ -1,5 +1,6 @@
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2012, 2013, 2014, 2015, 2019 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2021 Lars-Dominik Braun <lars@6xq.net>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -23,15 +24,15 @@
#:use-module (guix build-system gnu)
#:use-module (guix build gnu-build-system)
#:use-module (guix build utils)
+ #:use-module (guix build-system python)
#:use-module (guix store)
+ #:use-module (guix monads)
#:use-module (guix utils)
#:use-module (guix base32)
#:use-module (guix derivations)
#:use-module (gcrypt hash)
#:use-module (guix tests)
- #:use-module ((guix packages)
- #:select (package?
- package-derivation package-native-search-paths))
+ #:use-module (guix packages)
#:use-module (gnu packages bootstrap)
#:use-module (ice-9 match)
#:use-module (ice-9 textual-ports)
@@ -111,4 +112,83 @@
(call-with-input-file name get-string-all))))))))
compressors)
+
+;;;
+;;; Test the sanity-check phase of the Python build system.
+;;;
+
+(define* (make-python-dummy name #:key (setup-py-extra "")
+ (init-py "") (use-setuptools? #t))
+ (dummy-package (string-append "python-dummy-" name)
+ (version "0.1")
+ (build-system python-build-system)
+ (arguments
+ `(#:tests? #f
+ #:use-setuptools? ,use-setuptools?
+ #:phases
+ (modify-phases %standard-phases
+ (replace 'unpack
+ (lambda _
+ (mkdir-p "dummy")
+ (with-output-to-file "dummy/__init__.py"
+ (lambda _
+ (display ,init-py)))
+ (with-output-to-file "setup.py"
+ (lambda _
+ (format #t "\
+~a
+setup(
+ name='dummy-~a',
+ version='0.1',
+ packages=['dummy'],
+ ~a
+ )"
+ (if ,use-setuptools?
+ "from setuptools import setup"
+ "from distutils.core import setup")
+ ,name ,setup-py-extra))))))))))
+
+(define python-dummy-ok
+ (make-python-dummy "ok"))
+
+;; distutil won't install any metadata, so make sure our script does not fail
+;; on a otherwise fine package.
+(define python-dummy-no-setuptools
+ (make-python-dummy
+ "no-setuptools" #:use-setuptools? #f))
+
+(define python-dummy-fail-requirements
+ (make-python-dummy "fail-requirements"
+ #:setup-py-extra "install_requires=['nonexistent'],"))
+
+(define python-dummy-fail-import
+ (make-python-dummy "fail-import" #:init-py "import nonexistent"))
+
+(define python-dummy-fail-console-script
+ (make-python-dummy "fail-console-script"
+ #:setup-py-extra (string-append
"entry_points={'console_scripts': "
+ "['broken =
dummy:nonexistent']},")))
+
+(define (check-build-success store p)
+ (unless store (test-skip 1))
+ (test-assert (string-append "python-build-system: " (package-name p))
+ (let* ((drv (package-derivation store p)))
+ (build-derivations store (list drv)))))
+
+(define (check-build-failure store p)
+ (unless store (test-skip 1))
+ (test-assert (string-append "python-build-system: " (package-name p))
+ (not (false-if-exception (package-derivation store
python-dummy-fail-requirements)))))
+
+(with-external-store store
+ (for-each (lambda (p) (check-build-success store p))
+ (list
+ python-dummy-ok
+ python-dummy-no-setuptools))
+ (for-each (lambda (p) (check-build-failure store p))
+ (list
+ python-dummy-fail-requirements
+ python-dummy-fail-import
+ python-dummy-fail-console-script)))
+
(test-end "builders")
- branch cu/farewell-to-pythonpath created (now 96077e5), guix-commits, 2021/01/29
- 01/131: gnu: python: Replace PYTHONPATH by GUIX_PYTHONPATH., guix-commits, 2021/01/29
- 02/131: build/python: Replace PYTHONPATH by GUIX_PYTHONPATH., guix-commits, 2021/01/29
- 03/131: build/python: Always add the install prefix to the Guix PYTHONPATH., guix-commits, 2021/01/29
- 04/131: build/python: Add the installation bin directory to PATH., guix-commits, 2021/01/29
- 05/131: build/python: Adjust wrap phase to use the new GUIX_PYTHONPATH., guix-commits, 2021/01/29
- 06/131: build/python: Add a sanity check phase.,
guix-commits <=
- 08/131: gnu: python-fixtures-bootstrap: Do not sanity check., guix-commits, 2021/01/29
- 10/131: gnu: python-pyfakefs: Disable unreliable test, guix-commits, 2021/01/29
- 09/131: gnu: python-pytest-pep8: Fix package., guix-commits, 2021/01/29
- 11/131: gnu: python-slugify: Add missing input., guix-commits, 2021/01/29
- 12/131: gnu: python-websockets: Fix Python package name., guix-commits, 2021/01/29
- 14/131: gnu: python-traitlets: Add missing input., guix-commits, 2021/01/29
- 15/131: gnu: python-idna-ssl: Add missing input., guix-commits, 2021/01/29
- 17/131: gnu: python-automat: Remove broken console script., guix-commits, 2021/01/29
- 07/131: gnu: python-pytest-xdist: Add missing input, relax pytest requirement., guix-commits, 2021/01/29
- 16/131: gnu: python-twisted: Remove broken console scripts., guix-commits, 2021/01/29