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