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