From 3a96221c795879f517cd4f7549d454ea8c632929 Mon Sep 17 00:00:00 2001 From: Nicolas Graves Date: Wed, 29 Oct 2025 15:11:55 +0100 Subject: [PATCH] build-system/pyproject: Ignore selected pytest inputs. This commit includes squashed changes from https://codeberg.org/guix/guix/pulls/7220 and https://codeberg.org/guix/guix/pulls/7338. * gnu/packages/aux-files/python/pytest_guix.py: New file. * Makefile.am: Record it. * guix/build/pyproject-build-system.scm (check): Preload pytest_guix plugin when available. * guix/build-system/pyproject.scm (default-pytest-guix-plugin): New package, generated from pytest_guix.py. (lower): Add python-pytest-guix argument, and inject it if python-pytest is in the native-inputs. Change-Id: I13263b461e9962aad340347657b9c9685db63927 Signed-off-by: Sharlatan Hellseher --- Makefile.am | 1 + gnu/packages/aux-files/python/pytest_guix.py | 68 ++++++++++++++++++++ guix/build-system/pyproject.scm | 58 +++++++++++++++-- guix/build/pyproject-build-system.scm | 20 +++--- 4 files changed, 135 insertions(+), 12 deletions(-) create mode 100644 gnu/packages/aux-files/python/pytest_guix.py diff --git a/Makefile.am b/Makefile.am index 1ef57af7a5..10a2cd1a79 100644 --- a/Makefile.am +++ b/Makefile.am @@ -484,6 +484,7 @@ AUX_FILES = \ gnu/packages/aux-files/linux-libre/5.10-x86_64.conf \ gnu/packages/aux-files/ovmf/51-edk2-ovmf-2m-raw-x64-nosb.json \ gnu/packages/aux-files/pack-audit.c \ + gnu/packages/aux-files/python/pytest_guix.py \ gnu/packages/aux-files/python/sanity-check.py \ gnu/packages/aux-files/python/sitecustomize.py \ gnu/packages/aux-files/renpy/renpy.in \ diff --git a/gnu/packages/aux-files/python/pytest_guix.py b/gnu/packages/aux-files/python/pytest_guix.py new file mode 100644 index 0000000000..750b1e99e9 --- /dev/null +++ b/gnu/packages/aux-files/python/pytest_guix.py @@ -0,0 +1,68 @@ +# GNU Guix --- Functional package management for GNU +# Copyright © 2025 Nicolas Graves +# +# 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 . + +import importlib.util + + +def pytest_addoption(parser): + """Add stub options to be ignored by pytest. + + More precisely, inject all options provided in .pytest_guix_options.json, + except options whose plugin is indeed installed. + + For example, if the json file records --cov: + if the pytest_cov module is installed, its --cov will be used. + otherwise, --cov is ignored (read by this parser, but nothing is done + with it). + + This allows to remove development packages, which are not required at build + time while at the same time avoiding the need to adjust test options in + pyproject.toml or other configuration files. + """ + plugin_options = { + "cov": [ + "--cov", + "--cov-reset", + "--cov-report", + "--cov-config", + "--no-cov-on-fail", + "--no-cov", + "--cov-fail-under", + "--cov-append", + "--cov-branch", + "--cov-context", + ], + "mypy": ["--mypy", "--mypy-config-file", "--mypy-ignore-missing-imports"], + "isort": ["--isort"], + "flake8": ["--flake8"], + "black": ["--black"], + "flakes": ["--flakes"], + "pep8": ["--pep8"], + "html": ["--html", "--self-contained-html", "--css"], + } + + group = parser.getgroup( + "guix", "Options ignored by the Guix pyproject-build-system" + ) + + # Only add options for plugins that are not present. + for key, options in plugin_options.items(): + if importlib.util.find_spec(f"pytest_{key}") is None: + # Plugin not found, add stub options + for option in options: + group.addoption(option, action="append", nargs="?") diff --git a/guix/build-system/pyproject.scm b/guix/build-system/pyproject.scm index d8939a7fde..d833be10b4 100644 --- a/guix/build-system/pyproject.scm +++ b/guix/build-system/pyproject.scm @@ -23,15 +23,19 @@ #:use-module (guix store) #:use-module (guix utils) #:use-module (guix gexp) + #:use-module ((guix licenses) #:prefix license:) #:use-module (guix monads) #:use-module (guix packages) #:use-module (guix search-paths) #:use-module (guix build-system) #:use-module (guix build-system gnu) #:use-module (guix build-system python) + #:use-module (guix build-system trivial) #:use-module (srfi srfi-1) + #:use-module (ice-9 match) #:export (%pyproject-build-system-modules default-python + default-pytest-guix-plugin default-sanity-check.py pyproject-build pyproject-build-system @@ -62,6 +66,37 @@ "Return the default guile-json package, resolved lazily." (@* (gnu packages guile) guile-json-4)) +;; Maybe try to upstream it at some point, it's currently flavored for guix +;; but the idea itself is more general. +(define (default-pytest-guix-plugin python) + (let* ((effective (version-major+minor (package-version python))) + (site (string-append "lib/python" effective "/site-packages/"))) + (package + (name "python-pytest-guix") + (version "0.0.1") + (source (local-file (search-auxiliary-file "python/pytest_guix.py"))) + (build-system trivial-build-system) + (arguments + (list + #:modules '((guix build utils)) + #:builder + #~(begin + (use-modules (guix build utils)) + (let* ((site (string-append #$output "/" #$site)) + (dist (string-append site "pytest_guix-" #$version + ".dist.info"))) + (mkdir-p dist) + (copy-file #$source (string-append site "/pytest_guix.py")) + (call-with-output-file (string-append dist "/entry_points.txt") + (lambda (port) + (format port "[pytest11]~%guix=pytest_guix~%"))))))) + (home-page "https://guix.gnu.org/") + (synopsis "Ignore selected pytest options") + (description + "This package provides the script to cleanly ignore pytest options at the +build-system level.") + (license license:gpl3+)))) + ;; TODO: On the next iteration of python-team, migrate the sanity-check to ;; importlib_metadata instead of setuptools. (define (default-sanity-check.py) @@ -69,13 +104,21 @@ (define* (lower name #:key source inputs native-inputs outputs system target + test-backend (python (default-python)) + (python-pytest-guix (default-pytest-guix-plugin python)) (sanity-check.py (default-sanity-check.py)) #:allow-other-keys #:rest arguments) "Return a bag for NAME." (define private-keywords - '(#:target #:python #:inputs #:native-inputs #:sanity-check.py)) + '(#:target #:python #:inputs #:native-inputs + #:python-pytest-guix #:sanity-check.py)) + (define native-inputs-labels (map car native-inputs)) + (define has-pytest? + (or (member "python-pytest-bootstrap" native-inputs-labels) + (member "python-pytest" native-inputs-labels))) + (and (not target) ;XXX: no cross-compilation (bag @@ -88,9 +131,16 @@ ;; Keep the standard inputs of 'gnu-build-system'. ,@(standard-packages))) - (build-inputs `(("python" ,python) - ("sanity-check.py" ,sanity-check.py) - ,@native-inputs)) + (build-inputs + `(("python" ,python) + ("sanity-check.py" ,sanity-check.py) + ,@(if (and has-pytest? + (match test-backend + ((or 'pytest-with-guix-plugin #f) #t) + (_ #f))) + `(("python-pytest-guix" ,python-pytest-guix)) + `()) + ,@native-inputs)) (outputs (append outputs '(wheel))) (build pyproject-build) (arguments (strip-keyword-arguments private-keywords arguments))))) diff --git a/guix/build/pyproject-build-system.scm b/guix/build/pyproject-build-system.scm index 21f356c67e..5685ddc033 100644 --- a/guix/build/pyproject-build-system.scm +++ b/guix/build/pyproject-build-system.scm @@ -314,7 +314,7 @@ without errors." (with-directory-excursion "/tmp" (invoke "python" sanity-check.py (site-packages inputs outputs))))) -(define* (check #:key tests? test-backend test-flags #:allow-other-keys) +(define* (check #:key inputs 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 @@ -330,20 +330,24 @@ without errors." (tests-found (find-files "." "test.*\\.py$")) (use-test-backend (or test-backend - ;; Prefer pytest - (if pytest 'pytest #f) - (if stestr 'stestr #f) - (if nosetests 'nose #f) - (if nose2 'nose2 #f) + ;; By order of preference. + (and (assoc-ref inputs "python-pytest-guix") + 'pytest-with-guix-plugin) + (and pytest 'pytest) + (and stestr 'stestr) + (and nosetests 'nose) + (and nose2 'nose2) ;; Fall back to setup.py. The command is deprecated, but is ;; a superset of unittest, so should work for most packages. ;; Keep it until setuptools removes `setup.py test'. ;; See https://setuptools.pypa.io/en/latest/deprecated/\ ;; commands.html#test-build-package-and-run-a-unittest-suite - (if have-setup-py 'setup.py #f) - (if tests-found 'unittest #f)))) + (and have-setup-py 'setup.py) + (and tests-found 'unittest)))) (format #t "Using ~a~%" use-test-backend) (match use-test-backend + ('pytest-with-guix-plugin + (apply invoke pytest "-vv" "-p" "pytest_guix" test-flags)) ('pytest (apply invoke pytest "-vv" test-flags)) ('nose