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