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