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>
This commit is contained in:
parent
30825c4629
commit
ca45da9fc9
|
@ -40,6 +40,7 @@
|
||||||
cabal-package?
|
cabal-package?
|
||||||
cabal-package-name
|
cabal-package-name
|
||||||
cabal-package-version
|
cabal-package-version
|
||||||
|
cabal-package-revision
|
||||||
cabal-package-license
|
cabal-package-license
|
||||||
cabal-package-home-page
|
cabal-package-home-page
|
||||||
cabal-package-source-repository
|
cabal-package-source-repository
|
||||||
|
@ -638,13 +639,14 @@ If #f use the function 'port-filename' to obtain it."
|
||||||
;; information of the Cabal file, but only the ones we currently are
|
;; information of the Cabal file, but only the ones we currently are
|
||||||
;; interested in.
|
;; interested in.
|
||||||
(define-record-type <cabal-package>
|
(define-record-type <cabal-package>
|
||||||
(make-cabal-package name version license home-page source-repository
|
(make-cabal-package name version revision license home-page source-repository
|
||||||
synopsis description
|
synopsis description
|
||||||
executables lib test-suites
|
executables lib test-suites
|
||||||
flags eval-environment custom-setup)
|
flags eval-environment custom-setup)
|
||||||
cabal-package?
|
cabal-package?
|
||||||
(name cabal-package-name)
|
(name cabal-package-name)
|
||||||
(version cabal-package-version)
|
(version cabal-package-version)
|
||||||
|
(revision cabal-package-revision)
|
||||||
(license cabal-package-license)
|
(license cabal-package-license)
|
||||||
(home-page cabal-package-home-page)
|
(home-page cabal-package-home-page)
|
||||||
(source-repository cabal-package-source-repository)
|
(source-repository cabal-package-source-repository)
|
||||||
|
@ -838,6 +840,7 @@ See the manual for limitations.")))))))
|
||||||
(define (cabal-evaluated-sexp->package evaluated-sexp)
|
(define (cabal-evaluated-sexp->package evaluated-sexp)
|
||||||
(let* ((name (lookup-join evaluated-sexp "name"))
|
(let* ((name (lookup-join evaluated-sexp "name"))
|
||||||
(version (lookup-join evaluated-sexp "version"))
|
(version (lookup-join evaluated-sexp "version"))
|
||||||
|
(revision (lookup-join evaluated-sexp "x-revision"))
|
||||||
(license (lookup-join evaluated-sexp "license"))
|
(license (lookup-join evaluated-sexp "license"))
|
||||||
(home-page (lookup-join evaluated-sexp "homepage"))
|
(home-page (lookup-join evaluated-sexp "homepage"))
|
||||||
(home-page-or-hackage
|
(home-page-or-hackage
|
||||||
|
@ -856,7 +859,7 @@ See the manual for limitations.")))))))
|
||||||
(custom-setup (match (make-cabal-section evaluated-sexp 'custom-setup)
|
(custom-setup (match (make-cabal-section evaluated-sexp 'custom-setup)
|
||||||
((x) x)
|
((x) x)
|
||||||
(_ #f))))
|
(_ #f))))
|
||||||
(make-cabal-package name version license home-page-or-hackage
|
(make-cabal-package name version revision license home-page-or-hackage
|
||||||
source-repository synopsis description executables lib
|
source-repository synopsis description executables lib
|
||||||
test-suites flags eval-environment custom-setup)))
|
test-suites flags eval-environment custom-setup)))
|
||||||
|
|
||||||
|
|
|
@ -117,19 +117,34 @@ version is returned."
|
||||||
(#f name)
|
(#f name)
|
||||||
(m (match:substring m 1)))))))
|
(m (match:substring m 1)))))))
|
||||||
|
|
||||||
|
(define (read-cabal-and-hash port)
|
||||||
|
"Read a Cabal file from PORT and return it and its hash in nix-base32
|
||||||
|
format as two values."
|
||||||
|
(let-values (((port get-hash) (open-sha256-input-port port)))
|
||||||
|
(values (read-cabal (canonical-newline-port port))
|
||||||
|
(bytevector->nix-base32-string (get-hash)))))
|
||||||
|
|
||||||
|
(define (hackage-fetch-and-hash name-version)
|
||||||
|
"Fetch the latest Cabal revision for the package NAME-VERSION, and return
|
||||||
|
two values: the parsed Cabal file and its hash in nix-base32 format. If the
|
||||||
|
version part is omitted from the package name, then fetch the latest
|
||||||
|
version. On failure, both return values will be #f."
|
||||||
|
(guard (c ((and (http-get-error? c)
|
||||||
|
(= 404 (http-get-error-code c)))
|
||||||
|
(values #f #f))) ;"expected" if package is unknown
|
||||||
|
(let*-values (((name version) (package-name->name+version name-version))
|
||||||
|
((url) (hackage-cabal-url name version))
|
||||||
|
((port _) (http-fetch url))
|
||||||
|
((cabal hash) (read-cabal-and-hash port)))
|
||||||
|
(close-port port)
|
||||||
|
(values cabal hash))))
|
||||||
|
|
||||||
(define (hackage-fetch name-version)
|
(define (hackage-fetch name-version)
|
||||||
"Return the Cabal file for the package NAME-VERSION, or #f on failure. If
|
"Return the Cabal file for the package NAME-VERSION, or #f on failure. If
|
||||||
the version part is omitted from the package name, then return the latest
|
the version part is omitted from the package name, then return the latest
|
||||||
version."
|
version."
|
||||||
(guard (c ((and (http-get-error? c)
|
(let-values (((cabal hash) (hackage-fetch-and-hash name-version)))
|
||||||
(= 404 (http-get-error-code c)))
|
cabal))
|
||||||
#f)) ;"expected" if package is unknown
|
|
||||||
(let-values (((name version) (package-name->name+version name-version)))
|
|
||||||
(let* ((url (hackage-cabal-url name version))
|
|
||||||
(port (http-fetch url))
|
|
||||||
(result (read-cabal (canonical-newline-port port))))
|
|
||||||
(close-port port)
|
|
||||||
result))))
|
|
||||||
|
|
||||||
(define string->license
|
(define string->license
|
||||||
;; List of valid values from
|
;; List of valid values from
|
||||||
|
@ -198,15 +213,20 @@ package being processed and is used to filter references to itself."
|
||||||
(cons own-name ghc-standard-libraries))))
|
(cons own-name ghc-standard-libraries))))
|
||||||
dependencies))
|
dependencies))
|
||||||
|
|
||||||
(define* (hackage-module->sexp cabal #:key (include-test-dependencies? #t))
|
(define* (hackage-module->sexp cabal cabal-hash
|
||||||
|
#:key (include-test-dependencies? #t))
|
||||||
"Return the `package' S-expression for a Cabal package. CABAL is the
|
"Return the `package' S-expression for a Cabal package. CABAL is the
|
||||||
representation of a Cabal file as produced by 'read-cabal'."
|
representation of a Cabal file as produced by 'read-cabal'. CABAL-HASH is
|
||||||
|
the hash of the Cabal file."
|
||||||
|
|
||||||
(define name
|
(define name
|
||||||
(cabal-package-name cabal))
|
(cabal-package-name cabal))
|
||||||
|
|
||||||
(define version
|
(define version
|
||||||
(cabal-package-version cabal))
|
(cabal-package-version cabal))
|
||||||
|
|
||||||
|
(define revision
|
||||||
|
(cabal-package-revision cabal))
|
||||||
|
|
||||||
(define source-url
|
(define source-url
|
||||||
(hackage-source-url name version))
|
(hackage-source-url name version))
|
||||||
|
@ -252,9 +272,14 @@ representation of a Cabal file as produced by 'read-cabal'."
|
||||||
(list 'quasiquote inputs))))))
|
(list 'quasiquote inputs))))))
|
||||||
|
|
||||||
(define (maybe-arguments)
|
(define (maybe-arguments)
|
||||||
(if (not include-test-dependencies?)
|
(match (append (if (not include-test-dependencies?)
|
||||||
'((arguments `(#:tests? #f)))
|
'(#:tests? #f)
|
||||||
'()))
|
'())
|
||||||
|
(if (not (string-null? revision))
|
||||||
|
`(#:cabal-revision (,revision ,cabal-hash))
|
||||||
|
'()))
|
||||||
|
(() '())
|
||||||
|
(args `((arguments (,'quasiquote ,args))))))
|
||||||
|
|
||||||
(let ((tarball (with-store store
|
(let ((tarball (with-store store
|
||||||
(download-to-store store source-url))))
|
(download-to-store store source-url))))
|
||||||
|
@ -294,10 +319,11 @@ symbol 'true' or 'false'. The value associated with other keys has to conform
|
||||||
to the Cabal file format definition. The default value associated with the
|
to the Cabal file format definition. The default value associated with the
|
||||||
keys \"os\", \"arch\" and \"impl\" is \"linux\", \"x86_64\" and \"ghc\"
|
keys \"os\", \"arch\" and \"impl\" is \"linux\", \"x86_64\" and \"ghc\"
|
||||||
respectively."
|
respectively."
|
||||||
(let ((cabal-meta (if port
|
(let-values (((cabal-meta cabal-hash)
|
||||||
(read-cabal (canonical-newline-port port))
|
(if port
|
||||||
(hackage-fetch package-name))))
|
(read-cabal-and-hash port)
|
||||||
(and=> cabal-meta (compose (cut hackage-module->sexp <>
|
(hackage-fetch-and-hash package-name))))
|
||||||
|
(and=> cabal-meta (compose (cut hackage-module->sexp <> cabal-hash
|
||||||
#:include-test-dependencies?
|
#:include-test-dependencies?
|
||||||
include-test-dependencies?)
|
include-test-dependencies?)
|
||||||
(cut eval-cabal <> cabal-environment)))))
|
(cut eval-cabal <> cabal-environment)))))
|
||||||
|
|
|
@ -274,6 +274,51 @@ executable cabal
|
||||||
(test-assert "hackage->guix-package test multiline desc (braced)"
|
(test-assert "hackage->guix-package test multiline desc (braced)"
|
||||||
(eval-test-with-cabal test-cabal-multiline-braced match-ghc-foo))
|
(eval-test-with-cabal test-cabal-multiline-braced match-ghc-foo))
|
||||||
|
|
||||||
|
;; Check Hackage Cabal revisions.
|
||||||
|
(define test-cabal-revision
|
||||||
|
"name: foo
|
||||||
|
version: 1.0.0
|
||||||
|
x-revision: 2
|
||||||
|
homepage: http://test.org
|
||||||
|
synopsis: synopsis
|
||||||
|
description: description
|
||||||
|
license: BSD3
|
||||||
|
executable cabal
|
||||||
|
build-depends:
|
||||||
|
HTTP >= 4000.2.5 && < 4000.3,
|
||||||
|
mtl >= 2.0 && < 3
|
||||||
|
")
|
||||||
|
|
||||||
|
(define-package-matcher match-ghc-foo-revision
|
||||||
|
('package
|
||||||
|
('name "ghc-foo")
|
||||||
|
('version "1.0.0")
|
||||||
|
('source
|
||||||
|
('origin
|
||||||
|
('method 'url-fetch)
|
||||||
|
('uri ('string-append
|
||||||
|
"https://hackage.haskell.org/package/foo/foo-"
|
||||||
|
'version
|
||||||
|
".tar.gz"))
|
||||||
|
('sha256
|
||||||
|
('base32
|
||||||
|
(? string? hash)))))
|
||||||
|
('build-system 'haskell-build-system)
|
||||||
|
('inputs
|
||||||
|
('quasiquote
|
||||||
|
(("ghc-http" ('unquote 'ghc-http)))))
|
||||||
|
('arguments
|
||||||
|
('quasiquote
|
||||||
|
('#:cabal-revision
|
||||||
|
("2" "0xxd88fb659f0krljidbvvmkh9ppjnx83j0nqzx8whcg4n5qbyng"))))
|
||||||
|
('home-page "http://test.org")
|
||||||
|
('synopsis (? string?))
|
||||||
|
('description (? string?))
|
||||||
|
('license 'bsd-3)))
|
||||||
|
|
||||||
|
(test-assert "hackage->guix-package test cabal revision"
|
||||||
|
(eval-test-with-cabal test-cabal-revision match-ghc-foo-revision))
|
||||||
|
|
||||||
(test-assert "read-cabal test 1"
|
(test-assert "read-cabal test 1"
|
||||||
(match (call-with-input-string test-read-cabal-1 read-cabal)
|
(match (call-with-input-string test-read-cabal-1 read-cabal)
|
||||||
((("name" ("test-me"))
|
((("name" ("test-me"))
|
||||||
|
|
Loading…
Reference in New Issue