utils: Switch to the new 'setvbuf' API.
[jackhill/guix/guix.git] / guix / build / utils.scm
CommitLineData
4155e2a9 1;;; GNU Guix --- Functional package management for GNU
782f1ea9 2;;; Copyright © 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 Ludovic Courtès <ludo@gnu.org>
11996d85 3;;; Copyright © 2013 Andreas Enge <andreas@enge.fr>
30db6af1 4;;; Copyright © 2013 Nikita Karetnikov <nikita@karetnikov.org>
cbdfa50d 5;;; Copyright © 2015, 2018 Mark H Weaver <mhw@netris.org>
89e7f90d 6;;; Copyright © 2018 Arun Isaac <arunisaac@systemreboot.net>
c36db98c 7;;;
4155e2a9 8;;; This file is part of GNU Guix.
c36db98c 9;;;
4155e2a9 10;;; GNU Guix is free software; you can redistribute it and/or modify it
c36db98c
LC
11;;; under the terms of the GNU General Public License as published by
12;;; the Free Software Foundation; either version 3 of the License, or (at
13;;; your option) any later version.
14;;;
4155e2a9 15;;; GNU Guix is distributed in the hope that it will be useful, but
c36db98c
LC
16;;; WITHOUT ANY WARRANTY; without even the implied warranty of
17;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;;; GNU General Public License for more details.
19;;;
20;;; You should have received a copy of the GNU General Public License
4155e2a9 21;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
c36db98c
LC
22
23(define-module (guix build utils)
24 #:use-module (srfi srfi-1)
b0e0d0e9 25 #:use-module (srfi srfi-11)
8c578a60 26 #:use-module (srfi srfi-26)
cbdfa50d
MW
27 #:use-module (srfi srfi-34)
28 #:use-module (srfi srfi-35)
251e8b2e 29 #:use-module (srfi srfi-60)
c0746cc9 30 #:use-module (ice-9 ftw)
b0e0d0e9
LC
31 #:use-module (ice-9 match)
32 #:use-module (ice-9 regex)
33 #:use-module (ice-9 rdelim)
c041886d 34 #:use-module (ice-9 format)
325ff6fe 35 #:use-module (ice-9 threads)
ebe2f31f
LC
36 #:use-module (rnrs bytevectors)
37 #:use-module (rnrs io ports)
d475b259 38 #:re-export (alist-cons
8b14773a
ST
39 alist-delete
40
41 ;; Note: Re-export 'delete' to allow for proper syntax matching
42 ;; in 'modify-phases' forms. See
43 ;; <https://debbugs.gnu.org/cgi/bugreport.cgi?bug=26805#16>.
44 delete)
8be3b8a3 45 #:export (%store-directory
dacd5d2c 46 store-file-name?
b7c7c03e 47 strip-store-file-name
8c578a60 48 package-name->name+version
e6039b9c
LC
49 parallel-job-count
50
8be3b8a3 51 directory-exists?
d0084152 52 executable-file?
9741aca9 53 symbolic-link?
c0895112 54 call-with-ascii-input-file
99533da5 55 elf-file?
91ee959b 56 ar-file?
95e7be97
LC
57 gzip-file?
58 reset-gzip-timestamp
b0e0d0e9 59 with-directory-excursion
7da95264 60 mkdir-p
4cc2ed98 61 install-file
5a64a791 62 make-file-writable
c0746cc9 63 copy-recursively
e65df6a6 64 delete-file-recursively
1968262a 65 file-name-predicate
4c261f41 66 find-files
5c6391b3 67 false-if-file-not-found
4c261f41 68
bd2fc4d8 69 search-path-as-list
b0e0d0e9 70 set-path-environment-variable
ebe2f31f
LC
71 search-path-as-string->list
72 list->search-path-as-string
7584f822
LC
73 which
74
57aff647 75 every*
b0e0d0e9
LC
76 alist-cons-before
77 alist-cons-after
78 alist-replace
8ddc41e1 79 modify-phases
57aff647 80
dcd72906 81 with-atomic-file-replacement
10c87717 82 substitute
ebe2f31f
LC
83 substitute*
84 dump-port
bc5bf85f 85 set-file-time
91133c2d 86 patch-shebang
c0895112 87 patch-makefile-SHELL
4eb01e54 88 patch-/usr/bin/file
91133c2d 89 fold-port-matches
30db6af1 90 remove-store-references
89e7f90d 91 wrapper?
251e8b2e 92 wrap-program
cbdfa50d 93
3f65c190 94 invoke
cbdfa50d
MW
95 invoke-error?
96 invoke-error-program
97 invoke-error-arguments
98 invoke-error-exit-status
99 invoke-error-term-signal
100 invoke-error-stop-signal
251e8b2e
LC
101
102 locale-category->string))
b0e0d0e9 103
782f1ea9
LC
104\f
105;;;
106;;; Guile 2.0 compatibility later.
107;;;
11996d85 108
782f1ea9
LC
109;; The bootstrap Guile is Guile 2.0, so provide a compatibility layer.
110(cond-expand
111 ((and guile-2 (not guile-2.2))
112 (define (setvbuf port mode . rest)
113 (apply (@ (guile) setvbuf) port
114 (match mode
115 ('line _IOLBF)
116 ('block _IOFBF)
117 ('none _IONBF)
118 (_ mode)) ;an _IO* integer
119 rest))
120
121 (module-replace! (current-module) '(setvbuf)))
122 (else #f))
123
124\f
b0e0d0e9
LC
125;;;
126;;; Directories.
127;;;
c36db98c 128
8be3b8a3
LC
129(define (%store-directory)
130 "Return the directory name of the store."
131 (or (getenv "NIX_STORE")
132 "/gnu/store"))
133
dacd5d2c
LC
134(define (store-file-name? file)
135 "Return true if FILE is in the store."
136 (string-prefix? (%store-directory) file))
137
b7c7c03e
LC
138(define (strip-store-file-name file)
139 "Strip the '/gnu/store' and hash from FILE, a store file name. The result
140is typically a \"PACKAGE-VERSION\" string."
141 (string-drop file
142 (+ 34 (string-length (%store-directory)))))
143
8c578a60
LC
144(define (package-name->name+version name)
145 "Given NAME, a package name like \"foo-0.9.1b\", return two values:
146\"foo\" and \"0.9.1b\". When the version part is unavailable, NAME and
147#f are returned. The first hyphen followed by a digit is considered to
148introduce the version part."
149 ;; See also `DrvName' in Nix.
150
151 (define number?
152 (cut char-set-contains? char-set:digit <>))
153
154 (let loop ((chars (string->list name))
155 (prefix '()))
156 (match chars
157 (()
158 (values name #f))
159 ((#\- (? number? n) rest ...)
160 (values (list->string (reverse prefix))
161 (list->string (cons n rest))))
162 ((head tail ...)
163 (loop tail (cons head prefix))))))
164
1d1fa932
LC
165(define parallel-job-count
166 ;; Number of processes to be passed next to GNU Make's `-j' argument.
167 (make-parameter
168 (match (getenv "NIX_BUILD_CORES") ;set by the daemon
169 (#f 1)
170 ("0" (current-processor-count))
171 (x (or (string->number x) 1)))))
e6039b9c 172
c36db98c
LC
173(define (directory-exists? dir)
174 "Return #t if DIR exists and is a directory."
9f55cf8d
LC
175 (let ((s (stat dir #f)))
176 (and s
177 (eq? 'directory (stat:type s)))))
c36db98c 178
d0084152
LC
179(define (executable-file? file)
180 "Return #t if FILE exists and is executable."
181 (let ((s (stat file #f)))
182 (and s
183 (not (zero? (logand (stat:mode s) #o100))))))
184
9741aca9
LC
185(define (symbolic-link? file)
186 "Return #t if FILE is a symbolic link (aka. \"symlink\".)"
187 (eq? (stat:type (lstat file)) 'symlink))
188
c0895112
LC
189(define (call-with-ascii-input-file file proc)
190 "Open FILE as an ASCII or binary file, and pass the resulting port to
191PROC. FILE is closed when PROC's dynamic extent is left. Return the
192return values of applying PROC to the port."
193 (let ((port (with-fluids ((%default-port-encoding #f))
194 ;; Use "b" so that `open-file' ignores `coding:' cookies.
195 (open-file file "rb"))))
196 (dynamic-wind
197 (lambda ()
198 #t)
199 (lambda ()
200 (proc port))
201 (lambda ()
202 (close-input-port port)))))
203
2bbc6db5
LC
204(define (file-header-match header)
205 "Return a procedure that returns true when its argument is a file starting
206with the bytes in HEADER, a bytevector."
207 (define len
208 (bytevector-length header))
99533da5 209
2bbc6db5
LC
210 (lambda (file)
211 "Return true if FILE starts with the right magic bytes."
212 (define (get-header)
213 (call-with-input-file file
214 (lambda (port)
215 (get-bytevector-n port len))
216 #:binary #t #:guess-encoding #f))
217
c23d1709
LC
218 (catch 'system-error
219 (lambda ()
220 (equal? (get-header) header))
221 (lambda args
222 (if (= EISDIR (system-error-errno args))
223 #f ;FILE is a directory
224 (apply throw args))))))
2bbc6db5
LC
225
226(define %elf-magic-bytes
227 ;; Magic bytes of ELF files. See <elf.h>.
228 (u8-list->bytevector (map char->integer (string->list "\x7FELF"))))
229
230(define elf-file?
231 (file-header-match %elf-magic-bytes))
99533da5 232
91ee959b
LC
233(define %ar-magic-bytes
234 ;; Magic bytes of archives created by 'ar'. See <ar.h>.
235 (u8-list->bytevector (map char->integer (string->list "!<arch>\n"))))
236
2bbc6db5
LC
237(define ar-file?
238 (file-header-match %ar-magic-bytes))
91ee959b 239
95e7be97
LC
240(define %gzip-magic-bytes
241 ;; Magic bytes of gzip file. Beware, it's a small header so there could be
242 ;; false positives.
243 #vu8(#x1f #x8b))
244
245(define gzip-file?
246 (file-header-match %gzip-magic-bytes))
247
248(define* (reset-gzip-timestamp file #:key (keep-mtime? #t))
249 "If FILE is a gzip file, reset its embedded timestamp (as with 'gzip
250--no-name') and return true. Otherwise return #f. When KEEP-MTIME? is true,
251preserve FILE's modification time."
252 (let ((stat (stat file))
253 (port (open file O_RDWR)))
254 (dynamic-wind
255 (const #t)
256 (lambda ()
257 (and (= 4 (seek port 4 SEEK_SET))
258 (put-bytevector port #vu8(0 0 0 0))))
259 (lambda ()
260 (close-port port)
261 (set-file-time file stat)))))
262
b0e0d0e9
LC
263(define-syntax-rule (with-directory-excursion dir body ...)
264 "Run BODY with DIR as the process's current directory."
265 (let ((init (getcwd)))
266 (dynamic-wind
267 (lambda ()
268 (chdir dir))
269 (lambda ()
270 body ...)
271 (lambda ()
272 (chdir init)))))
273
7da95264
LC
274(define (mkdir-p dir)
275 "Create directory DIR and all its ancestors."
276 (define absolute?
277 (string-prefix? "/" dir))
278
279 (define not-slash
280 (char-set-complement (char-set #\/)))
281
282 (let loop ((components (string-tokenize dir not-slash))
283 (root (if absolute?
284 ""
285 ".")))
286 (match components
287 ((head tail ...)
288 (let ((path (string-append root "/" head)))
289 (catch 'system-error
290 (lambda ()
291 (mkdir path)
292 (loop tail path))
293 (lambda args
294 (if (= EEXIST (system-error-errno args))
295 (loop tail path)
296 (apply throw args))))))
297 (() #t))))
298
4cc2ed98
LC
299(define (install-file file directory)
300 "Create DIRECTORY if it does not exist and copy FILE in there under the same
301name."
302 (mkdir-p directory)
303 (copy-file file (string-append directory "/" (basename file))))
304
5a64a791
MB
305(define (make-file-writable file)
306 "Make FILE writable for its owner."
307 (let ((stat (lstat file))) ;XXX: symlinks
308 (chmod file (logior #o600 (stat:perms stat)))))
309
c0746cc9 310(define* (copy-recursively source destination
12761f48
LC
311 #:key
312 (log (current-output-port))
8d846470
LC
313 (follow-symlinks? #f)
314 keep-mtime?)
12761f48 315 "Copy SOURCE directory to DESTINATION. Follow symlinks if FOLLOW-SYMLINKS?
8d846470
LC
316is true; otherwise, just preserve them. When KEEP-MTIME? is true, keep the
317modification time of the files in SOURCE on those of DESTINATION. Write
318verbose output to the LOG port."
c0746cc9
LC
319 (define strip-source
320 (let ((len (string-length source)))
321 (lambda (file)
322 (substring file len))))
323
324 (file-system-fold (const #t) ; enter?
325 (lambda (file stat result) ; leaf
326 (let ((dest (string-append destination
327 (strip-source file))))
328 (format log "`~a' -> `~a'~%" file dest)
12761f48
LC
329 (case (stat:type stat)
330 ((symlink)
331 (let ((target (readlink file)))
332 (symlink target dest)))
333 (else
8d846470
LC
334 (copy-file file dest)
335 (when keep-mtime?
336 (set-file-time dest stat))))))
c0746cc9 337 (lambda (dir stat result) ; down
8d846470
LC
338 (let ((target (string-append destination
339 (strip-source dir))))
340 (mkdir-p target)
341 (when keep-mtime?
342 (set-file-time target stat))))
c0746cc9
LC
343 (lambda (dir stat result) ; up
344 result)
345 (const #t) ; skip
346 (lambda (file stat errno result)
347 (format (current-error-port) "i/o error: ~a: ~a~%"
348 file (strerror errno))
349 #f)
350 #t
12761f48
LC
351 source
352
353 (if follow-symlinks?
354 stat
355 lstat)))
c0746cc9 356
d84a7be6
LC
357(define* (delete-file-recursively dir
358 #:key follow-mounts?)
359 "Delete DIR recursively, like `rm -rf', without following symlinks. Don't
360follow mount points either, unless FOLLOW-MOUNTS? is true. Report but ignore
361errors."
362 (let ((dev (stat:dev (lstat dir))))
363 (file-system-fold (lambda (dir stat result) ; enter?
364 (or follow-mounts?
365 (= dev (stat:dev stat))))
366 (lambda (file stat result) ; leaf
367 (delete-file file))
368 (const #t) ; down
369 (lambda (dir stat result) ; up
370 (rmdir dir))
371 (const #t) ; skip
372 (lambda (file stat errno result)
373 (format (current-error-port)
374 "warning: failed to delete ~a: ~a~%"
375 file (strerror errno)))
376 #t
377 dir
378
379 ;; Don't follow symlinks.
380 lstat)))
e65df6a6 381
1968262a
LC
382(define (file-name-predicate regexp)
383 "Return a predicate that returns true when passed a file name whose base
384name matches REGEXP."
385 (let ((file-rx (if (regexp? regexp)
386 regexp
387 (make-regexp regexp))))
388 (lambda (file stat)
389 (regexp-exec file-rx (basename file)))))
390
347f54ed 391(define* (find-files dir #:optional (pred (const #t))
f4ae827e
MW
392 #:key (stat lstat)
393 directories?
394 fail-on-error?)
1968262a
LC
395 "Return the lexicographically sorted list of files under DIR for which PRED
396returns true. PRED is passed two arguments: the absolute file name, and its
4ba3a84d
LC
397stat buffer; the default predicate always returns true. PRED can also be a
398regular expression, in which case it is equivalent to (file-name-predicate
347f54ed 399PRED). STAT is used to obtain file information; using 'lstat' means that
f4ae827e
MW
400symlinks are not followed. If DIRECTORIES? is true, then directories will
401also be included. If FAIL-ON-ERROR? is true, raise an exception upon error."
1968262a
LC
402 (let ((pred (if (procedure? pred)
403 pred
404 (file-name-predicate pred))))
405 ;; Sort the result to get deterministic results.
406 (sort (file-system-fold (const #t)
407 (lambda (file stat result) ; leaf
408 (if (pred file stat)
409 (cons file result)
410 result))
411 (lambda (dir stat result) ; down
f4ae827e
MW
412 (if (and directories?
413 (pred dir stat))
414 (cons dir result)
415 result))
1968262a
LC
416 (lambda (dir stat result) ; up
417 result)
418 (lambda (file stat result) ; skip
419 result)
420 (lambda (file stat errno result)
421 (format (current-error-port) "find-files: ~a: ~a~%"
422 file (strerror errno))
f4ae827e
MW
423 (when fail-on-error?
424 (error "find-files failed"))
1968262a
LC
425 result)
426 '()
347f54ed
LC
427 dir
428 stat)
1968262a 429 string<?)))
4c261f41 430
5c6391b3
LC
431(define-syntax-rule (false-if-file-not-found exp)
432 "Evaluate EXP but return #f if it raises to 'system-error with ENOENT."
433 (catch 'system-error
434 (lambda () exp)
435 (lambda args
436 (if (= ENOENT (system-error-errno args))
437 #f
438 (apply throw args)))))
439
b0e0d0e9
LC
440\f
441;;;
442;;; Search paths.
443;;;
444
6aa47e38 445(define* (search-path-as-list files input-dirs
7ec02d37 446 #:key (type 'directory) pattern)
6aa47e38
LC
447 "Return the list of directories among FILES of the given TYPE (a symbol as
448returned by 'stat:type') that exist in INPUT-DIRS. Example:
c36db98c
LC
449
450 (search-path-as-list '(\"share/emacs/site-lisp\" \"share/emacs/24.1\")
451 (list \"/package1\" \"/package2\" \"/package3\"))
452 => (\"/package1/share/emacs/site-lisp\"
453 \"/package3/share/emacs/site-lisp\")
454
7ec02d37
LC
455When PATTERN is true, it is a regular expression denoting file names to look
456for under the directories designated by FILES. For example:
457
458 (search-path-as-list '(\"xml\") (list docbook-xml docbook-xsl)
459 #:type 'regular
460 #:pattern \"^catalog\\\\.xml$\")
461 => (\"/…/xml/dtd/docbook/catalog.xml\"
462 \"/…/xml/xsl/docbook-xsl-1.78.1/catalog.xml\")
c36db98c
LC
463"
464 (append-map (lambda (input)
7ec02d37
LC
465 (append-map (lambda (file)
466 (let ((file (string-append input "/" file)))
7ec02d37 467 (if pattern
c6e030b2
EB
468 (find-files file (lambda (file stat)
469 (and stat
470 (eq? type (stat:type stat))
471 ((file-name-predicate pattern) file stat)))
472 #:stat stat
473 #:directories? #t)
7ec02d37
LC
474 (let ((stat (stat file #f)))
475 (if (and stat (eq? type (stat:type stat)))
476 (list file)
477 '())))))
6aa47e38 478 files))
d5b3de6a 479 (delete-duplicates input-dirs)))
c36db98c
LC
480
481(define (list->search-path-as-string lst separator)
fcd75bdb
LC
482 (if separator
483 (string-join lst separator)
484 (match lst
485 ((head rest ...) head)
486 (() ""))))
c36db98c 487
ebe2f31f 488(define* (search-path-as-string->list path #:optional (separator #\:))
fcd75bdb
LC
489 (if separator
490 (string-tokenize path
491 (char-set-complement (char-set separator)))
492 (list path)))
ebe2f31f 493
6aa47e38
LC
494(define* (set-path-environment-variable env-var files input-dirs
495 #:key
496 (separator ":")
7ec02d37
LC
497 (type 'directory)
498 pattern)
6aa47e38
LC
499 "Look for each of FILES of the given TYPE (a symbol as returned by
500'stat:type') in INPUT-DIRS. Set ENV-VAR to a SEPARATOR-separated path
501accordingly. Example:
c36db98c
LC
502
503 (set-path-environment-variable \"PKG_CONFIG\"
504 '(\"lib/pkgconfig\")
505 (list package1 package2))
7ec02d37
LC
506
507When PATTERN is not #f, it must be a regular expression (really a string)
508denoting file names to look for under the directories designated by FILES:
509
510 (set-path-environment-variable \"XML_CATALOG_FILES\"
511 '(\"xml\")
512 (list docbook-xml docbook-xsl)
513 #:type 'regular
514 #:pattern \"^catalog\\\\.xml$\")
c36db98c 515"
6aa47e38 516 (let* ((path (search-path-as-list files input-dirs
7ec02d37
LC
517 #:type type
518 #:pattern pattern))
9d9ef458 519 (value (list->search-path-as-string path separator)))
b15669f3
LC
520 (if (string-null? value)
521 (begin
522 ;; Never set ENV-VAR to an empty string because often, the empty
523 ;; string is equivalent to ".". This is the case for
524 ;; GUILE_LOAD_PATH in Guile 2.0, for instance.
525 (unsetenv env-var)
526 (format #t "environment variable `~a' unset~%" env-var))
527 (begin
528 (setenv env-var value)
529 (format #t "environment variable `~a' set to `~a'~%"
530 env-var value)))))
b0e0d0e9 531
7584f822
LC
532(define (which program)
533 "Return the complete file name for PROGRAM as found in $PATH, or #f if
534PROGRAM could not be found."
535 (search-path (search-path-as-string->list (getenv "PATH"))
536 program))
537
b0e0d0e9
LC
538\f
539;;;
540;;; Phases.
541;;;
542;;; In (guix build gnu-build-system), there are separate phases (configure,
543;;; build, test, install). They are represented as a list of name/procedure
544;;; pairs. The following procedures make it easy to change the list of
545;;; phases.
546;;;
547
57aff647
LC
548(define (every* pred lst)
549 "This is like 'every', but process all the elements of LST instead of
550stopping as soon as PRED returns false. This is useful when PRED has side
551effects, such as displaying warnings or error messages."
552 (let loop ((lst lst)
553 (result #t))
554 (match lst
555 (()
556 result)
557 ((head . tail)
558 (loop tail (and (pred head) result))))))
559
b0e0d0e9
LC
560(define* (alist-cons-before reference key value alist
561 #:optional (key=? equal?))
562 "Insert the KEY/VALUE pair before the first occurrence of a pair whose key
563is REFERENCE in ALIST. Use KEY=? to compare keys."
564 (let-values (((before after)
565 (break (match-lambda
566 ((k . _)
567 (key=? k reference)))
568 alist)))
569 (append before (alist-cons key value after))))
570
571(define* (alist-cons-after reference key value alist
572 #:optional (key=? equal?))
573 "Insert the KEY/VALUE pair after the first occurrence of a pair whose key
574is REFERENCE in ALIST. Use KEY=? to compare keys."
575 (let-values (((before after)
576 (break (match-lambda
577 ((k . _)
578 (key=? k reference)))
579 alist)))
580 (match after
581 ((reference after ...)
582 (append before (cons* reference `(,key . ,value) after)))
583 (()
584 (append before `((,key . ,value)))))))
585
586(define* (alist-replace key value alist #:optional (key=? equal?))
587 "Replace the first pair in ALIST whose car is KEY with the KEY/VALUE pair.
588An error is raised when no such pair exists."
589 (let-values (((before after)
590 (break (match-lambda
591 ((k . _)
592 (key=? k key)))
593 alist)))
594 (match after
595 ((_ after ...)
596 (append before (alist-cons key value after))))))
597
8ddc41e1
LC
598(define-syntax-rule (modify-phases phases mod-spec ...)
599 "Modify PHASES sequentially as per each MOD-SPEC, which may have one of the
600following forms:
601
602 (delete <old-phase-name>)
603 (replace <old-phase-name> <new-phase>)
604 (add-before <old-phase-name> <new-phase-name> <new-phase>)
605 (add-after <old-phase-name> <new-phase-name> <new-phase>)
606
7ee5a694
TUBK
607Where every <*-phase-name> is an expression evaluating to a symbol, and
608<new-phase> an expression evaluating to a procedure."
8ddc41e1
LC
609 (let* ((phases* phases)
610 (phases* (%modify-phases phases* mod-spec))
611 ...)
612 phases*))
613
614(define-syntax %modify-phases
615 (syntax-rules (delete replace add-before add-after)
616 ((_ phases (delete old-phase-name))
f8503e2b 617 (alist-delete old-phase-name phases))
8ddc41e1 618 ((_ phases (replace old-phase-name new-phase))
f8503e2b 619 (alist-replace old-phase-name new-phase phases))
8ddc41e1 620 ((_ phases (add-before old-phase-name new-phase-name new-phase))
f8503e2b 621 (alist-cons-before old-phase-name new-phase-name new-phase phases))
8ddc41e1 622 ((_ phases (add-after old-phase-name new-phase-name new-phase))
f8503e2b 623 (alist-cons-after old-phase-name new-phase-name new-phase phases))))
8ddc41e1 624
cbdfa50d
MW
625(define-condition-type &invoke-error &error
626 invoke-error?
627 (program invoke-error-program)
628 (arguments invoke-error-arguments)
629 (exit-status invoke-error-exit-status)
630 (term-signal invoke-error-term-signal)
631 (stop-signal invoke-error-stop-signal))
632
3f65c190 633(define (invoke program . args)
cbdfa50d
MW
634 "Invoke PROGRAM with the given ARGS. Raise an exception
635if the exit code is non-zero; otherwise return #t."
636 (let ((code (apply system* program args)))
637 (unless (zero? code)
638 (raise (condition (&invoke-error
639 (program program)
640 (arguments args)
641 (exit-status (status:exit-val code))
642 (term-signal (status:term-sig code))
643 (stop-signal (status:stop-sig code))))))
3f65c190
DM
644 #t))
645
b0e0d0e9
LC
646\f
647;;;
648;;; Text substitution (aka. sed).
649;;;
650
dcd72906
LC
651(define (with-atomic-file-replacement file proc)
652 "Call PROC with two arguments: an input port for FILE, and an output
653port for the file that is going to replace FILE. Upon success, FILE is
654atomically replaced by what has been written to the output port, and
655PROC's result is returned."
656 (let* ((template (string-append file ".XXXXXX"))
d9dbab18
LC
657 (out (mkstemp! template))
658 (mode (stat:mode (stat file))))
b0e0d0e9
LC
659 (with-throw-handler #t
660 (lambda ()
661 (call-with-input-file file
662 (lambda (in)
dcd72906
LC
663 (let ((result (proc in out)))
664 (close out)
665 (chmod template mode)
666 (rename-file template file)
667 result))))
b0e0d0e9
LC
668 (lambda (key . args)
669 (false-if-exception (delete-file template))))))
670
dcd72906 671(define (substitute file pattern+procs)
094b2efc
TUBK
672 "PATTERN+PROCS is a list of regexp/two-argument-procedure pairs. For each
673line of FILE, and for each PATTERN that it matches, call the corresponding
674PROC as (PROC LINE MATCHES); PROC must return the line that will be written as
675a substitution of the original line. Be careful about using '$' to match the
676end of a line; by itself it won't match the terminating newline of a line."
dcd72906
LC
677 (let ((rx+proc (map (match-lambda
678 (((? regexp? pattern) . proc)
679 (cons pattern proc))
680 ((pattern . proc)
681 (cons (make-regexp pattern regexp/extended)
682 proc)))
683 pattern+procs)))
684 (with-atomic-file-replacement file
685 (lambda (in out)
686 (let loop ((line (read-line in 'concat)))
687 (if (eof-object? line)
688 #t
689 (let ((line (fold (lambda (r+p line)
690 (match r+p
691 ((regexp . proc)
692 (match (list-matches regexp line)
693 ((and m+ (_ _ ...))
694 (proc line m+))
695 (_ line)))))
696 line
697 rx+proc)))
698 (display line out)
699 (loop (read-line in 'concat)))))))))
700
10c87717
LC
701
702(define-syntax let-matches
703 ;; Helper macro for `substitute*'.
704 (syntax-rules (_)
705 ((let-matches index match (_ vars ...) body ...)
706 (let-matches (+ 1 index) match (vars ...)
707 body ...))
708 ((let-matches index match (var vars ...) body ...)
709 (let ((var (match:substring match index)))
710 (let-matches (+ 1 index) match (vars ...)
711 body ...)))
712 ((let-matches index match () body ...)
713 (begin body ...))))
714
8773648e
LC
715(define-syntax substitute*
716 (syntax-rules ()
717 "Substitute REGEXP in FILE by the string returned by BODY. BODY is
10c87717
LC
718evaluated with each MATCH-VAR bound to the corresponding positional regexp
719sub-expression. For example:
720
4fa697e9 721 (substitute* file
20d83444
LC
722 ((\"hello\")
723 \"good morning\\n\")
724 ((\"foo([a-z]+)bar(.*)$\" all letters end)
725 (string-append \"baz\" letter end)))
4fa697e9
LC
726
727Here, anytime a line of FILE contains \"hello\", it is replaced by \"good
728morning\". Anytime a line of FILE matches the second regexp, ALL is bound to
729the complete match, LETTERS is bound to the first sub-expression, and END is
730bound to the last one.
731
732When one of the MATCH-VAR is `_', no variable is bound to the corresponding
20d83444
LC
733match substring.
734
735Alternatively, FILE may be a list of file names, in which case they are
094b2efc
TUBK
736all subject to the substitutions.
737
738Be careful about using '$' to match the end of a line; by itself it won't
739match the terminating newline of a line."
8773648e 740 ((substitute* file ((regexp match-var ...) body ...) ...)
20d83444
LC
741 (let ()
742 (define (substitute-one-file file-name)
743 (substitute
744 file-name
745 (list (cons regexp
746 (lambda (l m+)
747 ;; Iterate over matches M+ and return the
748 ;; modified line based on L.
749 (let loop ((m* m+) ; matches
750 (o 0) ; offset in L
751 (r '())) ; result
752 (match m*
753 (()
754 (let ((r (cons (substring l o) r)))
755 (string-concatenate-reverse r)))
756 ((m . rest)
757 (let-matches 0 m (match-var ...)
758 (loop rest
759 (match:end m)
760 (cons*
761 (begin body ...)
762 (substring l o (match:start m))
763 r))))))))
764 ...)))
765
766 (match file
767 ((files (... ...))
768 (for-each substitute-one-file files))
769 ((? string? f)
770 (substitute-one-file f)))))))
10c87717 771
ebe2f31f
LC
772\f
773;;;
8be3b8a3 774;;; Patching shebangs---e.g., /bin/sh -> /gnu/store/xyz...-bash/bin/sh.
ebe2f31f
LC
775;;;
776
a18b4d08
LC
777(define* (dump-port in out
778 #:key (buffer-size 16384)
779 (progress (lambda (t k) (k))))
2c1fb353
LC
780 "Read as much data as possible from IN and write it to OUT, using chunks of
781BUFFER-SIZE bytes. Call PROGRESS at the beginning and after each successful
782transfer of BUFFER-SIZE bytes or less, passing it the total number of bytes
783transferred and the continuation of the transfer as a thunk."
ebe2f31f
LC
784 (define buffer
785 (make-bytevector buffer-size))
786
2c1fb353 787 (define (loop total bytes)
ebe2f31f 788 (or (eof-object? bytes)
a18b4d08 789 (let ((total (+ total bytes)))
ebe2f31f 790 (put-bytevector out buffer 0 bytes)
a18b4d08
LC
791 (progress total
792 (lambda ()
793 (loop total
2c1fb353
LC
794 (get-bytevector-n! in buffer 0 buffer-size)))))))
795
796 ;; Make sure PROGRESS is called when we start so that it can measure
797 ;; throughput.
798 (progress 0
799 (lambda ()
800 (loop 0 (get-bytevector-n! in buffer 0 buffer-size)))))
ebe2f31f 801
bc5bf85f
LC
802(define (set-file-time file stat)
803 "Set the atime/mtime of FILE to that specified by STAT."
804 (utime file
805 (stat:atime stat)
806 (stat:mtime stat)
807 (stat:atimensec stat)
808 (stat:mtimensec stat)))
809
ca1e3ad2
LC
810(define (get-char* p)
811 ;; We call it `get-char', but that's really a binary version
812 ;; thereof. (The real `get-char' cannot be used here because our
813 ;; bootstrap Guile is hacked to always use UTF-8.)
814 (match (get-u8 p)
815 ((? integer? x) (integer->char x))
816 (x x)))
817
ebe2f31f 818(define patch-shebang
11996d85 819 (let ((shebang-rx (make-regexp "^[[:blank:]]*([[:graph:]]+)[[:blank:]]*([[:graph:]]*)(.*)$")))
525a59d6 820 (lambda* (file
bc5bf85f
LC
821 #:optional
822 (path (search-path-as-string->list (getenv "PATH")))
823 #:key (keep-mtime? #t))
525a59d6
LC
824 "Replace the #! interpreter file name in FILE by a valid one found in
825PATH, when FILE actually starts with a shebang. Return #t when FILE was
bc5bf85f
LC
826patched, #f otherwise. When KEEP-MTIME? is true, the atime/mtime of
827FILE are kept unchanged."
ebe2f31f
LC
828 (define (patch p interpreter rest-of-line)
829 (let* ((template (string-append file ".XXXXXX"))
830 (out (mkstemp! template))
bc5bf85f
LC
831 (st (stat file))
832 (mode (stat:mode st)))
ebe2f31f
LC
833 (with-throw-handler #t
834 (lambda ()
835 (format out "#!~a~a~%"
836 interpreter rest-of-line)
837 (dump-port p out)
838 (close out)
839 (chmod template mode)
840 (rename-file template file)
bc5bf85f
LC
841 (when keep-mtime?
842 (set-file-time file st))
ebe2f31f
LC
843 #t)
844 (lambda (key . args)
845 (format (current-error-port)
846 "patch-shebang: ~a: error: ~a ~s~%"
847 file key args)
848 (false-if-exception (delete-file template))
849 #f))))
850
c0895112
LC
851 (call-with-ascii-input-file file
852 (lambda (p)
ca1e3ad2
LC
853 (and (eq? #\# (get-char* p))
854 (eq? #\! (get-char* p))
c0895112
LC
855 (let ((line (false-if-exception (read-line p))))
856 (and=> (and line (regexp-exec shebang-rx line))
857 (lambda (m)
11996d85
AE
858 (let* ((interp (match:substring m 1))
859 (arg1 (match:substring m 2))
860 (rest (match:substring m 3))
861 (has-env (string-suffix? "/env" interp))
862 (cmd (if has-env arg1 (basename interp)))
863 (bin (search-path path cmd)))
c0895112 864 (if bin
11996d85 865 (if (string=? bin interp)
c0895112 866 #f ; nothing to do
11996d85
AE
867 (if has-env
868 (begin
869 (format (current-error-port)
870 "patch-shebang: ~a: changing `~a' to `~a'~%"
871 file (string-append interp " " arg1) bin)
872 (patch p bin rest))
873 (begin
874 (format (current-error-port)
875 "patch-shebang: ~a: changing `~a' to `~a'~%"
876 file interp bin)
877 (patch p bin
ca8def6e
AE
878 (if (string-null? arg1)
879 ""
880 (string-append " " arg1 rest))))))
c0895112
LC
881 (begin
882 (format (current-error-port)
883 "patch-shebang: ~a: warning: no binary for interpreter `~a' found in $PATH~%"
884 file (basename cmd))
885 #f))))))))))))
886
bc5bf85f
LC
887(define* (patch-makefile-SHELL file #:key (keep-mtime? #t))
888 "Patch the `SHELL' variable in FILE, which is supposedly a makefile.
889When KEEP-MTIME? is true, the atime/mtime of FILE are kept unchanged."
c0895112
LC
890
891 ;; For instance, Gettext-generated po/Makefile.in.in do not honor $SHELL.
892
893 ;; XXX: Unlike with `patch-shebang', FILE is always touched.
894
895 (define (find-shell name)
5e5deea9 896 (let ((shell (which name)))
c0895112
LC
897 (unless shell
898 (format (current-error-port)
899 "patch-makefile-SHELL: warning: no binary for shell `~a' found in $PATH~%"
900 name))
901 shell))
902
bc5bf85f 903 (let ((st (stat file)))
dd0a8ef1
LC
904 ;; Consider FILE is using an 8-bit encoding to avoid errors.
905 (with-fluids ((%default-port-encoding #f))
906 (substitute* file
907 (("^ *SHELL[[:blank:]]*:?=[[:blank:]]*([[:graph:]]*/)([[:graph:]]+)(.*)$"
908 _ dir shell args)
909 (let* ((old (string-append dir shell))
910 (new (or (find-shell shell) old)))
911 (unless (string=? new old)
912 (format (current-error-port)
913 "patch-makefile-SHELL: ~a: changing `SHELL' from `~a' to `~a'~%"
914 file old new))
915 (string-append "SHELL = " new args)))))
bc5bf85f
LC
916
917 (when keep-mtime?
918 (set-file-time file st))))
10c87717 919
4eb01e54
LC
920(define* (patch-/usr/bin/file file
921 #:key
922 (file-command (which "file"))
923 (keep-mtime? #t))
924 "Patch occurrences of \"/usr/bin/file\" in FILE, replacing them with
925FILE-COMMAND. When KEEP-MTIME? is true, keep FILE's modification time
926unchanged."
927 (if (not file-command)
928 (format (current-error-port)
929 "patch-/usr/bin/file: warning: \
930no replacement 'file' command, doing nothing~%")
931 (let ((st (stat file)))
dd0a8ef1
LC
932 ;; Consider FILE is using an 8-bit encoding to avoid errors.
933 (with-fluids ((%default-port-encoding #f))
934 (substitute* file
935 (("/usr/bin/file")
936 (begin
937 (format (current-error-port)
938 "patch-/usr/bin/file: ~a: changing `~a' to `~a'~%"
939 file "/usr/bin/file" file-command)
940 file-command))))
4eb01e54
LC
941
942 (when keep-mtime?
943 (set-file-time file st)))))
944
91133c2d
LC
945(define* (fold-port-matches proc init pattern port
946 #:optional (unmatched (lambda (_ r) r)))
947 "Read from PORT character-by-character; for each match against
948PATTERN, call (PROC MATCH RESULT), where RESULT is seeded with INIT.
949PATTERN is a list of SRFI-14 char-sets. Call (UNMATCHED CHAR RESULT)
950for each unmatched character."
951 (define initial-pattern
952 ;; The poor developer's regexp.
953 (if (string? pattern)
954 (map char-set (string->list pattern))
955 pattern))
956
957 ;; Note: we're not really striving for performance here...
958 (let loop ((chars '())
959 (pattern initial-pattern)
960 (matched '())
961 (result init))
962 (cond ((null? chars)
ca1e3ad2 963 (loop (list (get-char* port))
91133c2d
LC
964 pattern
965 matched
966 result))
967 ((null? pattern)
968 (loop chars
969 initial-pattern
970 '()
971 (proc (list->string (reverse matched)) result)))
972 ((eof-object? (car chars))
973 (fold-right unmatched result matched))
974 ((char-set-contains? (car pattern) (car chars))
975 (loop (cdr chars)
976 (cdr pattern)
977 (cons (car chars) matched)
978 result))
979 ((null? matched) ; common case
980 (loop (cdr chars)
981 pattern
982 matched
983 (unmatched (car chars) result)))
984 (else
985 (let ((matched (reverse matched)))
986 (loop (append (cdr matched) chars)
987 initial-pattern
988 '()
989 (unmatched (car matched) result)))))))
990
991(define* (remove-store-references file
8be3b8a3 992 #:optional (store (%store-directory)))
91133c2d
LC
993 "Remove from FILE occurrences of file names in STORE; return #t when
994store paths were encountered in FILE, #f otherwise. This procedure is
995known as `nuke-refs' in Nixpkgs."
996 (define pattern
997 (let ((nix-base32-chars
998 '(#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9
999 #\a #\b #\c #\d #\f #\g #\h #\i #\j #\k #\l #\m #\n
1000 #\p #\q #\r #\s #\v #\w #\x #\y #\z)))
1001 `(,@(map char-set (string->list store))
1002 ,(char-set #\/)
1003 ,@(make-list 32 (list->char-set nix-base32-chars))
1004 ,(char-set #\-))))
1005
1006 (with-fluids ((%default-port-encoding #f))
1007 (with-atomic-file-replacement file
1008 (lambda (in out)
1009 ;; We cannot use `regexp-exec' here because it cannot deal with
1010 ;; strings containing NUL characters.
1011 (format #t "removing store references from `~a'...~%" file)
782f1ea9
LC
1012 (setvbuf in 'block 65536)
1013 (setvbuf out 'block 65536)
91133c2d 1014 (fold-port-matches (lambda (match result)
93b03575
LC
1015 (put-bytevector out (string->utf8 store))
1016 (put-u8 out (char->integer #\/))
1017 (put-bytevector out
1018 (string->utf8
1019 "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-"))
91133c2d
LC
1020 #t)
1021 #f
1022 pattern
1023 in
1024 (lambda (char result)
93b03575 1025 (put-u8 out (char->integer char))
91133c2d
LC
1026 result))))))
1027
89e7f90d
AI
1028(define (wrapper? prog)
1029 "Return #t if PROG is a wrapper as produced by 'wrap-program'."
1030 (and (file-exists? prog)
1031 (let ((base (basename prog)))
1032 (and (string-prefix? "." base)
1033 (string-suffix? "-real" base)))))
1034
30db6af1 1035(define* (wrap-program prog #:rest vars)
de611138 1036 "Make a wrapper for PROG. VARS should look like this:
30db6af1
NK
1037
1038 '(VARIABLE DELIMITER POSITION LIST-OF-DIRECTORIES)
1039
1040where DELIMITER is optional. ':' will be used if DELIMITER is not given.
1041
1042For example, this command:
1043
1044 (wrap-program \"foo\"
de611138
EB
1045 '(\"PATH\" \":\" = (\"/gnu/.../bar/bin\"))
1046 '(\"CERT_PATH\" suffix (\"/gnu/.../baz/certs\"
30db6af1
NK
1047 \"/qux/certs\")))
1048
1049will copy 'foo' to '.foo-real' and create the file 'foo' with the following
1050contents:
1051
1052 #!location/of/bin/bash
de611138
EB
1053 export PATH=\"/gnu/.../bar/bin\"
1054 export CERT_PATH=\"$CERT_PATH${CERT_PATH:+:}/gnu/.../baz/certs:/qux/certs\"
b01f8967 1055 exec -a $0 location/of/.foo-real \"$@\"
30db6af1
NK
1056
1057This is useful for scripts that expect particular programs to be in $PATH, for
1058programs that expect particular shared libraries to be in $LD_LIBRARY_PATH, or
de611138
EB
1059modules in $GUILE_LOAD_PATH, etc.
1060
b14a8385
LC
1061If PROG has previously been wrapped by 'wrap-program', the wrapper is extended
1062with definitions for VARS."
1063 (define wrapped-file
1064 (string-append (dirname prog) "/." (basename prog) "-real"))
1065
1066 (define already-wrapped?
1067 (file-exists? wrapped-file))
1068
1069 (define (last-line port)
1070 ;; Return the last line read from PORT and leave PORT's cursor right
1071 ;; before it.
1072 (let loop ((previous-line-offset 0)
1073 (previous-line "")
1074 (position (seek port 0 SEEK_CUR)))
1075 (match (read-line port 'concat)
1076 ((? eof-object?)
1077 (seek port previous-line-offset SEEK_SET)
1078 previous-line)
1079 ((? string? line)
1080 (loop position line (+ (string-length line) position))))))
1081
1082 (define (export-variable lst)
1083 ;; Return a string that exports an environment variable.
1084 (match lst
1085 ((var sep '= rest)
1086 (format #f "export ~a=\"~a\""
1087 var (string-join rest sep)))
1088 ((var sep 'prefix rest)
e6c4e411
JL
1089 (format #f "export ~a=\"~a${~a:+~a}$~a\""
1090 var (string-join rest sep) var sep var))
b14a8385 1091 ((var sep 'suffix rest)
e6c4e411
JL
1092 (format #f "export ~a=\"$~a${~a+~a}~a\""
1093 var var var sep (string-join rest sep)))
b14a8385
LC
1094 ((var '= rest)
1095 (format #f "export ~a=\"~a\""
1096 var (string-join rest ":")))
1097 ((var 'prefix rest)
1098 (format #f "export ~a=\"~a${~a:+:}$~a\""
1099 var (string-join rest ":") var var))
1100 ((var 'suffix rest)
1101 (format #f "export ~a=\"$~a${~a:+:}~a\""
1102 var var var (string-join rest ":")))))
1103
1104 (if already-wrapped?
1105
1106 ;; PROG is already a wrapper: add the new "export VAR=VALUE" lines just
1107 ;; before the last line.
1108 (let* ((port (open-file prog "r+"))
1109 (last (last-line port)))
1110 (for-each (lambda (var)
1111 (display (export-variable var) port)
1112 (newline port))
1113 vars)
1114 (display last port)
1115 (close-port port))
1116
1117 ;; PROG is not wrapped yet: create a shell script that sets VARS.
1118 (let ((prog-tmp (string-append wrapped-file "-tmp")))
1119 (link prog wrapped-file)
1120
1121 (call-with-output-file prog-tmp
1122 (lambda (port)
1123 (format port
1124 "#!~a~%~a~%exec -a \"$0\" \"~a\" \"$@\"~%"
1125 (which "bash")
1126 (string-join (map export-variable vars) "\n")
1127 (canonicalize-path wrapped-file))))
1128
1129 (chmod prog-tmp #o755)
1130 (rename-file prog-tmp prog))))
30db6af1 1131
251e8b2e
LC
1132\f
1133;;;
1134;;; Locales.
1135;;;
1136
1137(define (locale-category->string category)
1138 "Return the name of locale category CATEGORY, one of the 'LC_' constants.
1139If CATEGORY is a bitwise or of several 'LC_' constants, an approximation is
1140returned."
1141 (letrec-syntax ((convert (syntax-rules ()
1142 ((_)
1143 (number->string category))
1144 ((_ first rest ...)
1145 (if (= first category)
1146 (symbol->string 'first)
1147 (convert rest ...))))))
1148 (convert LC_ADDRESS LC_ALL LC_COLLATE LC_CTYPE
1149 LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES LC_MONETARY
1150 LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE
1151 LC_TIME)))
1152
b0e0d0e9
LC
1153;;; Local Variables:
1154;;; eval: (put 'call-with-output-file/atomic 'scheme-indent-function 1)
99533da5 1155;;; eval: (put 'call-with-ascii-input-file 'scheme-indent-function 1)
b0e0d0e9 1156;;; eval: (put 'with-throw-handler 'scheme-indent-function 1)
8197c978 1157;;; eval: (put 'let-matches 'scheme-indent-function 3)
dcd72906 1158;;; eval: (put 'with-atomic-file-replacement 'scheme-indent-function 1)
b0e0d0e9 1159;;; End: