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