gnu: r-rgraphviz: Move to (gnu packages bioconductor).
[jackhill/guix/guix.git] / guix / import / utils.scm
CommitLineData
1b3e9685 1;;; GNU Guix --- Functional package management for GNU
2d0409a4 2;;; Copyright © 2012, 2013, 2018, 2019, 2020 Ludovic Courtès <ludo@gnu.org>
59b20347 3;;; Copyright © 2016 Jelle Licht <jlicht@fsfe.org>
11e296ef 4;;; Copyright © 2016 David Craven <david@craven.ch>
3532fc39 5;;; Copyright © 2017, 2019, 2020 Ricardo Wurmus <rekado@elephly.net>
ae9e5d66 6;;; Copyright © 2018 Oleg Pykhalov <go.wigust@gmail.com>
5b315f3e 7;;; Copyright © 2019 Robert Vollmert <rob@vllmrt.net>
1b3e9685
DT
8;;;
9;;; This file is part of GNU Guix.
10;;;
11;;; GNU Guix is free software; you can redistribute it and/or modify it
12;;; under the terms of the GNU General Public License as published by
13;;; the Free Software Foundation; either version 3 of the License, or (at
14;;; your option) any later version.
15;;;
16;;; GNU Guix is distributed in the hope that it will be useful, but
17;;; WITHOUT ANY WARRANTY; without even the implied warranty of
18;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19;;; GNU General Public License for more details.
20;;;
21;;; You should have received a copy of the GNU General Public License
22;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
23
24(define-module (guix import utils)
140b3048 25 #:use-module (guix base32)
fbe9c101 26 #:use-module ((guix build download) #:prefix build:)
ce0be567 27 #:use-module ((gcrypt hash) #:hide (sha256))
bb3f36ed 28 #:use-module (guix http-client)
a34b236c 29 #:use-module ((guix licenses) #:prefix license:)
1ff2619b 30 #:use-module (guix utils)
5e892bc3
RW
31 #:use-module (guix packages)
32 #:use-module (guix discovery)
33 #:use-module (guix build-system)
34 #:use-module (guix gexp)
35 #:use-module (guix store)
36 #:use-module (guix download)
ddd59159 37 #:use-module (guix sets)
5e892bc3 38 #:use-module (gnu packages)
fbe9c101 39 #:use-module (ice-9 match)
6b46b04f
RW
40 #:use-module (ice-9 rdelim)
41 #:use-module (ice-9 receive)
fbe9c101 42 #:use-module (ice-9 regex)
fbe9c101 43 #:use-module (srfi srfi-1)
ddd59159 44 #:use-module (srfi srfi-9)
5e892bc3 45 #:use-module (srfi srfi-11)
ae9e5d66 46 #:use-module (srfi srfi-26)
1ff2619b
EB
47 #:export (factorize-uri
48
1ff2619b 49 flatten
1ff2619b 50
140b3048
DT
51 url-fetch
52 guix-hash-url
53
5a9ef8a9 54 package-names->package-inputs
bb3f36ed
DC
55 maybe-inputs
56 maybe-native-inputs
57 package->definition
58
59b20347 59 spdx-string->license
140b3048
DT
60 license->symbol
61
2028a2c9 62 snake-case
5e892bc3
RW
63 beautify-description
64
6b46b04f
RW
65 alist->package
66
67 read-lines
ae9e5d66
OP
68 chunk-lines
69
70 guix-name
71
72 recursive-import))
1b3e9685
DT
73
74(define (factorize-uri uri version)
75 "Factorize URI, a package tarball URI as a string, such that any occurrences
76of the string VERSION is replaced by the symbol 'version."
77 (let ((version-rx (make-regexp (regexp-quote version))))
78 (match (regexp-exec version-rx uri)
79 (#f
80 uri)
81 (_
82 (let ((indices (fold-matches version-rx uri
83 '((0))
84 (lambda (m result)
85 (match result
86 (((start) rest ...)
87 `((,(match:end m))
88 (,start . ,(match:start m))
89 ,@rest)))))))
90 (fold (lambda (index result)
91 (match index
92 ((start)
93 (cons (substring uri start)
94 result))
95 ((start . end)
96 (cons* (substring uri start end)
97 'version
98 result))))
99 '()
100 indices))))))
1ff2619b 101
1ff2619b
EB
102(define (flatten lst)
103 "Return a list that recursively concatenates all sub-lists of LST."
104 (fold-right
105 (match-lambda*
106 (((sub-list ...) memo)
107 (append (flatten sub-list) memo))
108 ((elem memo)
109 (cons elem memo)))
110 '() lst))
111
1ff2619b
EB
112(define (url-fetch url file-name)
113 "Save the contents of URL to FILE-NAME. Return #f on failure."
114 (parameterize ((current-output-port (current-error-port)))
115 (build:url-fetch url file-name)))
140b3048
DT
116
117(define (guix-hash-url filename)
118 "Return the hash of FILENAME in nix-base32 format."
119 (bytevector->nix-base32-string (file-sha256 filename)))
120
59b20347
DC
121(define (spdx-string->license str)
122 "Convert STR, a SPDX formatted license identifier, to a license object.
123 Return #f if STR does not match any known identifiers."
124 ;; https://spdx.org/licenses/
125 ;; The psfl, gfl1.0, nmap, repoze
126 ;; licenses doesn't have SPDX identifiers
140b3048 127 (match str
59b20347
DC
128 ("AGPL-1.0" 'license:agpl-1.0)
129 ("AGPL-3.0" 'license:agpl-3.0)
130 ("Apache-1.1" 'license:asl1.1)
131 ("Apache-2.0" 'license:asl2.0)
132 ("BSL-1.0" 'license:boost1.0)
133 ("BSD-2-Clause-FreeBSD" 'license:bsd-2)
134 ("BSD-3-Clause" 'license:bsd-3)
135 ("BSD-4-Clause" 'license:bsd-4)
136 ("CC0-1.0" 'license:cc0)
137 ("CC-BY-2.0" 'license:cc-by2.0)
138 ("CC-BY-3.0" 'license:cc-by3.0)
139 ("CC-BY-SA-2.0" 'license:cc-by-sa2.0)
140 ("CC-BY-SA-3.0" 'license:cc-by-sa3.0)
141 ("CC-BY-SA-4.0" 'license:cc-by-sa4.0)
142 ("CDDL-1.0" 'license:cddl1.0)
143 ("CECILL-C" 'license:cecill-c)
144 ("Artistic-2.0" 'license:artistic2.0)
145 ("ClArtistic" 'license:clarified-artistic)
146 ("CPL-1.0" 'license:cpl1.0)
147 ("EPL-1.0" 'license:epl1.0)
148 ("MIT" 'license:expat)
149 ("FTL" 'license:freetype)
150 ("GFDL-1.1" 'license:fdl1.1+)
151 ("GFDL-1.2" 'license:fdl1.2+)
152 ("GFDL-1.3" 'license:fdl1.3+)
153 ("Giftware" 'license:giftware)
154 ("GPL-1.0" 'license:gpl1)
155 ("GPL-1.0+" 'license:gpl1+)
156 ("GPL-2.0" 'license:gpl2)
157 ("GPL-2.0+" 'license:gpl2+)
158 ("GPL-3.0" 'license:gpl3)
159 ("GPL-3.0+" 'license:gpl3+)
160 ("ISC" 'license:isc)
161 ("IJG" 'license:ijg)
162 ("Imlib2" 'license:imlib2)
163 ("IPA" 'license:ipa)
164 ("IPL-1.0" 'license:ibmpl1.0)
165 ("LGPL-2.0" 'license:lgpl2.0)
166 ("LGPL-2.0+" 'license:lgpl2.0+)
167 ("LGPL-2.1" 'license:lgpl2.1)
168 ("LGPL-2.1+" 'license:lgpl2.1+)
169 ("LGPL-3.0" 'license:lgpl3.0)
170 ("LGPL-3.0+" 'license:lgpl3.0+)
171 ("MPL-1.0" 'license:mpl1.0)
172 ("MPL-1.1" 'license:mpl1.1)
173 ("MPL-2.0" 'license:mpl2.0)
174 ("MS-PL" 'license:ms-pl)
175 ("NCSA" 'license:ncsa)
176 ("OpenSSL" 'license:openssl)
177 ("OLDAP-2.8" 'license:openldap2.8)
178 ("CUA-OPL-1.0" 'license:opl1.0)
179 ("QPL-1.0" 'license:qpl)
180 ("Ruby" 'license:ruby)
181 ("SGI-B-2.0" 'license:sgifreeb2.0)
182 ("OFL-1.1" 'license:silofl1.1)
183 ("Sleepycat" 'license:sleepycat)
184 ("TCL" 'license:tcl/tk)
185 ("Unlicense" 'license:unlicense)
186 ("Vim" 'license:vim)
187 ("X11" 'license:x11)
188 ("ZPL-2.1" 'license:zpl2.1)
189 ("Zlib" 'license:zlib)
140b3048
DT
190 (_ #f)))
191
192(define (license->symbol license)
193 "Convert license to a symbol representing the variable the object is bound
194to in the (guix licenses) module, or #f if there is no such known license."
11e296ef
DC
195 (define licenses
196 (module-map (lambda (sym var) `(,(variable-ref var) . ,sym))
197 (resolve-interface '(guix licenses) #:prefix 'license:)))
198 (assoc-ref licenses license))
140b3048
DT
199
200(define (snake-case str)
201 "Return a downcased version of the string STR where underscores are replaced
202with dashes."
203 (string-join (string-split (string-downcase str) #\_) "-"))
2028a2c9
BW
204
205(define (beautify-description description)
206 "Improve the package DESCRIPTION by turning a beginning sentence fragment
207into a proper sentence and by using two spaces between sentences."
6e377b88
RW
208 (let ((cleaned (cond
209 ((string-prefix? "A " description)
210 (string-append "This package provides a"
211 (substring description 1)))
212 ((string-prefix? "Provides " description)
213 (string-append "This package provides"
214 (substring description
215 (string-length "Provides"))))
216 ((string-prefix? "Functions " description)
217 (string-append "This package provides functions"
218 (substring description
219 (string-length "Functions"))))
220 (else description))))
2028a2c9
BW
221 ;; Use double spacing between sentences
222 (regexp-substitute/global #f "\\. \\b"
223 cleaned 'pre ". " 'post)))
bb3f36ed 224
f1d13695 225(define* (package-names->package-inputs names #:optional (output #f))
5a9ef8a9
IP
226 "Given a list of PACKAGE-NAMES, and an optional OUTPUT, tries to generate a
227quoted list of inputs, as suitable to use in an 'inputs' field of a package
228definition."
bb3f36ed 229 (map (lambda (input)
f1d13695
DC
230 (cons* input (list 'unquote (string->symbol input))
231 (or (and output (list output))
232 '())))
bb3f36ed
DC
233 names))
234
f1d13695 235(define* (maybe-inputs package-names #:optional (output #f))
bb3f36ed
DC
236 "Given a list of PACKAGE-NAMES, tries to generate the 'inputs' field of a
237package definition."
f1d13695 238 (match (package-names->package-inputs package-names output)
bb3f36ed
DC
239 (()
240 '())
241 ((package-inputs ...)
242 `((inputs (,'quasiquote ,package-inputs))))))
243
f1d13695 244(define* (maybe-native-inputs package-names #:optional (output #f))
bb3f36ed
DC
245 "Given a list of PACKAGE-NAMES, tries to generate the 'inputs' field of a
246package definition."
f1d13695 247 (match (package-names->package-inputs package-names output)
bb3f36ed
DC
248 (()
249 '())
250 ((package-inputs ...)
251 `((native-inputs (,'quasiquote ,package-inputs))))))
252
253(define (package->definition guix-package)
254 (match guix-package
255 (('package ('name (? string? name)) _ ...)
ad553ec4
RW
256 `(define-public ,(string->symbol name)
257 ,guix-package))
258 (('let anything ('package ('name (? string? name)) _ ...))
bb3f36ed
DC
259 `(define-public ,(string->symbol name)
260 ,guix-package))))
5e892bc3
RW
261
262(define (build-system-modules)
263 (all-modules (map (lambda (entry)
264 `(,entry . "guix/build-system"))
265 %load-path)))
266
267(define (lookup-build-system-by-name name)
268 "Return a <build-system> value for the symbol NAME, representing the name of
269the build system."
270 (fold-module-public-variables (lambda (obj result)
271 (if (and (build-system? obj)
272 (eq? name (build-system-name obj)))
273 obj result))
274 #f
275 (build-system-modules)))
276
277(define (specs->package-lists specs)
278 "Convert each string in the SPECS list to a list of a package label and a
279package value."
280 (map (lambda (spec)
281 (let-values (((pkg out) (specification->package+output spec)))
282 (match out
f54cab27 283 ("out" (list (package-name pkg) pkg))
5e892bc3
RW
284 (_ (list (package-name pkg) pkg out)))))
285 specs))
286
287(define (source-spec->object source)
288 "Generate an <origin> object from a SOURCE specification. The SOURCE can
289either be a simple URL string, #F, or an alist containing entries for each of
290the expected fields of an <origin> object."
291 (match source
292 ((? string? source-url)
293 (let ((tarball (with-store store (download-to-store store source-url))))
294 (origin
295 (method url-fetch)
296 (uri source-url)
297 (sha256 (base32 (guix-hash-url tarball))))))
298 (#f #f)
299 (orig (let ((sha (match (assoc-ref orig "sha256")
300 ((("base32" . value))
301 (base32 value))
302 (_ #f))))
303 (origin
304 (method (match (assoc-ref orig "method")
305 ("url-fetch" (@ (guix download) url-fetch))
306 ("git-fetch" (@ (guix git-download) git-fetch))
307 ("svn-fetch" (@ (guix svn-download) svn-fetch))
308 ("hg-fetch" (@ (guix hg-download) hg-fetch))
309 (_ #f)))
310 (uri (assoc-ref orig "uri"))
311 (sha256 sha))))))
312
3532fc39
RW
313(define* (alist->package meta #:optional (known-inputs '()))
314 "Return a package value generated from the alist META. If the list of
315strings KNOWN-INPUTS is provided, do not treat the mentioned inputs as
316specifications to look up and replace them with plain symbols instead."
317 (define (process-inputs which)
318 (let-values (((regular known)
319 (lset-diff+intersection
320 string=?
321 (vector->list (or (assoc-ref meta which) #()))
322 known-inputs)))
323 (append (specs->package-lists regular)
324 (map string->symbol known))))
3fd4c4c8
RW
325 (define (process-arguments arguments)
326 (append-map (match-lambda
327 ((key . value)
328 (list (symbol->keyword (string->symbol key)) value)))
329 arguments))
5e892bc3
RW
330 (package
331 (name (assoc-ref meta "name"))
332 (version (assoc-ref meta "version"))
333 (source (source-spec->object (assoc-ref meta "source")))
334 (build-system
335 (lookup-build-system-by-name
336 (string->symbol (assoc-ref meta "build-system"))))
3fd4c4c8
RW
337 (arguments
338 (or (and=> (assoc-ref meta "arguments")
339 process-arguments)
340 '()))
3532fc39
RW
341 (native-inputs (process-inputs "native-inputs"))
342 (inputs (process-inputs "inputs"))
343 (propagated-inputs (process-inputs "propagated-inputs"))
5e892bc3
RW
344 (home-page
345 (assoc-ref meta "home-page"))
346 (synopsis
347 (assoc-ref meta "synopsis"))
348 (description
349 (assoc-ref meta "description"))
350 (license
5e2495d0
LC
351 (match (assoc-ref meta "license")
352 (#f #f)
353 (l
354 (or (module-ref (resolve-interface '(guix licenses) #:prefix 'license:)
355 (spdx-string->license l))
356 (license:fsdg-compatible l)))))))
6b46b04f
RW
357
358(define* (read-lines #:optional (port (current-input-port)))
359 "Read lines from PORT and return them as a list."
360 (let loop ((line (read-line port))
361 (lines '()))
362 (if (eof-object? line)
363 (reverse lines)
364 (loop (read-line port)
365 (cons line lines)))))
366
367(define* (chunk-lines lines #:optional (pred string-null?))
368 "Return a list of chunks, each of which is a list of lines. The chunks are
369separated by PRED."
370 (let loop ((rest lines)
371 (parts '()))
372 (receive (before after)
373 (break pred rest)
374 (let ((res (cons before parts)))
375 (if (null? after)
376 (reverse res)
377 (loop (cdr after) res))))))
ae9e5d66
OP
378
379(define (guix-name prefix name)
380 "Return a Guix package name for a given package name."
381 (string-append prefix (string-map (match-lambda
382 (#\_ #\-)
383 (#\. #\-)
384 (chr (char-downcase chr)))
385 name)))
386
ddd59159
LC
387(define (topological-sort nodes
388 node-dependencies
389 node-name)
390 "Perform a breadth-first traversal of the graph rooted at NODES, a list of
391nodes, and return the list of nodes sorted in topological order. Call
392NODE-DEPENDENCIES to obtain the dependencies of a node, and NODE-NAME to
393obtain a node's uniquely identifying \"key\"."
394 (let loop ((nodes nodes)
395 (result '())
396 (visited (set)))
397 (match nodes
398 (()
399 result)
400 ((head . tail)
401 (if (set-contains? visited (node-name head))
402 (loop tail result visited)
403 (let ((dependencies (node-dependencies head)))
404 (loop (append dependencies tail)
405 (cons head result)
406 (set-insert (node-name head) visited))))))))
407
ae9e5d66
OP
408(define* (recursive-import package-name repo
409 #:key repo->guix-package guix-name
410 #:allow-other-keys)
6212146f 411 "Return a list of package expressions for PACKAGE-NAME and all its
ddd59159
LC
412dependencies, sorted in topological order. For each package,
413call (REPO->GUIX-PACKAGE NAME REPO), which should return a package expression
414and a list of dependencies; call (GUIX-NAME NAME) to obtain the Guix package
415name corresponding to the upstream name."
416 (define-record-type <node>
417 (make-node name package dependencies)
418 node?
419 (name node-name)
420 (package node-package)
421 (dependencies node-dependencies))
422
423 (define (exists? name)
424 (not (null? (find-packages-by-name (guix-name name)))))
425
426 (define (lookup-node name)
427 (receive (package dependencies) (repo->guix-package name repo)
428 (make-node name package dependencies)))
429
70a8e132
LC
430 (map node-package
431 (topological-sort (list (lookup-node package-name))
432 (lambda (node)
433 (map lookup-node
434 (remove exists?
435 (node-dependencies node))))
436 node-name)))