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