1 ;;; GNU Guix --- Functional package management for GNU
2 ;;; Copyright © 2012, 2013, 2014 Ludovic Courtès <ludo@gnu.org>
3 ;;; Copyright © 2013 Mark H Weaver <mhw@netris.org>
5 ;;; This file is part of GNU Guix.
7 ;;; GNU Guix is free software; you can redistribute it and/or modify it
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.
12 ;;; GNU Guix is distributed in the hope that it will be useful, but
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.
17 ;;; You should have received a copy of the GNU General Public License
18 ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
20 (define-module (guix utils)
21 #:use-module (guix config)
22 #:use-module (srfi srfi-1)
23 #:use-module (srfi srfi-9)
24 #:use-module (srfi srfi-26)
25 #:use-module (srfi srfi-39)
26 #:use-module (srfi srfi-60)
27 #:use-module (rnrs bytevectors)
28 #:use-module ((rnrs io ports) #:select (put-bytevector))
29 #:use-module ((guix build utils) #:select (dump-port))
30 #:use-module (ice-9 vlist)
31 #:use-module (ice-9 format)
32 #:autoload (ice-9 popen) (open-pipe*)
33 #:autoload (ice-9 rdelim) (read-line)
34 #:use-module (ice-9 regex)
35 #:use-module (ice-9 match)
36 #:use-module (ice-9 format)
37 #:use-module (system foreign)
38 #:export (bytevector->base16-string
39 base16-string->bytevector
48 default-keyword-arguments
49 substitute-keyword-arguments
57 source-properties->location
59 gnu-triplet->nix-system
61 %current-target-system
65 package-name->name+version
67 string-replace-substring
70 call-with-temporary-output-file
71 with-atomic-file-output
77 ;;; Compile-time computations.
80 (define-syntax compile-time-value
82 "Evaluate the given expression at compile time. The expression must
83 evaluate to a simple datum."
85 (let-syntax ((v (lambda (s)
88 (_ #`'#,(datum->syntax s val)))))))
96 (define (bytevector->base16-string bv)
97 "Return the hexadecimal representation of BV's contents."
99 (bytevector-length bv))
101 (let-syntax ((base16-chars (lambda (s)
104 (let ((v (list->vector
105 (unfold (cut > <> 255)
107 (format #f "~2,'0x" n))
111 (define chars base16-chars)
115 (string-concatenate-reverse r)
117 (cons (vector-ref chars (bytevector-u8-ref bv i)) r))))))
119 (define base16-string->bytevector
120 (let ((chars->value (fold (lambda (i r)
121 (vhash-consv (string-ref (number->string i 16)
127 "Return the bytevector whose hexadecimal representation is string S."
129 (make-bytevector (quotient (string-length s) 2) 0))
131 (string-fold (lambda (chr i)
132 (let ((j (quotient i 2))
133 (v (and=> (vhash-assv chr chars->value) cdr)))
135 (if (zero? (logand i 1))
136 (bytevector-u8-set! bv j
137 (arithmetic-shift v 4))
138 (let ((w (bytevector-u8-ref bv j)))
139 (bytevector-u8-set! bv j (logior v w))))
140 (error "invalid hexadecimal character" chr)))
149 ;;; Filtering & pipes.
152 (define (filtered-port command input)
153 "Return an input port where data drained from INPUT is filtered through
154 COMMAND (a list). In addition, return a list of PIDs that the caller must
155 wait. When INPUT is a file port, it must be unbuffered; otherwise, any
156 buffered data is lost."
157 (let loop ((input input)
159 (if (file-port? input)
162 (match (primitive-fork)
165 (close-port (current-input-port))
166 (dup2 (fileno input) 0)
167 (close-port (current-output-port))
168 (dup2 (fileno out) 1)
169 (apply execl (car command) command))
172 (values in (cons child pids))))))
174 ;; INPUT is not a file port, so fork just for the sake of tunneling it
175 ;; through a file port.
178 (match (primitive-fork)
184 (dump-port input out))
186 (false-if-exception (close out))
187 (primitive-exit 0))))
190 (loop in (cons child pids)))))))))
197 (define %nixpkgs-directory
199 ;; Capture the build-time value of $NIXPKGS.
201 (and=> (getenv "NIXPKGS")
203 ;; Bail out when passed an empty string, otherwise
204 ;; `nix-instantiate' will sit there and attempt to read
205 ;; from its standard input.
206 (if (string=? val "")
210 (define* (nixpkgs-derivation attribute #:optional (system (%current-system)))
211 "Return the derivation path of ATTRIBUTE in Nixpkgs."
212 (let* ((p (open-pipe* OPEN_READ (or (getenv "NIX_INSTANTIATE")
214 "-A" attribute (%nixpkgs-directory)
215 "--argstr" "system" system))
218 (and (zero? (status:exit-val s))
219 (not (eof-object? l))
222 (define-syntax-rule (nixpkgs-derivation* attribute)
223 "Evaluate the given Nixpkgs derivation at compile-time."
224 (compile-time-value (nixpkgs-derivation attribute)))
228 ;;; Advisory file locking.
231 (define %struct-flock
232 ;; 'struct flock' from <fcntl.h>.
240 ;; On Linux-based systems, this is usually 7, but not always
241 ;; (exceptions include SPARC.) On GNU/Hurd, it's 9.
243 (cond ((string-contains %host-type "sparc") 9) ; sparc-*-linux-gnu
244 ((string-contains %host-type "linux") 7) ; *-linux-gnu
248 ;; Likewise: GNU/Hurd and SPARC use 8, while the others typically use 6.
250 (cond ((string-contains %host-type "sparc") 8) ; sparc-*-linux-gnu
251 ((string-contains %host-type "linux") 6) ; *-linux-gnu
255 ;; The F_RDLCK, F_WRLCK, and F_UNLCK constants.
257 (cond ((string-contains %host-type "sparc") #(1 2 3)) ; sparc-*-linux-gnu
258 ((string-contains %host-type "hppa") #(1 2 3)) ; hppa-*-linux-gnu
259 ((string-contains %host-type "linux") #(0 1 2)) ; *-linux-gnu
260 (else #(1 2 3))))) ; *-gnu*
262 (define %libc-errno-pointer
263 ;; Glibc's 'errno' pointer.
264 (let ((errno-loc (dynamic-func "__errno_location" (dynamic-link))))
266 (let ((proc (pointer->procedure '* errno-loc '())))
270 "Return the current errno."
271 ;; XXX: We assume that nothing changes 'errno' while we're doing all this.
272 ;; In particular, that means that no async must be running here.
273 (if %libc-errno-pointer
274 (let ((bv (pointer->bytevector %libc-errno-pointer (sizeof int))))
275 (bytevector-sint-ref bv 0 (native-endianness) (sizeof int)))
279 (let* ((ptr (dynamic-func "fcntl" (dynamic-link)))
280 (proc (pointer->procedure int ptr `(,int ,int *))))
281 (lambda* (fd-or-port operation #:key (wait? #t))
282 "Perform locking OPERATION on the file beneath FD-OR-PORT. OPERATION
283 must be a symbol, one of 'read-lock, 'write-lock, or 'unlock. When WAIT? is
284 true, block until the lock is acquired; otherwise, thrown an 'flock-error'
285 exception if it's already taken."
286 (define (operation->int op)
288 ((read-lock) (vector-ref F_xxLCK 0))
289 ((write-lock) (vector-ref F_xxLCK 1))
290 ((unlock) (vector-ref F_xxLCK 2))
291 (else (error "invalid fcntl-flock operation" op))))
294 (if (port? fd-or-port)
298 ;; XXX: 'fcntl' is a vararg function, but here we happily use the
299 ;; standard ABI; crossing fingers.
302 F_SETLKW ; lock & wait
303 F_SETLK) ; non-blocking attempt
304 (make-c-struct %struct-flock
305 (list (operation->int operation)
311 ;; Presumably we got EAGAIN or so.
312 (throw 'flock-error (errno)))))))
319 (define (memoize proc)
320 "Return a memoizing version of PROC."
321 (let ((cache (make-hash-table)))
323 (let ((results (hash-ref cache args)))
325 (apply values results)
326 (let ((results (call-with-values (lambda ()
329 (hash-set! cache args results)
330 (apply values results)))))))
332 (define (default-keyword-arguments args defaults)
333 "Return ARGS augmented with any keyword/value from DEFAULTS for
334 keywords not already present in ARGS."
335 (let loop ((defaults defaults)
340 (if (assoc-ref kw args)
342 (cons* kw value args))))
346 (define-syntax substitute-keyword-arguments
348 "Return a new list of arguments where the value for keyword arg KW is
349 replaced by EXP. EXP is evaluated in a context where VAR is boud to the
350 previous value of the keyword argument."
351 ((_ original-args ((kw var) exp) ...)
352 (let loop ((args original-args)
355 ((kw var rest (... ...))
356 (loop rest (cons* exp kw before)))
359 (loop rest (cons x before)))
361 (reverse before)))))))
363 (define (gnu-triplet->nix-system triplet)
364 "Return the Nix system type corresponding to TRIPLET, a GNU triplet as
365 returned by `config.guess'."
366 (let ((triplet (cond ((string-match "^i[345]86-(.*)$" triplet)
369 (string-append "i686-" (match:substring m 1))))
371 (cond ((string-match "^([^-]+)-([^-]+-)?linux-gnu.*" triplet)
374 ;; Nix omits `-gnu' for GNU/Linux.
375 (string-append (match:substring m 1) "-linux")))
376 ((string-match "^([^-]+)-([^-]+-)?([[:alpha:]]+)([0-9]+\\.?)*$" triplet)
379 ;; Nix strip the version number from names such as `gnu0.3',
380 ;; `darwin10.2.0', etc., and always strips the vendor part.
381 (string-append (match:substring m 1) "-"
382 (match:substring m 3))))
385 (define %current-system
386 ;; System type as expected by Nix, usually ARCHITECTURE-KERNEL.
387 ;; By default, this is equal to (gnu-triplet->nix-system %host-type).
388 (make-parameter %system))
390 (define %current-target-system
391 ;; Either #f or a GNU triplet representing the target system we are
392 ;; cross-building to.
395 (define version-compare
397 (let ((sym (or (dynamic-func "strverscmp" (dynamic-link))
398 (error "could not find `strverscmp' (from GNU libc)"))))
399 (pointer->procedure int sym (list '* '*)))))
401 "Return '> when A denotes a newer version than B,
402 '< when A denotes a older version than B,
403 or '= when they denote equal versions."
404 (let ((result (strverscmp (string->pointer a) (string->pointer b))))
405 (cond ((positive? result) '>)
406 ((negative? result) '<)
409 (define (version>? a b)
410 "Return #t when A denotes a newer version than B."
411 (eq? '> (version-compare a b)))
413 (define (guile-version>? str)
414 "Return #t if the running Guile version is greater than STR."
415 ;; Note: Using (version>? (version) "2.0.5") or similar doesn't work,
416 ;; because the result of (version) can have a prefix, like "2.0.5-deb1".
417 (version>? (string-append (major-version) "."
422 (define (package-name->name+version name)
423 "Given NAME, a package name like \"foo-0.9.1b\", return two values:
424 \"foo\" and \"0.9.1b\". When the version part is unavailable, NAME and
425 #f are returned. The first hyphen followed by a digit is considered to
426 introduce the version part."
427 ;; See also `DrvName' in Nix.
430 (cut char-set-contains? char-set:digit <>))
432 (let loop ((chars (string->list name))
437 ((#\- (? number? n) rest ...)
438 (values (list->string (reverse prefix))
439 (list->string (cons n rest))))
441 (loop tail (cons head prefix))))))
443 (define (file-extension file)
444 "Return the extension of FILE or #f if there is none."
445 (let ((dot (string-rindex file #\.)))
446 (and dot (substring file (+ 1 dot) (string-length file)))))
448 (define (file-sans-extension file)
449 "Return the substring of FILE without its extension, if any."
450 (let ((dot (string-rindex file #\.)))
452 (substring file 0 dot)
455 (define (string-tokenize* string separator)
456 "Return the list of substrings of STRING separated by SEPARATOR. This is
457 like `string-tokenize', but SEPARATOR is a string."
458 (define (index string what)
459 (let loop ((string string)
461 (cond ((string-null? string)
463 ((string-prefix? what string)
466 (loop (string-drop string 1) (+ 1 offset))))))
469 (string-length separator))
471 (let loop ((string string)
473 (cond ((index string separator)
476 (loop (string-drop string (+ offset len))
477 (cons (substring string 0 offset)
480 (reverse (cons string result))))))
482 (define* (string-replace-substring str substr replacement
485 (end (string-length str)))
486 "Replace all occurrences of SUBSTR in the START--END range of STR by
488 (match (string-length substr)
490 (error "string-replace-substring: empty substring"))
492 (let loop ((start start)
493 (pieces (list (substring str 0 start))))
494 (match (string-contains str substr start end)
496 (string-concatenate-reverse
497 (cons (substring str start) pieces)))
499 (loop (+ index substr-length)
501 (substring str start index)
504 (define (call-with-temporary-output-file proc)
505 "Call PROC with a name of a temporary file and open output port to that
506 file; close the file and delete it when leaving the dynamic extent of this
508 (let* ((template (string-copy "guix-file.XXXXXX"))
509 (out (mkstemp! template)))
516 (false-if-exception (close out))
517 (false-if-exception (delete-file template))))))
519 (define (with-atomic-file-output file proc)
520 "Call PROC with an output port for the file that is going to replace FILE.
521 Upon success, FILE is atomically replaced by what has been written to the
522 output port, and PROC's result is returned."
523 (let* ((template (string-append file ".XXXXXX"))
524 (out (mkstemp! template)))
525 (with-throw-handler #t
527 (let ((result (proc out)))
529 (rename-file template file)
532 (false-if-exception (delete-file template))))))
536 ((proc seed1 seed2 lst)
537 "Like `fold', but with a single list and two seeds."
538 (let loop ((result1 seed1)
542 (values result1 result2)
544 (lambda () (proc (car lst) result1 result2))
545 (lambda (result1 result2)
546 (loop result1 result2 (cdr lst)))))))
547 ((proc seed1 seed2 lst1 lst2)
548 "Like `fold', but with a two lists and two seeds."
549 (let loop ((result1 seed1)
553 (if (or (null? lst1) (null? lst2))
554 (values result1 result2)
556 (lambda () (proc (car lst1) (car lst2) result1 result2))
557 (lambda (result1 result2)
558 (fold2 proc result1 result2 (cdr lst1) (cdr lst2)))))))))
565 ;; A source location.
566 (define-record-type <location>
567 (make-location file line column)
569 (file location-file) ; file name
570 (line location-line) ; 1-indexed line
571 (column location-column)) ; 0-indexed column
575 (lambda (file line column)
576 "Return the <location> object for the given FILE, LINE, and COLUMN."
577 (and line column file
578 (make-location file line column)))))
580 (define (source-properties->location loc)
581 "Return a location object based on the info in LOC, an alist as returned
582 by Guile's `source-properties', `frame-source', `current-source-location',
584 (let ((file (assq-ref loc 'filename))
585 (line (assq-ref loc 'line))
586 (col (assq-ref loc 'column)))
587 ;; In accordance with the GCS, start line and column numbers at 1. Note
588 ;; that unlike LINE and `port-column', COL is actually 1-indexed here...
589 (location file (and line (+ line 1)) col)))