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>
4 ;;; Copyright © 2014 Eric Bavier <bavier@member.fsf.org>
5 ;;; Copyright © 2014 Ian Denhardt <ian@zenhack.net>
7 ;;; This file is part of GNU Guix.
9 ;;; GNU Guix is free software; you can redistribute it and/or modify it
10 ;;; under the terms of the GNU General Public License as published by
11 ;;; the Free Software Foundation; either version 3 of the License, or (at
12 ;;; your option) any later version.
14 ;;; GNU Guix is distributed in the hope that it will be useful, but
15 ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 ;;; GNU General Public License for more details.
19 ;;; You should have received a copy of the GNU General Public License
20 ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
22 (define-module (guix utils)
23 #:use-module (guix config)
24 #:use-module (srfi srfi-1)
25 #:use-module (srfi srfi-9)
26 #:use-module (srfi srfi-11)
27 #:use-module (srfi srfi-26)
28 #:use-module (srfi srfi-39)
29 #:use-module (srfi srfi-60)
30 #:use-module (rnrs bytevectors)
31 #:use-module ((rnrs io ports) #:select (put-bytevector))
32 #:use-module ((guix build utils) #:select (dump-port))
33 #:use-module ((guix build syscalls) #:select (errno))
34 #:use-module (ice-9 vlist)
35 #:use-module (ice-9 format)
36 #:autoload (ice-9 popen) (open-pipe*)
37 #:autoload (ice-9 rdelim) (read-line)
38 #:use-module (ice-9 regex)
39 #:use-module (ice-9 match)
40 #:use-module (ice-9 format)
41 #:use-module (system foreign)
42 #:export (bytevector->base16-string
43 base16-string->bytevector
52 strip-keyword-arguments
53 default-keyword-arguments
54 substitute-keyword-arguments
62 source-properties->location
64 gnu-triplet->nix-system
66 %current-target-system
72 package-name->name+version
74 string-replace-substring
77 call-with-temporary-output-file
78 with-atomic-file-output
86 call-with-decompressed-port
87 compressed-output-port
88 call-with-compressed-output-port))
92 ;;; Compile-time computations.
95 (define-syntax compile-time-value
97 "Evaluate the given expression at compile time. The expression must
98 evaluate to a simple datum."
100 (let-syntax ((v (lambda (s)
103 (_ #`'#,(datum->syntax s val)))))))
111 (define (bytevector->base16-string bv)
112 "Return the hexadecimal representation of BV's contents."
114 (bytevector-length bv))
116 (let-syntax ((base16-chars (lambda (s)
119 (let ((v (list->vector
120 (unfold (cut > <> 255)
122 (format #f "~2,'0x" n))
126 (define chars base16-chars)
130 (string-concatenate r)
133 (cons (vector-ref chars (bytevector-u8-ref bv i)) r)))))))
135 (define base16-string->bytevector
136 (let ((chars->value (fold (lambda (i r)
137 (vhash-consv (string-ref (number->string i 16)
143 "Return the bytevector whose hexadecimal representation is string S."
145 (make-bytevector (quotient (string-length s) 2) 0))
147 (string-fold (lambda (chr i)
148 (let ((j (quotient i 2))
149 (v (and=> (vhash-assv chr chars->value) cdr)))
151 (if (zero? (logand i 1))
152 (bytevector-u8-set! bv j
153 (arithmetic-shift v 4))
154 (let ((w (bytevector-u8-ref bv j)))
155 (bytevector-u8-set! bv j (logior v w))))
156 (error "invalid hexadecimal character" chr)))
165 ;;; Filtering & pipes.
168 (define (filtered-port command input)
169 "Return an input port where data drained from INPUT is filtered through
170 COMMAND (a list). In addition, return a list of PIDs that the caller must
171 wait. When INPUT is a file port, it must be unbuffered; otherwise, any
172 buffered data is lost."
173 (let loop ((input input)
175 (if (file-port? input)
178 (match (primitive-fork)
184 (close-port (current-input-port))
185 (dup2 (fileno input) 0)
186 (close-port (current-output-port))
187 (dup2 (fileno out) 1)
190 (apply execl (car command) command))
192 (format (current-error-port)
193 "filtered-port: failed to execute '~{~a ~}': ~a~%"
194 command (strerror (system-error-errno args))))))
196 (primitive-_exit 1))))
199 (values in (cons child pids))))))
201 ;; INPUT is not a file port, so fork just for the sake of tunneling it
202 ;; through a file port.
205 (match (primitive-fork)
211 (dump-port input out))
213 (false-if-exception (close out))
214 (primitive-_exit 0))))
217 (loop in (cons child pids)))))))))
219 (define (decompressed-port compression input)
220 "Return an input port where INPUT is decompressed according to COMPRESSION,
221 a symbol such as 'xz."
223 ((or #f 'none) (values input '()))
224 ('bzip2 (filtered-port `(,%bzip2 "-dc") input))
225 ('xz (filtered-port `(,%xz "-dc") input))
226 ('gzip (filtered-port `(,%gzip "-dc") input))
227 (else (error "unsupported compression scheme" compression))))
229 (define (compressed-port compression input)
230 "Return an input port where INPUT is decompressed according to COMPRESSION,
231 a symbol such as 'xz."
233 ((or #f 'none) (values input '()))
234 ('bzip2 (filtered-port `(,%bzip2 "-c") input))
235 ('xz (filtered-port `(,%xz "-c") input))
236 ('gzip (filtered-port `(,%gzip "-c") input))
237 (else (error "unsupported compression scheme" compression))))
239 (define (call-with-decompressed-port compression port proc)
240 "Call PROC with a wrapper around PORT, a file port, that decompresses data
241 read from PORT according to COMPRESSION, a symbol such as 'xz."
242 (let-values (((decompressed pids)
243 (decompressed-port compression port)))
249 (close-port decompressed)
250 (unless (every (compose zero? cdr waitpid) pids)
251 (error "decompressed-port failure" pids))))))
253 (define (filtered-output-port command output)
254 "Return an output port. Data written to that port is filtered through
255 COMMAND and written to OUTPUT, an output file port. In addition, return a
256 list of PIDs to wait for. OUTPUT must be unbuffered; otherwise, any buffered
260 (match (primitive-fork)
266 (close-port (current-input-port))
268 (close-port (current-output-port))
269 (dup2 (fileno output) 1)
272 (apply execl (car command) command))
274 (format (current-error-port)
275 "filtered-output-port: failed to execute '~{~a ~}': ~a~%"
276 command (strerror (system-error-errno args))))))
278 (primitive-_exit 1))))
281 (values out (list child)))))))
283 (define (compressed-output-port compression output)
284 "Return an output port whose input is compressed according to COMPRESSION,
285 a symbol such as 'xz, and then written to OUTPUT. In addition return a list
286 of PIDs to wait for."
288 ((or #f 'none) (values output '()))
289 ('bzip2 (filtered-output-port `(,%bzip2 "-c") output))
290 ('xz (filtered-output-port `(,%xz "-c") output))
291 ('gzip (filtered-output-port `(,%gzip "-c") output))
292 (else (error "unsupported compression scheme" compression))))
294 (define (call-with-compressed-output-port compression port proc)
295 "Call PROC with a wrapper around PORT, a file port, that compresses data
296 that goes to PORT according to COMPRESSION, a symbol such as 'xz."
297 (let-values (((compressed pids)
298 (compressed-output-port compression port)))
304 (close-port compressed)
305 (unless (every (compose zero? cdr waitpid) pids)
306 (error "compressed-output-port failure" pids))))))
313 (define %nixpkgs-directory
315 ;; Capture the build-time value of $NIXPKGS.
317 (and=> (getenv "NIXPKGS")
319 ;; Bail out when passed an empty string, otherwise
320 ;; `nix-instantiate' will sit there and attempt to read
321 ;; from its standard input.
322 (if (string=? val "")
326 (define* (nixpkgs-derivation attribute #:optional (system (%current-system)))
327 "Return the derivation path of ATTRIBUTE in Nixpkgs."
328 (let* ((p (open-pipe* OPEN_READ (or (getenv "NIX_INSTANTIATE")
330 "-A" attribute (%nixpkgs-directory)
331 "--argstr" "system" system))
334 (and (zero? (status:exit-val s))
335 (not (eof-object? l))
338 (define-syntax-rule (nixpkgs-derivation* attribute)
339 "Evaluate the given Nixpkgs derivation at compile-time."
340 (compile-time-value (nixpkgs-derivation attribute)))
344 ;;; Advisory file locking.
347 (define %struct-flock
348 ;; 'struct flock' from <fcntl.h>.
356 ;; On Linux-based systems, this is usually 7, but not always
357 ;; (exceptions include SPARC.) On GNU/Hurd, it's 9.
359 (cond ((string-contains %host-type "sparc") 9) ; sparc-*-linux-gnu
360 ((string-contains %host-type "linux") 7) ; *-linux-gnu
364 ;; Likewise: GNU/Hurd and SPARC use 8, while the others typically use 6.
366 (cond ((string-contains %host-type "sparc") 8) ; sparc-*-linux-gnu
367 ((string-contains %host-type "linux") 6) ; *-linux-gnu
371 ;; The F_RDLCK, F_WRLCK, and F_UNLCK constants.
373 (cond ((string-contains %host-type "sparc") #(1 2 3)) ; sparc-*-linux-gnu
374 ((string-contains %host-type "hppa") #(1 2 3)) ; hppa-*-linux-gnu
375 ((string-contains %host-type "linux") #(0 1 2)) ; *-linux-gnu
376 (else #(1 2 3))))) ; *-gnu*
379 (let* ((ptr (dynamic-func "fcntl" (dynamic-link)))
380 (proc (pointer->procedure int ptr `(,int ,int *))))
381 (lambda* (fd-or-port operation #:key (wait? #t))
382 "Perform locking OPERATION on the file beneath FD-OR-PORT. OPERATION
383 must be a symbol, one of 'read-lock, 'write-lock, or 'unlock. When WAIT? is
384 true, block until the lock is acquired; otherwise, thrown an 'flock-error'
385 exception if it's already taken."
386 (define (operation->int op)
388 ((read-lock) (vector-ref F_xxLCK 0))
389 ((write-lock) (vector-ref F_xxLCK 1))
390 ((unlock) (vector-ref F_xxLCK 2))
391 (else (error "invalid fcntl-flock operation" op))))
394 (if (port? fd-or-port)
398 ;; XXX: 'fcntl' is a vararg function, but here we happily use the
399 ;; standard ABI; crossing fingers.
402 F_SETLKW ; lock & wait
403 F_SETLK) ; non-blocking attempt
404 (make-c-struct %struct-flock
405 (list (operation->int operation)
411 ;; Presumably we got EAGAIN or so.
412 (throw 'flock-error (errno)))))))
419 (define (memoize proc)
420 "Return a memoizing version of PROC."
421 (let ((cache (make-hash-table)))
423 (let ((results (hash-ref cache args)))
425 (apply values results)
426 (let ((results (call-with-values (lambda ()
429 (hash-set! cache args results)
430 (apply values results)))))))
432 (define (strip-keyword-arguments keywords args)
433 "Remove all of the keyword arguments listed in KEYWORDS from ARGS."
434 (let loop ((args args)
439 (((? keyword? kw) arg . rest)
441 (if (memq kw keywords)
443 (cons* arg kw result))))
445 (loop tail (cons head result))))))
447 (define (default-keyword-arguments args defaults)
448 "Return ARGS augmented with any keyword/value from DEFAULTS for
449 keywords not already present in ARGS."
450 (let loop ((defaults defaults)
455 (if (assoc-ref kw args)
457 (cons* kw value args))))
461 (define-syntax substitute-keyword-arguments
463 "Return a new list of arguments where the value for keyword arg KW is
464 replaced by EXP. EXP is evaluated in a context where VAR is boud to the
465 previous value of the keyword argument."
466 ((_ original-args ((kw var) exp) ...)
467 (let loop ((args original-args)
470 ((kw var rest (... ...))
471 (loop rest (cons* exp kw before)))
474 (loop rest (cons x before)))
476 (reverse before)))))))
478 (define (gnu-triplet->nix-system triplet)
479 "Return the Nix system type corresponding to TRIPLET, a GNU triplet as
480 returned by `config.guess'."
481 (let ((triplet (cond ((string-match "^i[345]86-(.*)$" triplet)
484 (string-append "i686-" (match:substring m 1))))
486 (cond ((string-match "^([^-]+)-([^-]+-)?linux-gnu.*" triplet)
489 ;; Nix omits `-gnu' for GNU/Linux.
490 (string-append (match:substring m 1) "-linux")))
491 ((string-match "^([^-]+)-([^-]+-)?([[:alpha:]]+)([0-9]+\\.?)*$" triplet)
494 ;; Nix strip the version number from names such as `gnu0.3',
495 ;; `darwin10.2.0', etc., and always strips the vendor part.
496 (string-append (match:substring m 1) "-"
497 (match:substring m 3))))
500 (define %current-system
501 ;; System type as expected by Nix, usually ARCHITECTURE-KERNEL.
502 ;; By default, this is equal to (gnu-triplet->nix-system %host-type).
503 (make-parameter %system))
505 (define %current-target-system
506 ;; Either #f or a GNU triplet representing the target system we are
507 ;; cross-building to.
510 (define version-compare
512 (let ((sym (or (dynamic-func "strverscmp" (dynamic-link))
513 (error "could not find `strverscmp' (from GNU libc)"))))
514 (pointer->procedure int sym (list '* '*)))))
516 "Return '> when A denotes a newer version than B,
517 '< when A denotes a older version than B,
518 or '= when they denote equal versions."
519 (let ((result (strverscmp (string->pointer a) (string->pointer b))))
520 (cond ((positive? result) '>)
521 ((negative? result) '<)
524 (define (version-prefix version-string num-parts)
525 "Truncate version-string to the first num-parts components of the version.
526 For example, (version-prefix \"2.1.47.4.23\" 3) returns \"2.1.47\""
527 (string-join (take (string-split version-string #\.) num-parts) "."))
530 (define (version-major+minor version-string)
531 "Return \"<major>.<minor>\", where major and minor are the major and
532 minor version numbers from version-string."
533 (version-prefix version-string 2))
535 (define (version>? a b)
536 "Return #t when A denotes a newer version than B."
537 (eq? '> (version-compare a b)))
539 (define (guile-version>? str)
540 "Return #t if the running Guile version is greater than STR."
541 ;; Note: Using (version>? (version) "2.0.5") or similar doesn't work,
542 ;; because the result of (version) can have a prefix, like "2.0.5-deb1".
543 (version>? (string-append (major-version) "."
548 (define (package-name->name+version name)
549 "Given NAME, a package name like \"foo-0.9.1b\", return two values:
550 \"foo\" and \"0.9.1b\". When the version part is unavailable, NAME and
551 #f are returned. The first hyphen followed by a digit is considered to
552 introduce the version part."
553 ;; See also `DrvName' in Nix.
556 (cut char-set-contains? char-set:digit <>))
558 (let loop ((chars (string->list name))
563 ((#\- (? number? n) rest ...)
564 (values (list->string (reverse prefix))
565 (list->string (cons n rest))))
567 (loop tail (cons head prefix))))))
569 (define (file-extension file)
570 "Return the extension of FILE or #f if there is none."
571 (let ((dot (string-rindex file #\.)))
572 (and dot (substring file (+ 1 dot) (string-length file)))))
574 (define (file-sans-extension file)
575 "Return the substring of FILE without its extension, if any."
576 (let ((dot (string-rindex file #\.)))
578 (substring file 0 dot)
581 (define (string-tokenize* string separator)
582 "Return the list of substrings of STRING separated by SEPARATOR. This is
583 like `string-tokenize', but SEPARATOR is a string."
584 (define (index string what)
585 (let loop ((string string)
587 (cond ((string-null? string)
589 ((string-prefix? what string)
592 (loop (string-drop string 1) (+ 1 offset))))))
595 (string-length separator))
597 (let loop ((string string)
599 (cond ((index string separator)
602 (loop (string-drop string (+ offset len))
603 (cons (substring string 0 offset)
606 (reverse (cons string result))))))
608 (define* (string-replace-substring str substr replacement
611 (end (string-length str)))
612 "Replace all occurrences of SUBSTR in the START--END range of STR by
614 (match (string-length substr)
616 (error "string-replace-substring: empty substring"))
618 (let loop ((start start)
619 (pieces (list (substring str 0 start))))
620 (match (string-contains str substr start end)
622 (string-concatenate-reverse
623 (cons (substring str start) pieces)))
625 (loop (+ index substr-length)
627 (substring str start index)
630 (define (call-with-temporary-output-file proc)
631 "Call PROC with a name of a temporary file and open output port to that
632 file; close the file and delete it when leaving the dynamic extent of this
634 (let* ((directory (or (getenv "TMPDIR") "/tmp"))
635 (template (string-append directory "/guix-file.XXXXXX"))
636 (out (mkstemp! template)))
643 (false-if-exception (close out))
644 (false-if-exception (delete-file template))))))
646 (define (with-atomic-file-output file proc)
647 "Call PROC with an output port for the file that is going to replace FILE.
648 Upon success, FILE is atomically replaced by what has been written to the
649 output port, and PROC's result is returned."
650 (let* ((template (string-append file ".XXXXXX"))
651 (out (mkstemp! template)))
652 (with-throw-handler #t
654 (let ((result (proc out)))
656 (rename-file template file)
659 (false-if-exception (delete-file template))))))
663 ((proc seed1 seed2 lst)
664 "Like `fold', but with a single list and two seeds."
665 (let loop ((result1 seed1)
669 (values result1 result2)
671 (lambda () (proc (car lst) result1 result2))
672 (lambda (result1 result2)
673 (loop result1 result2 (cdr lst)))))))
674 ((proc seed1 seed2 lst1 lst2)
675 "Like `fold', but with a two lists and two seeds."
676 (let loop ((result1 seed1)
680 (if (or (null? lst1) (null? lst2))
681 (values result1 result2)
683 (lambda () (proc (car lst1) (car lst2) result1 result2))
684 (lambda (result1 result2)
685 (fold2 proc result1 result2 (cdr lst1) (cdr lst2)))))))))
687 (define (fold-tree proc init children roots)
688 "Call (PROC NODE RESULT) for each node in the tree that is reachable from
689 ROOTS, using INIT as the initial value of RESULT. The order in which nodes
690 are traversed is not specified, however, each node is visited only once, based
691 on an eq? check. Children of a node to be visited are generated by
692 calling (CHILDREN NODE), the result of which should be a list of nodes that
693 are connected to NODE in the tree, or '() or #f if NODE is a leaf node."
694 (let loop ((result init)
700 (if (not (vhash-assq head seen))
701 (loop (proc head result)
702 (vhash-consq head #t seen)
703 (match (children head)
705 (children (append tail children))))
706 (loop result seen tail))))))
708 (define (fold-tree-leaves proc init children roots)
709 "Like fold-tree, but call (PROC NODE RESULT) only for leaf nodes."
711 (lambda (node result)
712 (match (children node)
713 ((or () #f) (proc node result))
715 init children roots))
722 ;; A source location.
723 (define-record-type <location>
724 (make-location file line column)
726 (file location-file) ; file name
727 (line location-line) ; 1-indexed line
728 (column location-column)) ; 0-indexed column
732 (lambda (file line column)
733 "Return the <location> object for the given FILE, LINE, and COLUMN."
734 (and line column file
735 (make-location file line column)))))
737 (define (source-properties->location loc)
738 "Return a location object based on the info in LOC, an alist as returned
739 by Guile's `source-properties', `frame-source', `current-source-location',
741 (let ((file (assq-ref loc 'filename))
742 (line (assq-ref loc 'line))
743 (col (assq-ref loc 'column)))
744 ;; In accordance with the GCS, start line and column numbers at 1. Note
745 ;; that unlike LINE and `port-column', COL is actually 1-indexed here...
746 (location file (and line (+ line 1)) col)))