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