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