gnu: texinfo: Don't propagate Perl.
[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 sha256
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
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
d0a92b75
LC
140\f
141;;;
142;;; Hash.
143;;;
144
39b9372c 145(define sha256
d388c2c4
LC
146 (let ((hash (pointer->procedure void
147 (dynamic-func "gcry_md_hash_buffer"
148 (dynamic-link %libgcrypt))
149 `(,int * * ,size_t)))
150 (sha256 8)) ; GCRY_MD_SHA256, as of 1.5.0
39b9372c
LC
151 (lambda (bv)
152 "Return the SHA256 of BV as a bytevector."
d388c2c4
LC
153 (let ((digest (make-bytevector (/ 256 8))))
154 (hash sha256 (bytevector->pointer digest)
155 (bytevector->pointer bv) (bytevector-length bv))
156 digest))))
de4c3f26
LC
157
158\f
e0fbbc88
LC
159;;;
160;;; Filtering & pipes.
161;;;
162
163(define (filtered-port command input)
164 "Return an input port where data drained from INPUT is filtered through
165COMMAND (a list). In addition, return a list of PIDs that the caller must
166wait."
167 (let loop ((input input)
168 (pids '()))
169 (if (file-port? input)
170 (match (pipe)
171 ((in . out)
172 (match (primitive-fork)
173 (0
174 (close-port in)
175 (close-port (current-input-port))
176 (dup2 (fileno input) 0)
177 (close-port (current-output-port))
178 (dup2 (fileno out) 1)
179 (apply execl (car command) command))
180 (child
181 (close-port out)
182 (values in (cons child pids))))))
183
184 ;; INPUT is not a file port, so fork just for the sake of tunneling it
185 ;; through a file port.
186 (match (pipe)
187 ((in . out)
188 (match (primitive-fork)
189 (0
190 (dynamic-wind
191 (const #t)
192 (lambda ()
193 (close-port in)
194 (dump-port input out))
195 (lambda ()
196 (false-if-exception (close out))
197 (primitive-exit 0))))
198 (child
199 (close-port out)
200 (loop in (cons child pids)))))))))
201
202\f
de4c3f26
LC
203;;;
204;;; Nixpkgs.
205;;;
206
207(define %nixpkgs-directory
900f7267
LC
208 (make-parameter
209 ;; Capture the build-time value of $NIXPKGS.
a09ec3a5
LC
210 (or %nixpkgs
211 (and=> (getenv "NIXPKGS")
212 (lambda (val)
213 ;; Bail out when passed an empty string, otherwise
214 ;; `nix-instantiate' will sit there and attempt to read
215 ;; from its standard input.
216 (if (string=? val "")
217 #f
218 val))))))
de4c3f26 219
fbc93bed 220(define* (nixpkgs-derivation attribute #:optional (system (%current-system)))
de4c3f26 221 "Return the derivation path of ATTRIBUTE in Nixpkgs."
b86b0056 222 (let* ((p (open-pipe* OPEN_READ (or (getenv "NIX_INSTANTIATE")
00e219d1 223 %nix-instantiate)
fbc93bed
LC
224 "-A" attribute (%nixpkgs-directory)
225 "--argstr" "system" system))
de4c3f26
LC
226 (l (read-line p))
227 (s (close-pipe p)))
228 (and (zero? (status:exit-val s))
229 (not (eof-object? l))
230 l)))
231
ce5d658c
LC
232(define-syntax-rule (nixpkgs-derivation* attribute)
233 "Evaluate the given Nixpkgs derivation at compile-time."
234 (compile-time-value (nixpkgs-derivation attribute)))
235
de4c3f26
LC
236\f
237;;;
238;;; Miscellaneous.
239;;;
240
241(define (memoize proc)
242 "Return a memoizing version of PROC."
243 (let ((cache (make-hash-table)))
244 (lambda args
245 (let ((results (hash-ref cache args)))
246 (if results
247 (apply values results)
248 (let ((results (call-with-values (lambda ()
249 (apply proc args))
250 list)))
251 (hash-set! cache args results)
252 (apply values results)))))))
98090557 253
0af2c24e
LC
254(define (default-keyword-arguments args defaults)
255 "Return ARGS augmented with any keyword/value from DEFAULTS for
256keywords not already present in ARGS."
257 (let loop ((defaults defaults)
258 (args args))
259 (match defaults
260 ((kw value rest ...)
261 (loop rest
262 (if (assoc-ref kw args)
263 args
264 (cons* kw value args))))
265 (()
266 args))))
267
268(define-syntax substitute-keyword-arguments
269 (syntax-rules ()
270 "Return a new list of arguments where the value for keyword arg KW is
271replaced by EXP. EXP is evaluated in a context where VAR is boud to the
272previous value of the keyword argument."
273 ((_ original-args ((kw var) exp) ...)
274 (let loop ((args original-args)
275 (before '()))
276 (match args
277 ((kw var rest (... ...))
278 (loop rest (cons* exp kw before)))
279 ...
280 ((x rest (... ...))
281 (loop rest (cons x before)))
282 (()
283 (reverse before)))))))
284
98090557
LC
285(define (gnu-triplet->nix-system triplet)
286 "Return the Nix system type corresponding to TRIPLET, a GNU triplet as
287returned by `config.guess'."
288 (let ((triplet (cond ((string-match "^i[345]86-(.*)$" triplet)
289 =>
290 (lambda (m)
291 (string-append "i686-" (match:substring m 1))))
292 (else triplet))))
293 (cond ((string-match "^([^-]+)-([^-]+-)?linux-gnu.*" triplet)
294 =>
295 (lambda (m)
296 ;; Nix omits `-gnu' for GNU/Linux.
297 (string-append (match:substring m 1) "-linux")))
298 ((string-match "^([^-]+)-([^-]+-)?([[:alpha:]]+)([0-9]+\\.?)*$" triplet)
299 =>
300 (lambda (m)
301 ;; Nix strip the version number from names such as `gnu0.3',
302 ;; `darwin10.2.0', etc., and always strips the vendor part.
303 (string-append (match:substring m 1) "-"
304 (match:substring m 3))))
305 (else triplet))))
306
307(define %current-system
308 ;; System type as expected by Nix, usually ARCHITECTURE-KERNEL.
d8eea3d2
LC
309 ;; By default, this is equal to (gnu-triplet->nix-system %host-type).
310 (make-parameter %system))
ff352cfb 311
0d1e6ce4
MW
312(define version-compare
313 (let ((strverscmp
314 (let ((sym (or (dynamic-func "strverscmp" (dynamic-link))
315 (error "could not find `strverscmp' (from GNU libc)"))))
316 (pointer->procedure int sym (list '* '*)))))
317 (lambda (a b)
318 "Return '> when A denotes a newer version than B,
319'< when A denotes a older version than B,
320or '= when they denote equal versions."
321 (let ((result (strverscmp (string->pointer a) (string->pointer b))))
322 (cond ((positive? result) '>)
323 ((negative? result) '<)
324 (else '=))))))
325
326(define (version>? a b)
327 "Return #t when A denotes a newer version than B."
328 (eq? '> (version-compare a b)))
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
2bcfb9e0
LC
356(define (string-tokenize* string separator)
357 "Return the list of substrings of STRING separated by SEPARATOR. This is
358like `string-tokenize', but SEPARATOR is a string."
359 (define (index string what)
360 (let loop ((string string)
361 (offset 0))
362 (cond ((string-null? string)
363 #f)
364 ((string-prefix? what string)
365 offset)
366 (else
367 (loop (string-drop string 1) (+ 1 offset))))))
368
369 (define len
370 (string-length separator))
371
372 (let loop ((string string)
373 (result '()))
374 (cond ((index string separator)
375 =>
376 (lambda (offset)
377 (loop (string-drop string (+ offset len))
378 (cons (substring string 0 offset)
379 result))))
380 (else
381 (reverse (cons string result))))))
382
861693f3
LC
383(define (call-with-temporary-output-file proc)
384 "Call PROC with a name of a temporary file and open output port to that
385file; close the file and delete it when leaving the dynamic extent of this
386call."
387 (let* ((template (string-copy "guix-file.XXXXXX"))
388 (out (mkstemp! template)))
389 (dynamic-wind
390 (lambda ()
391 #t)
392 (lambda ()
393 (proc template out))
394 (lambda ()
395 (false-if-exception (close out))
396 (false-if-exception (delete-file template))))))
397
04fd96ca
LC
398(define fold2
399 (case-lambda
400 ((proc seed1 seed2 lst)
401 "Like `fold', but with a single list and two seeds."
402 (let loop ((result1 seed1)
403 (result2 seed2)
404 (lst lst))
405 (if (null? lst)
406 (values result1 result2)
407 (call-with-values
408 (lambda () (proc (car lst) result1 result2))
409 (lambda (result1 result2)
410 (loop result1 result2 (cdr lst)))))))
411 ((proc seed1 seed2 lst1 lst2)
412 "Like `fold', but with a two lists and two seeds."
413 (let loop ((result1 seed1)
414 (result2 seed2)
415 (lst1 lst1)
416 (lst2 lst2))
417 (if (or (null? lst1) (null? lst2))
418 (values result1 result2)
419 (call-with-values
420 (lambda () (proc (car lst1) (car lst2) result1 result2))
421 (lambda (result1 result2)
422 (fold2 proc result1 result2 (cdr lst1) (cdr lst2)))))))))
423
ff352cfb
LC
424\f
425;;;
426;;; Source location.
427;;;
428
429;; A source location.
430(define-record-type <location>
431 (make-location file line column)
432 location?
433 (file location-file) ; file name
434 (line location-line) ; 1-indexed line
435 (column location-column)) ; 0-indexed column
436
437(define location
438 (memoize
439 (lambda (file line column)
440 "Return the <location> object for the given FILE, LINE, and COLUMN."
441 (and line column file
442 (make-location file line column)))))
443
444(define (source-properties->location loc)
445 "Return a location object based on the info in LOC, an alist as returned
446by Guile's `source-properties', `frame-source', `current-source-location',
447etc."
448 (let ((file (assq-ref loc 'filename))
449 (line (assq-ref loc 'line))
450 (col (assq-ref loc 'column)))
5fe21fbe
LC
451 ;; In accordance with the GCS, start line and column numbers at 1. Note
452 ;; that unlike LINE and `port-column', COL is actually 1-indexed here...
453 (location file (and line (+ line 1)) col)))