packages: Add 'file-pattern' field to 'search-path-specification'.
[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
6aa47e38 293(define* (search-path-as-list files input-dirs
7ec02d37 294 #:key (type 'directory) pattern)
6aa47e38
LC
295 "Return the list of directories among FILES of the given TYPE (a symbol as
296returned by 'stat:type') that exist in INPUT-DIRS. Example:
c36db98c
LC
297
298 (search-path-as-list '(\"share/emacs/site-lisp\" \"share/emacs/24.1\")
299 (list \"/package1\" \"/package2\" \"/package3\"))
300 => (\"/package1/share/emacs/site-lisp\"
301 \"/package3/share/emacs/site-lisp\")
302
7ec02d37
LC
303When PATTERN is true, it is a regular expression denoting file names to look
304for under the directories designated by FILES. For example:
305
306 (search-path-as-list '(\"xml\") (list docbook-xml docbook-xsl)
307 #:type 'regular
308 #:pattern \"^catalog\\\\.xml$\")
309 => (\"/…/xml/dtd/docbook/catalog.xml\"
310 \"/…/xml/xsl/docbook-xsl-1.78.1/catalog.xml\")
c36db98c
LC
311"
312 (append-map (lambda (input)
7ec02d37
LC
313 (append-map (lambda (file)
314 (let ((file (string-append input "/" file)))
315 ;; XXX: By using 'find-files', we implicitly
316 ;; assume #:type 'regular.
317 (if pattern
318 (find-files file pattern)
319 (let ((stat (stat file #f)))
320 (if (and stat (eq? type (stat:type stat)))
321 (list file)
322 '())))))
6aa47e38 323 files))
c36db98c
LC
324 input-dirs))
325
326(define (list->search-path-as-string lst separator)
327 (string-join lst separator))
328
ebe2f31f
LC
329(define* (search-path-as-string->list path #:optional (separator #\:))
330 (string-tokenize path (char-set-complement (char-set separator))))
331
6aa47e38
LC
332(define* (set-path-environment-variable env-var files input-dirs
333 #:key
334 (separator ":")
7ec02d37
LC
335 (type 'directory)
336 pattern)
6aa47e38
LC
337 "Look for each of FILES of the given TYPE (a symbol as returned by
338'stat:type') in INPUT-DIRS. Set ENV-VAR to a SEPARATOR-separated path
339accordingly. Example:
c36db98c
LC
340
341 (set-path-environment-variable \"PKG_CONFIG\"
342 '(\"lib/pkgconfig\")
343 (list package1 package2))
7ec02d37
LC
344
345When PATTERN is not #f, it must be a regular expression (really a string)
346denoting file names to look for under the directories designated by FILES:
347
348 (set-path-environment-variable \"XML_CATALOG_FILES\"
349 '(\"xml\")
350 (list docbook-xml docbook-xsl)
351 #:type 'regular
352 #:pattern \"^catalog\\\\.xml$\")
c36db98c 353"
6aa47e38 354 (let* ((path (search-path-as-list files input-dirs
7ec02d37
LC
355 #:type type
356 #:pattern pattern))
9d9ef458 357 (value (list->search-path-as-string path separator)))
b15669f3
LC
358 (if (string-null? value)
359 (begin
360 ;; Never set ENV-VAR to an empty string because often, the empty
361 ;; string is equivalent to ".". This is the case for
362 ;; GUILE_LOAD_PATH in Guile 2.0, for instance.
363 (unsetenv env-var)
364 (format #t "environment variable `~a' unset~%" env-var))
365 (begin
366 (setenv env-var value)
367 (format #t "environment variable `~a' set to `~a'~%"
368 env-var value)))))
b0e0d0e9 369
7584f822
LC
370(define (which program)
371 "Return the complete file name for PROGRAM as found in $PATH, or #f if
372PROGRAM could not be found."
373 (search-path (search-path-as-string->list (getenv "PATH"))
374 program))
375
b0e0d0e9
LC
376\f
377;;;
378;;; Phases.
379;;;
380;;; In (guix build gnu-build-system), there are separate phases (configure,
381;;; build, test, install). They are represented as a list of name/procedure
382;;; pairs. The following procedures make it easy to change the list of
383;;; phases.
384;;;
385
386(define* (alist-cons-before reference key value alist
387 #:optional (key=? equal?))
388 "Insert the KEY/VALUE pair before the first occurrence of a pair whose key
389is REFERENCE in ALIST. Use KEY=? to compare keys."
390 (let-values (((before after)
391 (break (match-lambda
392 ((k . _)
393 (key=? k reference)))
394 alist)))
395 (append before (alist-cons key value after))))
396
397(define* (alist-cons-after reference key value alist
398 #:optional (key=? equal?))
399 "Insert the KEY/VALUE pair after the first occurrence of a pair whose key
400is REFERENCE in ALIST. Use KEY=? to compare keys."
401 (let-values (((before after)
402 (break (match-lambda
403 ((k . _)
404 (key=? k reference)))
405 alist)))
406 (match after
407 ((reference after ...)
408 (append before (cons* reference `(,key . ,value) after)))
409 (()
410 (append before `((,key . ,value)))))))
411
412(define* (alist-replace key value alist #:optional (key=? equal?))
413 "Replace the first pair in ALIST whose car is KEY with the KEY/VALUE pair.
414An error is raised when no such pair exists."
415 (let-values (((before after)
416 (break (match-lambda
417 ((k . _)
418 (key=? k key)))
419 alist)))
420 (match after
421 ((_ after ...)
422 (append before (alist-cons key value after))))))
423
424\f
425;;;
426;;; Text substitution (aka. sed).
427;;;
428
dcd72906
LC
429(define (with-atomic-file-replacement file proc)
430 "Call PROC with two arguments: an input port for FILE, and an output
431port for the file that is going to replace FILE. Upon success, FILE is
432atomically replaced by what has been written to the output port, and
433PROC's result is returned."
434 (let* ((template (string-append file ".XXXXXX"))
d9dbab18
LC
435 (out (mkstemp! template))
436 (mode (stat:mode (stat file))))
b0e0d0e9
LC
437 (with-throw-handler #t
438 (lambda ()
439 (call-with-input-file file
440 (lambda (in)
dcd72906
LC
441 (let ((result (proc in out)))
442 (close out)
443 (chmod template mode)
444 (rename-file template file)
445 result))))
b0e0d0e9
LC
446 (lambda (key . args)
447 (false-if-exception (delete-file template))))))
448
dcd72906 449(define (substitute file pattern+procs)
094b2efc
TUBK
450 "PATTERN+PROCS is a list of regexp/two-argument-procedure pairs. For each
451line of FILE, and for each PATTERN that it matches, call the corresponding
452PROC as (PROC LINE MATCHES); PROC must return the line that will be written as
453a substitution of the original line. Be careful about using '$' to match the
454end of a line; by itself it won't match the terminating newline of a line."
dcd72906
LC
455 (let ((rx+proc (map (match-lambda
456 (((? regexp? pattern) . proc)
457 (cons pattern proc))
458 ((pattern . proc)
459 (cons (make-regexp pattern regexp/extended)
460 proc)))
461 pattern+procs)))
462 (with-atomic-file-replacement file
463 (lambda (in out)
464 (let loop ((line (read-line in 'concat)))
465 (if (eof-object? line)
466 #t
467 (let ((line (fold (lambda (r+p line)
468 (match r+p
469 ((regexp . proc)
470 (match (list-matches regexp line)
471 ((and m+ (_ _ ...))
472 (proc line m+))
473 (_ line)))))
474 line
475 rx+proc)))
476 (display line out)
477 (loop (read-line in 'concat)))))))))
478
10c87717
LC
479
480(define-syntax let-matches
481 ;; Helper macro for `substitute*'.
482 (syntax-rules (_)
483 ((let-matches index match (_ vars ...) body ...)
484 (let-matches (+ 1 index) match (vars ...)
485 body ...))
486 ((let-matches index match (var vars ...) body ...)
487 (let ((var (match:substring match index)))
488 (let-matches (+ 1 index) match (vars ...)
489 body ...)))
490 ((let-matches index match () body ...)
491 (begin body ...))))
492
8773648e
LC
493(define-syntax substitute*
494 (syntax-rules ()
495 "Substitute REGEXP in FILE by the string returned by BODY. BODY is
10c87717
LC
496evaluated with each MATCH-VAR bound to the corresponding positional regexp
497sub-expression. For example:
498
4fa697e9 499 (substitute* file
20d83444
LC
500 ((\"hello\")
501 \"good morning\\n\")
502 ((\"foo([a-z]+)bar(.*)$\" all letters end)
503 (string-append \"baz\" letter end)))
4fa697e9
LC
504
505Here, anytime a line of FILE contains \"hello\", it is replaced by \"good
506morning\". Anytime a line of FILE matches the second regexp, ALL is bound to
507the complete match, LETTERS is bound to the first sub-expression, and END is
508bound to the last one.
509
510When one of the MATCH-VAR is `_', no variable is bound to the corresponding
20d83444
LC
511match substring.
512
513Alternatively, FILE may be a list of file names, in which case they are
094b2efc
TUBK
514all subject to the substitutions.
515
516Be careful about using '$' to match the end of a line; by itself it won't
517match the terminating newline of a line."
8773648e 518 ((substitute* file ((regexp match-var ...) body ...) ...)
20d83444
LC
519 (let ()
520 (define (substitute-one-file file-name)
521 (substitute
522 file-name
523 (list (cons regexp
524 (lambda (l m+)
525 ;; Iterate over matches M+ and return the
526 ;; modified line based on L.
527 (let loop ((m* m+) ; matches
528 (o 0) ; offset in L
529 (r '())) ; result
530 (match m*
531 (()
532 (let ((r (cons (substring l o) r)))
533 (string-concatenate-reverse r)))
534 ((m . rest)
535 (let-matches 0 m (match-var ...)
536 (loop rest
537 (match:end m)
538 (cons*
539 (begin body ...)
540 (substring l o (match:start m))
541 r))))))))
542 ...)))
543
544 (match file
545 ((files (... ...))
546 (for-each substitute-one-file files))
547 ((? string? f)
548 (substitute-one-file f)))))))
10c87717 549
ebe2f31f
LC
550\f
551;;;
8be3b8a3 552;;; Patching shebangs---e.g., /bin/sh -> /gnu/store/xyz...-bash/bin/sh.
ebe2f31f
LC
553;;;
554
a18b4d08
LC
555(define* (dump-port in out
556 #:key (buffer-size 16384)
557 (progress (lambda (t k) (k))))
74baf333 558 "Read as much data as possible from IN and write it to OUT, using
a18b4d08
LC
559chunks of BUFFER-SIZE bytes. Call PROGRESS after each successful
560transfer of BUFFER-SIZE bytes or less, passing it the total number of
561bytes transferred and the continuation of the transfer as a thunk."
ebe2f31f
LC
562 (define buffer
563 (make-bytevector buffer-size))
564
a18b4d08
LC
565 (let loop ((total 0)
566 (bytes (get-bytevector-n! in buffer 0 buffer-size)))
ebe2f31f 567 (or (eof-object? bytes)
a18b4d08 568 (let ((total (+ total bytes)))
ebe2f31f 569 (put-bytevector out buffer 0 bytes)
a18b4d08
LC
570 (progress total
571 (lambda ()
572 (loop total
573 (get-bytevector-n! in buffer 0 buffer-size))))))))
ebe2f31f 574
bc5bf85f
LC
575(define (set-file-time file stat)
576 "Set the atime/mtime of FILE to that specified by STAT."
577 (utime file
578 (stat:atime stat)
579 (stat:mtime stat)
580 (stat:atimensec stat)
581 (stat:mtimensec stat)))
582
ebe2f31f 583(define patch-shebang
11996d85 584 (let ((shebang-rx (make-regexp "^[[:blank:]]*([[:graph:]]+)[[:blank:]]*([[:graph:]]*)(.*)$")))
525a59d6 585 (lambda* (file
bc5bf85f
LC
586 #:optional
587 (path (search-path-as-string->list (getenv "PATH")))
588 #:key (keep-mtime? #t))
525a59d6
LC
589 "Replace the #! interpreter file name in FILE by a valid one found in
590PATH, when FILE actually starts with a shebang. Return #t when FILE was
bc5bf85f
LC
591patched, #f otherwise. When KEEP-MTIME? is true, the atime/mtime of
592FILE are kept unchanged."
ebe2f31f
LC
593 (define (patch p interpreter rest-of-line)
594 (let* ((template (string-append file ".XXXXXX"))
595 (out (mkstemp! template))
bc5bf85f
LC
596 (st (stat file))
597 (mode (stat:mode st)))
ebe2f31f
LC
598 (with-throw-handler #t
599 (lambda ()
600 (format out "#!~a~a~%"
601 interpreter rest-of-line)
602 (dump-port p out)
603 (close out)
604 (chmod template mode)
605 (rename-file template file)
bc5bf85f
LC
606 (when keep-mtime?
607 (set-file-time file st))
ebe2f31f
LC
608 #t)
609 (lambda (key . args)
610 (format (current-error-port)
611 "patch-shebang: ~a: error: ~a ~s~%"
612 file key args)
613 (false-if-exception (delete-file template))
614 #f))))
615
c0895112
LC
616 (call-with-ascii-input-file file
617 (lambda (p)
618 (and (eq? #\# (read-char p))
619 (eq? #\! (read-char p))
620 (let ((line (false-if-exception (read-line p))))
621 (and=> (and line (regexp-exec shebang-rx line))
622 (lambda (m)
11996d85
AE
623 (let* ((interp (match:substring m 1))
624 (arg1 (match:substring m 2))
625 (rest (match:substring m 3))
626 (has-env (string-suffix? "/env" interp))
627 (cmd (if has-env arg1 (basename interp)))
628 (bin (search-path path cmd)))
c0895112 629 (if bin
11996d85 630 (if (string=? bin interp)
c0895112 631 #f ; nothing to do
11996d85
AE
632 (if has-env
633 (begin
634 (format (current-error-port)
635 "patch-shebang: ~a: changing `~a' to `~a'~%"
636 file (string-append interp " " arg1) bin)
637 (patch p bin rest))
638 (begin
639 (format (current-error-port)
640 "patch-shebang: ~a: changing `~a' to `~a'~%"
641 file interp bin)
642 (patch p bin
ca8def6e
AE
643 (if (string-null? arg1)
644 ""
645 (string-append " " arg1 rest))))))
c0895112
LC
646 (begin
647 (format (current-error-port)
648 "patch-shebang: ~a: warning: no binary for interpreter `~a' found in $PATH~%"
649 file (basename cmd))
650 #f))))))))))))
651
bc5bf85f
LC
652(define* (patch-makefile-SHELL file #:key (keep-mtime? #t))
653 "Patch the `SHELL' variable in FILE, which is supposedly a makefile.
654When KEEP-MTIME? is true, the atime/mtime of FILE are kept unchanged."
c0895112
LC
655
656 ;; For instance, Gettext-generated po/Makefile.in.in do not honor $SHELL.
657
658 ;; XXX: Unlike with `patch-shebang', FILE is always touched.
659
660 (define (find-shell name)
5e5deea9 661 (let ((shell (which name)))
c0895112
LC
662 (unless shell
663 (format (current-error-port)
664 "patch-makefile-SHELL: warning: no binary for shell `~a' found in $PATH~%"
665 name))
666 shell))
667
bc5bf85f
LC
668 (let ((st (stat file)))
669 (substitute* file
c809ec94 670 (("^ *SHELL[[:blank:]]*:?=[[:blank:]]*([[:graph:]]*/)([[:graph:]]+)(.*)$"
83291101 671 _ dir shell args)
bc5bf85f
LC
672 (let* ((old (string-append dir shell))
673 (new (or (find-shell shell) old)))
674 (unless (string=? new old)
675 (format (current-error-port)
676 "patch-makefile-SHELL: ~a: changing `SHELL' from `~a' to `~a'~%"
677 file old new))
11459384 678 (string-append "SHELL = " new args))))
bc5bf85f
LC
679
680 (when keep-mtime?
681 (set-file-time file st))))
10c87717 682
91133c2d
LC
683(define* (fold-port-matches proc init pattern port
684 #:optional (unmatched (lambda (_ r) r)))
685 "Read from PORT character-by-character; for each match against
686PATTERN, call (PROC MATCH RESULT), where RESULT is seeded with INIT.
687PATTERN is a list of SRFI-14 char-sets. Call (UNMATCHED CHAR RESULT)
688for each unmatched character."
689 (define initial-pattern
690 ;; The poor developer's regexp.
691 (if (string? pattern)
692 (map char-set (string->list pattern))
693 pattern))
694
93b03575
LC
695 (define (get-char p)
696 ;; We call it `get-char', but that's really a binary version
697 ;; thereof. (The real `get-char' cannot be used here because our
698 ;; bootstrap Guile is hacked to always use UTF-8.)
699 (match (get-u8 p)
700 ((? integer? x) (integer->char x))
701 (x x)))
702
91133c2d
LC
703 ;; Note: we're not really striving for performance here...
704 (let loop ((chars '())
705 (pattern initial-pattern)
706 (matched '())
707 (result init))
708 (cond ((null? chars)
709 (loop (list (get-char port))
710 pattern
711 matched
712 result))
713 ((null? pattern)
714 (loop chars
715 initial-pattern
716 '()
717 (proc (list->string (reverse matched)) result)))
718 ((eof-object? (car chars))
719 (fold-right unmatched result matched))
720 ((char-set-contains? (car pattern) (car chars))
721 (loop (cdr chars)
722 (cdr pattern)
723 (cons (car chars) matched)
724 result))
725 ((null? matched) ; common case
726 (loop (cdr chars)
727 pattern
728 matched
729 (unmatched (car chars) result)))
730 (else
731 (let ((matched (reverse matched)))
732 (loop (append (cdr matched) chars)
733 initial-pattern
734 '()
735 (unmatched (car matched) result)))))))
736
737(define* (remove-store-references file
8be3b8a3 738 #:optional (store (%store-directory)))
91133c2d
LC
739 "Remove from FILE occurrences of file names in STORE; return #t when
740store paths were encountered in FILE, #f otherwise. This procedure is
741known as `nuke-refs' in Nixpkgs."
742 (define pattern
743 (let ((nix-base32-chars
744 '(#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9
745 #\a #\b #\c #\d #\f #\g #\h #\i #\j #\k #\l #\m #\n
746 #\p #\q #\r #\s #\v #\w #\x #\y #\z)))
747 `(,@(map char-set (string->list store))
748 ,(char-set #\/)
749 ,@(make-list 32 (list->char-set nix-base32-chars))
750 ,(char-set #\-))))
751
752 (with-fluids ((%default-port-encoding #f))
753 (with-atomic-file-replacement file
754 (lambda (in out)
755 ;; We cannot use `regexp-exec' here because it cannot deal with
756 ;; strings containing NUL characters.
757 (format #t "removing store references from `~a'...~%" file)
758 (setvbuf in _IOFBF 65536)
759 (setvbuf out _IOFBF 65536)
760 (fold-port-matches (lambda (match result)
93b03575
LC
761 (put-bytevector out (string->utf8 store))
762 (put-u8 out (char->integer #\/))
763 (put-bytevector out
764 (string->utf8
765 "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-"))
91133c2d
LC
766 #t)
767 #f
768 pattern
769 in
770 (lambda (char result)
93b03575 771 (put-u8 out (char->integer char))
91133c2d
LC
772 result))))))
773
30db6af1 774(define* (wrap-program prog #:rest vars)
de611138 775 "Make a wrapper for PROG. VARS should look like this:
30db6af1
NK
776
777 '(VARIABLE DELIMITER POSITION LIST-OF-DIRECTORIES)
778
779where DELIMITER is optional. ':' will be used if DELIMITER is not given.
780
781For example, this command:
782
783 (wrap-program \"foo\"
de611138
EB
784 '(\"PATH\" \":\" = (\"/gnu/.../bar/bin\"))
785 '(\"CERT_PATH\" suffix (\"/gnu/.../baz/certs\"
30db6af1
NK
786 \"/qux/certs\")))
787
788will copy 'foo' to '.foo-real' and create the file 'foo' with the following
789contents:
790
791 #!location/of/bin/bash
de611138
EB
792 export PATH=\"/gnu/.../bar/bin\"
793 export CERT_PATH=\"$CERT_PATH${CERT_PATH:+:}/gnu/.../baz/certs:/qux/certs\"
2ed11b3a 794 exec -a location/of/foo location/of/.foo-real \"$@\"
30db6af1
NK
795
796This is useful for scripts that expect particular programs to be in $PATH, for
797programs that expect particular shared libraries to be in $LD_LIBRARY_PATH, or
de611138
EB
798modules in $GUILE_LOAD_PATH, etc.
799
800If PROG has previously been wrapped by wrap-program the wrapper will point to
801the previous wrapper."
802 (define (wrapper-file-name number)
803 (format #f "~a/.~a-wrap-~2'0d" (dirname prog) (basename prog) number))
804 (define (next-wrapper-number)
805 (let ((wrappers
806 (find-files (dirname prog)
807 (string-append "\\." (basename prog) "-wrap-.*"))))
808 (if (null? wrappers)
809 0
810 (string->number (string-take-right (last wrappers) 2)))))
811 (define (wrapper-target number)
812 (if (zero? number)
813 (let ((prog-real (string-append (dirname prog) "/."
814 (basename prog) "-real")))
815 (copy-file prog prog-real)
816 prog-real)
817 (wrapper-file-name number)))
2ed11b3a 818
de611138
EB
819 (let* ((number (next-wrapper-number))
820 (target (wrapper-target number))
821 (wrapper (wrapper-file-name (1+ number)))
822 (prog-tmp (string-append target "-tmp")))
30db6af1
NK
823 (define (export-variable lst)
824 ;; Return a string that exports an environment variable.
825 (match lst
826 ((var sep '= rest)
827 (format #f "export ~a=\"~a\""
828 var (string-join rest sep)))
829 ((var sep 'prefix rest)
830 (format #f "export ~a=\"~a${~a~a+~a}$~a\""
831 var (string-join rest sep) var sep sep var))
832 ((var sep 'suffix rest)
833 (format #f "export ~a=\"$~a${~a~a+~a}~a\""
834 var var var sep sep (string-join rest sep)))
835 ((var '= rest)
836 (format #f "export ~a=\"~a\""
837 var (string-join rest ":")))
838 ((var 'prefix rest)
839 (format #f "export ~a=\"~a${~a:+:}$~a\""
840 var (string-join rest ":") var var))
841 ((var 'suffix rest)
842 (format #f "export ~a=\"$~a${~a:+:}~a\""
843 var var var (string-join rest ":")))))
844
30db6af1
NK
845 (with-output-to-file prog-tmp
846 (lambda ()
847 (format #t
2ed11b3a 848 "#!~a~%~a~%exec -a \"~a\" \"~a\" \"$@\"~%"
30db6af1
NK
849 (which "bash")
850 (string-join (map export-variable vars)
851 "\n")
2ed11b3a 852 (canonicalize-path prog)
de611138 853 (canonicalize-path target))))
30db6af1
NK
854
855 (chmod prog-tmp #o755)
de611138
EB
856 (rename-file prog-tmp wrapper)
857 (symlink wrapper prog-tmp)
30db6af1
NK
858 (rename-file prog-tmp prog)))
859
b0e0d0e9
LC
860;;; Local Variables:
861;;; eval: (put 'call-with-output-file/atomic 'scheme-indent-function 1)
99533da5 862;;; eval: (put 'call-with-ascii-input-file 'scheme-indent-function 1)
b0e0d0e9 863;;; eval: (put 'with-throw-handler 'scheme-indent-function 1)
8197c978 864;;; eval: (put 'let-matches 'scheme-indent-function 3)
dcd72906 865;;; eval: (put 'with-atomic-file-replacement 'scheme-indent-function 1)
b0e0d0e9 866;;; End: