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