build-system/gnu: Set $LC_ALL (or similar) to the chosen locale.
[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))))
74baf333 591 "Read as much data as possible from IN and write it to OUT, using
a18b4d08
LC
592chunks of BUFFER-SIZE bytes. Call PROGRESS after each successful
593transfer of BUFFER-SIZE bytes or less, passing it the total number of
594bytes transferred and the continuation of the transfer as a thunk."
ebe2f31f
LC
595 (define buffer
596 (make-bytevector buffer-size))
597
a18b4d08
LC
598 (let loop ((total 0)
599 (bytes (get-bytevector-n! in buffer 0 buffer-size)))
ebe2f31f 600 (or (eof-object? bytes)
a18b4d08 601 (let ((total (+ total bytes)))
ebe2f31f 602 (put-bytevector out buffer 0 bytes)
a18b4d08
LC
603 (progress total
604 (lambda ()
605 (loop total
606 (get-bytevector-n! in buffer 0 buffer-size))))))))
ebe2f31f 607
bc5bf85f
LC
608(define (set-file-time file stat)
609 "Set the atime/mtime of FILE to that specified by STAT."
610 (utime file
611 (stat:atime stat)
612 (stat:mtime stat)
613 (stat:atimensec stat)
614 (stat:mtimensec stat)))
615
ebe2f31f 616(define patch-shebang
11996d85 617 (let ((shebang-rx (make-regexp "^[[:blank:]]*([[:graph:]]+)[[:blank:]]*([[:graph:]]*)(.*)$")))
525a59d6 618 (lambda* (file
bc5bf85f
LC
619 #:optional
620 (path (search-path-as-string->list (getenv "PATH")))
621 #:key (keep-mtime? #t))
525a59d6
LC
622 "Replace the #! interpreter file name in FILE by a valid one found in
623PATH, when FILE actually starts with a shebang. Return #t when FILE was
bc5bf85f
LC
624patched, #f otherwise. When KEEP-MTIME? is true, the atime/mtime of
625FILE are kept unchanged."
ebe2f31f
LC
626 (define (patch p interpreter rest-of-line)
627 (let* ((template (string-append file ".XXXXXX"))
628 (out (mkstemp! template))
bc5bf85f
LC
629 (st (stat file))
630 (mode (stat:mode st)))
ebe2f31f
LC
631 (with-throw-handler #t
632 (lambda ()
633 (format out "#!~a~a~%"
634 interpreter rest-of-line)
635 (dump-port p out)
636 (close out)
637 (chmod template mode)
638 (rename-file template file)
bc5bf85f
LC
639 (when keep-mtime?
640 (set-file-time file st))
ebe2f31f
LC
641 #t)
642 (lambda (key . args)
643 (format (current-error-port)
644 "patch-shebang: ~a: error: ~a ~s~%"
645 file key args)
646 (false-if-exception (delete-file template))
647 #f))))
648
c0895112
LC
649 (call-with-ascii-input-file file
650 (lambda (p)
651 (and (eq? #\# (read-char p))
652 (eq? #\! (read-char p))
653 (let ((line (false-if-exception (read-line p))))
654 (and=> (and line (regexp-exec shebang-rx line))
655 (lambda (m)
11996d85
AE
656 (let* ((interp (match:substring m 1))
657 (arg1 (match:substring m 2))
658 (rest (match:substring m 3))
659 (has-env (string-suffix? "/env" interp))
660 (cmd (if has-env arg1 (basename interp)))
661 (bin (search-path path cmd)))
c0895112 662 (if bin
11996d85 663 (if (string=? bin interp)
c0895112 664 #f ; nothing to do
11996d85
AE
665 (if has-env
666 (begin
667 (format (current-error-port)
668 "patch-shebang: ~a: changing `~a' to `~a'~%"
669 file (string-append interp " " arg1) bin)
670 (patch p bin rest))
671 (begin
672 (format (current-error-port)
673 "patch-shebang: ~a: changing `~a' to `~a'~%"
674 file interp bin)
675 (patch p bin
ca8def6e
AE
676 (if (string-null? arg1)
677 ""
678 (string-append " " arg1 rest))))))
c0895112
LC
679 (begin
680 (format (current-error-port)
681 "patch-shebang: ~a: warning: no binary for interpreter `~a' found in $PATH~%"
682 file (basename cmd))
683 #f))))))))))))
684
bc5bf85f
LC
685(define* (patch-makefile-SHELL file #:key (keep-mtime? #t))
686 "Patch the `SHELL' variable in FILE, which is supposedly a makefile.
687When KEEP-MTIME? is true, the atime/mtime of FILE are kept unchanged."
c0895112
LC
688
689 ;; For instance, Gettext-generated po/Makefile.in.in do not honor $SHELL.
690
691 ;; XXX: Unlike with `patch-shebang', FILE is always touched.
692
693 (define (find-shell name)
5e5deea9 694 (let ((shell (which name)))
c0895112
LC
695 (unless shell
696 (format (current-error-port)
697 "patch-makefile-SHELL: warning: no binary for shell `~a' found in $PATH~%"
698 name))
699 shell))
700
bc5bf85f
LC
701 (let ((st (stat file)))
702 (substitute* file
c809ec94 703 (("^ *SHELL[[:blank:]]*:?=[[:blank:]]*([[:graph:]]*/)([[:graph:]]+)(.*)$"
83291101 704 _ dir shell args)
bc5bf85f
LC
705 (let* ((old (string-append dir shell))
706 (new (or (find-shell shell) old)))
707 (unless (string=? new old)
708 (format (current-error-port)
709 "patch-makefile-SHELL: ~a: changing `SHELL' from `~a' to `~a'~%"
710 file old new))
11459384 711 (string-append "SHELL = " new args))))
bc5bf85f
LC
712
713 (when keep-mtime?
714 (set-file-time file st))))
10c87717 715
4eb01e54
LC
716(define* (patch-/usr/bin/file file
717 #:key
718 (file-command (which "file"))
719 (keep-mtime? #t))
720 "Patch occurrences of \"/usr/bin/file\" in FILE, replacing them with
721FILE-COMMAND. When KEEP-MTIME? is true, keep FILE's modification time
722unchanged."
723 (if (not file-command)
724 (format (current-error-port)
725 "patch-/usr/bin/file: warning: \
726no replacement 'file' command, doing nothing~%")
727 (let ((st (stat file)))
728 (substitute* file
729 (("/usr/bin/file")
730 (begin
731 (format (current-error-port)
732 "patch-/usr/bin/file: ~a: changing `~a' to `~a'~%"
733 file "/usr/bin/file" file-command)
734 file-command)))
735
736 (when keep-mtime?
737 (set-file-time file st)))))
738
91133c2d
LC
739(define* (fold-port-matches proc init pattern port
740 #:optional (unmatched (lambda (_ r) r)))
741 "Read from PORT character-by-character; for each match against
742PATTERN, call (PROC MATCH RESULT), where RESULT is seeded with INIT.
743PATTERN is a list of SRFI-14 char-sets. Call (UNMATCHED CHAR RESULT)
744for each unmatched character."
745 (define initial-pattern
746 ;; The poor developer's regexp.
747 (if (string? pattern)
748 (map char-set (string->list pattern))
749 pattern))
750
93b03575
LC
751 (define (get-char p)
752 ;; We call it `get-char', but that's really a binary version
753 ;; thereof. (The real `get-char' cannot be used here because our
754 ;; bootstrap Guile is hacked to always use UTF-8.)
755 (match (get-u8 p)
756 ((? integer? x) (integer->char x))
757 (x x)))
758
91133c2d
LC
759 ;; Note: we're not really striving for performance here...
760 (let loop ((chars '())
761 (pattern initial-pattern)
762 (matched '())
763 (result init))
764 (cond ((null? chars)
765 (loop (list (get-char port))
766 pattern
767 matched
768 result))
769 ((null? pattern)
770 (loop chars
771 initial-pattern
772 '()
773 (proc (list->string (reverse matched)) result)))
774 ((eof-object? (car chars))
775 (fold-right unmatched result matched))
776 ((char-set-contains? (car pattern) (car chars))
777 (loop (cdr chars)
778 (cdr pattern)
779 (cons (car chars) matched)
780 result))
781 ((null? matched) ; common case
782 (loop (cdr chars)
783 pattern
784 matched
785 (unmatched (car chars) result)))
786 (else
787 (let ((matched (reverse matched)))
788 (loop (append (cdr matched) chars)
789 initial-pattern
790 '()
791 (unmatched (car matched) result)))))))
792
793(define* (remove-store-references file
8be3b8a3 794 #:optional (store (%store-directory)))
91133c2d
LC
795 "Remove from FILE occurrences of file names in STORE; return #t when
796store paths were encountered in FILE, #f otherwise. This procedure is
797known as `nuke-refs' in Nixpkgs."
798 (define pattern
799 (let ((nix-base32-chars
800 '(#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9
801 #\a #\b #\c #\d #\f #\g #\h #\i #\j #\k #\l #\m #\n
802 #\p #\q #\r #\s #\v #\w #\x #\y #\z)))
803 `(,@(map char-set (string->list store))
804 ,(char-set #\/)
805 ,@(make-list 32 (list->char-set nix-base32-chars))
806 ,(char-set #\-))))
807
808 (with-fluids ((%default-port-encoding #f))
809 (with-atomic-file-replacement file
810 (lambda (in out)
811 ;; We cannot use `regexp-exec' here because it cannot deal with
812 ;; strings containing NUL characters.
813 (format #t "removing store references from `~a'...~%" file)
814 (setvbuf in _IOFBF 65536)
815 (setvbuf out _IOFBF 65536)
816 (fold-port-matches (lambda (match result)
93b03575
LC
817 (put-bytevector out (string->utf8 store))
818 (put-u8 out (char->integer #\/))
819 (put-bytevector out
820 (string->utf8
821 "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-"))
91133c2d
LC
822 #t)
823 #f
824 pattern
825 in
826 (lambda (char result)
93b03575 827 (put-u8 out (char->integer char))
91133c2d
LC
828 result))))))
829
30db6af1 830(define* (wrap-program prog #:rest vars)
de611138 831 "Make a wrapper for PROG. VARS should look like this:
30db6af1
NK
832
833 '(VARIABLE DELIMITER POSITION LIST-OF-DIRECTORIES)
834
835where DELIMITER is optional. ':' will be used if DELIMITER is not given.
836
837For example, this command:
838
839 (wrap-program \"foo\"
de611138
EB
840 '(\"PATH\" \":\" = (\"/gnu/.../bar/bin\"))
841 '(\"CERT_PATH\" suffix (\"/gnu/.../baz/certs\"
30db6af1
NK
842 \"/qux/certs\")))
843
844will copy 'foo' to '.foo-real' and create the file 'foo' with the following
845contents:
846
847 #!location/of/bin/bash
de611138
EB
848 export PATH=\"/gnu/.../bar/bin\"
849 export CERT_PATH=\"$CERT_PATH${CERT_PATH:+:}/gnu/.../baz/certs:/qux/certs\"
b01f8967 850 exec -a $0 location/of/.foo-real \"$@\"
30db6af1
NK
851
852This is useful for scripts that expect particular programs to be in $PATH, for
853programs that expect particular shared libraries to be in $LD_LIBRARY_PATH, or
de611138
EB
854modules in $GUILE_LOAD_PATH, etc.
855
856If PROG has previously been wrapped by wrap-program the wrapper will point to
857the previous wrapper."
858 (define (wrapper-file-name number)
859 (format #f "~a/.~a-wrap-~2'0d" (dirname prog) (basename prog) number))
860 (define (next-wrapper-number)
861 (let ((wrappers
862 (find-files (dirname prog)
863 (string-append "\\." (basename prog) "-wrap-.*"))))
864 (if (null? wrappers)
865 0
866 (string->number (string-take-right (last wrappers) 2)))))
867 (define (wrapper-target number)
868 (if (zero? number)
869 (let ((prog-real (string-append (dirname prog) "/."
870 (basename prog) "-real")))
da466f7f 871 (rename-file prog prog-real)
de611138
EB
872 prog-real)
873 (wrapper-file-name number)))
2ed11b3a 874
de611138
EB
875 (let* ((number (next-wrapper-number))
876 (target (wrapper-target number))
877 (wrapper (wrapper-file-name (1+ number)))
878 (prog-tmp (string-append target "-tmp")))
30db6af1
NK
879 (define (export-variable lst)
880 ;; Return a string that exports an environment variable.
881 (match lst
882 ((var sep '= rest)
883 (format #f "export ~a=\"~a\""
884 var (string-join rest sep)))
885 ((var sep 'prefix rest)
886 (format #f "export ~a=\"~a${~a~a+~a}$~a\""
887 var (string-join rest sep) var sep sep var))
888 ((var sep 'suffix rest)
889 (format #f "export ~a=\"$~a${~a~a+~a}~a\""
890 var var var sep sep (string-join rest sep)))
891 ((var '= rest)
892 (format #f "export ~a=\"~a\""
893 var (string-join rest ":")))
894 ((var 'prefix rest)
895 (format #f "export ~a=\"~a${~a:+:}$~a\""
896 var (string-join rest ":") var var))
897 ((var 'suffix rest)
898 (format #f "export ~a=\"$~a${~a:+:}~a\""
899 var var var (string-join rest ":")))))
900
30db6af1
NK
901 (with-output-to-file prog-tmp
902 (lambda ()
903 (format #t
b01f8967 904 "#!~a~%~a~%exec -a \"$0\" \"~a\" \"$@\"~%"
30db6af1
NK
905 (which "bash")
906 (string-join (map export-variable vars)
907 "\n")
de611138 908 (canonicalize-path target))))
30db6af1
NK
909
910 (chmod prog-tmp #o755)
de611138
EB
911 (rename-file prog-tmp wrapper)
912 (symlink wrapper prog-tmp)
30db6af1
NK
913 (rename-file prog-tmp prog)))
914
251e8b2e
LC
915\f
916;;;
917;;; Locales.
918;;;
919
920(define (locale-category->string category)
921 "Return the name of locale category CATEGORY, one of the 'LC_' constants.
922If CATEGORY is a bitwise or of several 'LC_' constants, an approximation is
923returned."
924 (letrec-syntax ((convert (syntax-rules ()
925 ((_)
926 (number->string category))
927 ((_ first rest ...)
928 (if (= first category)
929 (symbol->string 'first)
930 (convert rest ...))))))
931 (convert LC_ADDRESS LC_ALL LC_COLLATE LC_CTYPE
932 LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES LC_MONETARY
933 LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE
934 LC_TIME)))
935
b0e0d0e9
LC
936;;; Local Variables:
937;;; eval: (put 'call-with-output-file/atomic 'scheme-indent-function 1)
99533da5 938;;; eval: (put 'call-with-ascii-input-file 'scheme-indent-function 1)
b0e0d0e9 939;;; eval: (put 'with-throw-handler 'scheme-indent-function 1)
8197c978 940;;; eval: (put 'let-matches 'scheme-indent-function 3)
dcd72906 941;;; eval: (put 'with-atomic-file-replacement 'scheme-indent-function 1)
b0e0d0e9 942;;; End: