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