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