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