gnu: gcc-final: Add Texinfo as an input.
[jackhill/guix/guix.git] / guix / build / utils.scm
CommitLineData
4155e2a9 1;;; GNU Guix --- Functional package management for GNU
8be3b8a3 2;;; Copyright © 2012, 2013, 2014 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)
c0746cc9 24 #:use-module (ice-9 ftw)
b0e0d0e9
LC
25 #:use-module (ice-9 match)
26 #:use-module (ice-9 regex)
27 #:use-module (ice-9 rdelim)
ebe2f31f
LC
28 #:use-module (rnrs bytevectors)
29 #:use-module (rnrs io ports)
d475b259
LC
30 #:re-export (alist-cons
31 alist-delete)
8be3b8a3
LC
32 #:export (%store-directory
33 directory-exists?
d0084152 34 executable-file?
c0895112 35 call-with-ascii-input-file
b0e0d0e9 36 with-directory-excursion
7da95264 37 mkdir-p
c0746cc9 38 copy-recursively
e65df6a6 39 delete-file-recursively
4c261f41
LC
40 find-files
41
b0e0d0e9 42 set-path-environment-variable
ebe2f31f
LC
43 search-path-as-string->list
44 list->search-path-as-string
7584f822
LC
45 which
46
b0e0d0e9
LC
47 alist-cons-before
48 alist-cons-after
49 alist-replace
dcd72906 50 with-atomic-file-replacement
10c87717 51 substitute
ebe2f31f
LC
52 substitute*
53 dump-port
bc5bf85f 54 set-file-time
91133c2d 55 patch-shebang
c0895112 56 patch-makefile-SHELL
91133c2d 57 fold-port-matches
30db6af1
NK
58 remove-store-references
59 wrap-program))
b0e0d0e9 60
11996d85 61
b0e0d0e9
LC
62;;;
63;;; Directories.
64;;;
c36db98c 65
8be3b8a3
LC
66(define (%store-directory)
67 "Return the directory name of the store."
68 (or (getenv "NIX_STORE")
69 "/gnu/store"))
70
c36db98c
LC
71(define (directory-exists? dir)
72 "Return #t if DIR exists and is a directory."
9f55cf8d
LC
73 (let ((s (stat dir #f)))
74 (and s
75 (eq? 'directory (stat:type s)))))
c36db98c 76
d0084152
LC
77(define (executable-file? file)
78 "Return #t if FILE exists and is executable."
79 (let ((s (stat file #f)))
80 (and s
81 (not (zero? (logand (stat:mode s) #o100))))))
82
c0895112
LC
83(define (call-with-ascii-input-file file proc)
84 "Open FILE as an ASCII or binary file, and pass the resulting port to
85PROC. FILE is closed when PROC's dynamic extent is left. Return the
86return values of applying PROC to the port."
87 (let ((port (with-fluids ((%default-port-encoding #f))
88 ;; Use "b" so that `open-file' ignores `coding:' cookies.
89 (open-file file "rb"))))
90 (dynamic-wind
91 (lambda ()
92 #t)
93 (lambda ()
94 (proc port))
95 (lambda ()
96 (close-input-port port)))))
97
b0e0d0e9
LC
98(define-syntax-rule (with-directory-excursion dir body ...)
99 "Run BODY with DIR as the process's current directory."
100 (let ((init (getcwd)))
101 (dynamic-wind
102 (lambda ()
103 (chdir dir))
104 (lambda ()
105 body ...)
106 (lambda ()
107 (chdir init)))))
108
7da95264
LC
109(define (mkdir-p dir)
110 "Create directory DIR and all its ancestors."
111 (define absolute?
112 (string-prefix? "/" dir))
113
114 (define not-slash
115 (char-set-complement (char-set #\/)))
116
117 (let loop ((components (string-tokenize dir not-slash))
118 (root (if absolute?
119 ""
120 ".")))
121 (match components
122 ((head tail ...)
123 (let ((path (string-append root "/" head)))
124 (catch 'system-error
125 (lambda ()
126 (mkdir path)
127 (loop tail path))
128 (lambda args
129 (if (= EEXIST (system-error-errno args))
130 (loop tail path)
131 (apply throw args))))))
132 (() #t))))
133
c0746cc9 134(define* (copy-recursively source destination
12761f48
LC
135 #:key
136 (log (current-output-port))
137 (follow-symlinks? #f))
138 "Copy SOURCE directory to DESTINATION. Follow symlinks if FOLLOW-SYMLINKS?
139is true; otherwise, just preserve them. Write verbose output to the LOG port."
c0746cc9
LC
140 (define strip-source
141 (let ((len (string-length source)))
142 (lambda (file)
143 (substring file len))))
144
145 (file-system-fold (const #t) ; enter?
146 (lambda (file stat result) ; leaf
147 (let ((dest (string-append destination
148 (strip-source file))))
149 (format log "`~a' -> `~a'~%" file dest)
12761f48
LC
150 (case (stat:type stat)
151 ((symlink)
152 (let ((target (readlink file)))
153 (symlink target dest)))
154 (else
155 (copy-file file dest)))))
c0746cc9
LC
156 (lambda (dir stat result) ; down
157 (mkdir-p (string-append destination
158 (strip-source dir))))
159 (lambda (dir stat result) ; up
160 result)
161 (const #t) ; skip
162 (lambda (file stat errno result)
163 (format (current-error-port) "i/o error: ~a: ~a~%"
164 file (strerror errno))
165 #f)
166 #t
12761f48
LC
167 source
168
169 (if follow-symlinks?
170 stat
171 lstat)))
c0746cc9 172
e65df6a6
LC
173(define (delete-file-recursively dir)
174 "Delete DIR recursively, like `rm -rf', without following symlinks. Report
175but ignore errors."
176 (file-system-fold (const #t) ; enter?
177 (lambda (file stat result) ; leaf
178 (delete-file file))
179 (const #t) ; down
180 (lambda (dir stat result) ; up
181 (rmdir dir))
182 (const #t) ; skip
183 (lambda (file stat errno result)
184 (format (current-error-port)
185 "warning: failed to delete ~a: ~a~%"
186 file (strerror errno)))
187 #t
188 dir
189
190 ;; Don't follow symlinks.
191 lstat))
192
4c261f41 193(define (find-files dir regexp)
66018f1c
LC
194 "Return the lexicographically sorted list of files under DIR whose basename
195matches REGEXP."
4c261f41
LC
196 (define file-rx
197 (if (regexp? regexp)
198 regexp
199 (make-regexp regexp)))
200
66018f1c
LC
201 ;; Sort the result to get deterministic results.
202 (sort (file-system-fold (const #t)
203 (lambda (file stat result) ; leaf
204 (if (regexp-exec file-rx (basename file))
205 (cons file result)
206 result))
207 (lambda (dir stat result) ; down
208 result)
209 (lambda (dir stat result) ; up
210 result)
211 (lambda (file stat result) ; skip
212 result)
213 (lambda (file stat errno result)
214 (format (current-error-port) "find-files: ~a: ~a~%"
215 file (strerror errno))
f1352766 216 result)
66018f1c
LC
217 '()
218 dir)
219 string<?))
4c261f41 220
b0e0d0e9
LC
221\f
222;;;
223;;; Search paths.
224;;;
225
c36db98c
LC
226(define (search-path-as-list sub-directories input-dirs)
227 "Return the list of directories among SUB-DIRECTORIES that exist in
228INPUT-DIRS. Example:
229
230 (search-path-as-list '(\"share/emacs/site-lisp\" \"share/emacs/24.1\")
231 (list \"/package1\" \"/package2\" \"/package3\"))
232 => (\"/package1/share/emacs/site-lisp\"
233 \"/package3/share/emacs/site-lisp\")
234
235"
236 (append-map (lambda (input)
237 (filter-map (lambda (dir)
238 (let ((dir (string-append input "/"
239 dir)))
240 (and (directory-exists? dir)
241 dir)))
242 sub-directories))
243 input-dirs))
244
245(define (list->search-path-as-string lst separator)
246 (string-join lst separator))
247
ebe2f31f
LC
248(define* (search-path-as-string->list path #:optional (separator #\:))
249 (string-tokenize path (char-set-complement (char-set separator))))
250
c36db98c
LC
251(define* (set-path-environment-variable env-var sub-directories input-dirs
252 #:key (separator ":"))
253 "Look for each of SUB-DIRECTORIES in INPUT-DIRS. Set ENV-VAR to a
254SEPARATOR-separated path accordingly. Example:
255
256 (set-path-environment-variable \"PKG_CONFIG\"
257 '(\"lib/pkgconfig\")
258 (list package1 package2))
259"
9d9ef458
LC
260 (let* ((path (search-path-as-list sub-directories input-dirs))
261 (value (list->search-path-as-string path separator)))
b15669f3
LC
262 (if (string-null? value)
263 (begin
264 ;; Never set ENV-VAR to an empty string because often, the empty
265 ;; string is equivalent to ".". This is the case for
266 ;; GUILE_LOAD_PATH in Guile 2.0, for instance.
267 (unsetenv env-var)
268 (format #t "environment variable `~a' unset~%" env-var))
269 (begin
270 (setenv env-var value)
271 (format #t "environment variable `~a' set to `~a'~%"
272 env-var value)))))
b0e0d0e9 273
7584f822
LC
274(define (which program)
275 "Return the complete file name for PROGRAM as found in $PATH, or #f if
276PROGRAM could not be found."
277 (search-path (search-path-as-string->list (getenv "PATH"))
278 program))
279
b0e0d0e9
LC
280\f
281;;;
282;;; Phases.
283;;;
284;;; In (guix build gnu-build-system), there are separate phases (configure,
285;;; build, test, install). They are represented as a list of name/procedure
286;;; pairs. The following procedures make it easy to change the list of
287;;; phases.
288;;;
289
290(define* (alist-cons-before reference key value alist
291 #:optional (key=? equal?))
292 "Insert the KEY/VALUE pair before the first occurrence of a pair whose key
293is REFERENCE in ALIST. Use KEY=? to compare keys."
294 (let-values (((before after)
295 (break (match-lambda
296 ((k . _)
297 (key=? k reference)))
298 alist)))
299 (append before (alist-cons key value after))))
300
301(define* (alist-cons-after reference key value alist
302 #:optional (key=? equal?))
303 "Insert the KEY/VALUE pair after the first occurrence of a pair whose key
304is REFERENCE in ALIST. Use KEY=? to compare keys."
305 (let-values (((before after)
306 (break (match-lambda
307 ((k . _)
308 (key=? k reference)))
309 alist)))
310 (match after
311 ((reference after ...)
312 (append before (cons* reference `(,key . ,value) after)))
313 (()
314 (append before `((,key . ,value)))))))
315
316(define* (alist-replace key value alist #:optional (key=? equal?))
317 "Replace the first pair in ALIST whose car is KEY with the KEY/VALUE pair.
318An error is raised when no such pair exists."
319 (let-values (((before after)
320 (break (match-lambda
321 ((k . _)
322 (key=? k key)))
323 alist)))
324 (match after
325 ((_ after ...)
326 (append before (alist-cons key value after))))))
327
328\f
329;;;
330;;; Text substitution (aka. sed).
331;;;
332
dcd72906
LC
333(define (with-atomic-file-replacement file proc)
334 "Call PROC with two arguments: an input port for FILE, and an output
335port for the file that is going to replace FILE. Upon success, FILE is
336atomically replaced by what has been written to the output port, and
337PROC's result is returned."
338 (let* ((template (string-append file ".XXXXXX"))
d9dbab18
LC
339 (out (mkstemp! template))
340 (mode (stat:mode (stat file))))
b0e0d0e9
LC
341 (with-throw-handler #t
342 (lambda ()
343 (call-with-input-file file
344 (lambda (in)
dcd72906
LC
345 (let ((result (proc in out)))
346 (close out)
347 (chmod template mode)
348 (rename-file template file)
349 result))))
b0e0d0e9
LC
350 (lambda (key . args)
351 (false-if-exception (delete-file template))))))
352
dcd72906
LC
353(define (substitute file pattern+procs)
354 "PATTERN+PROCS is a list of regexp/two-argument procedure. For each line
355of FILE, and for each PATTERN that it matches, call the corresponding PROC
356as (PROC LINE MATCHES); PROC must return the line that will be written as a
357substitution of the original line."
358 (let ((rx+proc (map (match-lambda
359 (((? regexp? pattern) . proc)
360 (cons pattern proc))
361 ((pattern . proc)
362 (cons (make-regexp pattern regexp/extended)
363 proc)))
364 pattern+procs)))
365 (with-atomic-file-replacement file
366 (lambda (in out)
367 (let loop ((line (read-line in 'concat)))
368 (if (eof-object? line)
369 #t
370 (let ((line (fold (lambda (r+p line)
371 (match r+p
372 ((regexp . proc)
373 (match (list-matches regexp line)
374 ((and m+ (_ _ ...))
375 (proc line m+))
376 (_ line)))))
377 line
378 rx+proc)))
379 (display line out)
380 (loop (read-line in 'concat)))))))))
381
10c87717
LC
382
383(define-syntax let-matches
384 ;; Helper macro for `substitute*'.
385 (syntax-rules (_)
386 ((let-matches index match (_ vars ...) body ...)
387 (let-matches (+ 1 index) match (vars ...)
388 body ...))
389 ((let-matches index match (var vars ...) body ...)
390 (let ((var (match:substring match index)))
391 (let-matches (+ 1 index) match (vars ...)
392 body ...)))
393 ((let-matches index match () body ...)
394 (begin body ...))))
395
8773648e
LC
396(define-syntax substitute*
397 (syntax-rules ()
398 "Substitute REGEXP in FILE by the string returned by BODY. BODY is
10c87717
LC
399evaluated with each MATCH-VAR bound to the corresponding positional regexp
400sub-expression. For example:
401
4fa697e9 402 (substitute* file
20d83444
LC
403 ((\"hello\")
404 \"good morning\\n\")
405 ((\"foo([a-z]+)bar(.*)$\" all letters end)
406 (string-append \"baz\" letter end)))
4fa697e9
LC
407
408Here, anytime a line of FILE contains \"hello\", it is replaced by \"good
409morning\". Anytime a line of FILE matches the second regexp, ALL is bound to
410the complete match, LETTERS is bound to the first sub-expression, and END is
411bound to the last one.
412
413When one of the MATCH-VAR is `_', no variable is bound to the corresponding
20d83444
LC
414match substring.
415
416Alternatively, FILE may be a list of file names, in which case they are
417all subject to the substitutions."
8773648e 418 ((substitute* file ((regexp match-var ...) body ...) ...)
20d83444
LC
419 (let ()
420 (define (substitute-one-file file-name)
421 (substitute
422 file-name
423 (list (cons regexp
424 (lambda (l m+)
425 ;; Iterate over matches M+ and return the
426 ;; modified line based on L.
427 (let loop ((m* m+) ; matches
428 (o 0) ; offset in L
429 (r '())) ; result
430 (match m*
431 (()
432 (let ((r (cons (substring l o) r)))
433 (string-concatenate-reverse r)))
434 ((m . rest)
435 (let-matches 0 m (match-var ...)
436 (loop rest
437 (match:end m)
438 (cons*
439 (begin body ...)
440 (substring l o (match:start m))
441 r))))))))
442 ...)))
443
444 (match file
445 ((files (... ...))
446 (for-each substitute-one-file files))
447 ((? string? f)
448 (substitute-one-file f)))))))
10c87717 449
ebe2f31f
LC
450\f
451;;;
8be3b8a3 452;;; Patching shebangs---e.g., /bin/sh -> /gnu/store/xyz...-bash/bin/sh.
ebe2f31f
LC
453;;;
454
a18b4d08
LC
455(define* (dump-port in out
456 #:key (buffer-size 16384)
457 (progress (lambda (t k) (k))))
74baf333 458 "Read as much data as possible from IN and write it to OUT, using
a18b4d08
LC
459chunks of BUFFER-SIZE bytes. Call PROGRESS after each successful
460transfer of BUFFER-SIZE bytes or less, passing it the total number of
461bytes transferred and the continuation of the transfer as a thunk."
ebe2f31f
LC
462 (define buffer
463 (make-bytevector buffer-size))
464
a18b4d08
LC
465 (let loop ((total 0)
466 (bytes (get-bytevector-n! in buffer 0 buffer-size)))
ebe2f31f 467 (or (eof-object? bytes)
a18b4d08 468 (let ((total (+ total bytes)))
ebe2f31f 469 (put-bytevector out buffer 0 bytes)
a18b4d08
LC
470 (progress total
471 (lambda ()
472 (loop total
473 (get-bytevector-n! in buffer 0 buffer-size))))))))
ebe2f31f 474
bc5bf85f
LC
475(define (set-file-time file stat)
476 "Set the atime/mtime of FILE to that specified by STAT."
477 (utime file
478 (stat:atime stat)
479 (stat:mtime stat)
480 (stat:atimensec stat)
481 (stat:mtimensec stat)))
482
ebe2f31f 483(define patch-shebang
11996d85 484 (let ((shebang-rx (make-regexp "^[[:blank:]]*([[:graph:]]+)[[:blank:]]*([[:graph:]]*)(.*)$")))
525a59d6 485 (lambda* (file
bc5bf85f
LC
486 #:optional
487 (path (search-path-as-string->list (getenv "PATH")))
488 #:key (keep-mtime? #t))
525a59d6
LC
489 "Replace the #! interpreter file name in FILE by a valid one found in
490PATH, when FILE actually starts with a shebang. Return #t when FILE was
bc5bf85f
LC
491patched, #f otherwise. When KEEP-MTIME? is true, the atime/mtime of
492FILE are kept unchanged."
ebe2f31f
LC
493 (define (patch p interpreter rest-of-line)
494 (let* ((template (string-append file ".XXXXXX"))
495 (out (mkstemp! template))
bc5bf85f
LC
496 (st (stat file))
497 (mode (stat:mode st)))
ebe2f31f
LC
498 (with-throw-handler #t
499 (lambda ()
500 (format out "#!~a~a~%"
501 interpreter rest-of-line)
502 (dump-port p out)
503 (close out)
504 (chmod template mode)
505 (rename-file template file)
bc5bf85f
LC
506 (when keep-mtime?
507 (set-file-time file st))
ebe2f31f
LC
508 #t)
509 (lambda (key . args)
510 (format (current-error-port)
511 "patch-shebang: ~a: error: ~a ~s~%"
512 file key args)
513 (false-if-exception (delete-file template))
514 #f))))
515
c0895112
LC
516 (call-with-ascii-input-file file
517 (lambda (p)
518 (and (eq? #\# (read-char p))
519 (eq? #\! (read-char p))
520 (let ((line (false-if-exception (read-line p))))
521 (and=> (and line (regexp-exec shebang-rx line))
522 (lambda (m)
11996d85
AE
523 (let* ((interp (match:substring m 1))
524 (arg1 (match:substring m 2))
525 (rest (match:substring m 3))
526 (has-env (string-suffix? "/env" interp))
527 (cmd (if has-env arg1 (basename interp)))
528 (bin (search-path path cmd)))
c0895112 529 (if bin
11996d85 530 (if (string=? bin interp)
c0895112 531 #f ; nothing to do
11996d85
AE
532 (if has-env
533 (begin
534 (format (current-error-port)
535 "patch-shebang: ~a: changing `~a' to `~a'~%"
536 file (string-append interp " " arg1) bin)
537 (patch p bin rest))
538 (begin
539 (format (current-error-port)
540 "patch-shebang: ~a: changing `~a' to `~a'~%"
541 file interp bin)
542 (patch p bin
ca8def6e
AE
543 (if (string-null? arg1)
544 ""
545 (string-append " " arg1 rest))))))
c0895112
LC
546 (begin
547 (format (current-error-port)
548 "patch-shebang: ~a: warning: no binary for interpreter `~a' found in $PATH~%"
549 file (basename cmd))
550 #f))))))))))))
551
bc5bf85f
LC
552(define* (patch-makefile-SHELL file #:key (keep-mtime? #t))
553 "Patch the `SHELL' variable in FILE, which is supposedly a makefile.
554When KEEP-MTIME? is true, the atime/mtime of FILE are kept unchanged."
c0895112
LC
555
556 ;; For instance, Gettext-generated po/Makefile.in.in do not honor $SHELL.
557
558 ;; XXX: Unlike with `patch-shebang', FILE is always touched.
559
560 (define (find-shell name)
561 (let ((shell
562 (search-path (search-path-as-string->list (getenv "PATH"))
563 name)))
564 (unless shell
565 (format (current-error-port)
566 "patch-makefile-SHELL: warning: no binary for shell `~a' found in $PATH~%"
567 name))
568 shell))
569
bc5bf85f
LC
570 (let ((st (stat file)))
571 (substitute* file
572 (("^ *SHELL[[:blank:]]*=[[:blank:]]*([[:graph:]]*/)([[:graph:]]+)[[:blank:]]*" _ dir shell)
573 (let* ((old (string-append dir shell))
574 (new (or (find-shell shell) old)))
575 (unless (string=? new old)
576 (format (current-error-port)
577 "patch-makefile-SHELL: ~a: changing `SHELL' from `~a' to `~a'~%"
578 file old new))
579 (string-append "SHELL = " new "\n"))))
580
581 (when keep-mtime?
582 (set-file-time file st))))
10c87717 583
91133c2d
LC
584(define* (fold-port-matches proc init pattern port
585 #:optional (unmatched (lambda (_ r) r)))
586 "Read from PORT character-by-character; for each match against
587PATTERN, call (PROC MATCH RESULT), where RESULT is seeded with INIT.
588PATTERN is a list of SRFI-14 char-sets. Call (UNMATCHED CHAR RESULT)
589for each unmatched character."
590 (define initial-pattern
591 ;; The poor developer's regexp.
592 (if (string? pattern)
593 (map char-set (string->list pattern))
594 pattern))
595
93b03575
LC
596 (define (get-char p)
597 ;; We call it `get-char', but that's really a binary version
598 ;; thereof. (The real `get-char' cannot be used here because our
599 ;; bootstrap Guile is hacked to always use UTF-8.)
600 (match (get-u8 p)
601 ((? integer? x) (integer->char x))
602 (x x)))
603
91133c2d
LC
604 ;; Note: we're not really striving for performance here...
605 (let loop ((chars '())
606 (pattern initial-pattern)
607 (matched '())
608 (result init))
609 (cond ((null? chars)
610 (loop (list (get-char port))
611 pattern
612 matched
613 result))
614 ((null? pattern)
615 (loop chars
616 initial-pattern
617 '()
618 (proc (list->string (reverse matched)) result)))
619 ((eof-object? (car chars))
620 (fold-right unmatched result matched))
621 ((char-set-contains? (car pattern) (car chars))
622 (loop (cdr chars)
623 (cdr pattern)
624 (cons (car chars) matched)
625 result))
626 ((null? matched) ; common case
627 (loop (cdr chars)
628 pattern
629 matched
630 (unmatched (car chars) result)))
631 (else
632 (let ((matched (reverse matched)))
633 (loop (append (cdr matched) chars)
634 initial-pattern
635 '()
636 (unmatched (car matched) result)))))))
637
638(define* (remove-store-references file
8be3b8a3 639 #:optional (store (%store-directory)))
91133c2d
LC
640 "Remove from FILE occurrences of file names in STORE; return #t when
641store paths were encountered in FILE, #f otherwise. This procedure is
642known as `nuke-refs' in Nixpkgs."
643 (define pattern
644 (let ((nix-base32-chars
645 '(#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9
646 #\a #\b #\c #\d #\f #\g #\h #\i #\j #\k #\l #\m #\n
647 #\p #\q #\r #\s #\v #\w #\x #\y #\z)))
648 `(,@(map char-set (string->list store))
649 ,(char-set #\/)
650 ,@(make-list 32 (list->char-set nix-base32-chars))
651 ,(char-set #\-))))
652
653 (with-fluids ((%default-port-encoding #f))
654 (with-atomic-file-replacement file
655 (lambda (in out)
656 ;; We cannot use `regexp-exec' here because it cannot deal with
657 ;; strings containing NUL characters.
658 (format #t "removing store references from `~a'...~%" file)
659 (setvbuf in _IOFBF 65536)
660 (setvbuf out _IOFBF 65536)
661 (fold-port-matches (lambda (match result)
93b03575
LC
662 (put-bytevector out (string->utf8 store))
663 (put-u8 out (char->integer #\/))
664 (put-bytevector out
665 (string->utf8
666 "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-"))
91133c2d
LC
667 #t)
668 #f
669 pattern
670 in
671 (lambda (char result)
93b03575 672 (put-u8 out (char->integer char))
91133c2d
LC
673 result))))))
674
30db6af1
NK
675(define* (wrap-program prog #:rest vars)
676 "Rename PROG to .PROG-real and make PROG a wrapper. VARS should look like
677this:
678
679 '(VARIABLE DELIMITER POSITION LIST-OF-DIRECTORIES)
680
681where DELIMITER is optional. ':' will be used if DELIMITER is not given.
682
683For example, this command:
684
685 (wrap-program \"foo\"
686 '(\"PATH\" \":\" = (\"/nix/.../bar/bin\"))
687 '(\"CERT_PATH\" suffix (\"/nix/.../baz/certs\"
688 \"/qux/certs\")))
689
690will copy 'foo' to '.foo-real' and create the file 'foo' with the following
691contents:
692
693 #!location/of/bin/bash
694 export PATH=\"/nix/.../bar/bin\"
695 export CERT_PATH=\"$CERT_PATH${CERT_PATH:+:}/nix/.../baz/certs:/qux/certs\"
696 exec location/of/.foo-real
697
698This is useful for scripts that expect particular programs to be in $PATH, for
699programs that expect particular shared libraries to be in $LD_LIBRARY_PATH, or
700modules in $GUILE_LOAD_PATH, etc."
01155b18
NK
701 (let ((prog-real (string-append (dirname prog) "/." (basename prog) "-real"))
702 (prog-tmp (string-append (dirname prog) "/." (basename prog) "-tmp")))
30db6af1
NK
703 (define (export-variable lst)
704 ;; Return a string that exports an environment variable.
705 (match lst
706 ((var sep '= rest)
707 (format #f "export ~a=\"~a\""
708 var (string-join rest sep)))
709 ((var sep 'prefix rest)
710 (format #f "export ~a=\"~a${~a~a+~a}$~a\""
711 var (string-join rest sep) var sep sep var))
712 ((var sep 'suffix rest)
713 (format #f "export ~a=\"$~a${~a~a+~a}~a\""
714 var var var sep sep (string-join rest sep)))
715 ((var '= rest)
716 (format #f "export ~a=\"~a\""
717 var (string-join rest ":")))
718 ((var 'prefix rest)
719 (format #f "export ~a=\"~a${~a:+:}$~a\""
720 var (string-join rest ":") var var))
721 ((var 'suffix rest)
722 (format #f "export ~a=\"$~a${~a:+:}~a\""
723 var var var (string-join rest ":")))))
724
725 (copy-file prog prog-real)
726
727 (with-output-to-file prog-tmp
728 (lambda ()
729 (format #t
01155b18 730 "#!~a~%~a~%exec \"~a\" \"$@\"~%"
30db6af1
NK
731 (which "bash")
732 (string-join (map export-variable vars)
733 "\n")
734 (canonicalize-path prog-real))))
735
736 (chmod prog-tmp #o755)
737 (rename-file prog-tmp prog)))
738
b0e0d0e9
LC
739;;; Local Variables:
740;;; eval: (put 'call-with-output-file/atomic 'scheme-indent-function 1)
741;;; eval: (put 'with-throw-handler 'scheme-indent-function 1)
8197c978 742;;; eval: (put 'let-matches 'scheme-indent-function 3)
dcd72906 743;;; eval: (put 'with-atomic-file-replacement 'scheme-indent-function 1)
b0e0d0e9 744;;; End: