Commit | Line | Data |
---|---|---|
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 | |
95 | tarball." | |
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 | |
102 | version 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 | |
128 | format 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 | |
135 | two values: the parsed Cabal file and its hash in nix-base32 format. If the | |
136 | version part is omitted from the package name, then fetch the latest | |
137 | version. 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 | |
150 | the version part is omitted from the package name, then return the latest | |
151 | version." | |
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, | |
183 | not 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 | |
198 | object." | |
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 | |
208 | object." | |
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 | |
216 | list with the names of dependencies. OWN-NAME is the name of the Cabal | |
217 | package 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 |
225 | representation of a Cabal file as produced by 'read-cabal'. CABAL-HASH is |
226 | the 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 |
319 | called with keyword parameter PORT, from PORT. Return the `package' |
320 | S-expression corresponding to that package, or #f on failure. | |
321 | CABAL-ENVIRONMENT is an alist defining the environment in which the Cabal | |
322 | conditionals are evaluated. The accepted keys are: \"os\", \"arch\", \"impl\" | |
323 | and the name of a flag. The value associated with a flag has to be either the | |
324 | symbol 'true' or 'false'. The value associated with other keys has to conform | |
325 | to the Cabal file format definition. The default value associated with the | |
326 | keys \"os\", \"arch\" and \"impl\" is \"linux\", \"x86_64\" and \"ghc\" | |
327 | respectively." | |
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 |