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