import: pypi: Support recursive importing.

* guix/import/pypi.scm (guess-requirements): Use upstream names.
(compute-inputs): Return the upstream dependency names as an additional value.
(make-pypi-sexp): Likewise.
(pypi->guix-package): Memoize it.
(pypi-recursive-import): New procedure.
* guix/scripts/import/pypi.scm (show-help, %options): Accept "recursive"
option.
(guix-import-pypi): Use pypi-recursive-import.
* doc/guix.texi (Invoking guix import): Document it.
This commit is contained in:
Ricardo Wurmus 2018-08-30 15:12:07 +02:00 committed by Ricardo Wurmus
parent e37f889404
commit ac906cb7bb
No known key found for this signature in database
GPG Key ID: 197A5888235FACAC
3 changed files with 99 additions and 56 deletions

View File

@ -6442,6 +6442,14 @@ package:
guix import pypi itsdangerous guix import pypi itsdangerous
@end example @end example
@table @code
@item --recursive
@itemx -r
Traverse the dependency graph of the given upstream package recursively
and generate package expressions for all those packages that are not yet
in Guix.
@end table
@item gem @item gem
@cindex gem @cindex gem
Import metadata from @uref{https://rubygems.org/, Import metadata from @uref{https://rubygems.org/,

View File

@ -25,6 +25,7 @@
#:use-module (ice-9 match) #:use-module (ice-9 match)
#:use-module (ice-9 pretty-print) #:use-module (ice-9 pretty-print)
#:use-module (ice-9 regex) #:use-module (ice-9 regex)
#:use-module (ice-9 receive)
#:use-module ((ice-9 rdelim) #:select (read-line)) #:use-module ((ice-9 rdelim) #:select (read-line))
#:use-module (srfi srfi-1) #:use-module (srfi srfi-1)
#:use-module (srfi srfi-26) #:use-module (srfi srfi-26)
@ -47,6 +48,7 @@
#:use-module ((guix licenses) #:prefix license:) #:use-module ((guix licenses) #:prefix license:)
#:use-module (guix build-system python) #:use-module (guix build-system python)
#:export (guix-package->pypi-name #:export (guix-package->pypi-name
pypi-recursive-import
pypi->guix-package pypi->guix-package
%pypi-updater)) %pypi-updater))
@ -162,7 +164,7 @@ cannot determine package dependencies"))
((or (string-null? line) (comment? line)) ((or (string-null? line) (comment? line))
(loop result)) (loop result))
(else (else
(loop (cons (python->package-name (clean-requirement line)) (loop (cons (clean-requirement line)
result)))))))))) result))))))))))
(define (read-wheel-metadata wheel-archive) (define (read-wheel-metadata wheel-archive)
@ -182,9 +184,7 @@ cannot determine package dependencies"))
(hash-ref (list-ref run_requires 0) (hash-ref (list-ref run_requires 0)
"requires") "requires")
'()))) '())))
(map (lambda (r) (map clean-requirement requirements)))))
(python->package-name (clean-requirement r)))
requirements)))))
(lambda () (lambda ()
(delete-file json-file) (delete-file json-file)
(rmdir dirname)))))) (rmdir dirname))))))
@ -237,16 +237,21 @@ cannot determine package dependencies"))
(define (compute-inputs source-url wheel-url tarball) (define (compute-inputs source-url wheel-url tarball)
"Given the SOURCE-URL of an already downloaded TARBALL, return a list of "Given the SOURCE-URL of an already downloaded TARBALL, return a list of
name/variable pairs describing the required inputs of this package." name/variable pairs describing the required inputs of this package. Also
(sort return the unaltered list of upstream dependency names."
(map (lambda (input) (let ((dependencies
(list input (list 'unquote (string->symbol input)))) (remove (cut string=? "argparse" <>)
(remove (cut string=? "python-argparse" <>) (guess-requirements source-url wheel-url tarball))))
(guess-requirements source-url wheel-url tarball))) (values (sort
(lambda args (map (lambda (input)
(match args (let ((guix-name (python->package-name input)))
(((a _ ...) (b _ ...)) (list guix-name (list 'unquote (string->symbol guix-name)))))
(string-ci<? a b)))))) dependencies)
(lambda args
(match args
(((a _ ...) (b _ ...))
(string-ci<? a b)))))
dependencies)))
(define (make-pypi-sexp name version source-url wheel-url home-page synopsis (define (make-pypi-sexp name version source-url wheel-url home-page synopsis
description license) description license)
@ -255,46 +260,58 @@ VERSION, SOURCE-URL, HOME-PAGE, SYNOPSIS, DESCRIPTION, and LICENSE."
(call-with-temporary-output-file (call-with-temporary-output-file
(lambda (temp port) (lambda (temp port)
(and (url-fetch source-url temp) (and (url-fetch source-url temp)
`(package (receive (input-package-names upstream-dependency-names)
(name ,(python->package-name name)) (compute-inputs source-url wheel-url temp)
(version ,version) (values
(source (origin `(package
(method url-fetch) (name ,(python->package-name name))
(version ,version)
(source (origin
(method url-fetch)
;; Sometimes 'pypi-uri' doesn't quite work due to mixed ;; Sometimes 'pypi-uri' doesn't quite work due to mixed
;; cases in NAME, for instance, as is the case with ;; cases in NAME, for instance, as is the case with
;; "uwsgi". In that case, fall back to a full URL. ;; "uwsgi". In that case, fall back to a full URL.
(uri (pypi-uri ,(string-downcase name) version)) (uri (pypi-uri ,(string-downcase name) version))
(sha256 (sha256
(base32 (base32
,(guix-hash-url temp))))) ,(guix-hash-url temp)))))
(build-system python-build-system) (build-system python-build-system)
,@(maybe-inputs (compute-inputs source-url wheel-url temp)) ,@(maybe-inputs input-package-names)
(home-page ,home-page) (home-page ,home-page)
(synopsis ,synopsis) (synopsis ,synopsis)
(description ,description) (description ,description)
(license ,(license->symbol license))))))) (license ,(license->symbol license)))
upstream-dependency-names))))))
(define (pypi->guix-package package-name) (define pypi->guix-package
"Fetch the metadata for PACKAGE-NAME from pypi.org, and return the (memoize
(lambda* (package-name)
"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."
(let ((package (pypi-fetch package-name))) (let ((package (pypi-fetch package-name)))
(and package (and package
(guard (c ((missing-source-error? c) (guard (c ((missing-source-error? c)
(let ((package (missing-source-error-package c))) (let ((package (missing-source-error-package c)))
(leave (G_ "no source release for pypi package ~a ~a~%") (leave (G_ "no source release for pypi package ~a ~a~%")
(assoc-ref* package "info" "name") (assoc-ref* package "info" "name")
(assoc-ref* package "info" "version"))))) (assoc-ref* package "info" "version")))))
(let ((name (assoc-ref* package "info" "name")) (let ((name (assoc-ref* package "info" "name"))
(version (assoc-ref* package "info" "version")) (version (assoc-ref* package "info" "version"))
(release (assoc-ref (latest-source-release package) "url")) (release (assoc-ref (latest-source-release package) "url"))
(wheel (assoc-ref (latest-wheel-release package) "url")) (wheel (assoc-ref (latest-wheel-release package) "url"))
(synopsis (assoc-ref* package "info" "summary")) (synopsis (assoc-ref* package "info" "summary"))
(description (assoc-ref* package "info" "summary")) (description (assoc-ref* package "info" "summary"))
(home-page (assoc-ref* package "info" "home_page")) (home-page (assoc-ref* package "info" "home_page"))
(license (string->license (assoc-ref* package "info" "license")))) (license (string->license (assoc-ref* package "info" "license"))))
(make-pypi-sexp name version release wheel home-page synopsis (make-pypi-sexp name version release wheel home-page synopsis
description license)))))) description license))))))))
(define (pypi-recursive-import package-name)
(recursive-import package-name #f
#:repo->guix-package (lambda (name repo)
(pypi->guix-package name))
#:guix-name python->package-name))
(define (string->license str) (define (string->license str)
"Convert the string STR into a license object." "Convert the string STR into a license object."

View File

@ -1,5 +1,6 @@
;;; GNU Guix --- Functional package management for GNU ;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2014 David Thompson <davet@gnu.org> ;;; Copyright © 2014 David Thompson <davet@gnu.org>
;;; Copyright © 2018 Ricardo Wurmus <rekado@elephly.net>
;;; ;;;
;;; This file is part of GNU Guix. ;;; This file is part of GNU Guix.
;;; ;;;
@ -25,6 +26,7 @@
#:use-module (srfi srfi-1) #:use-module (srfi srfi-1)
#:use-module (srfi srfi-11) #:use-module (srfi srfi-11)
#:use-module (srfi srfi-37) #:use-module (srfi srfi-37)
#:use-module (srfi srfi-41)
#:use-module (ice-9 match) #:use-module (ice-9 match)
#:use-module (ice-9 format) #:use-module (ice-9 format)
#:export (guix-import-pypi)) #:export (guix-import-pypi))
@ -43,6 +45,8 @@ Import and convert the PyPI package for PACKAGE-NAME.\n"))
(display (G_ " (display (G_ "
-h, --help display this help and exit")) -h, --help display this help and exit"))
(display (G_ " (display (G_ "
-r, --recursive import packages recursively"))
(display (G_ "
-V, --version display version information and exit")) -V, --version display version information and exit"))
(newline) (newline)
(show-bug-report-information)) (show-bug-report-information))
@ -56,6 +60,9 @@ Import and convert the PyPI package for PACKAGE-NAME.\n"))
(option '(#\V "version") #f #f (option '(#\V "version") #f #f
(lambda args (lambda args
(show-version-and-exit "guix import pypi"))) (show-version-and-exit "guix import pypi")))
(option '(#\r "recursive") #f #f
(lambda (opt name arg result)
(alist-cons 'recursive #t result)))
%standard-import-options)) %standard-import-options))
@ -81,11 +88,22 @@ Import and convert the PyPI package for PACKAGE-NAME.\n"))
(reverse opts)))) (reverse opts))))
(match args (match args
((package-name) ((package-name)
(let ((sexp (pypi->guix-package package-name))) (if (assoc-ref opts 'recursive)
(unless sexp ;; Recursive import
(leave (G_ "failed to download meta-data for package '~a'~%") (map (match-lambda
package-name)) ((and ('package ('name name) . rest) pkg)
sexp)) `(define-public ,(string->symbol name)
,pkg))
(_ #f))
(reverse
(stream->list
(pypi-recursive-import package-name))))
;; Single import
(let ((sexp (pypi->guix-package package-name)))
(unless sexp
(leave (G_ "failed to download meta-data for package '~a'~%")
package-name))
sexp)))
(() (()
(leave (G_ "too few arguments~%"))) (leave (G_ "too few arguments~%")))
((many ...) ((many ...)