gnu: gcc-static: Remove -lgcc_s from GNU_USER_TARGET_LIB_SPEC.
[jackhill/guix/guix.git] / guix / utils.scm
CommitLineData
233e7676 1;;; GNU Guix --- Functional package management for GNU
2cd5c038 2;;; Copyright © 2012, 2013, 2014 Ludovic Courtès <ludo@gnu.org>
56b943de 3;;; Copyright © 2013 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>
e3deeebb 6;;;
233e7676 7;;; This file is part of GNU Guix.
e3deeebb 8;;;
233e7676 9;;; GNU Guix is free software; you can redistribute it and/or modify it
e3deeebb
LC
10;;; under the terms of the GNU General Public License as published by
11;;; the Free Software Foundation; either version 3 of the License, or (at
12;;; your option) any later version.
13;;;
233e7676 14;;; GNU Guix is distributed in the hope that it will be useful, but
e3deeebb
LC
15;;; WITHOUT ANY WARRANTY; without even the implied warranty of
16;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17;;; GNU General Public License for more details.
18;;;
19;;; You should have received a copy of the GNU General Public License
233e7676 20;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
e3deeebb
LC
21
22(define-module (guix utils)
00e219d1 23 #:use-module (guix config)
38b3122a 24 #:use-module (srfi srfi-1)
72d86963 25 #:use-module (srfi srfi-9)
01ac19dc 26 #:use-module (srfi srfi-11)
38b3122a 27 #:use-module (srfi srfi-26)
de4c3f26 28 #:use-module (srfi srfi-39)
e3deeebb
LC
29 #:use-module (srfi srfi-60)
30 #:use-module (rnrs bytevectors)
dba6b34b 31 #:use-module ((rnrs io ports) #:select (put-bytevector))
e0fbbc88 32 #:use-module ((guix build utils) #:select (dump-port))
29fa45f4 33 #:use-module ((guix build syscalls) #:select (errno))
c8369cac 34 #:use-module (ice-9 vlist)
38b3122a 35 #:use-module (ice-9 format)
de4c3f26
LC
36 #:autoload (ice-9 popen) (open-pipe*)
37 #:autoload (ice-9 rdelim) (read-line)
98090557 38 #:use-module (ice-9 regex)
72d86963 39 #:use-module (ice-9 match)
8ef3401f 40 #:use-module (ice-9 format)
2cd5c038 41 #:use-module (system foreign)
ddc29a78 42 #:export (bytevector->base16-string
6d800a80 43 base16-string->bytevector
de4c3f26
LC
44
45 %nixpkgs-directory
46 nixpkgs-derivation
ce5d658c 47 nixpkgs-derivation*
de4c3f26 48
900f7267 49 compile-time-value
2cd5c038 50 fcntl-flock
98090557 51 memoize
5e110382 52 strip-keyword-arguments
0af2c24e
LC
53 default-keyword-arguments
54 substitute-keyword-arguments
ff352cfb 55
64fc89b6 56 <location>
ff352cfb
LC
57 location
58 location?
59 location-file
60 location-line
61 location-column
62 source-properties->location
63
98090557 64 gnu-triplet->nix-system
9b48fb88 65 %current-system
9c1edabd 66 %current-target-system
0d1e6ce4
MW
67 version-compare
68 version>?
29a7c98a
ID
69 version-prefix
70 version-major+minor
7db3ff4a 71 guile-version>?
04fd96ca 72 package-name->name+version
2bcfb9e0 73 string-tokenize*
56b943de 74 string-replace-substring
16eb115e 75 arguments-from-environment-variable
0fdd3bea 76 file-extension
ac10e0e1 77 file-sans-extension
861693f3 78 call-with-temporary-output-file
04d4c8a4 79 with-atomic-file-output
e0fbbc88 80 fold2
516e3b6f
EB
81 fold-tree
82 fold-tree-leaves
7a8024a3
LC
83
84 filtered-port
85 compressed-port
80dea563 86 decompressed-port
01ac19dc
LC
87 call-with-decompressed-port
88 compressed-output-port
89 call-with-compressed-output-port))
e3deeebb 90
38b3122a 91\f
dba6b34b
LC
92;;;
93;;; Compile-time computations.
94;;;
95
96(define-syntax compile-time-value
97 (syntax-rules ()
98 "Evaluate the given expression at compile time. The expression must
99evaluate to a simple datum."
100 ((_ exp)
101 (let-syntax ((v (lambda (s)
102 (let ((val exp))
103 (syntax-case s ()
104 (_ #`'#,(datum->syntax s val)))))))
105 v))))
106
107\f
38b3122a
LC
108;;;
109;;; Base 16.
110;;;
111
112(define (bytevector->base16-string bv)
113 "Return the hexadecimal representation of BV's contents."
114 (define len
115 (bytevector-length bv))
116
117 (let-syntax ((base16-chars (lambda (s)
118 (syntax-case s ()
119 (_
120 (let ((v (list->vector
121 (unfold (cut > <> 255)
122 (lambda (n)
123 (format #f "~2,'0x" n))
124 1+
125 0))))
126 v))))))
127 (define chars base16-chars)
998fb8fa 128 (let loop ((i len)
38b3122a 129 (r '()))
998fb8fa
LC
130 (if (zero? i)
131 (string-concatenate r)
132 (let ((i (- i 1)))
133 (loop i
134 (cons (vector-ref chars (bytevector-u8-ref bv i)) r)))))))
38b3122a 135
6d800a80
LC
136(define base16-string->bytevector
137 (let ((chars->value (fold (lambda (i r)
138 (vhash-consv (string-ref (number->string i 16)
139 0)
140 i r))
141 vlist-null
142 (iota 16))))
143 (lambda (s)
144 "Return the bytevector whose hexadecimal representation is string S."
145 (define bv
146 (make-bytevector (quotient (string-length s) 2) 0))
147
148 (string-fold (lambda (chr i)
149 (let ((j (quotient i 2))
150 (v (and=> (vhash-assv chr chars->value) cdr)))
151 (if v
152 (if (zero? (logand i 1))
153 (bytevector-u8-set! bv j
154 (arithmetic-shift v 4))
155 (let ((w (bytevector-u8-ref bv j)))
156 (bytevector-u8-set! bv j (logior v w))))
157 (error "invalid hexadecimal character" chr)))
158 (+ i 1))
159 0
160 s)
161 bv)))
162
de4c3f26
LC
163
164\f
e0fbbc88
LC
165;;;
166;;; Filtering & pipes.
167;;;
168
169(define (filtered-port command input)
170 "Return an input port where data drained from INPUT is filtered through
171COMMAND (a list). In addition, return a list of PIDs that the caller must
101d9f3f
LC
172wait. When INPUT is a file port, it must be unbuffered; otherwise, any
173buffered data is lost."
e0fbbc88 174 (let loop ((input input)
443eb4e9 175 (pids '()))
e0fbbc88
LC
176 (if (file-port? input)
177 (match (pipe)
178 ((in . out)
179 (match (primitive-fork)
180 (0
443eb4e9
LC
181 (dynamic-wind
182 (const #f)
183 (lambda ()
184 (close-port in)
185 (close-port (current-input-port))
186 (dup2 (fileno input) 0)
187 (close-port (current-output-port))
188 (dup2 (fileno out) 1)
189 (catch 'system-error
190 (lambda ()
191 (apply execl (car command) command))
192 (lambda args
193 (format (current-error-port)
194 "filtered-port: failed to execute '~{~a ~}': ~a~%"
195 command (strerror (system-error-errno args))))))
196 (lambda ()
197 (primitive-_exit 1))))
e0fbbc88
LC
198 (child
199 (close-port out)
200 (values in (cons child pids))))))
201
202 ;; INPUT is not a file port, so fork just for the sake of tunneling it
203 ;; through a file port.
204 (match (pipe)
205 ((in . out)
206 (match (primitive-fork)
207 (0
208 (dynamic-wind
209 (const #t)
210 (lambda ()
211 (close-port in)
212 (dump-port input out))
213 (lambda ()
214 (false-if-exception (close out))
443eb4e9 215 (primitive-_exit 0))))
e0fbbc88
LC
216 (child
217 (close-port out)
218 (loop in (cons child pids)))))))))
219
7a8024a3
LC
220(define (decompressed-port compression input)
221 "Return an input port where INPUT is decompressed according to COMPRESSION,
222a symbol such as 'xz."
223 (match compression
224 ((or #f 'none) (values input '()))
225 ('bzip2 (filtered-port `(,%bzip2 "-dc") input))
226 ('xz (filtered-port `(,%xz "-dc") input))
227 ('gzip (filtered-port `(,%gzip "-dc") input))
228 (else (error "unsupported compression scheme" compression))))
229
230(define (compressed-port compression input)
231 "Return an input port where INPUT is decompressed according to COMPRESSION,
232a symbol such as 'xz."
233 (match compression
234 ((or #f 'none) (values input '()))
235 ('bzip2 (filtered-port `(,%bzip2 "-c") input))
236 ('xz (filtered-port `(,%xz "-c") input))
237 ('gzip (filtered-port `(,%gzip "-c") input))
238 (else (error "unsupported compression scheme" compression))))
239
01ac19dc
LC
240(define (call-with-decompressed-port compression port proc)
241 "Call PROC with a wrapper around PORT, a file port, that decompresses data
30ce8012 242read from PORT according to COMPRESSION, a symbol such as 'xz."
01ac19dc
LC
243 (let-values (((decompressed pids)
244 (decompressed-port compression port)))
245 (dynamic-wind
246 (const #f)
247 (lambda ()
01ac19dc
LC
248 (proc decompressed))
249 (lambda ()
250 (close-port decompressed)
251 (unless (every (compose zero? cdr waitpid) pids)
252 (error "decompressed-port failure" pids))))))
253
80dea563
LC
254(define (filtered-output-port command output)
255 "Return an output port. Data written to that port is filtered through
256COMMAND and written to OUTPUT, an output file port. In addition, return a
257list of PIDs to wait for. OUTPUT must be unbuffered; otherwise, any buffered
258data is lost."
259 (match (pipe)
260 ((in . out)
261 (match (primitive-fork)
262 (0
263 (dynamic-wind
264 (const #f)
265 (lambda ()
266 (close-port out)
267 (close-port (current-input-port))
268 (dup2 (fileno in) 0)
269 (close-port (current-output-port))
270 (dup2 (fileno output) 1)
271 (catch 'system-error
272 (lambda ()
273 (apply execl (car command) command))
274 (lambda args
275 (format (current-error-port)
276 "filtered-output-port: failed to execute '~{~a ~}': ~a~%"
277 command (strerror (system-error-errno args))))))
278 (lambda ()
279 (primitive-_exit 1))))
280 (child
281 (close-port in)
282 (values out (list child)))))))
283
284(define (compressed-output-port compression output)
285 "Return an output port whose input is compressed according to COMPRESSION,
286a symbol such as 'xz, and then written to OUTPUT. In addition return a list
287of PIDs to wait for."
288 (match compression
289 ((or #f 'none) (values output '()))
290 ('bzip2 (filtered-output-port `(,%bzip2 "-c") output))
291 ('xz (filtered-output-port `(,%xz "-c") output))
292 ('gzip (filtered-output-port `(,%gzip "-c") output))
293 (else (error "unsupported compression scheme" compression))))
294
01ac19dc
LC
295(define (call-with-compressed-output-port compression port proc)
296 "Call PROC with a wrapper around PORT, a file port, that compresses data
30ce8012 297that goes to PORT according to COMPRESSION, a symbol such as 'xz."
01ac19dc
LC
298 (let-values (((compressed pids)
299 (compressed-output-port compression port)))
300 (dynamic-wind
301 (const #f)
302 (lambda ()
01ac19dc
LC
303 (proc compressed))
304 (lambda ()
305 (close-port compressed)
306 (unless (every (compose zero? cdr waitpid) pids)
307 (error "compressed-output-port failure" pids))))))
308
e0fbbc88 309\f
de4c3f26
LC
310;;;
311;;; Nixpkgs.
312;;;
313
314(define %nixpkgs-directory
900f7267
LC
315 (make-parameter
316 ;; Capture the build-time value of $NIXPKGS.
a09ec3a5
LC
317 (or %nixpkgs
318 (and=> (getenv "NIXPKGS")
319 (lambda (val)
320 ;; Bail out when passed an empty string, otherwise
321 ;; `nix-instantiate' will sit there and attempt to read
322 ;; from its standard input.
323 (if (string=? val "")
324 #f
325 val))))))
de4c3f26 326
fbc93bed 327(define* (nixpkgs-derivation attribute #:optional (system (%current-system)))
de4c3f26 328 "Return the derivation path of ATTRIBUTE in Nixpkgs."
b86b0056 329 (let* ((p (open-pipe* OPEN_READ (or (getenv "NIX_INSTANTIATE")
00e219d1 330 %nix-instantiate)
fbc93bed
LC
331 "-A" attribute (%nixpkgs-directory)
332 "--argstr" "system" system))
de4c3f26
LC
333 (l (read-line p))
334 (s (close-pipe p)))
335 (and (zero? (status:exit-val s))
336 (not (eof-object? l))
337 l)))
338
ce5d658c
LC
339(define-syntax-rule (nixpkgs-derivation* attribute)
340 "Evaluate the given Nixpkgs derivation at compile-time."
341 (compile-time-value (nixpkgs-derivation attribute)))
342
de4c3f26 343\f
2cd5c038
LC
344;;;
345;;; Advisory file locking.
346;;;
347
348(define %struct-flock
349 ;; 'struct flock' from <fcntl.h>.
350 (list short ; l_type
351 short ; l_whence
352 size_t ; l_start
353 size_t ; l_len
354 int)) ; l_pid
355
356(define F_SETLKW
357 ;; On Linux-based systems, this is usually 7, but not always
358 ;; (exceptions include SPARC.) On GNU/Hurd, it's 9.
359 (compile-time-value
360 (cond ((string-contains %host-type "sparc") 9) ; sparc-*-linux-gnu
361 ((string-contains %host-type "linux") 7) ; *-linux-gnu
362 (else 9)))) ; *-gnu*
363
c7445833
LC
364(define F_SETLK
365 ;; Likewise: GNU/Hurd and SPARC use 8, while the others typically use 6.
366 (compile-time-value
367 (cond ((string-contains %host-type "sparc") 8) ; sparc-*-linux-gnu
368 ((string-contains %host-type "linux") 6) ; *-linux-gnu
369 (else 8)))) ; *-gnu*
370
2cd5c038
LC
371(define F_xxLCK
372 ;; The F_RDLCK, F_WRLCK, and F_UNLCK constants.
373 (compile-time-value
374 (cond ((string-contains %host-type "sparc") #(1 2 3)) ; sparc-*-linux-gnu
375 ((string-contains %host-type "hppa") #(1 2 3)) ; hppa-*-linux-gnu
376 ((string-contains %host-type "linux") #(0 1 2)) ; *-linux-gnu
377 (else #(1 2 3))))) ; *-gnu*
378
379(define fcntl-flock
380 (let* ((ptr (dynamic-func "fcntl" (dynamic-link)))
381 (proc (pointer->procedure int ptr `(,int ,int *))))
c7445833 382 (lambda* (fd-or-port operation #:key (wait? #t))
2cd5c038 383 "Perform locking OPERATION on the file beneath FD-OR-PORT. OPERATION
c7445833
LC
384must be a symbol, one of 'read-lock, 'write-lock, or 'unlock. When WAIT? is
385true, block until the lock is acquired; otherwise, thrown an 'flock-error'
386exception if it's already taken."
2cd5c038
LC
387 (define (operation->int op)
388 (case op
389 ((read-lock) (vector-ref F_xxLCK 0))
390 ((write-lock) (vector-ref F_xxLCK 1))
391 ((unlock) (vector-ref F_xxLCK 2))
392 (else (error "invalid fcntl-flock operation" op))))
393
394 (define fd
395 (if (port? fd-or-port)
396 (fileno fd-or-port)
397 fd-or-port))
398
399 ;; XXX: 'fcntl' is a vararg function, but here we happily use the
400 ;; standard ABI; crossing fingers.
401 (let ((err (proc fd
c7445833
LC
402 (if wait?
403 F_SETLKW ; lock & wait
404 F_SETLK) ; non-blocking attempt
2cd5c038
LC
405 (make-c-struct %struct-flock
406 (list (operation->int operation)
407 SEEK_SET
408 0 0 ; whole file
409 0)))))
410 (or (zero? err)
411
412 ;; Presumably we got EAGAIN or so.
9ea3ef26 413 (throw 'flock-error (errno)))))))
2cd5c038
LC
414
415\f
de4c3f26
LC
416;;;
417;;; Miscellaneous.
418;;;
419
420(define (memoize proc)
421 "Return a memoizing version of PROC."
422 (let ((cache (make-hash-table)))
423 (lambda args
424 (let ((results (hash-ref cache args)))
425 (if results
426 (apply values results)
427 (let ((results (call-with-values (lambda ()
428 (apply proc args))
429 list)))
430 (hash-set! cache args results)
431 (apply values results)))))))
98090557 432
5e110382
LC
433(define (strip-keyword-arguments keywords args)
434 "Remove all of the keyword arguments listed in KEYWORDS from ARGS."
435 (let loop ((args args)
436 (result '()))
437 (match args
438 (()
439 (reverse result))
440 (((? keyword? kw) arg . rest)
441 (loop rest
442 (if (memq kw keywords)
443 result
444 (cons* arg kw result))))
445 ((head . tail)
446 (loop tail (cons head result))))))
447
0af2c24e
LC
448(define (default-keyword-arguments args defaults)
449 "Return ARGS augmented with any keyword/value from DEFAULTS for
450keywords not already present in ARGS."
451 (let loop ((defaults defaults)
452 (args args))
453 (match defaults
454 ((kw value rest ...)
455 (loop rest
456 (if (assoc-ref kw args)
457 args
458 (cons* kw value args))))
459 (()
460 args))))
461
462(define-syntax substitute-keyword-arguments
463 (syntax-rules ()
464 "Return a new list of arguments where the value for keyword arg KW is
465replaced by EXP. EXP is evaluated in a context where VAR is boud to the
466previous value of the keyword argument."
467 ((_ original-args ((kw var) exp) ...)
468 (let loop ((args original-args)
469 (before '()))
470 (match args
471 ((kw var rest (... ...))
472 (loop rest (cons* exp kw before)))
473 ...
474 ((x rest (... ...))
475 (loop rest (cons x before)))
476 (()
477 (reverse before)))))))
478
98090557
LC
479(define (gnu-triplet->nix-system triplet)
480 "Return the Nix system type corresponding to TRIPLET, a GNU triplet as
481returned by `config.guess'."
482 (let ((triplet (cond ((string-match "^i[345]86-(.*)$" triplet)
483 =>
484 (lambda (m)
485 (string-append "i686-" (match:substring m 1))))
486 (else triplet))))
487 (cond ((string-match "^([^-]+)-([^-]+-)?linux-gnu.*" triplet)
488 =>
489 (lambda (m)
490 ;; Nix omits `-gnu' for GNU/Linux.
491 (string-append (match:substring m 1) "-linux")))
492 ((string-match "^([^-]+)-([^-]+-)?([[:alpha:]]+)([0-9]+\\.?)*$" triplet)
493 =>
494 (lambda (m)
495 ;; Nix strip the version number from names such as `gnu0.3',
496 ;; `darwin10.2.0', etc., and always strips the vendor part.
497 (string-append (match:substring m 1) "-"
498 (match:substring m 3))))
499 (else triplet))))
500
501(define %current-system
502 ;; System type as expected by Nix, usually ARCHITECTURE-KERNEL.
d8eea3d2
LC
503 ;; By default, this is equal to (gnu-triplet->nix-system %host-type).
504 (make-parameter %system))
ff352cfb 505
9c1edabd
LC
506(define %current-target-system
507 ;; Either #f or a GNU triplet representing the target system we are
508 ;; cross-building to.
509 (make-parameter #f))
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
MW
536(define (version>? a b)
537 "Return #t when A denotes a newer version than B."
538 (eq? '> (version-compare a b)))
539
7db3ff4a
LC
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
9b48fb88
LC
549(define (package-name->name+version name)
550 "Given NAME, a package name like \"foo-0.9.1b\", return two values:
551\"foo\" and \"0.9.1b\". When the version part is unavailable, NAME and
552#f are returned. The first hyphen followed by a digit is considered to
553introduce the version part."
554 ;; See also `DrvName' in Nix.
555
556 (define number?
557 (cut char-set-contains? char-set:digit <>))
558
559 (let loop ((chars (string->list name))
560 (prefix '()))
561 (match chars
562 (()
563 (values name #f))
564 ((#\- (? number? n) rest ...)
565 (values (list->string (reverse prefix))
566 (list->string (cons n rest))))
567 ((head tail ...)
568 (loop tail (cons head prefix))))))
569
0fdd3bea
LC
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
ac10e0e1
LC
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
2bcfb9e0
LC
582(define (string-tokenize* string separator)
583 "Return the list of substrings of STRING separated by SEPARATOR. This is
584like `string-tokenize', but SEPARATOR is a string."
585 (define (index string what)
586 (let loop ((string string)
587 (offset 0))
588 (cond ((string-null? string)
589 #f)
590 ((string-prefix? what string)
591 offset)
592 (else
593 (loop (string-drop string 1) (+ 1 offset))))))
594
595 (define len
596 (string-length separator))
597
598 (let loop ((string string)
599 (result '()))
600 (cond ((index string separator)
601 =>
602 (lambda (offset)
603 (loop (string-drop string (+ offset len))
604 (cons (substring string 0 offset)
605 result))))
606 (else
607 (reverse (cons string result))))))
608
56b943de
LC
609(define* (string-replace-substring str substr replacement
610 #:optional
611 (start 0)
612 (end (string-length str)))
613 "Replace all occurrences of SUBSTR in the START--END range of STR by
614REPLACEMENT."
615 (match (string-length substr)
616 (0
617 (error "string-replace-substring: empty substring"))
618 (substr-length
619 (let loop ((start start)
620 (pieces (list (substring str 0 start))))
621 (match (string-contains str substr start end)
622 (#f
623 (string-concatenate-reverse
624 (cons (substring str start) pieces)))
625 (index
626 (loop (+ index substr-length)
627 (cons* replacement
628 (substring str start index)
629 pieces))))))))
630
16eb115e
DP
631(define (arguments-from-environment-variable variable)
632 "Retrieve value of environment variable denoted by string VARIABLE in the
633form of a list of strings (`char-set:graphic' tokens) suitable for consumption
634by `args-fold', if VARIABLE is defined, otherwise return an empty list."
635 (let ((env (getenv variable)))
636 (if env
637 (string-tokenize env char-set:graphic)
638 '())))
639
861693f3
LC
640(define (call-with-temporary-output-file proc)
641 "Call PROC with a name of a temporary file and open output port to that
642file; close the file and delete it when leaving the dynamic extent of this
643call."
57db49cc
LC
644 (let* ((directory (or (getenv "TMPDIR") "/tmp"))
645 (template (string-append directory "/guix-file.XXXXXX"))
646 (out (mkstemp! template)))
861693f3
LC
647 (dynamic-wind
648 (lambda ()
649 #t)
650 (lambda ()
651 (proc template out))
652 (lambda ()
653 (false-if-exception (close out))
654 (false-if-exception (delete-file template))))))
04d4c8a4
LC
655
656(define (with-atomic-file-output file proc)
657 "Call PROC with an output port for the file that is going to replace FILE.
658Upon success, FILE is atomically replaced by what has been written to the
659output port, and PROC's result is returned."
660 (let* ((template (string-append file ".XXXXXX"))
661 (out (mkstemp! template)))
662 (with-throw-handler #t
663 (lambda ()
664 (let ((result (proc out)))
665 (close out)
666 (rename-file template file)
667 result))
668 (lambda (key . args)
669 (false-if-exception (delete-file template))))))
861693f3 670
04fd96ca
LC
671(define fold2
672 (case-lambda
673 ((proc seed1 seed2 lst)
674 "Like `fold', but with a single list and two seeds."
675 (let loop ((result1 seed1)
676 (result2 seed2)
677 (lst lst))
678 (if (null? lst)
679 (values result1 result2)
680 (call-with-values
681 (lambda () (proc (car lst) result1 result2))
682 (lambda (result1 result2)
683 (loop result1 result2 (cdr lst)))))))
684 ((proc seed1 seed2 lst1 lst2)
685 "Like `fold', but with a two lists and two seeds."
686 (let loop ((result1 seed1)
687 (result2 seed2)
688 (lst1 lst1)
689 (lst2 lst2))
690 (if (or (null? lst1) (null? lst2))
691 (values result1 result2)
692 (call-with-values
693 (lambda () (proc (car lst1) (car lst2) result1 result2))
694 (lambda (result1 result2)
695 (fold2 proc result1 result2 (cdr lst1) (cdr lst2)))))))))
696
516e3b6f
EB
697(define (fold-tree proc init children roots)
698 "Call (PROC NODE RESULT) for each node in the tree that is reachable from
699ROOTS, using INIT as the initial value of RESULT. The order in which nodes
700are traversed is not specified, however, each node is visited only once, based
701on an eq? check. Children of a node to be visited are generated by
702calling (CHILDREN NODE), the result of which should be a list of nodes that
703are connected to NODE in the tree, or '() or #f if NODE is a leaf node."
704 (let loop ((result init)
705 (seen vlist-null)
706 (lst roots))
707 (match lst
708 (() result)
709 ((head . tail)
710 (if (not (vhash-assq head seen))
711 (loop (proc head result)
712 (vhash-consq head #t seen)
713 (match (children head)
714 ((or () #f) tail)
715 (children (append tail children))))
716 (loop result seen tail))))))
717
718(define (fold-tree-leaves proc init children roots)
719 "Like fold-tree, but call (PROC NODE RESULT) only for leaf nodes."
720 (fold-tree
721 (lambda (node result)
722 (match (children node)
723 ((or () #f) (proc node result))
724 (else result)))
725 init children roots))
726
ff352cfb
LC
727\f
728;;;
729;;; Source location.
730;;;
731
732;; A source location.
733(define-record-type <location>
734 (make-location file line column)
735 location?
736 (file location-file) ; file name
737 (line location-line) ; 1-indexed line
738 (column location-column)) ; 0-indexed column
739
740(define location
741 (memoize
742 (lambda (file line column)
743 "Return the <location> object for the given FILE, LINE, and COLUMN."
744 (and line column file
745 (make-location file line column)))))
746
747(define (source-properties->location loc)
748 "Return a location object based on the info in LOC, an alist as returned
749by Guile's `source-properties', `frame-source', `current-source-location',
750etc."
751 (let ((file (assq-ref loc 'filename))
752 (line (assq-ref loc 'line))
753 (col (assq-ref loc 'column)))
5fe21fbe
LC
754 ;; In accordance with the GCS, start line and column numbers at 1. Note
755 ;; that unlike LINE and `port-column', COL is actually 1-indexed here...
756 (location file (and line (+ line 1)) col)))