gexp: 'load-path-expression' produces an expression that deletes duplicates.
[jackhill/guix/guix.git] / guix / import / hackage.scm
CommitLineData
b29455cf
FB
1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2015 Federico Beffa <beffa@fbengineering.ch>
f9ea74ad 3;;; Copyright © 2016 Eric Bavier <bavier@member.fsf.org>
47956fa0 4;;; Copyright © 2016 ng0 <ng0@n0.is>
f6078422 5;;; Copyright © 2018 Ricardo Wurmus <rekado@elephly.net>
e07f7655 6;;; Copyright © 2019 Robert Vollmert <rob@vllmrt.net>
b29455cf
FB
7;;;
8;;; This file is part of GNU Guix.
9;;;
10;;; GNU Guix is free software; you can redistribute it and/or modify it
11;;; under the terms of the GNU General Public License as published by
12;;; the Free Software Foundation; either version 3 of the License, or (at
13;;; your option) any later version.
14;;;
15;;; GNU Guix is distributed in the hope that it will be useful, but
16;;; WITHOUT ANY WARRANTY; without even the implied warranty of
17;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;;; GNU General Public License for more details.
19;;;
20;;; You should have received a copy of the GNU General Public License
21;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
22
23(define-module (guix import hackage)
24 #:use-module (ice-9 match)
42efe27a 25 #:use-module (ice-9 regex)
a4824c60 26 #:use-module (srfi srfi-34)
b29455cf 27 #:use-module (srfi srfi-26)
b29455cf
FB
28 #:use-module (srfi srfi-11)
29 #:use-module (srfi srfi-1)
42efe27a 30 #:use-module ((guix download) #:select (download-to-store url-fetch))
96018e21
FB
31 #:use-module ((guix utils) #:select (package-name->name+version
32 canonical-newline-port))
2ae9c63f 33 #:use-module (guix http-client)
a9285961 34 #:use-module ((guix import utils) #:select (factorize-uri recursive-import))
a4154748 35 #:use-module (guix import cabal)
b29455cf 36 #:use-module (guix store)
ca719424 37 #:use-module (gcrypt hash)
b29455cf 38 #:use-module (guix base32)
a9285961 39 #:use-module (guix memoization)
42efe27a
EB
40 #:use-module (guix upstream)
41 #:use-module (guix packages)
b29455cf 42 #:use-module ((guix utils) #:select (call-with-temporary-output-file))
42efe27a 43 #:export (hackage->guix-package
a9285961 44 hackage-recursive-import
bc5844d1
FB
45 %hackage-updater
46
47 guix-package->hackage-name
a3ece51a 48 hackage-name->package-name
bc5844d1
FB
49 hackage-fetch
50 hackage-source-url
51 hackage-cabal-url
52 hackage-package?))
b29455cf 53
b29455cf 54(define ghc-standard-libraries
1cc12357 55 ;; List of libraries distributed with ghc (8.4.3).
e07f7655 56 ;; Contents of ...-ghc-8.4.3/lib/ghc-8.4.3.
1cc12357
RV
57 '("ghc"
58 "cabal" ;; in the output of `ghc-pkg list` Cabal is uppercased, but
59 ;; hackage-name->package-name takes this into account.
60 "win32" ;; similarly uppercased
61 "array"
b29455cf 62 "base"
b29455cf
FB
63 "binary"
64 "bytestring"
65 "containers"
759756a9
PW
66 "deepseq"
67 "directory"
68 "filepath"
e07f7655 69 "ghc"
1cc12357 70 "ghc-boot"
e07f7655 71 "ghc-boot-th"
1cc12357 72 "ghc-compact"
b29455cf 73 "ghc-prim"
1cc12357 74 "ghci"
759756a9 75 "haskeline"
759756a9 76 "hpc"
b29455cf 77 "integer-gmp"
1cc12357
RV
78 "mtl"
79 "parsec"
e07f7655 80 "pretty"
b29455cf 81 "process"
e07f7655 82 "stm"
759756a9 83 "template-haskell"
e07f7655 84 "terminfo"
1cc12357 85 "text"
759756a9
PW
86 "time"
87 "transformers"
b29455cf 88 "unix"
759756a9 89 "xhtml"))
b29455cf
FB
90
91(define package-name-prefix "ghc-")
92
f9ea74ad
EB
93(define (hackage-source-url name version)
94 "Given a Hackage package NAME and VERSION, return a url to the source
95tarball."
18f74735 96 (string-append "https://hackage.haskell.org/package/" name
f9ea74ad
EB
97 "/" name "-" version ".tar.gz"))
98
99(define* (hackage-cabal-url name #:optional version)
100 "Given a Hackage package NAME and VERSION, return a url to the corresponding
101.cabal file on Hackage. If VERSION is #f or missing, the url for the latest
102version is returned."
103 (if version
18f74735 104 (string-append "https://hackage.haskell.org/package/"
f9ea74ad 105 name "-" version "/" name ".cabal")
18f74735 106 (string-append "https://hackage.haskell.org/package/"
f9ea74ad
EB
107 name "/" name ".cabal")))
108
b29455cf 109(define (hackage-name->package-name name)
a4154748 110 "Given the NAME of a Cabal package, return the corresponding Guix name."
b29455cf
FB
111 (if (string-prefix? package-name-prefix name)
112 (string-downcase name)
113 (string-append package-name-prefix (string-downcase name))))
114
42efe27a
EB
115(define guix-package->hackage-name
116 (let ((uri-rx (make-regexp "https?://hackage.haskell.org/package/([^/]+)/.*"))
117 (name-rx (make-regexp "(.*)-[0-9\\.]+")))
118 (lambda (package)
119 "Given a Guix package name, return the corresponding Hackage name."
120 (let* ((source-url (and=> (package-source package) origin-uri))
121 (name (match:substring (regexp-exec uri-rx source-url) 1)))
122 (match (regexp-exec name-rx name)
123 (#f name)
124 (m (match:substring m 1)))))))
125
ca45da9f
RV
126(define (read-cabal-and-hash port)
127 "Read a Cabal file from PORT and return it and its hash in nix-base32
128format as two values."
129 (let-values (((port get-hash) (open-sha256-input-port port)))
130 (values (read-cabal (canonical-newline-port port))
131 (bytevector->nix-base32-string (get-hash)))))
132
133(define (hackage-fetch-and-hash name-version)
134 "Fetch the latest Cabal revision for the package NAME-VERSION, and return
135two values: the parsed Cabal file and its hash in nix-base32 format. If the
136version part is omitted from the package name, then fetch the latest
137version. On failure, both return values will be #f."
138 (guard (c ((and (http-get-error? c)
139 (= 404 (http-get-error-code c)))
140 (values #f #f))) ;"expected" if package is unknown
141 (let*-values (((name version) (package-name->name+version name-version))
142 ((url) (hackage-cabal-url name version))
143 ((port _) (http-fetch url))
144 ((cabal hash) (read-cabal-and-hash port)))
145 (close-port port)
146 (values cabal hash))))
147
b29455cf
FB
148(define (hackage-fetch name-version)
149 "Return the Cabal file for the package NAME-VERSION, or #f on failure. If
150the version part is omitted from the package name, then return the latest
151version."
ca45da9f
RV
152 (let-values (((cabal hash) (hackage-fetch-and-hash name-version)))
153 cabal))
b29455cf
FB
154
155(define string->license
156 ;; List of valid values from
157 ;; https://www.haskell.org
158 ;; /cabal/release/cabal-latest/doc/API/Cabal/Distribution-License.html.
159 (match-lambda
160 ("GPL-2" 'gpl2)
161 ("GPL-3" 'gpl3)
162 ("GPL" "'gpl??")
163 ("AGPL-3" 'agpl3)
164 ("AGPL" "'agpl??")
165 ("LGPL-2.1" 'lgpl2.1)
166 ("LGPL-3" 'lgpl3)
167 ("LGPL" "'lgpl??")
168 ("BSD2" 'bsd-2)
169 ("BSD3" 'bsd-3)
0607f890 170 ("BSD-3-Clause" 'bsd-3)
b29455cf
FB
171 ("MIT" 'expat)
172 ("ISC" 'isc)
173 ("MPL" 'mpl2.0)
174 ("Apache-2.0" 'asl2.0)
0607f890 175 ("PublicDomain" 'public-domain)
b29455cf
FB
176 ((x) (string->license x))
177 ((lst ...) `(list ,@(map string->license lst)))
178 (_ #f)))
179
a4154748 180
d804d0b9
DM
181(define (cabal-dependencies->names cabal)
182 "Return the list of dependencies names from the CABAL package object,
183not including test suite dependencies or custom-setup dependencies."
a4154748
FB
184 (let* ((lib (cabal-package-library cabal))
185 (lib-deps (if (pair? lib)
186 (map cabal-dependency-name
187 (append-map cabal-library-dependencies lib))
188 '()))
189 (exe (cabal-package-executables cabal))
190 (exe-deps (if (pair? exe)
191 (map cabal-dependency-name
192 (append-map cabal-executable-dependencies exe))
a4154748 193 '())))
d804d0b9
DM
194 (delete-duplicates (append lib-deps exe-deps))))
195
196(define (cabal-test-dependencies->names cabal)
197 "Return the list of test suite dependencies from the CABAL package
198object."
199 (let* ((ts (cabal-package-test-suites cabal))
200 (ts-deps (if (pair? ts)
201 (map cabal-dependency-name
202 (append-map cabal-test-suite-dependencies ts))
203 '())))
204 ts-deps))
205
206(define (cabal-custom-setup-dependencies->names cabal)
207 "Return the list of custom-setup dependencies from the CABAL package
208object."
f6078422
RW
209 (let* ((custom-setup-dependencies (or (and=> (cabal-package-custom-setup cabal)
210 cabal-custom-setup-dependencies)
211 '())))
d804d0b9 212 (map cabal-dependency-name custom-setup-dependencies)))
a4154748
FB
213
214(define (filter-dependencies dependencies own-name)
215 "Filter the dependencies included with the GHC compiler from DEPENDENCIES, a
216list with the names of dependencies. OWN-NAME is the name of the Cabal
217package being processed and is used to filter references to itself."
218 (filter (lambda (d) (not (member (string-downcase d)
219 (cons own-name ghc-standard-libraries))))
220 dependencies))
221
ca45da9f
RV
222(define* (hackage-module->sexp cabal cabal-hash
223 #:key (include-test-dependencies? #t))
a4154748 224 "Return the `package' S-expression for a Cabal package. CABAL is the
ca45da9f
RV
225representation of a Cabal file as produced by 'read-cabal'. CABAL-HASH is
226the hash of the Cabal file."
b29455cf
FB
227
228 (define name
a4154748 229 (cabal-package-name cabal))
b29455cf
FB
230
231 (define version
a4154748 232 (cabal-package-version cabal))
ca45da9f
RV
233
234 (define revision
235 (cabal-package-revision cabal))
b29455cf
FB
236
237 (define source-url
f9ea74ad 238 (hackage-source-url name version))
b29455cf 239
a9285961
RW
240 (define hackage-dependencies
241 ((compose (cut filter-dependencies <>
242 (cabal-package-name cabal))
243 (cut cabal-dependencies->names <>))
244 cabal))
245
246 (define hackage-native-dependencies
69b0a847
AI
247 (lset-difference
248 equal?
249 ((compose (cut filter-dependencies <>
250 (cabal-package-name cabal))
251 ;; FIXME: Check include-test-dependencies?
252 (lambda (cabal)
253 (append (if include-test-dependencies?
254 (cabal-test-dependencies->names cabal)
255 '())
256 (cabal-custom-setup-dependencies->names cabal))))
257 cabal)
258 hackage-dependencies))
a9285961 259
a4154748 260 (define dependencies
a9285961
RW
261 (map (lambda (name)
262 (list name (list 'unquote (string->symbol name))))
263 (map hackage-name->package-name
264 hackage-dependencies)))
d804d0b9
DM
265
266 (define native-dependencies
a9285961
RW
267 (map (lambda (name)
268 (list name (list 'unquote (string->symbol name))))
269 (map hackage-name->package-name
270 hackage-native-dependencies)))
b29455cf
FB
271
272 (define (maybe-inputs input-type inputs)
273 (match inputs
274 (()
275 '())
276 ((inputs ...)
277 (list (list input-type
278 (list 'quasiquote inputs))))))
279
a4154748 280 (define (maybe-arguments)
ca45da9f
RV
281 (match (append (if (not include-test-dependencies?)
282 '(#:tests? #f)
283 '())
284 (if (not (string-null? revision))
285 `(#:cabal-revision (,revision ,cabal-hash))
286 '()))
287 (() '())
288 (args `((arguments (,'quasiquote ,args))))))
a4154748 289
b29455cf
FB
290 (let ((tarball (with-store store
291 (download-to-store store source-url))))
a9285961
RW
292 (values
293 `(package
294 (name ,(hackage-name->package-name name))
295 (version ,version)
296 (source (origin
297 (method url-fetch)
298 (uri (string-append ,@(factorize-uri source-url version)))
299 (sha256
300 (base32
301 ,(if tarball
302 (bytevector->nix-base32-string (file-sha256 tarball))
303 "failed to download tar archive")))))
304 (build-system haskell-build-system)
305 ,@(maybe-inputs 'inputs dependencies)
306 ,@(maybe-inputs 'native-inputs native-dependencies)
307 ,@(maybe-arguments)
308 (home-page ,(cabal-package-home-page cabal))
309 (synopsis ,(cabal-package-synopsis cabal))
310 (description ,(cabal-package-description cabal))
311 (license ,(string->license (cabal-package-license cabal))))
312 (append hackage-dependencies hackage-native-dependencies))))
a4154748 313
ad7466aa
LC
314(define* (hackage->guix-package package-name #:key
315 (include-test-dependencies? #t)
316 (port #f)
317 (cabal-environment '()))
318 "Fetch the Cabal file for PACKAGE-NAME from hackage.haskell.org, or, if the
a4154748
FB
319called with keyword parameter PORT, from PORT. Return the `package'
320S-expression corresponding to that package, or #f on failure.
321CABAL-ENVIRONMENT is an alist defining the environment in which the Cabal
322conditionals are evaluated. The accepted keys are: \"os\", \"arch\", \"impl\"
323and the name of a flag. The value associated with a flag has to be either the
324symbol 'true' or 'false'. The value associated with other keys has to conform
325to the Cabal file format definition. The default value associated with the
326keys \"os\", \"arch\" and \"impl\" is \"linux\", \"x86_64\" and \"ghc\"
327respectively."
ca45da9f
RV
328 (let-values (((cabal-meta cabal-hash)
329 (if port
330 (read-cabal-and-hash port)
331 (hackage-fetch-and-hash package-name))))
332 (and=> cabal-meta (compose (cut hackage-module->sexp <> cabal-hash
ad7466aa
LC
333 #:include-test-dependencies?
334 include-test-dependencies?)
335 (cut eval-cabal <> cabal-environment)))))
336
337(define hackage->guix-package/m ;memoized variant
338 (memoize hackage->guix-package))
a9285961
RW
339
340(define* (hackage-recursive-import package-name . args)
341 (recursive-import package-name #f
342 #:repo->guix-package (lambda (name repo)
ad7466aa
LC
343 (apply hackage->guix-package/m
344 (cons name args)))
a9285961 345 #:guix-name hackage-name->package-name))
b29455cf 346
42efe27a
EB
347(define (hackage-package? package)
348 "Return #t if PACKAGE is a Haskell package from Hackage."
349
350 (define haskell-url?
351 (let ((hackage-rx (make-regexp "https?://hackage.haskell.org")))
352 (lambda (url)
353 (regexp-exec hackage-rx url))))
354
355 (let ((source-url (and=> (package-source package) origin-uri))
356 (fetch-method (and=> (package-source package) origin-method)))
357 (and (eq? fetch-method url-fetch)
358 (match source-url
359 ((? string?)
360 (haskell-url? source-url))
361 ((source-url ...)
362 (any haskell-url? source-url))))))
363
7d27a025
LC
364(define (latest-release package)
365 "Return an <upstream-source> for the latest release of PACKAGE."
366 (let* ((hackage-name (guix-package->hackage-name package))
42efe27a
EB
367 (cabal-meta (hackage-fetch hackage-name)))
368 (match cabal-meta
369 (#f
370 (format (current-error-port)
371 "warning: failed to parse ~a~%"
372 (hackage-cabal-url hackage-name))
373 #f)
374 ((_ *** ("version" (version)))
375 (let ((url (hackage-source-url hackage-name version)))
376 (upstream-source
7d27a025 377 (package (package-name package))
42efe27a
EB
378 (version version)
379 (urls (list url))))))))
380
381(define %hackage-updater
382 (upstream-updater
383 (name 'hackage)
384 (description "Updater for Hackage packages")
385 (pred hackage-package?)
386 (latest latest-release)))
387
b29455cf 388;;; cabal.scm ends here