packages: Turn 'cache!' into a single-value-return cache.
[jackhill/guix/guix.git] / guix / utils.scm
CommitLineData
233e7676 1;;; GNU Guix --- Functional package management for GNU
437f62f0 2;;; Copyright © 2012, 2013, 2014, 2015, 2016, 2017, 2018 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>
e45b573c 8;;; Copyright © 2017 Mathieu Othacehe <m.othacehe@gmail.com>
259341cf 9;;; Copyright © 2018 Marius Bakke <mbakke@fastmail.com>
e3deeebb 10;;;
233e7676 11;;; This file is part of GNU Guix.
e3deeebb 12;;;
233e7676 13;;; GNU Guix is free software; you can redistribute it and/or modify it
e3deeebb
LC
14;;; under the terms of the GNU General Public License as published by
15;;; the Free Software Foundation; either version 3 of the License, or (at
16;;; your option) any later version.
17;;;
233e7676 18;;; GNU Guix is distributed in the hope that it will be useful, but
e3deeebb
LC
19;;; WITHOUT ANY WARRANTY; without even the implied warranty of
20;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21;;; GNU General Public License for more details.
22;;;
23;;; You should have received a copy of the GNU General Public License
233e7676 24;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
e3deeebb
LC
25
26(define-module (guix utils)
00e219d1 27 #:use-module (guix config)
38b3122a 28 #:use-module (srfi srfi-1)
72d86963 29 #:use-module (srfi srfi-9)
01ac19dc 30 #:use-module (srfi srfi-11)
38b3122a 31 #:use-module (srfi srfi-26)
23735137 32 #:use-module (srfi srfi-35)
de4c3f26 33 #:use-module (srfi srfi-39)
2535635f 34 #:use-module (ice-9 binary-ports)
27f7cbc9 35 #:use-module (ice-9 ftw)
31044751 36 #:autoload (rnrs io ports) (make-custom-binary-input-port)
c8be6f0d 37 #:use-module ((rnrs bytevectors) #:select (bytevector-u8-set!))
f9704f17 38 #:use-module (guix memoization)
27f7cbc9 39 #:use-module ((guix build utils) #:select (dump-port mkdir-p delete-file-recursively))
1752a17a 40 #:use-module ((guix build syscalls) #:select (mkdtemp! fdatasync))
38b3122a 41 #:use-module (ice-9 format)
de4c3f26
LC
42 #:autoload (ice-9 popen) (open-pipe*)
43 #:autoload (ice-9 rdelim) (read-line)
98090557 44 #:use-module (ice-9 regex)
72d86963 45 #:use-module (ice-9 match)
8ef3401f 46 #:use-module (ice-9 format)
31044751 47 #:use-module ((ice-9 iconv) #:prefix iconv:)
2cd5c038 48 #:use-module (system foreign)
19e1d5f7 49 #:re-export (memoize) ; for backwards compatibility
4c0c4db0 50 #:export (strip-keyword-arguments
0af2c24e
LC
51 default-keyword-arguments
52 substitute-keyword-arguments
6071122b 53 ensure-keyword-arguments
ff352cfb 54
07c8a98c
LC
55 current-source-directory
56
64fc89b6 57 <location>
ff352cfb
LC
58 location
59 location?
60 location-file
61 location-line
62 location-column
63 source-properties->location
f7df1e3e 64 location->source-properties
ff352cfb 65
23735137
LC
66 &error-location
67 error-location?
68 error-location
69
70 &fix-hint
71 fix-hint?
72 condition-fix-hint
73
8eb80484 74 nix-system->gnu-triplet
98090557 75 gnu-triplet->nix-system
9b48fb88 76 %current-system
9c1edabd 77 %current-target-system
1b846da8 78 package-name->name+version
cba36e64 79 target-mingw?
e45b573c 80 target-arm32?
259341cf 81 target-64bit?
0d1e6ce4
MW
82 version-compare
83 version>?
cde1e967 84 version>=?
29a7c98a
ID
85 version-prefix
86 version-major+minor
47dc9a0d 87 version-major
7db3ff4a 88 guile-version>?
437f62f0 89 version-prefix?
56b943de 90 string-replace-substring
16eb115e 91 arguments-from-environment-variable
0fdd3bea 92 file-extension
ac10e0e1 93 file-sans-extension
089b1678 94 compressed-file?
3bb168b0 95 switch-symlinks
861693f3 96 call-with-temporary-output-file
db6e5e2b 97 call-with-temporary-directory
04d4c8a4 98 with-atomic-file-output
f0e492f0
LC
99
100 config-directory
739ab68b 101 cache-directory
f0e492f0 102
d50cb56d 103 readlink*
50a3d594 104 edit-expression
7a8024a3
LC
105
106 filtered-port
107 compressed-port
80dea563 108 decompressed-port
01ac19dc
LC
109 call-with-decompressed-port
110 compressed-output-port
c8be6f0d 111 call-with-compressed-output-port
8c348825 112 canonical-newline-port))
e3deeebb 113
38b3122a 114\f
e0fbbc88
LC
115;;;
116;;; Filtering & pipes.
117;;;
118
119(define (filtered-port command input)
120 "Return an input port where data drained from INPUT is filtered through
121COMMAND (a list). In addition, return a list of PIDs that the caller must
101d9f3f
LC
122wait. When INPUT is a file port, it must be unbuffered; otherwise, any
123buffered data is lost."
e0fbbc88 124 (let loop ((input input)
443eb4e9 125 (pids '()))
e0fbbc88
LC
126 (if (file-port? input)
127 (match (pipe)
128 ((in . out)
129 (match (primitive-fork)
130 (0
443eb4e9
LC
131 (dynamic-wind
132 (const #f)
133 (lambda ()
134 (close-port in)
135 (close-port (current-input-port))
136 (dup2 (fileno input) 0)
137 (close-port (current-output-port))
138 (dup2 (fileno out) 1)
139 (catch 'system-error
140 (lambda ()
141 (apply execl (car command) command))
142 (lambda args
143 (format (current-error-port)
144 "filtered-port: failed to execute '~{~a ~}': ~a~%"
145 command (strerror (system-error-errno args))))))
146 (lambda ()
147 (primitive-_exit 1))))
e0fbbc88
LC
148 (child
149 (close-port out)
150 (values in (cons child pids))))))
151
152 ;; INPUT is not a file port, so fork just for the sake of tunneling it
153 ;; through a file port.
154 (match (pipe)
155 ((in . out)
156 (match (primitive-fork)
157 (0
158 (dynamic-wind
159 (const #t)
160 (lambda ()
161 (close-port in)
162 (dump-port input out))
163 (lambda ()
5efa0e4d 164 (close-port input)
e0fbbc88 165 (false-if-exception (close out))
443eb4e9 166 (primitive-_exit 0))))
e0fbbc88 167 (child
5efa0e4d 168 (close-port input)
e0fbbc88
LC
169 (close-port out)
170 (loop in (cons child pids)))))))))
171
7a8024a3
LC
172(define (decompressed-port compression input)
173 "Return an input port where INPUT is decompressed according to COMPRESSION,
174a symbol such as 'xz."
175 (match compression
176 ((or #f 'none) (values input '()))
177 ('bzip2 (filtered-port `(,%bzip2 "-dc") input))
e9be2c54 178 ('xz (filtered-port `(,%xz "-dc") input))
7a8024a3
LC
179 ('gzip (filtered-port `(,%gzip "-dc") input))
180 (else (error "unsupported compression scheme" compression))))
181
182(define (compressed-port compression input)
183 "Return an input port where INPUT is decompressed according to COMPRESSION,
184a symbol such as 'xz."
185 (match compression
186 ((or #f 'none) (values input '()))
187 ('bzip2 (filtered-port `(,%bzip2 "-c") input))
e9be2c54 188 ('xz (filtered-port `(,%xz "-c") input))
7a8024a3
LC
189 ('gzip (filtered-port `(,%gzip "-c") input))
190 (else (error "unsupported compression scheme" compression))))
191
01ac19dc
LC
192(define (call-with-decompressed-port compression port proc)
193 "Call PROC with a wrapper around PORT, a file port, that decompresses data
30ce8012 194read from PORT according to COMPRESSION, a symbol such as 'xz."
01ac19dc
LC
195 (let-values (((decompressed pids)
196 (decompressed-port compression port)))
197 (dynamic-wind
198 (const #f)
199 (lambda ()
01ac19dc
LC
200 (proc decompressed))
201 (lambda ()
202 (close-port decompressed)
203 (unless (every (compose zero? cdr waitpid) pids)
204 (error "decompressed-port failure" pids))))))
205
80dea563
LC
206(define (filtered-output-port command output)
207 "Return an output port. Data written to that port is filtered through
208COMMAND and written to OUTPUT, an output file port. In addition, return a
209list of PIDs to wait for. OUTPUT must be unbuffered; otherwise, any buffered
210data is lost."
211 (match (pipe)
212 ((in . out)
213 (match (primitive-fork)
214 (0
215 (dynamic-wind
216 (const #f)
217 (lambda ()
218 (close-port out)
219 (close-port (current-input-port))
220 (dup2 (fileno in) 0)
221 (close-port (current-output-port))
222 (dup2 (fileno output) 1)
223 (catch 'system-error
224 (lambda ()
225 (apply execl (car command) command))
226 (lambda args
227 (format (current-error-port)
228 "filtered-output-port: failed to execute '~{~a ~}': ~a~%"
229 command (strerror (system-error-errno args))))))
230 (lambda ()
231 (primitive-_exit 1))))
232 (child
233 (close-port in)
234 (values out (list child)))))))
235
62d2b571
LC
236(define* (compressed-output-port compression output
237 #:key (options '()))
80dea563
LC
238 "Return an output port whose input is compressed according to COMPRESSION,
239a symbol such as 'xz, and then written to OUTPUT. In addition return a list
62d2b571
LC
240of PIDs to wait for. OPTIONS is a list of strings passed to the compression
241program--e.g., '(\"--fast\")."
80dea563
LC
242 (match compression
243 ((or #f 'none) (values output '()))
62d2b571 244 ('bzip2 (filtered-output-port `(,%bzip2 "-c" ,@options) output))
e9be2c54 245 ('xz (filtered-output-port `(,%xz "-c" ,@options) output))
62d2b571 246 ('gzip (filtered-output-port `(,%gzip "-c" ,@options) output))
80dea563
LC
247 (else (error "unsupported compression scheme" compression))))
248
62d2b571
LC
249(define* (call-with-compressed-output-port compression port proc
250 #:key (options '()))
01ac19dc 251 "Call PROC with a wrapper around PORT, a file port, that compresses data
62d2b571
LC
252that goes to PORT according to COMPRESSION, a symbol such as 'xz. OPTIONS is
253a list of command-line arguments passed to the compression program."
01ac19dc 254 (let-values (((compressed pids)
62d2b571
LC
255 (compressed-output-port compression port
256 #:options options)))
01ac19dc
LC
257 (dynamic-wind
258 (const #f)
259 (lambda ()
01ac19dc
LC
260 (proc compressed))
261 (lambda ()
262 (close-port compressed)
263 (unless (every (compose zero? cdr waitpid) pids)
264 (error "compressed-output-port failure" pids))))))
265
50a3d594
SB
266(define* (edit-expression source-properties proc #:key (encoding "UTF-8"))
267 "Edit the expression specified by SOURCE-PROPERTIES using PROC, which should
268be a procedure that takes the original expression in string and returns a new
269one. ENCODING will be used to interpret all port I/O, it default to UTF-8.
270This procedure returns #t on success."
271 (with-fluids ((%default-port-encoding encoding))
272 (let* ((file (assq-ref source-properties 'filename))
273 (line (assq-ref source-properties 'line))
274 (column (assq-ref source-properties 'column))
275 (in (open-input-file file))
276 ;; The start byte position of the expression.
277 (start (begin (while (not (and (= line (port-line in))
278 (= column (port-column in))))
279 (when (eof-object? (read-char in))
280 (error (format #f "~a: end of file~%" in))))
281 (ftell in)))
282 ;; The end byte position of the expression.
283 (end (begin (read in) (ftell in))))
284 (seek in 0 SEEK_SET) ; read from the beginning of the file.
285 (let* ((pre-bv (get-bytevector-n in start))
286 ;; The expression in string form.
31044751 287 (str (iconv:bytevector->string
50a3d594
SB
288 (get-bytevector-n in (- end start))
289 (port-encoding in)))
290 (post-bv (get-bytevector-all in))
291 (str* (proc str)))
292 ;; Verify the edited expression is still a scheme expression.
293 (call-with-input-string str* read)
294 ;; Update the file with edited expression.
295 (with-atomic-file-output file
296 (lambda (out)
297 (put-bytevector out pre-bv)
298 (display str* out)
299 ;; post-bv maybe the end-of-file object.
300 (when (not (eof-object? post-bv))
301 (put-bytevector out post-bv))
302 #t))))))
303
e0fbbc88 304\f
de4c3f26 305;;;
958dd3ce 306;;; Keyword arguments.
de4c3f26
LC
307;;;
308
5e110382
LC
309(define (strip-keyword-arguments keywords args)
310 "Remove all of the keyword arguments listed in KEYWORDS from ARGS."
311 (let loop ((args args)
312 (result '()))
313 (match args
314 (()
315 (reverse result))
316 (((? keyword? kw) arg . rest)
317 (loop rest
318 (if (memq kw keywords)
319 result
320 (cons* arg kw result))))
321 ((head . tail)
322 (loop tail (cons head result))))))
323
0af2c24e
LC
324(define (default-keyword-arguments args defaults)
325 "Return ARGS augmented with any keyword/value from DEFAULTS for
326keywords not already present in ARGS."
327 (let loop ((defaults defaults)
328 (args args))
329 (match defaults
330 ((kw value rest ...)
331 (loop rest
347df601 332 (if (memq kw args)
0af2c24e
LC
333 args
334 (cons* kw value args))))
335 (()
336 args))))
337
b8b129eb
EB
338(define-syntax collect-default-args
339 (syntax-rules ()
340 ((_)
341 '())
342 ((_ (_ _) rest ...)
343 (collect-default-args rest ...))
344 ((_ (kw _ dflt) rest ...)
345 (cons* kw dflt (collect-default-args rest ...)))))
346
0af2c24e
LC
347(define-syntax substitute-keyword-arguments
348 (syntax-rules ()
349 "Return a new list of arguments where the value for keyword arg KW is
b8b129eb
EB
350replaced by EXP. EXP is evaluated in a context where VAR is bound to the
351previous value of the keyword argument, or DFLT if given."
352 ((_ original-args ((kw var dflt ...) exp) ...)
353 (let loop ((args (default-keyword-arguments
354 original-args
355 (collect-default-args (kw var dflt ...) ...)))
0af2c24e
LC
356 (before '()))
357 (match args
358 ((kw var rest (... ...))
359 (loop rest (cons* exp kw before)))
360 ...
361 ((x rest (... ...))
362 (loop rest (cons x before)))
363 (()
364 (reverse before)))))))
365
6071122b
LC
366(define (delkw kw lst)
367 "Remove KW and its associated value from LST, a keyword/value list such
368as '(#:foo 1 #:bar 2)."
369 (let loop ((lst lst)
370 (result '()))
371 (match lst
372 (()
373 (reverse result))
374 ((kw? value rest ...)
375 (if (eq? kw? kw)
376 (append (reverse result) rest)
377 (loop rest (cons* value kw? result)))))))
378
379(define (ensure-keyword-arguments args kw/values)
380 "Force the keywords arguments KW/VALUES in the keyword argument list ARGS.
381For instance:
382
383 (ensure-keyword-arguments '(#:foo 2) '(#:foo 2))
384 => (#:foo 2)
385
386 (ensure-keyword-arguments '(#:foo 2) '(#:bar 3))
387 => (#:foo 2 #:bar 3)
388
389 (ensure-keyword-arguments '(#:foo 2) '(#:bar 3 #:foo 42))
390 => (#:foo 42 #:bar 3)
391"
392 (let loop ((args args)
393 (kw/values kw/values)
394 (result '()))
395 (match args
396 (()
397 (append (reverse result) kw/values))
398 ((kw value rest ...)
399 (match (memq kw kw/values)
400 ((_ value . _)
401 (loop rest (delkw kw kw/values) (cons* value kw result)))
402 (#f
403 (loop rest kw/values (cons* value kw result))))))))
404
958dd3ce
LC
405\f
406;;;
407;;; System strings.
408;;;
409
8eb80484
MW
410(define* (nix-system->gnu-triplet
411 #:optional (system (%current-system)) (vendor "unknown"))
412 "Return a guess of the GNU triplet corresponding to Nix system
413identifier SYSTEM."
3f00ff8b
MW
414 (match system
415 ("armhf-linux"
416 (string-append "arm-" vendor "-linux-gnueabihf"))
417 (_
418 (let* ((dash (string-index system #\-))
419 (arch (substring system 0 dash))
420 (os (substring system (+ 1 dash))))
421 (string-append arch
422 "-" vendor "-"
423 (if (string=? os "linux")
424 "linux-gnu"
425 os))))))
8eb80484 426
98090557
LC
427(define (gnu-triplet->nix-system triplet)
428 "Return the Nix system type corresponding to TRIPLET, a GNU triplet as
429returned by `config.guess'."
430 (let ((triplet (cond ((string-match "^i[345]86-(.*)$" triplet)
431 =>
432 (lambda (m)
433 (string-append "i686-" (match:substring m 1))))
434 (else triplet))))
3f00ff8b
MW
435 (cond ((string-match "^arm[^-]*-([^-]+-)?linux-gnueabihf" triplet)
436 "armhf-linux")
437 ((string-match "^([^-]+)-([^-]+-)?linux-gnu.*" triplet)
98090557
LC
438 =>
439 (lambda (m)
440 ;; Nix omits `-gnu' for GNU/Linux.
441 (string-append (match:substring m 1) "-linux")))
442 ((string-match "^([^-]+)-([^-]+-)?([[:alpha:]]+)([0-9]+\\.?)*$" triplet)
443 =>
444 (lambda (m)
445 ;; Nix strip the version number from names such as `gnu0.3',
446 ;; `darwin10.2.0', etc., and always strips the vendor part.
447 (string-append (match:substring m 1) "-"
448 (match:substring m 3))))
449 (else triplet))))
450
451(define %current-system
452 ;; System type as expected by Nix, usually ARCHITECTURE-KERNEL.
d8eea3d2
LC
453 ;; By default, this is equal to (gnu-triplet->nix-system %host-type).
454 (make-parameter %system))
ff352cfb 455
9c1edabd
LC
456(define %current-target-system
457 ;; Either #f or a GNU triplet representing the target system we are
458 ;; cross-building to.
459 (make-parameter #f))
460
aa042770
LC
461(define* (package-name->name+version spec
462 #:optional (delimiter #\@))
1b846da8
ML
463 "Given SPEC, a package name like \"foo@0.9.1b\", return two values: \"foo\"
464and \"0.9.1b\". When the version part is unavailable, SPEC and #f are
aa042770
LC
465returned. Both parts must not contain any '@'. Optionally, DELIMITER can be
466a character other than '@'."
467 (match (string-rindex spec delimiter)
1b846da8
ML
468 (#f (values spec #f))
469 (idx (values (substring spec 0 idx)
470 (substring spec (1+ idx))))))
471
cba36e64
JN
472(define* (target-mingw? #:optional (target (%current-target-system)))
473 (and target
474 (string-suffix? "-mingw32" target)))
475
e45b573c
MO
476(define (target-arm32?)
477 (string-prefix? "arm" (or (%current-target-system) (%current-system))))
478
259341cf
MB
479(define (target-64bit?)
480 (let ((system (or (%current-target-system) (%current-system))))
481 (any (cut string-prefix? <> system) '("x86_64" "aarch64" "mips64" "ppc64"))))
482
0d1e6ce4
MW
483(define version-compare
484 (let ((strverscmp
485 (let ((sym (or (dynamic-func "strverscmp" (dynamic-link))
486 (error "could not find `strverscmp' (from GNU libc)"))))
487 (pointer->procedure int sym (list '* '*)))))
488 (lambda (a b)
489 "Return '> when A denotes a newer version than B,
490'< when A denotes a older version than B,
491or '= when they denote equal versions."
492 (let ((result (strverscmp (string->pointer a) (string->pointer b))))
493 (cond ((positive? result) '>)
494 ((negative? result) '<)
495 (else '=))))))
496
29a7c98a
ID
497(define (version-prefix version-string num-parts)
498 "Truncate version-string to the first num-parts components of the version.
499For example, (version-prefix \"2.1.47.4.23\" 3) returns \"2.1.47\""
500 (string-join (take (string-split version-string #\.) num-parts) "."))
501
502
503(define (version-major+minor version-string)
504 "Return \"<major>.<minor>\", where major and minor are the major and
505minor version numbers from version-string."
506 (version-prefix version-string 2))
507
47dc9a0d 508(define (version-major version-string)
509 "Return the major version number as string from the version-string."
510 (version-prefix version-string 1))
511
0d1e6ce4 512(define (version>? a b)
cde1e967 513 "Return #t when A denotes a version strictly newer than B."
0d1e6ce4
MW
514 (eq? '> (version-compare a b)))
515
cde1e967
LC
516(define (version>=? a b)
517 "Return #t when A denotes a version newer or equal to B."
518 (case (version-compare a b)
519 ((> =) #t)
520 (else #f)))
521
7db3ff4a
LC
522(define (guile-version>? str)
523 "Return #t if the running Guile version is greater than STR."
524 ;; Note: Using (version>? (version) "2.0.5") or similar doesn't work,
525 ;; because the result of (version) can have a prefix, like "2.0.5-deb1".
526 (version>? (string-append (major-version) "."
527 (minor-version) "."
528 (micro-version))
529 str))
530
437f62f0
LC
531(define version-prefix?
532 (let ((not-dot (char-set-complement (char-set #\.))))
533 (lambda (v1 v2)
534 "Return true if V1 is a version prefix of V2:
535
536 (version-prefix? \"4.1\" \"4.16.2\") => #f
537 (version-prefix? \"4.1\" \"4.1.2\") => #t
538"
539 (define (list-prefix? lst1 lst2)
540 (match lst1
541 (() #t)
542 ((head1 tail1 ...)
543 (match lst2
544 (() #f)
545 ((head2 tail2 ...)
546 (and (equal? head1 head2)
547 (list-prefix? tail1 tail2)))))))
548
549 (list-prefix? (string-tokenize v1 not-dot)
550 (string-tokenize v2 not-dot)))))
551
0fdd3bea
LC
552(define (file-extension file)
553 "Return the extension of FILE or #f if there is none."
554 (let ((dot (string-rindex file #\.)))
555 (and dot (substring file (+ 1 dot) (string-length file)))))
556
ac10e0e1
LC
557(define (file-sans-extension file)
558 "Return the substring of FILE without its extension, if any."
559 (let ((dot (string-rindex file #\.)))
560 (if dot
561 (substring file 0 dot)
562 file)))
563
089b1678
LC
564(define (compressed-file? file)
565 "Return true if FILE denotes a compressed file."
566 (->bool (member (file-extension file)
f4111243 567 '("gz" "bz2" "xz" "lz" "lzma" "tgz" "tbz2" "zip"))))
089b1678 568
3bb168b0
LC
569(define (switch-symlinks link target)
570 "Atomically switch LINK, a symbolic link, to point to TARGET. Works
571both when LINK already exists and when it does not."
572 (let ((pivot (string-append link ".new")))
573 (symlink target pivot)
574 (rename-file pivot link)))
575
56b943de
LC
576(define* (string-replace-substring str substr replacement
577 #:optional
578 (start 0)
579 (end (string-length str)))
580 "Replace all occurrences of SUBSTR in the START--END range of STR by
581REPLACEMENT."
582 (match (string-length substr)
583 (0
584 (error "string-replace-substring: empty substring"))
585 (substr-length
586 (let loop ((start start)
587 (pieces (list (substring str 0 start))))
588 (match (string-contains str substr start end)
589 (#f
590 (string-concatenate-reverse
591 (cons (substring str start) pieces)))
592 (index
593 (loop (+ index substr-length)
594 (cons* replacement
595 (substring str start index)
596 pieces))))))))
597
16eb115e
DP
598(define (arguments-from-environment-variable variable)
599 "Retrieve value of environment variable denoted by string VARIABLE in the
600form of a list of strings (`char-set:graphic' tokens) suitable for consumption
601by `args-fold', if VARIABLE is defined, otherwise return an empty list."
602 (let ((env (getenv variable)))
603 (if env
604 (string-tokenize env char-set:graphic)
605 '())))
606
861693f3
LC
607(define (call-with-temporary-output-file proc)
608 "Call PROC with a name of a temporary file and open output port to that
609file; close the file and delete it when leaving the dynamic extent of this
610call."
57db49cc
LC
611 (let* ((directory (or (getenv "TMPDIR") "/tmp"))
612 (template (string-append directory "/guix-file.XXXXXX"))
613 (out (mkstemp! template)))
861693f3
LC
614 (dynamic-wind
615 (lambda ()
616 #t)
617 (lambda ()
618 (proc template out))
619 (lambda ()
620 (false-if-exception (close out))
621 (false-if-exception (delete-file template))))))
04d4c8a4 622
db6e5e2b
DT
623(define (call-with-temporary-directory proc)
624 "Call PROC with a name of a temporary directory; close the directory and
625delete it when leaving the dynamic extent of this call."
626 (let* ((directory (or (getenv "TMPDIR") "/tmp"))
627 (template (string-append directory "/guix-directory.XXXXXX"))
628 (tmp-dir (mkdtemp! template)))
629 (dynamic-wind
630 (const #t)
631 (lambda ()
632 (proc tmp-dir))
633 (lambda ()
27f7cbc9 634 (false-if-exception (delete-file-recursively tmp-dir))))))
db6e5e2b 635
04d4c8a4
LC
636(define (with-atomic-file-output file proc)
637 "Call PROC with an output port for the file that is going to replace FILE.
638Upon success, FILE is atomically replaced by what has been written to the
639output port, and PROC's result is returned."
640 (let* ((template (string-append file ".XXXXXX"))
641 (out (mkstemp! template)))
642 (with-throw-handler #t
643 (lambda ()
644 (let ((result (proc out)))
1752a17a
LC
645 (fdatasync out)
646 (close-port out)
04d4c8a4
LC
647 (rename-file template file)
648 result))
649 (lambda (key . args)
c25637df
LC
650 (false-if-exception (delete-file template))
651 (close-port out)))))
861693f3 652
f0e492f0
LC
653(define* (xdg-directory variable suffix #:key (ensure? #t))
654 "Return the name of the XDG directory that matches VARIABLE and SUFFIX,
655after making sure that it exists if ENSURE? is true. VARIABLE is an
656environment variable name like \"XDG_CONFIG_HOME\"; SUFFIX is a suffix like
657\"/.config\". Honor the XDG specs,
658<http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html>."
659 (let ((dir (and=> (or (getenv variable)
660 (and=> (or (getenv "HOME")
661 (passwd:dir (getpwuid (getuid))))
662 (cut string-append <> suffix)))
663 (cut string-append <> "/guix"))))
664 (when ensure?
665 (mkdir-p dir))
666 dir))
667
668(define config-directory
669 (cut xdg-directory "XDG_CONFIG_HOME" "/.config" <...>))
670
671(define cache-directory
672 (cut xdg-directory "XDG_CACHE_HOME" "/.cache" <...>))
739ab68b 673
d50cb56d
LC
674(define (readlink* file)
675 "Call 'readlink' until the result is not a symlink."
676 (define %max-symlink-depth 50)
677
678 (let loop ((file file)
679 (depth 0))
680 (define (absolute target)
681 (if (absolute-file-name? target)
682 target
683 (string-append (dirname file) "/" target)))
684
685 (if (>= depth %max-symlink-depth)
686 file
687 (call-with-values
688 (lambda ()
689 (catch 'system-error
690 (lambda ()
691 (values #t (readlink file)))
692 (lambda args
693 (let ((errno (system-error-errno args)))
694 (if (or (= errno EINVAL))
695 (values #f file)
696 (apply throw args))))))
697 (lambda (success? target)
698 (if success?
699 (loop (absolute target) (+ depth 1))
700 file))))))
c8be6f0d
FB
701
702(define (canonical-newline-port port)
703 "Return an input port that wraps PORT such that all newlines consist
704 of a single carriage return."
705 (define (get-position)
706 (if (port-has-port-position? port) (port-position port) #f))
707 (define (set-position! position)
708 (if (port-has-set-port-position!? port)
709 (set-port-position! position port)
710 #f))
711 (define (close) (close-port port))
712 (define (read! bv start n)
713 (let loop ((count 0)
714 (byte (get-u8 port)))
715 (cond ((eof-object? byte) count)
716 ((= count (- n 1))
717 (bytevector-u8-set! bv (+ start count) byte)
718 n)
719 ;; XXX: consume all LFs even if not followed by CR.
720 ((eqv? byte (char->integer #\return)) (loop count (get-u8 port)))
721 (else
722 (bytevector-u8-set! bv (+ start count) byte)
723 (loop (+ count 1) (get-u8 port))))))
724 (make-custom-binary-input-port "canonical-newline-port"
725 read!
726 get-position
727 set-position!
728 close))
ff352cfb
LC
729\f
730;;;
731;;; Source location.
732;;;
733
cbbbb7be
LC
734(define (absolute-dirname file)
735 "Return the absolute name of the directory containing FILE, or #f upon
736failure."
737 (match (search-path %load-path file)
738 (#f #f)
739 ((? string? file)
740 ;; If there are relative names in %LOAD-PATH, FILE can be relative and
741 ;; needs to be canonicalized.
742 (if (string-prefix? "/" file)
743 (dirname file)
744 (canonicalize-path (dirname file))))))
745
5dbae738
LC
746(define-syntax current-source-directory
747 (lambda (s)
d4dd37fc
LC
748 "Return the absolute name of the current directory, or #f if it could not
749be determined."
5dbae738
LC
750 (syntax-case s ()
751 ((_)
82781d87 752 (match (assq 'filename (or (syntax-source s) '()))
5dbae738 753 (('filename . (? string? file-name))
d4dd37fc 754 ;; If %FILE-PORT-NAME-CANONICALIZATION is 'relative, then FILE-NAME
cbbbb7be
LC
755 ;; can be relative. In that case, we try to find out at run time
756 ;; the absolute file name by looking at %LOAD-PATH; doing this at
757 ;; run time rather than expansion time is necessary to allow files
758 ;; to be moved on the file system.
a68d0f6f
LC
759 (cond ((not file-name)
760 #f) ;raising an error would upset Geiser users
761 ((string-prefix? "/" file-name)
762 (dirname file-name))
763 (else
764 #`(absolute-dirname #,file-name))))
82781d87 765 (#f
5dbae738 766 #f))))))
07c8a98c 767
ff352cfb
LC
768;; A source location.
769(define-record-type <location>
770 (make-location file line column)
771 location?
772 (file location-file) ; file name
773 (line location-line) ; 1-indexed line
774 (column location-column)) ; 0-indexed column
775
3059a35a
LC
776(define (location file line column)
777 "Return the <location> object for the given FILE, LINE, and COLUMN."
778 (and line column file
779 (make-location file line column)))
ff352cfb
LC
780
781(define (source-properties->location loc)
782 "Return a location object based on the info in LOC, an alist as returned
783by Guile's `source-properties', `frame-source', `current-source-location',
784etc."
223fa5b3
LC
785 ;; In accordance with the GCS, start line and column numbers at 1. Note
786 ;; that unlike LINE and `port-column', COL is actually 1-indexed here...
787 (match loc
788 ((('line . line) ('column . col) ('filename . file)) ;common case
789 (and file line col
790 (make-location file (+ line 1) col)))
791 (#f
792 #f)
793 (_
794 (let ((file (assq-ref loc 'filename))
795 (line (assq-ref loc 'line))
796 (col (assq-ref loc 'column)))
797 (location file (and line (+ line 1)) col)))))
f7df1e3e
SB
798
799(define (location->source-properties loc)
800 "Return the source property association list based on the info in LOC,
801a location object."
802 `((line . ,(and=> (location-line loc) 1-))
803 (column . ,(location-column loc))
804 (filename . ,(location-file loc))))
79864851 805
23735137
LC
806(define-condition-type &error-location &error
807 error-location?
808 (location error-location)) ;<location>
809
810(define-condition-type &fix-hint &condition
811 fix-hint?
812 (hint condition-fix-hint)) ;string
813
79864851
SB
814;;; Local Variables:
815;;; eval: (put 'call-with-progress-reporter 'scheme-indent-function 1)
816;;; End: