gnu: samba: Update to 4.3.8.
[jackhill/guix/guix.git] / guix / utils.scm
CommitLineData
233e7676 1;;; GNU Guix --- Functional package management for GNU
6071122b 2;;; Copyright © 2012, 2013, 2014, 2015, 2016 Ludovic Courtès <ludo@gnu.org>
3f00ff8b 3;;; Copyright © 2013, 2014, 2015 Mark H Weaver <mhw@netris.org>
516e3b6f 4;;; Copyright © 2014 Eric Bavier <bavier@member.fsf.org>
29a7c98a 5;;; Copyright © 2014 Ian Denhardt <ian@zenhack.net>
1b846da8 6;;; Copyright © 2016 Mathieu Lirzin <mthl@gnu.org>
bbd00d20 7;;; Copyright © 2015 David Thompson <davet@gnu.org>
e3deeebb 8;;;
233e7676 9;;; This file is part of GNU Guix.
e3deeebb 10;;;
233e7676 11;;; GNU Guix is free software; you can redistribute it and/or modify it
e3deeebb
LC
12;;; under the terms of the GNU General Public License as published by
13;;; the Free Software Foundation; either version 3 of the License, or (at
14;;; your option) any later version.
15;;;
233e7676 16;;; GNU Guix is distributed in the hope that it will be useful, but
e3deeebb
LC
17;;; WITHOUT ANY WARRANTY; without even the implied warranty of
18;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19;;; GNU General Public License for more details.
20;;;
21;;; You should have received a copy of the GNU General Public License
233e7676 22;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
e3deeebb
LC
23
24(define-module (guix utils)
00e219d1 25 #:use-module (guix config)
38b3122a 26 #:use-module (srfi srfi-1)
72d86963 27 #:use-module (srfi srfi-9)
01ac19dc 28 #:use-module (srfi srfi-11)
38b3122a 29 #:use-module (srfi srfi-26)
de4c3f26 30 #:use-module (srfi srfi-39)
e3deeebb
LC
31 #:use-module (srfi srfi-60)
32 #:use-module (rnrs bytevectors)
c8be6f0d
FB
33 #:use-module (rnrs io ports)
34 #:use-module ((rnrs bytevectors) #:select (bytevector-u8-set!))
1b846da8 35 #:use-module ((guix build utils) #:select (dump-port))
db6e5e2b 36 #:use-module ((guix build syscalls) #:select (errno mkdtemp!))
c8369cac 37 #:use-module (ice-9 vlist)
38b3122a 38 #:use-module (ice-9 format)
de4c3f26
LC
39 #:autoload (ice-9 popen) (open-pipe*)
40 #:autoload (ice-9 rdelim) (read-line)
98090557 41 #:use-module (ice-9 regex)
72d86963 42 #:use-module (ice-9 match)
8ef3401f 43 #:use-module (ice-9 format)
2cd5c038 44 #:use-module (system foreign)
ddc29a78 45 #:export (bytevector->base16-string
6d800a80 46 base16-string->bytevector
de4c3f26 47
900f7267 48 compile-time-value
2cd5c038 49 fcntl-flock
98090557 50 memoize
5e110382 51 strip-keyword-arguments
0af2c24e
LC
52 default-keyword-arguments
53 substitute-keyword-arguments
6071122b 54 ensure-keyword-arguments
ff352cfb 55
64fc89b6 56 <location>
ff352cfb
LC
57 location
58 location?
59 location-file
60 location-line
61 location-column
62 source-properties->location
63
8eb80484 64 nix-system->gnu-triplet
98090557 65 gnu-triplet->nix-system
9b48fb88 66 %current-system
9c1edabd 67 %current-target-system
1b846da8 68 package-name->name+version
0d1e6ce4
MW
69 version-compare
70 version>?
cde1e967 71 version>=?
29a7c98a
ID
72 version-prefix
73 version-major+minor
7db3ff4a 74 guile-version>?
56b943de 75 string-replace-substring
16eb115e 76 arguments-from-environment-variable
0fdd3bea 77 file-extension
ac10e0e1 78 file-sans-extension
3bb168b0 79 switch-symlinks
861693f3 80 call-with-temporary-output-file
db6e5e2b 81 call-with-temporary-directory
04d4c8a4 82 with-atomic-file-output
e0fbbc88 83 fold2
516e3b6f
EB
84 fold-tree
85 fold-tree-leaves
bbd00d20 86 split
739ab68b 87 cache-directory
d50cb56d 88 readlink*
7a8024a3
LC
89
90 filtered-port
91 compressed-port
80dea563 92 decompressed-port
01ac19dc
LC
93 call-with-decompressed-port
94 compressed-output-port
c8be6f0d
FB
95 call-with-compressed-output-port
96 canonical-newline-port))
e3deeebb 97
38b3122a 98\f
dba6b34b
LC
99;;;
100;;; Compile-time computations.
101;;;
102
103(define-syntax compile-time-value
104 (syntax-rules ()
105 "Evaluate the given expression at compile time. The expression must
106evaluate to a simple datum."
107 ((_ exp)
108 (let-syntax ((v (lambda (s)
109 (let ((val exp))
110 (syntax-case s ()
111 (_ #`'#,(datum->syntax s val)))))))
112 v))))
113
114\f
38b3122a
LC
115;;;
116;;; Base 16.
117;;;
118
119(define (bytevector->base16-string bv)
120 "Return the hexadecimal representation of BV's contents."
121 (define len
122 (bytevector-length bv))
123
124 (let-syntax ((base16-chars (lambda (s)
125 (syntax-case s ()
126 (_
127 (let ((v (list->vector
128 (unfold (cut > <> 255)
129 (lambda (n)
130 (format #f "~2,'0x" n))
131 1+
132 0))))
133 v))))))
134 (define chars base16-chars)
998fb8fa 135 (let loop ((i len)
38b3122a 136 (r '()))
998fb8fa
LC
137 (if (zero? i)
138 (string-concatenate r)
139 (let ((i (- i 1)))
140 (loop i
141 (cons (vector-ref chars (bytevector-u8-ref bv i)) r)))))))
38b3122a 142
6d800a80
LC
143(define base16-string->bytevector
144 (let ((chars->value (fold (lambda (i r)
145 (vhash-consv (string-ref (number->string i 16)
146 0)
147 i r))
148 vlist-null
149 (iota 16))))
150 (lambda (s)
151 "Return the bytevector whose hexadecimal representation is string S."
152 (define bv
153 (make-bytevector (quotient (string-length s) 2) 0))
154
155 (string-fold (lambda (chr i)
156 (let ((j (quotient i 2))
157 (v (and=> (vhash-assv chr chars->value) cdr)))
158 (if v
159 (if (zero? (logand i 1))
160 (bytevector-u8-set! bv j
161 (arithmetic-shift v 4))
162 (let ((w (bytevector-u8-ref bv j)))
163 (bytevector-u8-set! bv j (logior v w))))
164 (error "invalid hexadecimal character" chr)))
165 (+ i 1))
166 0
167 s)
168 bv)))
169
de4c3f26
LC
170
171\f
e0fbbc88
LC
172;;;
173;;; Filtering & pipes.
174;;;
175
176(define (filtered-port command input)
177 "Return an input port where data drained from INPUT is filtered through
178COMMAND (a list). In addition, return a list of PIDs that the caller must
101d9f3f
LC
179wait. When INPUT is a file port, it must be unbuffered; otherwise, any
180buffered data is lost."
e0fbbc88 181 (let loop ((input input)
443eb4e9 182 (pids '()))
e0fbbc88
LC
183 (if (file-port? input)
184 (match (pipe)
185 ((in . out)
186 (match (primitive-fork)
187 (0
443eb4e9
LC
188 (dynamic-wind
189 (const #f)
190 (lambda ()
191 (close-port in)
192 (close-port (current-input-port))
193 (dup2 (fileno input) 0)
194 (close-port (current-output-port))
195 (dup2 (fileno out) 1)
196 (catch 'system-error
197 (lambda ()
198 (apply execl (car command) command))
199 (lambda args
200 (format (current-error-port)
201 "filtered-port: failed to execute '~{~a ~}': ~a~%"
202 command (strerror (system-error-errno args))))))
203 (lambda ()
204 (primitive-_exit 1))))
e0fbbc88
LC
205 (child
206 (close-port out)
207 (values in (cons child pids))))))
208
209 ;; INPUT is not a file port, so fork just for the sake of tunneling it
210 ;; through a file port.
211 (match (pipe)
212 ((in . out)
213 (match (primitive-fork)
214 (0
215 (dynamic-wind
216 (const #t)
217 (lambda ()
218 (close-port in)
219 (dump-port input out))
220 (lambda ()
221 (false-if-exception (close out))
443eb4e9 222 (primitive-_exit 0))))
e0fbbc88
LC
223 (child
224 (close-port out)
225 (loop in (cons child pids)))))))))
226
7a8024a3
LC
227(define (decompressed-port compression input)
228 "Return an input port where INPUT is decompressed according to COMPRESSION,
229a symbol such as 'xz."
230 (match compression
231 ((or #f 'none) (values input '()))
232 ('bzip2 (filtered-port `(,%bzip2 "-dc") input))
233 ('xz (filtered-port `(,%xz "-dc") input))
234 ('gzip (filtered-port `(,%gzip "-dc") input))
235 (else (error "unsupported compression scheme" compression))))
236
237(define (compressed-port compression input)
238 "Return an input port where INPUT is decompressed according to COMPRESSION,
239a symbol such as 'xz."
240 (match compression
241 ((or #f 'none) (values input '()))
242 ('bzip2 (filtered-port `(,%bzip2 "-c") input))
243 ('xz (filtered-port `(,%xz "-c") input))
244 ('gzip (filtered-port `(,%gzip "-c") input))
245 (else (error "unsupported compression scheme" compression))))
246
01ac19dc
LC
247(define (call-with-decompressed-port compression port proc)
248 "Call PROC with a wrapper around PORT, a file port, that decompresses data
30ce8012 249read from PORT according to COMPRESSION, a symbol such as 'xz."
01ac19dc
LC
250 (let-values (((decompressed pids)
251 (decompressed-port compression port)))
252 (dynamic-wind
253 (const #f)
254 (lambda ()
01ac19dc
LC
255 (proc decompressed))
256 (lambda ()
257 (close-port decompressed)
258 (unless (every (compose zero? cdr waitpid) pids)
259 (error "decompressed-port failure" pids))))))
260
80dea563
LC
261(define (filtered-output-port command output)
262 "Return an output port. Data written to that port is filtered through
263COMMAND and written to OUTPUT, an output file port. In addition, return a
264list of PIDs to wait for. OUTPUT must be unbuffered; otherwise, any buffered
265data is lost."
266 (match (pipe)
267 ((in . out)
268 (match (primitive-fork)
269 (0
270 (dynamic-wind
271 (const #f)
272 (lambda ()
273 (close-port out)
274 (close-port (current-input-port))
275 (dup2 (fileno in) 0)
276 (close-port (current-output-port))
277 (dup2 (fileno output) 1)
278 (catch 'system-error
279 (lambda ()
280 (apply execl (car command) command))
281 (lambda args
282 (format (current-error-port)
283 "filtered-output-port: failed to execute '~{~a ~}': ~a~%"
284 command (strerror (system-error-errno args))))))
285 (lambda ()
286 (primitive-_exit 1))))
287 (child
288 (close-port in)
289 (values out (list child)))))))
290
62d2b571
LC
291(define* (compressed-output-port compression output
292 #:key (options '()))
80dea563
LC
293 "Return an output port whose input is compressed according to COMPRESSION,
294a symbol such as 'xz, and then written to OUTPUT. In addition return a list
62d2b571
LC
295of PIDs to wait for. OPTIONS is a list of strings passed to the compression
296program--e.g., '(\"--fast\")."
80dea563
LC
297 (match compression
298 ((or #f 'none) (values output '()))
62d2b571
LC
299 ('bzip2 (filtered-output-port `(,%bzip2 "-c" ,@options) output))
300 ('xz (filtered-output-port `(,%xz "-c" ,@options) output))
301 ('gzip (filtered-output-port `(,%gzip "-c" ,@options) output))
80dea563
LC
302 (else (error "unsupported compression scheme" compression))))
303
62d2b571
LC
304(define* (call-with-compressed-output-port compression port proc
305 #:key (options '()))
01ac19dc 306 "Call PROC with a wrapper around PORT, a file port, that compresses data
62d2b571
LC
307that goes to PORT according to COMPRESSION, a symbol such as 'xz. OPTIONS is
308a list of command-line arguments passed to the compression program."
01ac19dc 309 (let-values (((compressed pids)
62d2b571
LC
310 (compressed-output-port compression port
311 #:options options)))
01ac19dc
LC
312 (dynamic-wind
313 (const #f)
314 (lambda ()
01ac19dc
LC
315 (proc compressed))
316 (lambda ()
317 (close-port compressed)
318 (unless (every (compose zero? cdr waitpid) pids)
319 (error "compressed-output-port failure" pids))))))
320
e0fbbc88 321\f
2cd5c038
LC
322;;;
323;;; Advisory file locking.
324;;;
325
326(define %struct-flock
327 ;; 'struct flock' from <fcntl.h>.
328 (list short ; l_type
329 short ; l_whence
330 size_t ; l_start
331 size_t ; l_len
332 int)) ; l_pid
333
334(define F_SETLKW
335 ;; On Linux-based systems, this is usually 7, but not always
336 ;; (exceptions include SPARC.) On GNU/Hurd, it's 9.
337 (compile-time-value
338 (cond ((string-contains %host-type "sparc") 9) ; sparc-*-linux-gnu
339 ((string-contains %host-type "linux") 7) ; *-linux-gnu
340 (else 9)))) ; *-gnu*
341
c7445833
LC
342(define F_SETLK
343 ;; Likewise: GNU/Hurd and SPARC use 8, while the others typically use 6.
344 (compile-time-value
345 (cond ((string-contains %host-type "sparc") 8) ; sparc-*-linux-gnu
346 ((string-contains %host-type "linux") 6) ; *-linux-gnu
347 (else 8)))) ; *-gnu*
348
2cd5c038
LC
349(define F_xxLCK
350 ;; The F_RDLCK, F_WRLCK, and F_UNLCK constants.
351 (compile-time-value
352 (cond ((string-contains %host-type "sparc") #(1 2 3)) ; sparc-*-linux-gnu
353 ((string-contains %host-type "hppa") #(1 2 3)) ; hppa-*-linux-gnu
354 ((string-contains %host-type "linux") #(0 1 2)) ; *-linux-gnu
355 (else #(1 2 3))))) ; *-gnu*
356
357(define fcntl-flock
358 (let* ((ptr (dynamic-func "fcntl" (dynamic-link)))
359 (proc (pointer->procedure int ptr `(,int ,int *))))
c7445833 360 (lambda* (fd-or-port operation #:key (wait? #t))
2cd5c038 361 "Perform locking OPERATION on the file beneath FD-OR-PORT. OPERATION
c7445833
LC
362must be a symbol, one of 'read-lock, 'write-lock, or 'unlock. When WAIT? is
363true, block until the lock is acquired; otherwise, thrown an 'flock-error'
364exception if it's already taken."
2cd5c038
LC
365 (define (operation->int op)
366 (case op
367 ((read-lock) (vector-ref F_xxLCK 0))
368 ((write-lock) (vector-ref F_xxLCK 1))
369 ((unlock) (vector-ref F_xxLCK 2))
370 (else (error "invalid fcntl-flock operation" op))))
371
372 (define fd
373 (if (port? fd-or-port)
374 (fileno fd-or-port)
375 fd-or-port))
376
377 ;; XXX: 'fcntl' is a vararg function, but here we happily use the
378 ;; standard ABI; crossing fingers.
379 (let ((err (proc fd
c7445833
LC
380 (if wait?
381 F_SETLKW ; lock & wait
382 F_SETLK) ; non-blocking attempt
2cd5c038
LC
383 (make-c-struct %struct-flock
384 (list (operation->int operation)
385 SEEK_SET
386 0 0 ; whole file
387 0)))))
388 (or (zero? err)
389
390 ;; Presumably we got EAGAIN or so.
9ea3ef26 391 (throw 'flock-error (errno)))))))
2cd5c038
LC
392
393\f
de4c3f26
LC
394;;;
395;;; Miscellaneous.
396;;;
397
398(define (memoize proc)
399 "Return a memoizing version of PROC."
400 (let ((cache (make-hash-table)))
401 (lambda args
402 (let ((results (hash-ref cache args)))
403 (if results
404 (apply values results)
405 (let ((results (call-with-values (lambda ()
406 (apply proc args))
407 list)))
408 (hash-set! cache args results)
409 (apply values results)))))))
98090557 410
5e110382
LC
411(define (strip-keyword-arguments keywords args)
412 "Remove all of the keyword arguments listed in KEYWORDS from ARGS."
413 (let loop ((args args)
414 (result '()))
415 (match args
416 (()
417 (reverse result))
418 (((? keyword? kw) arg . rest)
419 (loop rest
420 (if (memq kw keywords)
421 result
422 (cons* arg kw result))))
423 ((head . tail)
424 (loop tail (cons head result))))))
425
0af2c24e
LC
426(define (default-keyword-arguments args defaults)
427 "Return ARGS augmented with any keyword/value from DEFAULTS for
428keywords not already present in ARGS."
429 (let loop ((defaults defaults)
430 (args args))
431 (match defaults
432 ((kw value rest ...)
433 (loop rest
434 (if (assoc-ref kw args)
435 args
436 (cons* kw value args))))
437 (()
438 args))))
439
440(define-syntax substitute-keyword-arguments
441 (syntax-rules ()
442 "Return a new list of arguments where the value for keyword arg KW is
443replaced by EXP. EXP is evaluated in a context where VAR is boud to the
444previous value of the keyword argument."
445 ((_ original-args ((kw var) exp) ...)
446 (let loop ((args original-args)
447 (before '()))
448 (match args
449 ((kw var rest (... ...))
450 (loop rest (cons* exp kw before)))
451 ...
452 ((x rest (... ...))
453 (loop rest (cons x before)))
454 (()
455 (reverse before)))))))
456
6071122b
LC
457(define (delkw kw lst)
458 "Remove KW and its associated value from LST, a keyword/value list such
459as '(#:foo 1 #:bar 2)."
460 (let loop ((lst lst)
461 (result '()))
462 (match lst
463 (()
464 (reverse result))
465 ((kw? value rest ...)
466 (if (eq? kw? kw)
467 (append (reverse result) rest)
468 (loop rest (cons* value kw? result)))))))
469
470(define (ensure-keyword-arguments args kw/values)
471 "Force the keywords arguments KW/VALUES in the keyword argument list ARGS.
472For instance:
473
474 (ensure-keyword-arguments '(#:foo 2) '(#:foo 2))
475 => (#:foo 2)
476
477 (ensure-keyword-arguments '(#:foo 2) '(#:bar 3))
478 => (#:foo 2 #:bar 3)
479
480 (ensure-keyword-arguments '(#:foo 2) '(#:bar 3 #:foo 42))
481 => (#:foo 42 #:bar 3)
482"
483 (let loop ((args args)
484 (kw/values kw/values)
485 (result '()))
486 (match args
487 (()
488 (append (reverse result) kw/values))
489 ((kw value rest ...)
490 (match (memq kw kw/values)
491 ((_ value . _)
492 (loop rest (delkw kw kw/values) (cons* value kw result)))
493 (#f
494 (loop rest kw/values (cons* value kw result))))))))
495
8eb80484
MW
496(define* (nix-system->gnu-triplet
497 #:optional (system (%current-system)) (vendor "unknown"))
498 "Return a guess of the GNU triplet corresponding to Nix system
499identifier SYSTEM."
3f00ff8b
MW
500 (match system
501 ("armhf-linux"
502 (string-append "arm-" vendor "-linux-gnueabihf"))
503 (_
504 (let* ((dash (string-index system #\-))
505 (arch (substring system 0 dash))
506 (os (substring system (+ 1 dash))))
507 (string-append arch
508 "-" vendor "-"
509 (if (string=? os "linux")
510 "linux-gnu"
511 os))))))
8eb80484 512
98090557
LC
513(define (gnu-triplet->nix-system triplet)
514 "Return the Nix system type corresponding to TRIPLET, a GNU triplet as
515returned by `config.guess'."
516 (let ((triplet (cond ((string-match "^i[345]86-(.*)$" triplet)
517 =>
518 (lambda (m)
519 (string-append "i686-" (match:substring m 1))))
520 (else triplet))))
3f00ff8b
MW
521 (cond ((string-match "^arm[^-]*-([^-]+-)?linux-gnueabihf" triplet)
522 "armhf-linux")
523 ((string-match "^([^-]+)-([^-]+-)?linux-gnu.*" triplet)
98090557
LC
524 =>
525 (lambda (m)
526 ;; Nix omits `-gnu' for GNU/Linux.
527 (string-append (match:substring m 1) "-linux")))
528 ((string-match "^([^-]+)-([^-]+-)?([[:alpha:]]+)([0-9]+\\.?)*$" triplet)
529 =>
530 (lambda (m)
531 ;; Nix strip the version number from names such as `gnu0.3',
532 ;; `darwin10.2.0', etc., and always strips the vendor part.
533 (string-append (match:substring m 1) "-"
534 (match:substring m 3))))
535 (else triplet))))
536
537(define %current-system
538 ;; System type as expected by Nix, usually ARCHITECTURE-KERNEL.
d8eea3d2
LC
539 ;; By default, this is equal to (gnu-triplet->nix-system %host-type).
540 (make-parameter %system))
ff352cfb 541
9c1edabd
LC
542(define %current-target-system
543 ;; Either #f or a GNU triplet representing the target system we are
544 ;; cross-building to.
545 (make-parameter #f))
546
1b846da8
ML
547(define (package-name->name+version spec)
548 "Given SPEC, a package name like \"foo@0.9.1b\", return two values: \"foo\"
549and \"0.9.1b\". When the version part is unavailable, SPEC and #f are
550returned. Both parts must not contain any '@'."
551 (match (string-rindex spec #\@)
552 (#f (values spec #f))
553 (idx (values (substring spec 0 idx)
554 (substring spec (1+ idx))))))
555
0d1e6ce4
MW
556(define version-compare
557 (let ((strverscmp
558 (let ((sym (or (dynamic-func "strverscmp" (dynamic-link))
559 (error "could not find `strverscmp' (from GNU libc)"))))
560 (pointer->procedure int sym (list '* '*)))))
561 (lambda (a b)
562 "Return '> when A denotes a newer version than B,
563'< when A denotes a older version than B,
564or '= when they denote equal versions."
565 (let ((result (strverscmp (string->pointer a) (string->pointer b))))
566 (cond ((positive? result) '>)
567 ((negative? result) '<)
568 (else '=))))))
569
29a7c98a
ID
570(define (version-prefix version-string num-parts)
571 "Truncate version-string to the first num-parts components of the version.
572For example, (version-prefix \"2.1.47.4.23\" 3) returns \"2.1.47\""
573 (string-join (take (string-split version-string #\.) num-parts) "."))
574
575
576(define (version-major+minor version-string)
577 "Return \"<major>.<minor>\", where major and minor are the major and
578minor version numbers from version-string."
579 (version-prefix version-string 2))
580
0d1e6ce4 581(define (version>? a b)
cde1e967 582 "Return #t when A denotes a version strictly newer than B."
0d1e6ce4
MW
583 (eq? '> (version-compare a b)))
584
cde1e967
LC
585(define (version>=? a b)
586 "Return #t when A denotes a version newer or equal to B."
587 (case (version-compare a b)
588 ((> =) #t)
589 (else #f)))
590
7db3ff4a
LC
591(define (guile-version>? str)
592 "Return #t if the running Guile version is greater than STR."
593 ;; Note: Using (version>? (version) "2.0.5") or similar doesn't work,
594 ;; because the result of (version) can have a prefix, like "2.0.5-deb1".
595 (version>? (string-append (major-version) "."
596 (minor-version) "."
597 (micro-version))
598 str))
599
0fdd3bea
LC
600(define (file-extension file)
601 "Return the extension of FILE or #f if there is none."
602 (let ((dot (string-rindex file #\.)))
603 (and dot (substring file (+ 1 dot) (string-length file)))))
604
ac10e0e1
LC
605(define (file-sans-extension file)
606 "Return the substring of FILE without its extension, if any."
607 (let ((dot (string-rindex file #\.)))
608 (if dot
609 (substring file 0 dot)
610 file)))
611
3bb168b0
LC
612(define (switch-symlinks link target)
613 "Atomically switch LINK, a symbolic link, to point to TARGET. Works
614both when LINK already exists and when it does not."
615 (let ((pivot (string-append link ".new")))
616 (symlink target pivot)
617 (rename-file pivot link)))
618
56b943de
LC
619(define* (string-replace-substring str substr replacement
620 #:optional
621 (start 0)
622 (end (string-length str)))
623 "Replace all occurrences of SUBSTR in the START--END range of STR by
624REPLACEMENT."
625 (match (string-length substr)
626 (0
627 (error "string-replace-substring: empty substring"))
628 (substr-length
629 (let loop ((start start)
630 (pieces (list (substring str 0 start))))
631 (match (string-contains str substr start end)
632 (#f
633 (string-concatenate-reverse
634 (cons (substring str start) pieces)))
635 (index
636 (loop (+ index substr-length)
637 (cons* replacement
638 (substring str start index)
639 pieces))))))))
640
16eb115e
DP
641(define (arguments-from-environment-variable variable)
642 "Retrieve value of environment variable denoted by string VARIABLE in the
643form of a list of strings (`char-set:graphic' tokens) suitable for consumption
644by `args-fold', if VARIABLE is defined, otherwise return an empty list."
645 (let ((env (getenv variable)))
646 (if env
647 (string-tokenize env char-set:graphic)
648 '())))
649
861693f3
LC
650(define (call-with-temporary-output-file proc)
651 "Call PROC with a name of a temporary file and open output port to that
652file; close the file and delete it when leaving the dynamic extent of this
653call."
57db49cc
LC
654 (let* ((directory (or (getenv "TMPDIR") "/tmp"))
655 (template (string-append directory "/guix-file.XXXXXX"))
656 (out (mkstemp! template)))
861693f3
LC
657 (dynamic-wind
658 (lambda ()
659 #t)
660 (lambda ()
661 (proc template out))
662 (lambda ()
663 (false-if-exception (close out))
664 (false-if-exception (delete-file template))))))
04d4c8a4 665
db6e5e2b
DT
666(define (call-with-temporary-directory proc)
667 "Call PROC with a name of a temporary directory; close the directory and
668delete it when leaving the dynamic extent of this call."
669 (let* ((directory (or (getenv "TMPDIR") "/tmp"))
670 (template (string-append directory "/guix-directory.XXXXXX"))
671 (tmp-dir (mkdtemp! template)))
672 (dynamic-wind
673 (const #t)
674 (lambda ()
675 (proc tmp-dir))
676 (lambda ()
677 (false-if-exception (rmdir tmp-dir))))))
678
04d4c8a4
LC
679(define (with-atomic-file-output file proc)
680 "Call PROC with an output port for the file that is going to replace FILE.
681Upon success, FILE is atomically replaced by what has been written to the
682output port, and PROC's result is returned."
683 (let* ((template (string-append file ".XXXXXX"))
684 (out (mkstemp! template)))
685 (with-throw-handler #t
686 (lambda ()
687 (let ((result (proc out)))
688 (close out)
689 (rename-file template file)
690 result))
691 (lambda (key . args)
692 (false-if-exception (delete-file template))))))
861693f3 693
04fd96ca
LC
694(define fold2
695 (case-lambda
696 ((proc seed1 seed2 lst)
697 "Like `fold', but with a single list and two seeds."
698 (let loop ((result1 seed1)
699 (result2 seed2)
700 (lst lst))
701 (if (null? lst)
702 (values result1 result2)
703 (call-with-values
704 (lambda () (proc (car lst) result1 result2))
705 (lambda (result1 result2)
706 (loop result1 result2 (cdr lst)))))))
707 ((proc seed1 seed2 lst1 lst2)
708 "Like `fold', but with a two lists and two seeds."
709 (let loop ((result1 seed1)
710 (result2 seed2)
711 (lst1 lst1)
712 (lst2 lst2))
713 (if (or (null? lst1) (null? lst2))
714 (values result1 result2)
715 (call-with-values
716 (lambda () (proc (car lst1) (car lst2) result1 result2))
717 (lambda (result1 result2)
718 (fold2 proc result1 result2 (cdr lst1) (cdr lst2)))))))))
719
516e3b6f
EB
720(define (fold-tree proc init children roots)
721 "Call (PROC NODE RESULT) for each node in the tree that is reachable from
722ROOTS, using INIT as the initial value of RESULT. The order in which nodes
723are traversed is not specified, however, each node is visited only once, based
724on an eq? check. Children of a node to be visited are generated by
725calling (CHILDREN NODE), the result of which should be a list of nodes that
726are connected to NODE in the tree, or '() or #f if NODE is a leaf node."
727 (let loop ((result init)
728 (seen vlist-null)
729 (lst roots))
730 (match lst
731 (() result)
732 ((head . tail)
733 (if (not (vhash-assq head seen))
734 (loop (proc head result)
735 (vhash-consq head #t seen)
736 (match (children head)
737 ((or () #f) tail)
738 (children (append tail children))))
739 (loop result seen tail))))))
740
741(define (fold-tree-leaves proc init children roots)
742 "Like fold-tree, but call (PROC NODE RESULT) only for leaf nodes."
743 (fold-tree
744 (lambda (node result)
745 (match (children node)
746 ((or () #f) (proc node result))
747 (else result)))
748 init children roots))
749
bbd00d20
DT
750(define (split lst e)
751 "Return two values, a list containing the elements of the list LST that
752appear before the first occurence of the object E and a list containing the
753elements after E."
754 (define (same? x)
755 (equal? e x))
756
757 (let loop ((rest lst)
758 (acc '()))
759 (match rest
760 (()
761 (values lst '()))
762 (((? same?) . tail)
763 (values (reverse acc) tail))
764 ((head . tail)
765 (loop tail (cons head acc))))))
766
739ab68b
LC
767(define (cache-directory)
768 "Return the cache directory for Guix, by default ~/.cache/guix."
769 (or (getenv "XDG_CONFIG_HOME")
770 (and=> (getenv "HOME")
771 (cut string-append <> "/.cache/guix"))))
772
d50cb56d
LC
773(define (readlink* file)
774 "Call 'readlink' until the result is not a symlink."
775 (define %max-symlink-depth 50)
776
777 (let loop ((file file)
778 (depth 0))
779 (define (absolute target)
780 (if (absolute-file-name? target)
781 target
782 (string-append (dirname file) "/" target)))
783
784 (if (>= depth %max-symlink-depth)
785 file
786 (call-with-values
787 (lambda ()
788 (catch 'system-error
789 (lambda ()
790 (values #t (readlink file)))
791 (lambda args
792 (let ((errno (system-error-errno args)))
793 (if (or (= errno EINVAL))
794 (values #f file)
795 (apply throw args))))))
796 (lambda (success? target)
797 (if success?
798 (loop (absolute target) (+ depth 1))
799 file))))))
c8be6f0d
FB
800
801(define (canonical-newline-port port)
802 "Return an input port that wraps PORT such that all newlines consist
803 of a single carriage return."
804 (define (get-position)
805 (if (port-has-port-position? port) (port-position port) #f))
806 (define (set-position! position)
807 (if (port-has-set-port-position!? port)
808 (set-port-position! position port)
809 #f))
810 (define (close) (close-port port))
811 (define (read! bv start n)
812 (let loop ((count 0)
813 (byte (get-u8 port)))
814 (cond ((eof-object? byte) count)
815 ((= count (- n 1))
816 (bytevector-u8-set! bv (+ start count) byte)
817 n)
818 ;; XXX: consume all LFs even if not followed by CR.
819 ((eqv? byte (char->integer #\return)) (loop count (get-u8 port)))
820 (else
821 (bytevector-u8-set! bv (+ start count) byte)
822 (loop (+ count 1) (get-u8 port))))))
823 (make-custom-binary-input-port "canonical-newline-port"
824 read!
825 get-position
826 set-position!
827 close))
ff352cfb
LC
828\f
829;;;
830;;; Source location.
831;;;
832
833;; A source location.
834(define-record-type <location>
835 (make-location file line column)
836 location?
837 (file location-file) ; file name
838 (line location-line) ; 1-indexed line
839 (column location-column)) ; 0-indexed column
840
841(define location
842 (memoize
843 (lambda (file line column)
844 "Return the <location> object for the given FILE, LINE, and COLUMN."
845 (and line column file
846 (make-location file line column)))))
847
848(define (source-properties->location loc)
849 "Return a location object based on the info in LOC, an alist as returned
850by Guile's `source-properties', `frame-source', `current-source-location',
851etc."
852 (let ((file (assq-ref loc 'filename))
853 (line (assq-ref loc 'line))
854 (col (assq-ref loc 'column)))
5fe21fbe
LC
855 ;; In accordance with the GCS, start line and column numbers at 1. Note
856 ;; that unlike LINE and `port-column', COL is actually 1-indexed here...
857 (location file (and line (+ line 1)) col)))