build-system/gnu: Add `static-package'.
[jackhill/guix/guix.git] / guix / build / utils.scm
CommitLineData
c36db98c
LC
1;;; Guix --- Nix package management from Guile. -*- coding: utf-8 -*-
2;;; Copyright (C) 2012 Ludovic Courtès <ludo@gnu.org>
3;;;
4;;; This file is part of Guix.
5;;;
6;;; Guix is free software; you can redistribute it and/or modify it
7;;; under the terms of the GNU General Public License as published by
8;;; the Free Software Foundation; either version 3 of the License, or (at
9;;; your option) any later version.
10;;;
11;;; Guix is distributed in the hope that it will be useful, but
12;;; WITHOUT ANY WARRANTY; without even the implied warranty of
13;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14;;; GNU General Public License for more details.
15;;;
16;;; You should have received a copy of the GNU General Public License
17;;; along with Guix. If not, see <http://www.gnu.org/licenses/>.
18
19(define-module (guix build utils)
20 #:use-module (srfi srfi-1)
b0e0d0e9 21 #:use-module (srfi srfi-11)
c0746cc9 22 #:use-module (ice-9 ftw)
b0e0d0e9
LC
23 #:use-module (ice-9 match)
24 #:use-module (ice-9 regex)
25 #:use-module (ice-9 rdelim)
ebe2f31f
LC
26 #:use-module (rnrs bytevectors)
27 #:use-module (rnrs io ports)
c36db98c 28 #:export (directory-exists?
d0084152 29 executable-file?
c0895112 30 call-with-ascii-input-file
b0e0d0e9 31 with-directory-excursion
7da95264 32 mkdir-p
c0746cc9 33 copy-recursively
4c261f41
LC
34 find-files
35
b0e0d0e9 36 set-path-environment-variable
ebe2f31f
LC
37 search-path-as-string->list
38 list->search-path-as-string
b0e0d0e9
LC
39 alist-cons-before
40 alist-cons-after
41 alist-replace
dcd72906 42 with-atomic-file-replacement
10c87717 43 substitute
ebe2f31f
LC
44 substitute*
45 dump-port
bc5bf85f 46 set-file-time
91133c2d 47 patch-shebang
c0895112 48 patch-makefile-SHELL
91133c2d
LC
49 fold-port-matches
50 remove-store-references))
b0e0d0e9
LC
51
52\f
53;;;
54;;; Directories.
55;;;
c36db98c
LC
56
57(define (directory-exists? dir)
58 "Return #t if DIR exists and is a directory."
9f55cf8d
LC
59 (let ((s (stat dir #f)))
60 (and s
61 (eq? 'directory (stat:type s)))))
c36db98c 62
d0084152
LC
63(define (executable-file? file)
64 "Return #t if FILE exists and is executable."
65 (let ((s (stat file #f)))
66 (and s
67 (not (zero? (logand (stat:mode s) #o100))))))
68
c0895112
LC
69(define (call-with-ascii-input-file file proc)
70 "Open FILE as an ASCII or binary file, and pass the resulting port to
71PROC. FILE is closed when PROC's dynamic extent is left. Return the
72return values of applying PROC to the port."
73 (let ((port (with-fluids ((%default-port-encoding #f))
74 ;; Use "b" so that `open-file' ignores `coding:' cookies.
75 (open-file file "rb"))))
76 (dynamic-wind
77 (lambda ()
78 #t)
79 (lambda ()
80 (proc port))
81 (lambda ()
82 (close-input-port port)))))
83
b0e0d0e9
LC
84(define-syntax-rule (with-directory-excursion dir body ...)
85 "Run BODY with DIR as the process's current directory."
86 (let ((init (getcwd)))
87 (dynamic-wind
88 (lambda ()
89 (chdir dir))
90 (lambda ()
91 body ...)
92 (lambda ()
93 (chdir init)))))
94
7da95264
LC
95(define (mkdir-p dir)
96 "Create directory DIR and all its ancestors."
97 (define absolute?
98 (string-prefix? "/" dir))
99
100 (define not-slash
101 (char-set-complement (char-set #\/)))
102
103 (let loop ((components (string-tokenize dir not-slash))
104 (root (if absolute?
105 ""
106 ".")))
107 (match components
108 ((head tail ...)
109 (let ((path (string-append root "/" head)))
110 (catch 'system-error
111 (lambda ()
112 (mkdir path)
113 (loop tail path))
114 (lambda args
115 (if (= EEXIST (system-error-errno args))
116 (loop tail path)
117 (apply throw args))))))
118 (() #t))))
119
c0746cc9
LC
120(define* (copy-recursively source destination
121 #:optional (log (current-output-port)))
122 "Copy SOURCE directory to DESTINATION."
123 (define strip-source
124 (let ((len (string-length source)))
125 (lambda (file)
126 (substring file len))))
127
128 (file-system-fold (const #t) ; enter?
129 (lambda (file stat result) ; leaf
130 (let ((dest (string-append destination
131 (strip-source file))))
132 (format log "`~a' -> `~a'~%" file dest)
133 (copy-file file dest)))
134 (lambda (dir stat result) ; down
135 (mkdir-p (string-append destination
136 (strip-source dir))))
137 (lambda (dir stat result) ; up
138 result)
139 (const #t) ; skip
140 (lambda (file stat errno result)
141 (format (current-error-port) "i/o error: ~a: ~a~%"
142 file (strerror errno))
143 #f)
144 #t
145 source))
146
4c261f41
LC
147(define (find-files dir regexp)
148 "Return the list of files under DIR whose basename matches REGEXP."
149 (define file-rx
150 (if (regexp? regexp)
151 regexp
152 (make-regexp regexp)))
153
154 (file-system-fold (const #t)
155 (lambda (file stat result) ; leaf
156 (if (regexp-exec file-rx (basename file))
157 (cons file result)
158 result))
159 (lambda (dir stat result) ; down
160 result)
161 (lambda (dir stat result) ; up
162 result)
163 (lambda (file stat result) ; skip
164 result)
165 (lambda (file stat errno result)
166 (format (current-error-port) "find-files: ~a: ~a~%"
167 file (strerror errno))
168 #f)
169 '()
170 dir))
171
b0e0d0e9
LC
172\f
173;;;
174;;; Search paths.
175;;;
176
c36db98c
LC
177(define (search-path-as-list sub-directories input-dirs)
178 "Return the list of directories among SUB-DIRECTORIES that exist in
179INPUT-DIRS. Example:
180
181 (search-path-as-list '(\"share/emacs/site-lisp\" \"share/emacs/24.1\")
182 (list \"/package1\" \"/package2\" \"/package3\"))
183 => (\"/package1/share/emacs/site-lisp\"
184 \"/package3/share/emacs/site-lisp\")
185
186"
187 (append-map (lambda (input)
188 (filter-map (lambda (dir)
189 (let ((dir (string-append input "/"
190 dir)))
191 (and (directory-exists? dir)
192 dir)))
193 sub-directories))
194 input-dirs))
195
196(define (list->search-path-as-string lst separator)
197 (string-join lst separator))
198
ebe2f31f
LC
199(define* (search-path-as-string->list path #:optional (separator #\:))
200 (string-tokenize path (char-set-complement (char-set separator))))
201
c36db98c
LC
202(define* (set-path-environment-variable env-var sub-directories input-dirs
203 #:key (separator ":"))
204 "Look for each of SUB-DIRECTORIES in INPUT-DIRS. Set ENV-VAR to a
205SEPARATOR-separated path accordingly. Example:
206
207 (set-path-environment-variable \"PKG_CONFIG\"
208 '(\"lib/pkgconfig\")
209 (list package1 package2))
210"
9d9ef458
LC
211 (let* ((path (search-path-as-list sub-directories input-dirs))
212 (value (list->search-path-as-string path separator)))
213 (setenv env-var value)
214 (format #t "environment variable `~a' set to `~a'~%"
215 env-var value)))
b0e0d0e9
LC
216
217\f
218;;;
219;;; Phases.
220;;;
221;;; In (guix build gnu-build-system), there are separate phases (configure,
222;;; build, test, install). They are represented as a list of name/procedure
223;;; pairs. The following procedures make it easy to change the list of
224;;; phases.
225;;;
226
227(define* (alist-cons-before reference key value alist
228 #:optional (key=? equal?))
229 "Insert the KEY/VALUE pair before the first occurrence of a pair whose key
230is REFERENCE in ALIST. Use KEY=? to compare keys."
231 (let-values (((before after)
232 (break (match-lambda
233 ((k . _)
234 (key=? k reference)))
235 alist)))
236 (append before (alist-cons key value after))))
237
238(define* (alist-cons-after reference key value alist
239 #:optional (key=? equal?))
240 "Insert the KEY/VALUE pair after the first occurrence of a pair whose key
241is REFERENCE in ALIST. Use KEY=? to compare keys."
242 (let-values (((before after)
243 (break (match-lambda
244 ((k . _)
245 (key=? k reference)))
246 alist)))
247 (match after
248 ((reference after ...)
249 (append before (cons* reference `(,key . ,value) after)))
250 (()
251 (append before `((,key . ,value)))))))
252
253(define* (alist-replace key value alist #:optional (key=? equal?))
254 "Replace the first pair in ALIST whose car is KEY with the KEY/VALUE pair.
255An error is raised when no such pair exists."
256 (let-values (((before after)
257 (break (match-lambda
258 ((k . _)
259 (key=? k key)))
260 alist)))
261 (match after
262 ((_ after ...)
263 (append before (alist-cons key value after))))))
264
265\f
266;;;
267;;; Text substitution (aka. sed).
268;;;
269
dcd72906
LC
270(define (with-atomic-file-replacement file proc)
271 "Call PROC with two arguments: an input port for FILE, and an output
272port for the file that is going to replace FILE. Upon success, FILE is
273atomically replaced by what has been written to the output port, and
274PROC's result is returned."
275 (let* ((template (string-append file ".XXXXXX"))
d9dbab18
LC
276 (out (mkstemp! template))
277 (mode (stat:mode (stat file))))
b0e0d0e9
LC
278 (with-throw-handler #t
279 (lambda ()
280 (call-with-input-file file
281 (lambda (in)
dcd72906
LC
282 (let ((result (proc in out)))
283 (close out)
284 (chmod template mode)
285 (rename-file template file)
286 result))))
b0e0d0e9
LC
287 (lambda (key . args)
288 (false-if-exception (delete-file template))))))
289
dcd72906
LC
290(define (substitute file pattern+procs)
291 "PATTERN+PROCS is a list of regexp/two-argument procedure. For each line
292of FILE, and for each PATTERN that it matches, call the corresponding PROC
293as (PROC LINE MATCHES); PROC must return the line that will be written as a
294substitution of the original line."
295 (let ((rx+proc (map (match-lambda
296 (((? regexp? pattern) . proc)
297 (cons pattern proc))
298 ((pattern . proc)
299 (cons (make-regexp pattern regexp/extended)
300 proc)))
301 pattern+procs)))
302 (with-atomic-file-replacement file
303 (lambda (in out)
304 (let loop ((line (read-line in 'concat)))
305 (if (eof-object? line)
306 #t
307 (let ((line (fold (lambda (r+p line)
308 (match r+p
309 ((regexp . proc)
310 (match (list-matches regexp line)
311 ((and m+ (_ _ ...))
312 (proc line m+))
313 (_ line)))))
314 line
315 rx+proc)))
316 (display line out)
317 (loop (read-line in 'concat)))))))))
318
10c87717
LC
319
320(define-syntax let-matches
321 ;; Helper macro for `substitute*'.
322 (syntax-rules (_)
323 ((let-matches index match (_ vars ...) body ...)
324 (let-matches (+ 1 index) match (vars ...)
325 body ...))
326 ((let-matches index match (var vars ...) body ...)
327 (let ((var (match:substring match index)))
328 (let-matches (+ 1 index) match (vars ...)
329 body ...)))
330 ((let-matches index match () body ...)
331 (begin body ...))))
332
8773648e
LC
333(define-syntax substitute*
334 (syntax-rules ()
335 "Substitute REGEXP in FILE by the string returned by BODY. BODY is
10c87717
LC
336evaluated with each MATCH-VAR bound to the corresponding positional regexp
337sub-expression. For example:
338
4fa697e9 339 (substitute* file
20d83444
LC
340 ((\"hello\")
341 \"good morning\\n\")
342 ((\"foo([a-z]+)bar(.*)$\" all letters end)
343 (string-append \"baz\" letter end)))
4fa697e9
LC
344
345Here, anytime a line of FILE contains \"hello\", it is replaced by \"good
346morning\". Anytime a line of FILE matches the second regexp, ALL is bound to
347the complete match, LETTERS is bound to the first sub-expression, and END is
348bound to the last one.
349
350When one of the MATCH-VAR is `_', no variable is bound to the corresponding
20d83444
LC
351match substring.
352
353Alternatively, FILE may be a list of file names, in which case they are
354all subject to the substitutions."
8773648e 355 ((substitute* file ((regexp match-var ...) body ...) ...)
20d83444
LC
356 (let ()
357 (define (substitute-one-file file-name)
358 (substitute
359 file-name
360 (list (cons regexp
361 (lambda (l m+)
362 ;; Iterate over matches M+ and return the
363 ;; modified line based on L.
364 (let loop ((m* m+) ; matches
365 (o 0) ; offset in L
366 (r '())) ; result
367 (match m*
368 (()
369 (let ((r (cons (substring l o) r)))
370 (string-concatenate-reverse r)))
371 ((m . rest)
372 (let-matches 0 m (match-var ...)
373 (loop rest
374 (match:end m)
375 (cons*
376 (begin body ...)
377 (substring l o (match:start m))
378 r))))))))
379 ...)))
380
381 (match file
382 ((files (... ...))
383 (for-each substitute-one-file files))
384 ((? string? f)
385 (substitute-one-file f)))))))
10c87717 386
ebe2f31f
LC
387\f
388;;;
389;;; Patching shebangs---e.g., /bin/sh -> /nix/store/xyz...-bash/bin/sh.
390;;;
391
a18b4d08
LC
392(define* (dump-port in out
393 #:key (buffer-size 16384)
394 (progress (lambda (t k) (k))))
74baf333 395 "Read as much data as possible from IN and write it to OUT, using
a18b4d08
LC
396chunks of BUFFER-SIZE bytes. Call PROGRESS after each successful
397transfer of BUFFER-SIZE bytes or less, passing it the total number of
398bytes transferred and the continuation of the transfer as a thunk."
ebe2f31f
LC
399 (define buffer
400 (make-bytevector buffer-size))
401
a18b4d08
LC
402 (let loop ((total 0)
403 (bytes (get-bytevector-n! in buffer 0 buffer-size)))
ebe2f31f 404 (or (eof-object? bytes)
a18b4d08 405 (let ((total (+ total bytes)))
ebe2f31f 406 (put-bytevector out buffer 0 bytes)
a18b4d08
LC
407 (progress total
408 (lambda ()
409 (loop total
410 (get-bytevector-n! in buffer 0 buffer-size))))))))
ebe2f31f 411
bc5bf85f
LC
412(define (set-file-time file stat)
413 "Set the atime/mtime of FILE to that specified by STAT."
414 (utime file
415 (stat:atime stat)
416 (stat:mtime stat)
417 (stat:atimensec stat)
418 (stat:mtimensec stat)))
419
ebe2f31f 420(define patch-shebang
525a59d6
LC
421 (let ((shebang-rx (make-regexp "^[[:blank:]]*([[:graph:]]+)(.*)$")))
422 (lambda* (file
bc5bf85f
LC
423 #:optional
424 (path (search-path-as-string->list (getenv "PATH")))
425 #:key (keep-mtime? #t))
525a59d6
LC
426 "Replace the #! interpreter file name in FILE by a valid one found in
427PATH, when FILE actually starts with a shebang. Return #t when FILE was
bc5bf85f
LC
428patched, #f otherwise. When KEEP-MTIME? is true, the atime/mtime of
429FILE are kept unchanged."
ebe2f31f
LC
430 (define (patch p interpreter rest-of-line)
431 (let* ((template (string-append file ".XXXXXX"))
432 (out (mkstemp! template))
bc5bf85f
LC
433 (st (stat file))
434 (mode (stat:mode st)))
ebe2f31f
LC
435 (with-throw-handler #t
436 (lambda ()
437 (format out "#!~a~a~%"
438 interpreter rest-of-line)
439 (dump-port p out)
440 (close out)
441 (chmod template mode)
442 (rename-file template file)
bc5bf85f
LC
443 (when keep-mtime?
444 (set-file-time file st))
ebe2f31f
LC
445 #t)
446 (lambda (key . args)
447 (format (current-error-port)
448 "patch-shebang: ~a: error: ~a ~s~%"
449 file key args)
450 (false-if-exception (delete-file template))
451 #f))))
452
c0895112
LC
453 (call-with-ascii-input-file file
454 (lambda (p)
455 (and (eq? #\# (read-char p))
456 (eq? #\! (read-char p))
457 (let ((line (false-if-exception (read-line p))))
458 (and=> (and line (regexp-exec shebang-rx line))
459 (lambda (m)
460 (let* ((cmd (match:substring m 1))
461 (bin (search-path path (basename cmd))))
462 (if bin
463 (if (string=? bin cmd)
464 #f ; nothing to do
465 (begin
466 (format (current-error-port)
467 "patch-shebang: ~a: changing `~a' to `~a'~%"
468 file cmd bin)
469 (patch p bin (match:substring m 2))))
470 (begin
471 (format (current-error-port)
472 "patch-shebang: ~a: warning: no binary for interpreter `~a' found in $PATH~%"
473 file (basename cmd))
474 #f))))))))))))
475
bc5bf85f
LC
476(define* (patch-makefile-SHELL file #:key (keep-mtime? #t))
477 "Patch the `SHELL' variable in FILE, which is supposedly a makefile.
478When KEEP-MTIME? is true, the atime/mtime of FILE are kept unchanged."
c0895112
LC
479
480 ;; For instance, Gettext-generated po/Makefile.in.in do not honor $SHELL.
481
482 ;; XXX: Unlike with `patch-shebang', FILE is always touched.
483
484 (define (find-shell name)
485 (let ((shell
486 (search-path (search-path-as-string->list (getenv "PATH"))
487 name)))
488 (unless shell
489 (format (current-error-port)
490 "patch-makefile-SHELL: warning: no binary for shell `~a' found in $PATH~%"
491 name))
492 shell))
493
bc5bf85f
LC
494 (let ((st (stat file)))
495 (substitute* file
496 (("^ *SHELL[[:blank:]]*=[[:blank:]]*([[:graph:]]*/)([[:graph:]]+)[[:blank:]]*" _ dir shell)
497 (let* ((old (string-append dir shell))
498 (new (or (find-shell shell) old)))
499 (unless (string=? new old)
500 (format (current-error-port)
501 "patch-makefile-SHELL: ~a: changing `SHELL' from `~a' to `~a'~%"
502 file old new))
503 (string-append "SHELL = " new "\n"))))
504
505 (when keep-mtime?
506 (set-file-time file st))))
10c87717 507
91133c2d
LC
508(define* (fold-port-matches proc init pattern port
509 #:optional (unmatched (lambda (_ r) r)))
510 "Read from PORT character-by-character; for each match against
511PATTERN, call (PROC MATCH RESULT), where RESULT is seeded with INIT.
512PATTERN is a list of SRFI-14 char-sets. Call (UNMATCHED CHAR RESULT)
513for each unmatched character."
514 (define initial-pattern
515 ;; The poor developer's regexp.
516 (if (string? pattern)
517 (map char-set (string->list pattern))
518 pattern))
519
520 ;; Note: we're not really striving for performance here...
521 (let loop ((chars '())
522 (pattern initial-pattern)
523 (matched '())
524 (result init))
525 (cond ((null? chars)
526 (loop (list (get-char port))
527 pattern
528 matched
529 result))
530 ((null? pattern)
531 (loop chars
532 initial-pattern
533 '()
534 (proc (list->string (reverse matched)) result)))
535 ((eof-object? (car chars))
536 (fold-right unmatched result matched))
537 ((char-set-contains? (car pattern) (car chars))
538 (loop (cdr chars)
539 (cdr pattern)
540 (cons (car chars) matched)
541 result))
542 ((null? matched) ; common case
543 (loop (cdr chars)
544 pattern
545 matched
546 (unmatched (car chars) result)))
547 (else
548 (let ((matched (reverse matched)))
549 (loop (append (cdr matched) chars)
550 initial-pattern
551 '()
552 (unmatched (car matched) result)))))))
553
554(define* (remove-store-references file
555 #:optional (store (or (getenv "NIX_STORE")
556 "/nix/store")))
557 "Remove from FILE occurrences of file names in STORE; return #t when
558store paths were encountered in FILE, #f otherwise. This procedure is
559known as `nuke-refs' in Nixpkgs."
560 (define pattern
561 (let ((nix-base32-chars
562 '(#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9
563 #\a #\b #\c #\d #\f #\g #\h #\i #\j #\k #\l #\m #\n
564 #\p #\q #\r #\s #\v #\w #\x #\y #\z)))
565 `(,@(map char-set (string->list store))
566 ,(char-set #\/)
567 ,@(make-list 32 (list->char-set nix-base32-chars))
568 ,(char-set #\-))))
569
570 (with-fluids ((%default-port-encoding #f))
571 (with-atomic-file-replacement file
572 (lambda (in out)
573 ;; We cannot use `regexp-exec' here because it cannot deal with
574 ;; strings containing NUL characters.
575 (format #t "removing store references from `~a'...~%" file)
576 (setvbuf in _IOFBF 65536)
577 (setvbuf out _IOFBF 65536)
578 (fold-port-matches (lambda (match result)
579 (put-string out store)
580 (put-char out #\/)
581 (put-string out
582 "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-")
583 #t)
584 #f
585 pattern
586 in
587 (lambda (char result)
588 (put-char out char)
589 result))))))
590
b0e0d0e9
LC
591;;; Local Variables:
592;;; eval: (put 'call-with-output-file/atomic 'scheme-indent-function 1)
593;;; eval: (put 'with-throw-handler 'scheme-indent-function 1)
8197c978 594;;; eval: (put 'let-matches 'scheme-indent-function 3)
dcd72906 595;;; eval: (put 'with-atomic-file-replacement 'scheme-indent-function 1)
b0e0d0e9 596;;; End: