summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--guix/import/pypi.scm77
-rw-r--r--tests/pypi.scm52
2 files changed, 83 insertions, 46 deletions
diff --git a/guix/import/pypi.scm b/guix/import/pypi.scm
index ad59a8b731..a6106ab4ec 100644
--- a/guix/import/pypi.scm
+++ b/guix/import/pypi.scm
@@ -39,7 +39,8 @@
#:use-module ((guix build utils)
#:select ((package-name->name+version
. hyphen-package-name->name+version)
- find-files))
+ find-files
+ invoke))
#:use-module (guix import utils)
#:use-module ((guix download) #:prefix download:)
#:use-module (guix import json)
@@ -189,28 +190,11 @@ requirement names."
(loop (cons (specification->requirement-name line)
result))))))))))
-(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
+(define (guess-requirements source-url wheel-url archive)
+ "Given SOURCE-URL, WHEEL-URL and a ARCHIVE of the package, return a list
+of the required packages specified in the requirements.txt file. ARCHIVE will
be extracted in a temporary directory."
- (define (tarball-directory url)
- ;; Given the URL of the package's tarball, return the name of the directory
- ;; that will be created upon decompressing it. If the filetype is not
- ;; supported, return #f.
- ;; TODO: Support more archive formats.
- (let ((basename (substring url (+ 1 (string-rindex url #\/)))))
- (cond
- ((string-suffix? ".tar.gz" basename)
- (string-drop-right basename 7))
- ((string-suffix? ".tar.bz2" basename)
- (string-drop-right basename 8))
- (else
- (begin
- (warning (G_ "Unsupported archive format: \
-cannot determine package dependencies"))
- #f)))))
-
(define (read-wheel-metadata wheel-archive)
;; Given WHEEL-ARCHIVE, a ZIP Python wheel archive, return the package's
;; requirements.
@@ -239,29 +223,34 @@ cannot determine package dependencies"))
(call-with-temporary-output-file
(lambda (temp port)
(if wheel-url
- (and (url-fetch wheel-url temp)
- (read-wheel-metadata temp))
- #f))))
+ (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)
- (call-with-temporary-directory
- (lambda (dir)
- (let* ((pypi-name (string-take dirname (string-rindex dirname #\-)))
- (requires.txt (string-append dirname "/" pypi-name
- ".egg-info" "/requires.txt"))
- (exit-code (parameterize ((current-error-port (%make-void-port "rw+"))
- (current-output-port (%make-void-port "rw+")))
- (system* "tar" "xf" tarball "-C" dir requires.txt))))
- (if (zero? exit-code)
- (parse-requires.txt (string-append dir "/" requires.txt))
- (begin
- (warning
- (G_ "Failed to extract file: ~a from source.~%")
- requires.txt)
- '())))))
+ (if (compressed-file? source-url)
+ (call-with-temporary-directory
+ (lambda (dir)
+ (parameterize ((current-error-port (%make-void-port "rw+"))
+ (current-output-port (%make-void-port "rw+")))
+ (if (string=? "zip" (file-extension source-url))
+ (invoke "unzip" archive "-d" dir)
+ (invoke "tar" "xf" archive "-C" dir)))
+ (let ((requires.txt-files
+ (find-files dir (lambda (abs-file-name _)
+ (string-match "\\.egg-info/requires.txt$"
+ abs-file-name)))))
+ (match requires.txt-files
+ (()
+ (warning (G_ "Cannot guess requirements from source archive:\
+ no requires.txt file found.~%"))
+ '())
+ (else (parse-requires.txt (first requires.txt-files)))))))
+ (begin
+ (warning (G_ "Unsupported archive format; \
+cannot determine package dependencies from source archive: ~a~%")
+ (basename source-url))
'())))
;; First, try to compute the requirements using the wheel, else, fallback to
@@ -270,13 +259,13 @@ cannot determine package dependencies"))
(or (guess-requirements-from-wheel)
(guess-requirements-from-source)))
-(define (compute-inputs source-url wheel-url tarball)
- "Given the SOURCE-URL of an already downloaded TARBALL, return a list of
+(define (compute-inputs source-url wheel-url archive)
+ "Given the SOURCE-URL of an already downloaded ARCHIVE, return a list of
name/variable pairs describing the required inputs of this package. Also
return the unaltered list of upstream dependency names."
(let ((dependencies
(remove (cut string=? "argparse" <>)
- (guess-requirements source-url wheel-url tarball))))
+ (guess-requirements source-url wheel-url archive))))
(values (sort
(map (lambda (input)
(let ((guix-name (python->package-name input)))
diff --git a/tests/pypi.scm b/tests/pypi.scm
index c40be6c21d..b45d2c9d2f 100644
--- a/tests/pypi.scm
+++ b/tests/pypi.scm
@@ -20,6 +20,7 @@
(define-module (test-pypi)
#:use-module (guix import pypi)
#:use-module (guix base32)
+ #:use-module (guix memoization)
#:use-module (gcrypt hash)
#:use-module (guix tests)
#:use-module (guix build-system python)
@@ -134,8 +135,9 @@ pytest (>=2.5.0)
(match url
("https://example.com/foo-1.0.0.tar.gz"
(begin
- (mkdir-p "foo-1.0.0/foo.egg-info/")
- (with-output-to-file "foo-1.0.0/foo.egg-info/requires.txt"
+ ;; Unusual requires.txt location should still be found.
+ (mkdir-p "foo-1.0.0/src/bizarre.egg-info")
+ (with-output-to-file "foo-1.0.0/src/bizarre.egg-info/requires.txt"
(lambda ()
(display test-requires.txt)))
(parameterize ((current-output-port (%make-void-port "rw+")))
@@ -241,4 +243,50 @@ pytest (>=2.5.0)
(x
(pk 'fail x #f))))))
+(test-assert "pypi->guix-package, no usable requirement file."
+ ;; Replace network resources with sample data.
+ (mock ((guix import utils) url-fetch
+ (lambda (url file-name)
+ (match url
+ ("https://example.com/foo-1.0.0.tar.gz"
+ (mkdir-p "foo-1.0.0/foo.egg-info/")
+ (parameterize ((current-output-port (%make-void-port "rw+")))
+ (system* "tar" "czvf" file-name "foo-1.0.0/"))
+ (delete-file-recursively "foo-1.0.0")
+ (set! test-source-hash
+ (call-with-input-file file-name port-sha256)))
+ ("https://example.com/foo-1.0.0-py2.py3-none-any.whl" #f)
+ (_ (error "Unexpected URL: " url)))))
+ (mock ((guix http-client) http-fetch
+ (lambda (url . rest)
+ (match url
+ ("https://pypi.org/pypi/foo/json"
+ (values (open-input-string test-json)
+ (string-length test-json)))
+ ("https://example.com/foo-1.0.0-py2.py3-none-any.whl" #f)
+ (_ (error "Unexpected URL: " url)))))
+ ;; Not clearing the memoization cache here would mean returning the value
+ ;; computed in the previous test.
+ (invalidate-memoization! pypi->guix-package)
+ (match (pypi->guix-package "foo")
+ (('package
+ ('name "python-foo")
+ ('version "1.0.0")
+ ('source ('origin
+ ('method 'url-fetch)
+ ('uri ('pypi-uri "foo" 'version))
+ ('sha256
+ ('base32
+ (? string? hash)))))
+ ('build-system 'python-build-system)
+ ('home-page "http://example.com")
+ ('synopsis "summary")
+ ('description "summary")
+ ('license 'license:lgpl2.0))
+ (string=? (bytevector->nix-base32-string
+ test-source-hash)
+ hash))
+ (x
+ (pk 'fail x #f))))))
+
(test-end "pypi")