summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--guix/import/cpan.scm151
-rw-r--r--tests/cpan.scm33
2 files changed, 116 insertions, 68 deletions
diff --git a/guix/import/cpan.scm b/guix/import/cpan.scm
index ec86f11743..4320f94c98 100644
--- a/guix/import/cpan.scm
+++ b/guix/import/cpan.scm
@@ -3,6 +3,7 @@
 ;;; Copyright © 2015 Mark H Weaver <mhw@netris.org>
 ;;; Copyright © 2016 Alex Sassmannshausen <alex@pompo.co>
 ;;; Copyright © 2017, 2018 Tobias Geerinckx-Rice <me@tobias.gr>
+;;; Copyright © 2020 Ludovic Courtès <ludo@gnu.org>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -27,19 +28,39 @@
   #:use-module (srfi srfi-1)
   #:use-module (srfi srfi-26)
   #:use-module (json)
+  #:use-module (guix json)
   #:use-module (gcrypt hash)
   #:use-module (guix store)
   #:use-module (guix utils)
   #:use-module (guix base32)
   #:use-module (guix ui)
   #:use-module ((guix download) #:select (download-to-store url-fetch))
-  #:use-module ((guix import utils) #:select (factorize-uri
-                                              flatten assoc-ref*))
+  #:use-module ((guix import utils) #:select (factorize-uri))
   #:use-module (guix import json)
   #:use-module (guix packages)
   #:use-module (guix upstream)
   #:use-module (guix derivations)
-  #:export (cpan->guix-package
+  #:export (cpan-dependency?
+            cpan-dependency-relationship
+            cpan-dependency-phase
+            cpan-dependency-module
+            cpan-dependency-version
+
+            cpan-release?
+            cpan-release-license
+            cpan-release-author
+            cpan-release-version
+            cpan-release-modle
+            cpan-release-distribution
+            cpan-release-download-url
+            cpan-release-abstract
+            cpan-release-home-page
+            cpan-release-dependencies
+            json->cpan-release
+
+            cpan-fetch
+            cpan->guix-package
+            metacpan-url->mirror-url
             %cpan-updater))
 
 ;;; Commentary:
@@ -49,6 +70,45 @@
 ;;;
 ;;; Code:
 
+;; Dependency of a "release".
+(define-json-mapping <cpan-dependency> make-cpan-dependency cpan-dependency?
+  json->cpan-dependency
+  (relationship cpan-dependency-relationship "relationship"
+                string->symbol)                   ;requires | suggests
+  (phase        cpan-dependency-phase "phase"
+                string->symbol)          ;develop | configure | test | runtime
+  (module       cpan-dependency-module)           ;string
+  (version      cpan-dependency-version))         ;string
+
+;; Release as returned by <https://fastapi.metacpan.org/v1/release/PKG>.
+(define-json-mapping <cpan-release> make-cpan-release cpan-release?
+  json->cpan-release
+  (license      cpan-release-license)
+  (author       cpan-release-author)
+  (version      cpan-release-version "version"
+                (match-lambda
+                  ((? number? version)
+                   ;; Version is sometimes not quoted in the module json, so
+                   ;; it gets imported into Guile as a number, so convert it
+                   ;; to a string (example: "X11-Protocol-Other").
+                   (number->string version))
+                  ((? string? version)
+                   ;; Sometimes we get a "v" prefix.  Strip it.
+                   (if (string-prefix? "v" version)
+                       (string-drop version 1)
+                       version))))
+  (module       cpan-release-module "main_module") ;e.g., "Test::Script"
+  (distribution cpan-release-distribution)         ;e.g., "Test-Script"
+  (download-url cpan-release-download-url "download_url")
+  (abstract     cpan-release-abstract "abstract")
+  (home-page    cpan-release-home-page "resources"
+                (match-lambda
+                  (#f #f)
+                  ((lst ...) (assoc-ref lst "homepage"))))
+  (dependencies cpan-release-dependencies "dependency"
+                (lambda (vector)
+                  (map json->cpan-dependency (vector->list vector)))))
+
 (define string->license
   (match-lambda
    ;; List of valid values from https://metacpan.org/pod/CPAN::Meta::Spec.
@@ -111,32 +171,25 @@ return \"Test-Simple\""
           (_ #f)))))
 
 (define (cpan-fetch name)
-  "Return an alist representation of the CPAN metadata for the perl module MODULE,
-or #f on failure.  MODULE should be e.g. \"Test::Script\""
+  "Return a <cpan-release> record for Perl module MODULE,
+or #f on failure.  MODULE should be the distribution name, such as
+\"Test-Script\" for the \"Test::Script\" module."
   ;; This API always returns the latest release of the module.
-  (json-fetch (string-append "https://fastapi.metacpan.org/v1/release/" name)))
+  (json->cpan-release
+   (json-fetch (string-append "https://fastapi.metacpan.org/v1/release/"
+                              name))))
 
 (define (cpan-home name)
   (string-append "https://metacpan.org/release/" name))
 
-(define (cpan-source-url meta)
-  "Return the download URL for a module's source tarball."
+(define (metacpan-url->mirror-url url)
+  "Replace 'https://cpan.metacpan.org' in URL with 'mirror://cpan'."
   (regexp-substitute/global #f "http[s]?://cpan.metacpan.org"
-                            (assoc-ref meta "download_url")
+                            url
                             'pre "mirror://cpan" 'post))
 
-(define (cpan-version meta)
-  "Return the version number from META."
-  (match (assoc-ref meta "version")
-    ((? number? version)
-     ;; version is sometimes not quoted in the module json, so it gets
-     ;; imported into Guile as a number, so convert it to a string.
-     (number->string version))
-    (version
-     ;; Sometimes we get a "v" prefix.  Strip it.
-     (if (string-prefix? "v" version)
-         (string-drop version 1)
-         version))))
+(define cpan-source-url
+  (compose metacpan-url->mirror-url cpan-release-download-url))
 
 (define (perl-package)
   "Return the 'perl' package.  This is a lazy reference so that we don't
@@ -179,42 +232,38 @@ depend on (gnu packages perl)."
                                        first perl-version last))))
                            (loop)))))))))))
 
-(define (cpan-module->sexp meta)
-  "Return the `package' s-expression for a CPAN module from the metadata in
-META."
+(define (cpan-module->sexp release)
+  "Return the 'package' s-expression for a CPAN module from the release data
+in RELEASE, a <cpan-release> record."
   (define name
-    (assoc-ref meta "distribution"))
+    (cpan-release-distribution release))
 
   (define (guix-name name)
     (if (string-prefix? "perl-" name)
         (string-downcase name)
         (string-append "perl-" (string-downcase name))))
 
-  (define version (cpan-version meta))
-  (define source-url (cpan-source-url meta))
+  (define version (cpan-release-version release))
+  (define source-url (cpan-source-url release))
 
   (define (convert-inputs phases)
     ;; Convert phase dependencies into a list of name/variable pairs.
-    (match (flatten
-            (map (lambda (ph)
-                   (filter-map (lambda (t)
-                                 (assoc-ref* meta "metadata" "prereqs" ph t))
-                               '("requires" "recommends" "suggests")))
-                 phases))
-      (#f
-       '())
+    (match (filter-map (lambda (dependency)
+                         (and (memq (cpan-dependency-phase dependency)
+                                    phases)
+                              (cpan-dependency-module dependency)))
+                       (cpan-release-dependencies release))
       ((inputs ...)
        (sort
         (delete-duplicates
          ;; Listed dependencies may include core modules.  Filter those out.
          (filter-map (match-lambda
-                      (("perl" . _)     ;implicit dependency
-                       #f)
-                      ((module . _)
-                       (and (not (core-module? module))
-                            (let ((name (guix-name (module->dist-name module))))
-                              (list name
-                                    (list 'unquote (string->symbol name)))))))
+                       ("perl" #f)                ;implicit dependency
+                       ((? core-module?) #f)
+                       (module
+                         (let ((name (guix-name (module->dist-name module))))
+                           (list name
+                                 (list 'unquote (string->symbol name))))))
                      inputs))
         (lambda args
           (match args
@@ -247,19 +296,19 @@ META."
                        ;; which says they are required during building.  We
                        ;; have not yet had a need for cross-compiled perl
                        ;; modules, however, so we leave it out.
-                       (convert-inputs '("configure" "build" "test")))
+                       (convert-inputs '(configure build test)))
        ,@(maybe-inputs 'propagated-inputs
-                       (convert-inputs '("runtime")))
+                       (convert-inputs '(runtime)))
        (home-page ,(cpan-home name))
-       (synopsis ,(assoc-ref meta "abstract"))
+       (synopsis ,(cpan-release-abstract release))
        (description fill-in-yourself!)
-       (license ,(string->license (assoc-ref meta "license"))))))
+       (license ,(string->license (cpan-release-license release))))))
 
 (define (cpan->guix-package module-name)
   "Fetch the metadata for PACKAGE-NAME from metacpan.org, and return the
 `package' s-expression corresponding to that package, or #f on failure."
-  (let ((module-meta (cpan-fetch (module->name module-name))))
-    (and=> module-meta cpan-module->sexp)))
+  (let ((release (cpan-fetch (module->name module-name))))
+    (and=> release cpan-module->sexp)))
 
 (define (cpan-package? package)
   "Return #t if PACKAGE is a package from CPAN."
@@ -285,7 +334,7 @@ META."
   "Return an <upstream-source> for the latest release of PACKAGE."
   (match (cpan-fetch (package->upstream-name package))
     (#f #f)
-    (meta
+    (release
      (let ((core-inputs
             (match (package-direct-inputs package)
               (((_ inputs _ ...) ...)
@@ -303,8 +352,8 @@ META."
                      (warning (G_ "input '~a' of ~a is in Perl core~%")
                               module (package-name package)))
                    core-inputs)))
-     (let ((version (cpan-version meta))
-           (url (cpan-source-url meta)))
+     (let ((version (cpan-release-version release))
+           (url     (cpan-source-url release)))
        (upstream-source
         (package (package-name package))
         (version version)
diff --git a/tests/cpan.scm b/tests/cpan.scm
index 189dd027e6..043d401032 100644
--- a/tests/cpan.scm
+++ b/tests/cpan.scm
@@ -1,6 +1,7 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2015 Eric Bavier <bavier@member.fsf.org>
 ;;; Copyright © 2016 Alex Sassmannshausen <alex@pompo.co>
+;;; Copyright © 2020 Ludovic Courtès <ludo@gnu.org>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -32,13 +33,6 @@
 (define test-json
   "{
   \"metadata\" : {
-    \"prereqs\" : {
-      \"runtime\" : {
-        \"requires\" : {
-          \"Test::Script\" : \"1.05\",
-        }
-      }
-    }
     \"name\" : \"Foo-Bar\",
     \"version\" : \"0.1\"
   }
@@ -47,6 +41,13 @@
   \"license\" : [
     \"perl_5\"
   ],
+  \"dependency\": [
+     { \"relationship\": \"requires\",
+       \"phase\": \"runtime\",
+       \"version\": \"1.05\",
+       \"module\": \"Test::Script\"
+     }
+  ],
   \"abstract\" : \"Fizzle Fuzz\",
   \"download_url\" : \"http://example.com/Foo-Bar-0.1.tar.gz\",
   \"author\" : \"Guix\",
@@ -107,16 +108,14 @@
                 (x
                  (pk 'fail x #f))))))
 
-(test-equal "source-url-http"
-  ((@@ (guix import cpan) cpan-source-url)
-   `(("download_url" .
-      "http://cpan.metacpan.org/authors/id/T/TE/TEST/Foo-Bar-0.1.tar.gz")))
-  "mirror://cpan/authors/id/T/TE/TEST/Foo-Bar-0.1.tar.gz")
+(test-equal "metacpan-url->mirror-url, http"
+  "mirror://cpan/authors/id/T/TE/TEST/Foo-Bar-0.1.tar.gz"
+  (metacpan-url->mirror-url
+   "http://cpan.metacpan.org/authors/id/T/TE/TEST/Foo-Bar-0.1.tar.gz"))
 
-(test-equal "source-url-https"
-  ((@@ (guix import cpan) cpan-source-url)
-   `(("download_url" .
-      "https://cpan.metacpan.org/authors/id/T/TE/TEST/Foo-Bar-0.1.tar.gz")))
-  "mirror://cpan/authors/id/T/TE/TEST/Foo-Bar-0.1.tar.gz")
+(test-equal "metacpan-url->mirror-url, https"
+  "mirror://cpan/authors/id/T/TE/TEST/Foo-Bar-0.1.tar.gz"
+  (metacpan-url->mirror-url
+   "https://cpan.metacpan.org/authors/id/T/TE/TEST/Foo-Bar-0.1.tar.gz"))
 
 (test-end "cpan")