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