From mboxrd@z Thu Jan 1 00:00:00 1970 From: Cyril Roelandt Subject: [PATCH] import: pypi: read requirements from wheels. Date: Thu, 21 Jan 2016 23:08:00 +0100 Message-ID: <1453414080-23296-1-git-send-email-tipecaml@gmail.com> Return-path: Received: from eggs.gnu.org ([2001:4830:134:3::10]:37984) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1aMNOl-0006zY-Gw for guix-devel@gnu.org; Thu, 21 Jan 2016 17:08:12 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1aMNOh-00052z-FN for guix-devel@gnu.org; Thu, 21 Jan 2016 17:08:11 -0500 Received: from mail-wm0-x232.google.com ([2a00:1450:400c:c09::232]:33127) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1aMNOh-00052v-4b for guix-devel@gnu.org; Thu, 21 Jan 2016 17:08:07 -0500 Received: by mail-wm0-x232.google.com with SMTP id 123so192285105wmz.0 for ; Thu, 21 Jan 2016 14:08:06 -0800 (PST) List-Id: "Development of GNU Guix and the GNU System distribution." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: guix-devel-bounces+gcggd-guix-devel=m.gmane.org@gnu.org Sender: guix-devel-bounces+gcggd-guix-devel=m.gmane.org@gnu.org To: guix-devel@gnu.org * 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-cilicense (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