diff --git a/guix/import/pypi.scm b/guix/import/pypi.scm index 6731d50891..be01cc99d2 100644 --- a/guix/import/pypi.scm +++ b/guix/import/pypi.scm @@ -1,7 +1,7 @@ ;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2014 David Thompson ;;; Copyright © 2015 Cyril Roelandt -;;; Copyright © 2015, 2016, 2017, 2019, 2020 Ludovic Courtès +;;; Copyright © 2015, 2016, 2017, 2019, 2020, 2021 Ludovic Courtès ;;; Copyright © 2017 Mathieu Othacehe ;;; Copyright © 2018 Ricardo Wurmus ;;; Copyright © 2019 Maxim Cournoyer @@ -232,10 +232,9 @@ the input field." "Given REQUIRES.TXT, a path to a Setuptools requires.txt file, return a list of lists of requirements. -The first list contains the required dependencies while the second the -optional test dependencies. Note that currently, optional, non-test -dependencies are omitted since these can be difficult or expensive to -satisfy." +The first list contains the required dependencies, the second contains test +dependencies, and the third one contains optional dependencies. Note that +optional, non-test dependencies can be difficult or expensive to satisfy." (define (comment? line) ;; Return #t if the given LINE is a comment, #f otherwise. @@ -249,6 +248,7 @@ satisfy." (lambda (port) (let loop ((required-deps '()) (test-deps '()) + (optional-deps '()) (inside-test-section? #f) (optional? #f)) (let ((line (read-line port))) @@ -259,27 +259,33 @@ satisfy." ;; pytest >= 3 ; python_version >= "3.3" ;; pytest < 3 ; python_version < "3.3" (map (compose reverse delete-duplicates) - (list required-deps test-deps))) + (list required-deps test-deps optional-deps))) ((or (string-null? line) (comment? line)) - (loop required-deps test-deps inside-test-section? optional?)) + (loop required-deps test-deps optional-deps + inside-test-section? optional?)) ((section-header? line) ;; Encountering a section means that all the requirements ;; listed below are optional. Since we want to pick only the ;; test dependencies from the optional dependencies, we must ;; track those separately. - (loop required-deps test-deps (test-section? line) #t)) + (loop required-deps test-deps optional-deps + (test-section? line) #t)) (inside-test-section? (loop required-deps (cons (specification->requirement-name line) test-deps) + optional-deps inside-test-section? optional?)) ((not optional?) (loop (cons (specification->requirement-name line) required-deps) - test-deps inside-test-section? optional?)) + test-deps optional-deps + inside-test-section? optional?)) (optional? - ;; Skip optional items. - (loop required-deps test-deps inside-test-section? optional?)) + (loop required-deps test-deps + (cons (specification->requirement-name line) + optional-deps) + inside-test-section? optional?)) (else (warning (G_ "parse-requires.txt reached an unexpected \ condition on line ~a~%") line)))))))) @@ -314,7 +320,8 @@ returned value." (cond ((eof-object? line) (map (compose reverse delete-duplicates) - (list required-deps test-deps))) + (list required-deps test-deps + '()))) ;XXX: no known optional dependencies ((and (requires-dist-header? line) (not (extra? line))) (loop (cons (specification->requirement-name (requires-dist-value line)) @@ -392,7 +399,7 @@ cannot determine package dependencies from source archive: ~a~%") (or (guess-requirements-from-wheel) (guess-requirements-from-source))) -(define (compute-inputs source-url wheel-url archive) +(define* (compute-inputs source-url wheel-url archive) "Given the SOURCE-URL and WHEEL-URL of an already downloaded ARCHIVE, return a pair of lists, each consisting of a list of name/variable pairs, for the propagated inputs and the native inputs, respectively. Also @@ -419,17 +426,19 @@ return the unaltered list of upstream dependency names." (values (map process-requirements dependencies) (concatenate dependencies)))) -(define (make-pypi-sexp name version source-url wheel-url home-page synopsis - description license) +(define* (make-pypi-sexp name version source-url wheel-url home-page synopsis + description license + #:key optional-dependencies?) "Return the `package' s-expression for a python package with the given NAME, -VERSION, SOURCE-URL, HOME-PAGE, SYNOPSIS, DESCRIPTION, and LICENSE." +VERSION, SOURCE-URL, HOME-PAGE, SYNOPSIS, DESCRIPTION, and LICENSE. When +OPTIONAL-DEPENDENCIES? is true, include optional dependencies." (call-with-temporary-output-file (lambda (temp port) (and (url-fetch source-url temp) (receive (guix-dependencies upstream-dependencies) (compute-inputs source-url wheel-url temp) (match guix-dependencies - ((required-inputs native-inputs) + ((required-inputs native-inputs optional-inputs) (when (string-suffix? ".zip" source-url) (set! native-inputs (cons '("unzip" ,unzip) @@ -462,7 +471,12 @@ VERSION, SOURCE-URL, HOME-PAGE, SYNOPSIS, DESCRIPTION, and LICENSE." (base32 ,(guix-hash-url temp))))) (build-system python-build-system) - ,@(maybe-inputs required-inputs 'propagated-inputs) + ,@(maybe-inputs + (append required-inputs + (if optional-dependencies? + optional-inputs ;TODO: emit a comment + '())) + 'propagated-inputs) ,@(maybe-inputs native-inputs 'native-inputs) (home-page ,home-page) (synopsis ,synopsis) @@ -472,9 +486,10 @@ VERSION, SOURCE-URL, HOME-PAGE, SYNOPSIS, DESCRIPTION, and LICENSE." (define pypi->guix-package (memoize - (lambda* (package-name #:key repo version) + (lambda* (package-name #:key repo version optional-dependencies?) "Fetch the metadata for PACKAGE-NAME from pypi.org, and return the -`package' s-expression corresponding to that package, or #f on failure." +`package' s-expression corresponding to that package, or #f on failure. When +OPTIONAL-DEPENDENCIES? is true, include optional dependencies." (let* ((project (pypi-fetch package-name)) (info (and project (pypi-project-info project)))) (and project @@ -493,11 +508,17 @@ VERSION, SOURCE-URL, HOME-PAGE, SYNOPSIS, DESCRIPTION, and LICENSE." (project-info-summary info) (project-info-summary info) (string->license - (project-info-license info))))))))) + (project-info-license info)) + #:optional-dependencies? + optional-dependencies?))))))) -(define (pypi-recursive-import package-name) +(define* (pypi-recursive-import package-name #:key optional-dependencies?) (recursive-import package-name - #:repo->guix-package pypi->guix-package + #:repo->guix-package + (lambda args + (apply pypi->guix-package + #:optional-dependencies? optional-dependencies? + args)) #:guix-name python->package-name)) (define (string->license str) diff --git a/guix/scripts/import/pypi.scm b/guix/scripts/import/pypi.scm index 33167174e2..58ae07f802 100644 --- a/guix/scripts/import/pypi.scm +++ b/guix/scripts/import/pypi.scm @@ -46,6 +46,8 @@ Import and convert the PyPI package for PACKAGE-NAME.\n")) (display (G_ " -r, --recursive import packages recursively")) (display (G_ " + -O, --optional include optional dependencies")) + (display (G_ " -V, --version display version information and exit")) (newline) (show-bug-report-information)) @@ -62,6 +64,9 @@ Import and convert the PyPI package for PACKAGE-NAME.\n")) (option '(#\r "recursive") #f #f (lambda (opt name arg result) (alist-cons 'recursive #t result))) + (option '(#\O "optional") #f #f + (lambda (opt name arg result) + (alist-cons 'optional-dependencies? #t result))) %standard-import-options)) @@ -85,6 +90,9 @@ Import and convert the PyPI package for PACKAGE-NAME.\n")) value) (_ #f)) (reverse opts)))) + (define optional-dependencies? + (assoc-ref opts 'optional-dependencies?)) + (match args ((package-name) (if (assoc-ref opts 'recursive) @@ -94,9 +102,13 @@ Import and convert the PyPI package for PACKAGE-NAME.\n")) `(define-public ,(string->symbol name) ,pkg)) (_ #f)) - (pypi-recursive-import package-name)) + (pypi-recursive-import package-name + #:optional-dependencies? + optional-dependencies?)) ;; Single import - (let ((sexp (pypi->guix-package package-name))) + (let ((sexp (pypi->guix-package package-name + #:optional-dependencies? + optional-dependencies?))) (unless sexp (leave (G_ "failed to download meta-data for package '~a'~%") package-name))