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