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