pk-crypto: Add canonical-sexp to sexp conversion procedures.
[jackhill/guix/guix.git] / guix / utils.scm
CommitLineData
233e7676 1;;; GNU Guix --- Functional package management for GNU
bbb7a00e 2;;; Copyright © 2012, 2013 Ludovic Courtès <ludo@gnu.org>
56b943de 3;;; Copyright © 2013 Mark H Weaver <mhw@netris.org>
e3deeebb 4;;;
233e7676 5;;; This file is part of GNU Guix.
e3deeebb 6;;;
233e7676 7;;; GNU Guix is free software; you can redistribute it and/or modify it
e3deeebb
LC
8;;; under the terms of the GNU General Public License as published by
9;;; the Free Software Foundation; either version 3 of the License, or (at
10;;; your option) any later version.
11;;;
233e7676 12;;; GNU Guix is distributed in the hope that it will be useful, but
e3deeebb
LC
13;;; WITHOUT ANY WARRANTY; without even the implied warranty of
14;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15;;; GNU General Public License for more details.
16;;;
17;;; You should have received a copy of the GNU General Public License
233e7676 18;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
e3deeebb
LC
19
20(define-module (guix utils)
00e219d1 21 #:use-module (guix config)
38b3122a 22 #:use-module (srfi srfi-1)
72d86963 23 #:use-module (srfi srfi-9)
38b3122a 24 #:use-module (srfi srfi-26)
de4c3f26 25 #:use-module (srfi srfi-39)
e3deeebb
LC
26 #:use-module (srfi srfi-60)
27 #:use-module (rnrs bytevectors)
dba6b34b 28 #:use-module ((rnrs io ports) #:select (put-bytevector))
e0fbbc88 29 #:use-module ((guix build utils) #:select (dump-port))
c8369cac 30 #:use-module (ice-9 vlist)
38b3122a 31 #:use-module (ice-9 format)
de4c3f26
LC
32 #:autoload (ice-9 popen) (open-pipe*)
33 #:autoload (ice-9 rdelim) (read-line)
98090557 34 #:use-module (ice-9 regex)
72d86963 35 #:use-module (ice-9 match)
8ef3401f 36 #:use-module (ice-9 format)
39b9372c 37 #:autoload (system foreign) (pointer->procedure)
ddc29a78 38 #:export (bytevector->base16-string
6d800a80 39 base16-string->bytevector
de4c3f26
LC
40
41 %nixpkgs-directory
42 nixpkgs-derivation
ce5d658c 43 nixpkgs-derivation*
de4c3f26 44
900f7267 45 compile-time-value
98090557 46 memoize
0af2c24e
LC
47 default-keyword-arguments
48 substitute-keyword-arguments
ff352cfb 49
64fc89b6 50 <location>
ff352cfb
LC
51 location
52 location?
53 location-file
54 location-line
55 location-column
56 source-properties->location
57
98090557 58 gnu-triplet->nix-system
9b48fb88 59 %current-system
9c1edabd 60 %current-target-system
0d1e6ce4
MW
61 version-compare
62 version>?
7db3ff4a 63 guile-version>?
04fd96ca 64 package-name->name+version
2bcfb9e0 65 string-tokenize*
56b943de 66 string-replace-substring
0fdd3bea 67 file-extension
ac10e0e1 68 file-sans-extension
861693f3 69 call-with-temporary-output-file
e0fbbc88
LC
70 fold2
71 filtered-port))
e3deeebb 72
38b3122a 73\f
dba6b34b
LC
74;;;
75;;; Compile-time computations.
76;;;
77
78(define-syntax compile-time-value
79 (syntax-rules ()
80 "Evaluate the given expression at compile time. The expression must
81evaluate to a simple datum."
82 ((_ exp)
83 (let-syntax ((v (lambda (s)
84 (let ((val exp))
85 (syntax-case s ()
86 (_ #`'#,(datum->syntax s val)))))))
87 v))))
88
89\f
38b3122a
LC
90;;;
91;;; Base 16.
92;;;
93
94(define (bytevector->base16-string bv)
95 "Return the hexadecimal representation of BV's contents."
96 (define len
97 (bytevector-length bv))
98
99 (let-syntax ((base16-chars (lambda (s)
100 (syntax-case s ()
101 (_
102 (let ((v (list->vector
103 (unfold (cut > <> 255)
104 (lambda (n)
105 (format #f "~2,'0x" n))
106 1+
107 0))))
108 v))))))
109 (define chars base16-chars)
110 (let loop ((i 0)
111 (r '()))
112 (if (= i len)
113 (string-concatenate-reverse r)
114 (loop (+ 1 i)
115 (cons (vector-ref chars (bytevector-u8-ref bv i)) r))))))
116
6d800a80
LC
117(define base16-string->bytevector
118 (let ((chars->value (fold (lambda (i r)
119 (vhash-consv (string-ref (number->string i 16)
120 0)
121 i r))
122 vlist-null
123 (iota 16))))
124 (lambda (s)
125 "Return the bytevector whose hexadecimal representation is string S."
126 (define bv
127 (make-bytevector (quotient (string-length s) 2) 0))
128
129 (string-fold (lambda (chr i)
130 (let ((j (quotient i 2))
131 (v (and=> (vhash-assv chr chars->value) cdr)))
132 (if v
133 (if (zero? (logand i 1))
134 (bytevector-u8-set! bv j
135 (arithmetic-shift v 4))
136 (let ((w (bytevector-u8-ref bv j)))
137 (bytevector-u8-set! bv j (logior v w))))
138 (error "invalid hexadecimal character" chr)))
139 (+ i 1))
140 0
141 s)
142 bv)))
143
de4c3f26
LC
144
145\f
e0fbbc88
LC
146;;;
147;;; Filtering & pipes.
148;;;
149
150(define (filtered-port command input)
151 "Return an input port where data drained from INPUT is filtered through
152COMMAND (a list). In addition, return a list of PIDs that the caller must
101d9f3f
LC
153wait. When INPUT is a file port, it must be unbuffered; otherwise, any
154buffered data is lost."
e0fbbc88
LC
155 (let loop ((input input)
156 (pids '()))
157 (if (file-port? input)
158 (match (pipe)
159 ((in . out)
160 (match (primitive-fork)
161 (0
162 (close-port in)
163 (close-port (current-input-port))
164 (dup2 (fileno input) 0)
165 (close-port (current-output-port))
166 (dup2 (fileno out) 1)
167 (apply execl (car command) command))
168 (child
169 (close-port out)
170 (values in (cons child pids))))))
171
172 ;; INPUT is not a file port, so fork just for the sake of tunneling it
173 ;; through a file port.
174 (match (pipe)
175 ((in . out)
176 (match (primitive-fork)
177 (0
178 (dynamic-wind
179 (const #t)
180 (lambda ()
181 (close-port in)
182 (dump-port input out))
183 (lambda ()
184 (false-if-exception (close out))
185 (primitive-exit 0))))
186 (child
187 (close-port out)
188 (loop in (cons child pids)))))))))
189
190\f
de4c3f26
LC
191;;;
192;;; Nixpkgs.
193;;;
194
195(define %nixpkgs-directory
900f7267
LC
196 (make-parameter
197 ;; Capture the build-time value of $NIXPKGS.
a09ec3a5
LC
198 (or %nixpkgs
199 (and=> (getenv "NIXPKGS")
200 (lambda (val)
201 ;; Bail out when passed an empty string, otherwise
202 ;; `nix-instantiate' will sit there and attempt to read
203 ;; from its standard input.
204 (if (string=? val "")
205 #f
206 val))))))
de4c3f26 207
fbc93bed 208(define* (nixpkgs-derivation attribute #:optional (system (%current-system)))
de4c3f26 209 "Return the derivation path of ATTRIBUTE in Nixpkgs."
b86b0056 210 (let* ((p (open-pipe* OPEN_READ (or (getenv "NIX_INSTANTIATE")
00e219d1 211 %nix-instantiate)
fbc93bed
LC
212 "-A" attribute (%nixpkgs-directory)
213 "--argstr" "system" system))
de4c3f26
LC
214 (l (read-line p))
215 (s (close-pipe p)))
216 (and (zero? (status:exit-val s))
217 (not (eof-object? l))
218 l)))
219
ce5d658c
LC
220(define-syntax-rule (nixpkgs-derivation* attribute)
221 "Evaluate the given Nixpkgs derivation at compile-time."
222 (compile-time-value (nixpkgs-derivation attribute)))
223
de4c3f26
LC
224\f
225;;;
226;;; Miscellaneous.
227;;;
228
229(define (memoize proc)
230 "Return a memoizing version of PROC."
231 (let ((cache (make-hash-table)))
232 (lambda args
233 (let ((results (hash-ref cache args)))
234 (if results
235 (apply values results)
236 (let ((results (call-with-values (lambda ()
237 (apply proc args))
238 list)))
239 (hash-set! cache args results)
240 (apply values results)))))))
98090557 241
0af2c24e
LC
242(define (default-keyword-arguments args defaults)
243 "Return ARGS augmented with any keyword/value from DEFAULTS for
244keywords not already present in ARGS."
245 (let loop ((defaults defaults)
246 (args args))
247 (match defaults
248 ((kw value rest ...)
249 (loop rest
250 (if (assoc-ref kw args)
251 args
252 (cons* kw value args))))
253 (()
254 args))))
255
256(define-syntax substitute-keyword-arguments
257 (syntax-rules ()
258 "Return a new list of arguments where the value for keyword arg KW is
259replaced by EXP. EXP is evaluated in a context where VAR is boud to the
260previous value of the keyword argument."
261 ((_ original-args ((kw var) exp) ...)
262 (let loop ((args original-args)
263 (before '()))
264 (match args
265 ((kw var rest (... ...))
266 (loop rest (cons* exp kw before)))
267 ...
268 ((x rest (... ...))
269 (loop rest (cons x before)))
270 (()
271 (reverse before)))))))
272
98090557
LC
273(define (gnu-triplet->nix-system triplet)
274 "Return the Nix system type corresponding to TRIPLET, a GNU triplet as
275returned by `config.guess'."
276 (let ((triplet (cond ((string-match "^i[345]86-(.*)$" triplet)
277 =>
278 (lambda (m)
279 (string-append "i686-" (match:substring m 1))))
280 (else triplet))))
281 (cond ((string-match "^([^-]+)-([^-]+-)?linux-gnu.*" triplet)
282 =>
283 (lambda (m)
284 ;; Nix omits `-gnu' for GNU/Linux.
285 (string-append (match:substring m 1) "-linux")))
286 ((string-match "^([^-]+)-([^-]+-)?([[:alpha:]]+)([0-9]+\\.?)*$" triplet)
287 =>
288 (lambda (m)
289 ;; Nix strip the version number from names such as `gnu0.3',
290 ;; `darwin10.2.0', etc., and always strips the vendor part.
291 (string-append (match:substring m 1) "-"
292 (match:substring m 3))))
293 (else triplet))))
294
295(define %current-system
296 ;; System type as expected by Nix, usually ARCHITECTURE-KERNEL.
d8eea3d2
LC
297 ;; By default, this is equal to (gnu-triplet->nix-system %host-type).
298 (make-parameter %system))
ff352cfb 299
9c1edabd
LC
300(define %current-target-system
301 ;; Either #f or a GNU triplet representing the target system we are
302 ;; cross-building to.
303 (make-parameter #f))
304
0d1e6ce4
MW
305(define version-compare
306 (let ((strverscmp
307 (let ((sym (or (dynamic-func "strverscmp" (dynamic-link))
308 (error "could not find `strverscmp' (from GNU libc)"))))
309 (pointer->procedure int sym (list '* '*)))))
310 (lambda (a b)
311 "Return '> when A denotes a newer version than B,
312'< when A denotes a older version than B,
313or '= when they denote equal versions."
314 (let ((result (strverscmp (string->pointer a) (string->pointer b))))
315 (cond ((positive? result) '>)
316 ((negative? result) '<)
317 (else '=))))))
318
319(define (version>? a b)
320 "Return #t when A denotes a newer version than B."
321 (eq? '> (version-compare a b)))
322
7db3ff4a
LC
323(define (guile-version>? str)
324 "Return #t if the running Guile version is greater than STR."
325 ;; Note: Using (version>? (version) "2.0.5") or similar doesn't work,
326 ;; because the result of (version) can have a prefix, like "2.0.5-deb1".
327 (version>? (string-append (major-version) "."
328 (minor-version) "."
329 (micro-version))
330 str))
331
9b48fb88
LC
332(define (package-name->name+version name)
333 "Given NAME, a package name like \"foo-0.9.1b\", return two values:
334\"foo\" and \"0.9.1b\". When the version part is unavailable, NAME and
335#f are returned. The first hyphen followed by a digit is considered to
336introduce the version part."
337 ;; See also `DrvName' in Nix.
338
339 (define number?
340 (cut char-set-contains? char-set:digit <>))
341
342 (let loop ((chars (string->list name))
343 (prefix '()))
344 (match chars
345 (()
346 (values name #f))
347 ((#\- (? number? n) rest ...)
348 (values (list->string (reverse prefix))
349 (list->string (cons n rest))))
350 ((head tail ...)
351 (loop tail (cons head prefix))))))
352
0fdd3bea
LC
353(define (file-extension file)
354 "Return the extension of FILE or #f if there is none."
355 (let ((dot (string-rindex file #\.)))
356 (and dot (substring file (+ 1 dot) (string-length file)))))
357
ac10e0e1
LC
358(define (file-sans-extension file)
359 "Return the substring of FILE without its extension, if any."
360 (let ((dot (string-rindex file #\.)))
361 (if dot
362 (substring file 0 dot)
363 file)))
364
2bcfb9e0
LC
365(define (string-tokenize* string separator)
366 "Return the list of substrings of STRING separated by SEPARATOR. This is
367like `string-tokenize', but SEPARATOR is a string."
368 (define (index string what)
369 (let loop ((string string)
370 (offset 0))
371 (cond ((string-null? string)
372 #f)
373 ((string-prefix? what string)
374 offset)
375 (else
376 (loop (string-drop string 1) (+ 1 offset))))))
377
378 (define len
379 (string-length separator))
380
381 (let loop ((string string)
382 (result '()))
383 (cond ((index string separator)
384 =>
385 (lambda (offset)
386 (loop (string-drop string (+ offset len))
387 (cons (substring string 0 offset)
388 result))))
389 (else
390 (reverse (cons string result))))))
391
56b943de
LC
392(define* (string-replace-substring str substr replacement
393 #:optional
394 (start 0)
395 (end (string-length str)))
396 "Replace all occurrences of SUBSTR in the START--END range of STR by
397REPLACEMENT."
398 (match (string-length substr)
399 (0
400 (error "string-replace-substring: empty substring"))
401 (substr-length
402 (let loop ((start start)
403 (pieces (list (substring str 0 start))))
404 (match (string-contains str substr start end)
405 (#f
406 (string-concatenate-reverse
407 (cons (substring str start) pieces)))
408 (index
409 (loop (+ index substr-length)
410 (cons* replacement
411 (substring str start index)
412 pieces))))))))
413
861693f3
LC
414(define (call-with-temporary-output-file proc)
415 "Call PROC with a name of a temporary file and open output port to that
416file; close the file and delete it when leaving the dynamic extent of this
417call."
418 (let* ((template (string-copy "guix-file.XXXXXX"))
419 (out (mkstemp! template)))
420 (dynamic-wind
421 (lambda ()
422 #t)
423 (lambda ()
424 (proc template out))
425 (lambda ()
426 (false-if-exception (close out))
427 (false-if-exception (delete-file template))))))
428
04fd96ca
LC
429(define fold2
430 (case-lambda
431 ((proc seed1 seed2 lst)
432 "Like `fold', but with a single list and two seeds."
433 (let loop ((result1 seed1)
434 (result2 seed2)
435 (lst lst))
436 (if (null? lst)
437 (values result1 result2)
438 (call-with-values
439 (lambda () (proc (car lst) result1 result2))
440 (lambda (result1 result2)
441 (loop result1 result2 (cdr lst)))))))
442 ((proc seed1 seed2 lst1 lst2)
443 "Like `fold', but with a two lists and two seeds."
444 (let loop ((result1 seed1)
445 (result2 seed2)
446 (lst1 lst1)
447 (lst2 lst2))
448 (if (or (null? lst1) (null? lst2))
449 (values result1 result2)
450 (call-with-values
451 (lambda () (proc (car lst1) (car lst2) result1 result2))
452 (lambda (result1 result2)
453 (fold2 proc result1 result2 (cdr lst1) (cdr lst2)))))))))
454
ff352cfb
LC
455\f
456;;;
457;;; Source location.
458;;;
459
460;; A source location.
461(define-record-type <location>
462 (make-location file line column)
463 location?
464 (file location-file) ; file name
465 (line location-line) ; 1-indexed line
466 (column location-column)) ; 0-indexed column
467
468(define location
469 (memoize
470 (lambda (file line column)
471 "Return the <location> object for the given FILE, LINE, and COLUMN."
472 (and line column file
473 (make-location file line column)))))
474
475(define (source-properties->location loc)
476 "Return a location object based on the info in LOC, an alist as returned
477by Guile's `source-properties', `frame-source', `current-source-location',
478etc."
479 (let ((file (assq-ref loc 'filename))
480 (line (assq-ref loc 'line))
481 (col (assq-ref loc 'column)))
5fe21fbe
LC
482 ;; In accordance with the GCS, start line and column numbers at 1. Note
483 ;; that unlike LINE and `port-column', COL is actually 1-indexed here...
484 (location file (and line (+ line 1)) col)))