summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Vollmert <rob@vllmrt.net>2019-06-13 21:39:14 +0200
committerTimothy Sample <samplet@ngyro.com>2019-06-13 22:18:52 -0400
commitca45da9fc9b1eee399ce4344b18cbb129daeca4c (patch)
tree361e65e3401f9c875ac5aa76ede6c6a5bcdab159
parent30825c46298c70028f70da1470eadbadf1e0d858 (diff)
import: hackage: Handle Hackage revisions.
Hackage packages can have metadata revisions (Cabal file only) that are not reflected in the source archive. The Haskell build system has support for this, but until now the Hackage importer would create a package based on the revised Cabal file which would then build using the old Cabal file. Fixes <https://bugs.gnu.org/35750>. * guix/import/cabal.scm (<cabal-package>): Add 'revision' field. (eval-cabal): Parse 'x-revision:' property. * guix/import/hackage.scm (read-cabal-and-hash): New procedure. (hackage-fetch-and-hash): New procedure. (hackage-fetch): Rewrite using 'hackage-fetch-and-hash'. (hackage-module->sexp): Add 'cabal-hash' argument and use it to populate the '#:cabal-revision' argument. (hackage->guix-package): Use the new '-and-hash' functions to get the hash of the Cabal file and pass it to 'hackage-module->sexp'. * guix/tests/hackage.scm: Test import of Cabal file revision. Signed-off-by: Timothy Sample <samplet@ngyro.com>
-rw-r--r--guix/import/cabal.scm7
-rw-r--r--guix/import/hackage.scm62
-rw-r--r--tests/hackage.scm45
3 files changed, 94 insertions, 20 deletions
diff --git a/guix/import/cabal.scm b/guix/import/cabal.scm
index 1a87be0b001..7dfe771e419 100644
--- a/guix/import/cabal.scm
+++ b/guix/import/cabal.scm
@@ -40,6 +40,7 @@
40 cabal-package? 40 cabal-package?
41 cabal-package-name 41 cabal-package-name
42 cabal-package-version 42 cabal-package-version
43 cabal-package-revision
43 cabal-package-license 44 cabal-package-license
44 cabal-package-home-page 45 cabal-package-home-page
45 cabal-package-source-repository 46 cabal-package-source-repository
@@ -638,13 +639,14 @@ If #f use the function 'port-filename' to obtain it."
638;; information of the Cabal file, but only the ones we currently are 639;; information of the Cabal file, but only the ones we currently are
639;; interested in. 640;; interested in.
640(define-record-type <cabal-package> 641(define-record-type <cabal-package>
641 (make-cabal-package name version license home-page source-repository 642 (make-cabal-package name version revision license home-page source-repository
642 synopsis description 643 synopsis description
643 executables lib test-suites 644 executables lib test-suites
644 flags eval-environment custom-setup) 645 flags eval-environment custom-setup)
645 cabal-package? 646 cabal-package?
646 (name cabal-package-name) 647 (name cabal-package-name)
647 (version cabal-package-version) 648 (version cabal-package-version)
649 (revision cabal-package-revision)
648 (license cabal-package-license) 650 (license cabal-package-license)
649 (home-page cabal-package-home-page) 651 (home-page cabal-package-home-page)
650 (source-repository cabal-package-source-repository) 652 (source-repository cabal-package-source-repository)
@@ -838,6 +840,7 @@ See the manual for limitations.")))))))
838 (define (cabal-evaluated-sexp->package evaluated-sexp) 840 (define (cabal-evaluated-sexp->package evaluated-sexp)
839 (let* ((name (lookup-join evaluated-sexp "name")) 841 (let* ((name (lookup-join evaluated-sexp "name"))
840 (version (lookup-join evaluated-sexp "version")) 842 (version (lookup-join evaluated-sexp "version"))
843 (revision (lookup-join evaluated-sexp "x-revision"))
841 (license (lookup-join evaluated-sexp "license")) 844 (license (lookup-join evaluated-sexp "license"))
842 (home-page (lookup-join evaluated-sexp "homepage")) 845 (home-page (lookup-join evaluated-sexp "homepage"))
843 (home-page-or-hackage 846 (home-page-or-hackage
@@ -856,7 +859,7 @@ See the manual for limitations.")))))))
856 (custom-setup (match (make-cabal-section evaluated-sexp 'custom-setup) 859 (custom-setup (match (make-cabal-section evaluated-sexp 'custom-setup)
857 ((x) x) 860 ((x) x)
858 (_ #f)))) 861 (_ #f))))
859 (make-cabal-package name version license home-page-or-hackage 862 (make-cabal-package name version revision license home-page-or-hackage
860 source-repository synopsis description executables lib 863 source-repository synopsis description executables lib
861 test-suites flags eval-environment custom-setup))) 864 test-suites flags eval-environment custom-setup)))
862 865
diff --git a/guix/import/hackage.scm b/guix/import/hackage.scm
index 366256b40d1..6f426af9006 100644
--- a/guix/import/hackage.scm
+++ b/guix/import/hackage.scm
@@ -117,19 +117,34 @@ version is returned."
117 (#f name) 117 (#f name)
118 (m (match:substring m 1))))))) 118 (m (match:substring m 1)))))))
119 119
120(define (read-cabal-and-hash port)
121 "Read a Cabal file from PORT and return it and its hash in nix-base32
122format as two values."
123 (let-values (((port get-hash) (open-sha256-input-port port)))
124 (values (read-cabal (canonical-newline-port port))
125 (bytevector->nix-base32-string (get-hash)))))
126
127(define (hackage-fetch-and-hash name-version)
128 "Fetch the latest Cabal revision for the package NAME-VERSION, and return
129two values: the parsed Cabal file and its hash in nix-base32 format. If the
130version part is omitted from the package name, then fetch the latest
131version. On failure, both return values will be #f."
132 (guard (c ((and (http-get-error? c)
133 (= 404 (http-get-error-code c)))
134 (values #f #f))) ;"expected" if package is unknown
135 (let*-values (((name version) (package-name->name+version name-version))
136 ((url) (hackage-cabal-url name version))
137 ((port _) (http-fetch url))
138 ((cabal hash) (read-cabal-and-hash port)))
139 (close-port port)
140 (values cabal hash))))
141
120(define (hackage-fetch name-version) 142(define (hackage-fetch name-version)
121 "Return the Cabal file for the package NAME-VERSION, or #f on failure. If 143 "Return the Cabal file for the package NAME-VERSION, or #f on failure. If
122the version part is omitted from the package name, then return the latest 144the version part is omitted from the package name, then return the latest
123version." 145version."
124 (guard (c ((and (http-get-error? c) 146 (let-values (((cabal hash) (hackage-fetch-and-hash name-version)))
125 (= 404 (http-get-error-code c))) 147 cabal))
126 #f)) ;"expected" if package is unknown
127 (let-values (((name version) (package-name->name+version name-version)))
128 (let* ((url (hackage-cabal-url name version))
129 (port (http-fetch url))
130 (result (read-cabal (canonical-newline-port port))))
131 (close-port port)
132 result))))
133 148
134(define string->license 149(define string->license
135 ;; List of valid values from 150 ;; List of valid values from
@@ -198,15 +213,20 @@ package being processed and is used to filter references to itself."
198 (cons own-name ghc-standard-libraries)))) 213 (cons own-name ghc-standard-libraries))))
199 dependencies)) 214 dependencies))
200 215
201(define* (hackage-module->sexp cabal #:key (include-test-dependencies? #t)) 216(define* (hackage-module->sexp cabal cabal-hash
217 #:key (include-test-dependencies? #t))
202 "Return the `package' S-expression for a Cabal package. CABAL is the 218 "Return the `package' S-expression for a Cabal package. CABAL is the
203representation of a Cabal file as produced by 'read-cabal'." 219representation of a Cabal file as produced by 'read-cabal'. CABAL-HASH is
220the hash of the Cabal file."
204 221
205 (define name 222 (define name
206 (cabal-package-name cabal)) 223 (cabal-package-name cabal))
207 224
208 (define version 225 (define version
209 (cabal-package-version cabal)) 226 (cabal-package-version cabal))
227
228 (define revision
229 (cabal-package-revision cabal))
210 230
211 (define source-url 231 (define source-url
212 (hackage-source-url name version)) 232 (hackage-source-url name version))
@@ -252,9 +272,14 @@ representation of a Cabal file as produced by 'read-cabal'."
252 (list 'quasiquote inputs)))))) 272 (list 'quasiquote inputs))))))
253 273
254 (define (maybe-arguments) 274 (define (maybe-arguments)
255 (if (not include-test-dependencies?) 275 (match (append (if (not include-test-dependencies?)
256 '((arguments `(#:tests? #f))) 276 '(#:tests? #f)
257 '())) 277 '())
278 (if (not (string-null? revision))
279 `(#:cabal-revision (,revision ,cabal-hash))
280 '()))
281 (() '())
282 (args `((arguments (,'quasiquote ,args))))))
258 283
259 (let ((tarball (with-store store 284 (let ((tarball (with-store store
260 (download-to-store store source-url)))) 285 (download-to-store store source-url))))
@@ -294,10 +319,11 @@ symbol 'true' or 'false'. The value associated with other keys has to conform
294to the Cabal file format definition. The default value associated with the 319to the Cabal file format definition. The default value associated with the
295keys \"os\", \"arch\" and \"impl\" is \"linux\", \"x86_64\" and \"ghc\" 320keys \"os\", \"arch\" and \"impl\" is \"linux\", \"x86_64\" and \"ghc\"
296respectively." 321respectively."
297 (let ((cabal-meta (if port 322 (let-values (((cabal-meta cabal-hash)
298 (read-cabal (canonical-newline-port port)) 323 (if port
299 (hackage-fetch package-name)))) 324 (read-cabal-and-hash port)
300 (and=> cabal-meta (compose (cut hackage-module->sexp <> 325 (hackage-fetch-and-hash package-name))))
326 (and=> cabal-meta (compose (cut hackage-module->sexp <> cabal-hash
301 #:include-test-dependencies? 327 #:include-test-dependencies?
302 include-test-dependencies?) 328 include-test-dependencies?)
303 (cut eval-cabal <> cabal-environment))))) 329 (cut eval-cabal <> cabal-environment)))))
diff --git a/tests/hackage.scm b/tests/hackage.scm
index 38a5825af72..14176b2cf9e 100644
--- a/tests/hackage.scm
+++ b/tests/hackage.scm
@@ -274,6 +274,51 @@ executable cabal
274(test-assert "hackage->guix-package test multiline desc (braced)" 274(test-assert "hackage->guix-package test multiline desc (braced)"
275 (eval-test-with-cabal test-cabal-multiline-braced match-ghc-foo)) 275 (eval-test-with-cabal test-cabal-multiline-braced match-ghc-foo))
276 276
277;; Check Hackage Cabal revisions.
278(define test-cabal-revision
279 "name: foo
280version: 1.0.0
281x-revision: 2
282homepage: http://test.org
283synopsis: synopsis
284description: description
285license: BSD3
286executable cabal
287 build-depends:
288 HTTP >= 4000.2.5 && < 4000.3,
289 mtl >= 2.0 && < 3
290")
291
292(define-package-matcher match-ghc-foo-revision
293 ('package
294 ('name "ghc-foo")
295 ('version "1.0.0")
296 ('source
297 ('origin
298 ('method 'url-fetch)
299 ('uri ('string-append
300 "https://hackage.haskell.org/package/foo/foo-"
301 'version
302 ".tar.gz"))
303 ('sha256
304 ('base32
305 (? string? hash)))))
306 ('build-system 'haskell-build-system)
307 ('inputs
308 ('quasiquote
309 (("ghc-http" ('unquote 'ghc-http)))))
310 ('arguments
311 ('quasiquote
312 ('#:cabal-revision
313 ("2" "0xxd88fb659f0krljidbvvmkh9ppjnx83j0nqzx8whcg4n5qbyng"))))
314 ('home-page "http://test.org")
315 ('synopsis (? string?))
316 ('description (? string?))
317 ('license 'bsd-3)))
318
319(test-assert "hackage->guix-package test cabal revision"
320 (eval-test-with-cabal test-cabal-revision match-ghc-foo-revision))
321
277(test-assert "read-cabal test 1" 322(test-assert "read-cabal test 1"
278 (match (call-with-input-string test-read-cabal-1 read-cabal) 323 (match (call-with-input-string test-read-cabal-1 read-cabal)
279 ((("name" ("test-me")) 324 ((("name" ("test-me"))