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