@@ -40,6 +40,7 @@
cabal-package?
cabal-package-name
cabal-package-version
+ cabal-package-revision
cabal-package-license
cabal-package-home-page
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
;; interested in.
(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
executables lib test-suites
flags eval-environment custom-setup)
cabal-package?
(name cabal-package-name)
(version cabal-package-version)
+ (revision cabal-package-revision)
(license cabal-package-license)
(home-page cabal-package-home-page)
(source-repository cabal-package-source-repository)
@@ -838,6 +840,7 @@ See the manual for limitations.")))))))
(define (cabal-evaluated-sexp->package evaluated-sexp)
(let* ((name (lookup-join evaluated-sexp "name"))
(version (lookup-join evaluated-sexp "version"))
+ (revision (lookup-join evaluated-sexp "x-revision"))
(license (lookup-join evaluated-sexp "license"))
(home-page (lookup-join evaluated-sexp "homepage"))
(home-page-or-hackage
@@ -856,7 +859,7 @@ See the manual for limitations.")))))))
(custom-setup (match (make-cabal-section evaluated-sexp 'custom-setup)
((x) x)
(_ #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
test-suites flags eval-environment custom-setup)))
@@ -117,20 +117,34 @@ version is returned."
(#f name)
(m (match:substring m 1)))))))
-(define (hackage-fetch name-version)
- "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
-version."
+(define (read-cabal-and-hash port)
+ "Read a cabal file and its base32 hash from a port."
+ (let-values (((port get-hash) (open-sha256-input-port port)))
+ (cons (read-cabal (canonical-newline-port port))
+ (bytevector->nix-base32-string (get-hash)))))
+
+(define (hackage-fetch-and-hash name-version)
+ "Return the Cabal file and hash for the package NAME-VERSION, or #f on
+failure. If the version part is omitted from the package name, then return
+the latest version."
(guard (c ((and (http-get-error? c)
(= 404 (http-get-error-code c)))
#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))))
+ (result (read-cabal-and-hash port)))
(close-port port)
result))))
+(define (hackage-fetch name-version)
+ "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
+version."
+ (match (hackage-fetch-and-hash name-version)
+ ((cabal . hash) cabal)
+ (_ #f)))
+
(define string->license
;; List of valid values from
;; https://www.haskell.org
@@ -198,15 +212,20 @@ package being processed and is used to filter references to itself."
(cons own-name ghc-standard-libraries))))
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
-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
(cabal-package-name cabal))
(define version
(cabal-package-version cabal))
+
+ (define revision
+ (cabal-package-revision cabal))
(define source-url
(hackage-source-url name version))
@@ -252,9 +271,14 @@ representation of a Cabal file as produced by 'read-cabal'."
(list 'quasiquote inputs))))))
(define (maybe-arguments)
- (if (not include-test-dependencies?)
- '((arguments `(#:tests? #f)))
- '()))
+ (match (append (if (not include-test-dependencies?)
+ '(#:tests? #f)
+ '())
+ (if (not (string-null? revision))
+ `(#:cabal-revision (,revision ,cabal-hash))
+ '()))
+ (() '())
+ (args `((arguments (,'quasiquote ,args))))))
(let ((tarball (with-store store
(download-to-store store source-url))))
@@ -294,13 +318,16 @@ 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
keys \"os\", \"arch\" and \"impl\" is \"linux\", \"x86_64\" and \"ghc\"
respectively."
- (let ((cabal-meta (if port
- (read-cabal (canonical-newline-port port))
- (hackage-fetch package-name))))
- (and=> cabal-meta (compose (cut hackage-module->sexp <>
- #:include-test-dependencies?
- include-test-dependencies?)
- (cut eval-cabal <> cabal-environment)))))
+ (match
+ (if port (read-cabal-and-hash port)
+ (hackage-fetch-and-hash package-name))
+ ((cabal-meta . cabal-hash)
+ (and=> cabal-meta (compose (cut hackage-module->sexp <>
+ cabal-hash
+ #:include-test-dependencies?
+ include-test-dependencies?)
+ (cut eval-cabal <> cabal-environment))))
+ (_ #f)))
(define hackage->guix-package/m ;memoized variant
(memoize hackage->guix-package))
@@ -274,6 +274,51 @@ executable cabal
(test-assert "hackage->guix-package test multiline desc (braced)"
(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"
(match (call-with-input-string test-read-cabal-1 read-cabal)
((("name" ("test-me"))