From: Cyril Roelandt <tipecaml@gmail.com>
To: guix-devel@gnu.org
Subject: [PATCH] import: pypi: read requirements from wheels.
Date: Thu, 21 Jan 2016 23:08:00 +0100 [thread overview]
Message-ID: <1453414080-23296-1-git-send-email-tipecaml@gmail.com> (raw)
* guix/import/pypi.scm (latest-wheel-release): New function.
---
guix/import/pypi.scm | 112 ++++++++++++++++++++++++++++++++++++++-------------
1 file changed, 85 insertions(+), 27 deletions(-)
diff --git a/guix/import/pypi.scm b/guix/import/pypi.scm
index d54bb9f..2614ab5 100644
--- a/guix/import/pypi.scm
+++ b/guix/import/pypi.scm
@@ -72,6 +72,16 @@ or #f on failure."
(raise (condition (&missing-source-error
(package pypi-package)))))))
+(define (latest-wheel-release pypi-package)
+ "Return the url of the wheel for the latest release of pypi-package, of #f if
+there isn't any."
+ (let ((releases (assoc-ref* pypi-package "releases"
+ (assoc-ref* pypi-package "info" "version"))))
+ (or (find (lambda (release)
+ (string=? "bdist_wheel" (assoc-ref release "packagetype")))
+ releases)
+ #f)))
+
(define (python->package-name name)
"Given the NAME of a package on PyPI, return a Guix-compliant name for the
package."
@@ -98,10 +108,10 @@ package definition."
((package-inputs ...)
`((inputs (,'quasiquote ,package-inputs))))))
-(define (guess-requirements source-url tarball)
- "Given SOURCE-URL and a TARBALL of the package, return a list of the required
-packages specified in the requirements.txt file. TARBALL will be extracted in
-the current directory, and will be deleted."
+(define (guess-requirements source-url wheel-url tarball)
+ "Given SOURCE-URL, WHEEL-URL and a TARBALL of the package, return a list of
+the required packages specified in the requirements.txt file. TARBALL will be
+extracted in the current directory, and will be deleted."
(define (tarball-directory url)
;; Given the URL of the package's tarball, return the name of the directory
@@ -148,26 +158,73 @@ cannot determine package dependencies"))
(loop (cons (python->package-name (clean-requirement line))
result))))))))))
- (let ((dirname (tarball-directory source-url)))
- (if (string? dirname)
- (let* ((req-file (string-append dirname "/requirements.txt"))
- (exit-code (system* "tar" "xf" tarball req-file)))
- ;; TODO: support more formats.
- (if (zero? exit-code)
- (dynamic-wind
- (const #t)
- (lambda ()
- (read-requirements req-file))
- (lambda ()
- (delete-file req-file)
- (rmdir dirname)))
- (begin
- (warning (_ "'tar xf' failed with exit code ~a\n")
- exit-code)
- '())))
- '())))
+ (define (read-wheel-metadata wheel-archive)
+ ;; Given WHEEL-ARCHIVE, a ZIP Python wheel archive, return the package's
+ ;; requirements.
+ (let* ((dirname (string-append
+ (string-join
+ (list-head
+ (string-split (last (string-split wheel-url #\/)) #\-) 2)
+ "-")
+ ".dist-info"))
+ (json-file (string-append dirname "/metadata.json")))
+ (and (system* "unzip" "-q" wheel-archive json-file)
+ (dynamic-wind
+ (const #t)
+ (lambda ()
+ (call-with-input-file json-file
+ (lambda (port)
+ (let* ((metadata (json->scm port))
+ (run_requires (hash-ref metadata "run_requires"))
+ (requirements (hash-ref (list-ref run_requires 0)
+ "requires")))
+ (map (lambda (r)
+ (python->package-name (clean-requirement r)))
+ requirements)))))
+ (lambda ()
+ (delete-file json-file)
+ (rmdir dirname))))))
+
+ (define (guess-requirements-from-wheel)
+ ;; Return the package's requirements using the wheel, or #f if an error
+ ;; occurs.
+ (call-with-temporary-output-file
+ (lambda (temp port)
+ (if wheel-url
+ (and (url-fetch wheel-url temp)
+ (read-wheel-metadata temp))
+ #f))))
+
+
+ (define (guess-requirements-from-source)
+ ;; Return the package's requirements by guessing them from the source.
+ (let ((dirname (tarball-directory source-url)))
+ (if (string? dirname)
+ (let* ((req-file (string-append dirname "/requirements.txt"))
+ (exit-code (system* "tar" "xf" tarball req-file)))
+ ;; TODO: support more formats.
+ (if (zero? exit-code)
+ (dynamic-wind
+ (const #t)
+ (lambda ()
+ (read-requirements req-file))
+ (lambda ()
+ (delete-file req-file)
+ (rmdir dirname)))
+ (begin
+ (warning (_ "'tar xf' failed with exit code ~a\n")
+ exit-code)
+ '())))
+ '())))
+
+ ;; First, try to compute the requirements using the wheel, since that is the
+ ;; most reliable option. If this does not work, try getting them by reading
+ ;; the "requirements.txt" file from the source.
+ (or (guess-requirements-from-wheel)
+ (guess-requirements-from-source)))
+
-(define (compute-inputs source-url tarball)
+(define (compute-inputs source-url wheel-url tarball)
"Given the SOURCE-URL of an already downloaded TARBALL, return a list of
name/variable pairs describing the required inputs of this package."
(sort
@@ -176,13 +233,13 @@ name/variable pairs describing the required inputs of this package."
(append '("python-setuptools")
;; Argparse has been part of Python since 2.7.
(remove (cut string=? "python-argparse" <>)
- (guess-requirements source-url tarball))))
+ (guess-requirements source-url wheel-url tarball))))
(lambda args
(match args
(((a _ ...) (b _ ...))
(string-ci<? a b))))))
-(define (make-pypi-sexp name version source-url home-page synopsis
+(define (make-pypi-sexp name version source-url wheel-url home-page synopsis
description license)
"Return the `package' s-expression for a python package with the given NAME,
VERSION, SOURCE-URL, HOME-PAGE, SYNOPSIS, DESCRIPTION, and LICENSE."
@@ -199,7 +256,7 @@ VERSION, SOURCE-URL, HOME-PAGE, SYNOPSIS, DESCRIPTION, and LICENSE."
(base32
,(guix-hash-url temp)))))
(build-system python-build-system)
- ,@(maybe-inputs (compute-inputs source-url temp))
+ ,@(maybe-inputs (compute-inputs source-url wheel-url temp))
(home-page ,home-page)
(synopsis ,synopsis)
(description ,description)
@@ -218,11 +275,12 @@ VERSION, SOURCE-URL, HOME-PAGE, SYNOPSIS, DESCRIPTION, and LICENSE."
(let ((name (assoc-ref* package "info" "name"))
(version (assoc-ref* package "info" "version"))
(release (assoc-ref (latest-source-release package) "url"))
+ (wheel (assoc-ref (latest-wheel-release package) "url"))
(synopsis (assoc-ref* package "info" "summary"))
(description (assoc-ref* package "info" "summary"))
(home-page (assoc-ref* package "info" "home_page"))
(license (string->license (assoc-ref* package "info" "license"))))
- (make-pypi-sexp name version release home-page synopsis
+ (make-pypi-sexp name version release wheel home-page synopsis
description license))))))
(define (pypi-package? package)
--
2.6.2
next reply other threads:[~2016-01-21 22:08 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2016-01-21 22:08 Cyril Roelandt [this message]
2016-01-24 20:08 ` [PATCH] import: pypi: read requirements from wheels Ludovic Courtès
2016-01-24 20:26 ` Cyril Roelandt
2016-01-26 10:11 ` Ludovic Courtès
2016-02-27 2:49 ` Cyril Roelandt
2016-03-02 9:54 ` Ludovic Courtès
2016-03-25 23:24 ` Cyril Roelandt
2016-03-26 1:45 ` Cyril Roelandt
2016-05-06 20:27 ` Leo Famulari
2016-05-15 19:49 ` Ludovic Courtès
2016-06-14 20:14 ` Cyril Roelandt
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: https://guix.gnu.org/
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1453414080-23296-1-git-send-email-tipecaml@gmail.com \
--to=tipecaml@gmail.com \
--cc=guix-devel@gnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://git.savannah.gnu.org/cgit/guix.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).