gnu: r-qtl2: Move to (gnu packages cran).
[jackhill/guix/guix.git] / guix / build / utils.scm
CommitLineData
4155e2a9 1;;; GNU Guix --- Functional package management for GNU
782f1ea9 2;;; Copyright © 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 Ludovic Courtès <ludo@gnu.org>
11996d85 3;;; Copyright © 2013 Andreas Enge <andreas@enge.fr>
30db6af1 4;;; Copyright © 2013 Nikita Karetnikov <nikita@karetnikov.org>
cbdfa50d 5;;; Copyright © 2015, 2018 Mark H Weaver <mhw@netris.org>
89e7f90d 6;;; Copyright © 2018 Arun Isaac <arunisaac@systemreboot.net>
0fb9a8df 7;;; Copyright © 2018, 2019 Ricardo Wurmus <rekado@elephly.net>
c36db98c 8;;;
4155e2a9 9;;; This file is part of GNU Guix.
c36db98c 10;;;
4155e2a9 11;;; GNU Guix is free software; you can redistribute it and/or modify it
c36db98c
LC
12;;; under the terms of the GNU General Public License as published by
13;;; the Free Software Foundation; either version 3 of the License, or (at
14;;; your option) any later version.
15;;;
4155e2a9 16;;; GNU Guix is distributed in the hope that it will be useful, but
c36db98c
LC
17;;; WITHOUT ANY WARRANTY; without even the implied warranty of
18;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19;;; GNU General Public License for more details.
20;;;
21;;; You should have received a copy of the GNU General Public License
4155e2a9 22;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
c36db98c
LC
23
24(define-module (guix build utils)
25 #:use-module (srfi srfi-1)
b0e0d0e9 26 #:use-module (srfi srfi-11)
8c578a60 27 #:use-module (srfi srfi-26)
cbdfa50d
MW
28 #:use-module (srfi srfi-34)
29 #:use-module (srfi srfi-35)
251e8b2e 30 #:use-module (srfi srfi-60)
c0746cc9 31 #:use-module (ice-9 ftw)
b0e0d0e9
LC
32 #:use-module (ice-9 match)
33 #:use-module (ice-9 regex)
34 #:use-module (ice-9 rdelim)
c041886d 35 #:use-module (ice-9 format)
325ff6fe 36 #:use-module (ice-9 threads)
ebe2f31f
LC
37 #:use-module (rnrs bytevectors)
38 #:use-module (rnrs io ports)
d475b259 39 #:re-export (alist-cons
8b14773a
ST
40 alist-delete
41
42 ;; Note: Re-export 'delete' to allow for proper syntax matching
43 ;; in 'modify-phases' forms. See
44 ;; <https://debbugs.gnu.org/cgi/bugreport.cgi?bug=26805#16>.
45 delete)
8be3b8a3 46 #:export (%store-directory
dacd5d2c 47 store-file-name?
b7c7c03e 48 strip-store-file-name
8c578a60 49 package-name->name+version
e6039b9c
LC
50 parallel-job-count
51
8be3b8a3 52 directory-exists?
d0084152 53 executable-file?
9741aca9 54 symbolic-link?
c0895112 55 call-with-ascii-input-file
99533da5 56 elf-file?
91ee959b 57 ar-file?
95e7be97
LC
58 gzip-file?
59 reset-gzip-timestamp
b0e0d0e9 60 with-directory-excursion
7da95264 61 mkdir-p
4cc2ed98 62 install-file
5a64a791 63 make-file-writable
c0746cc9 64 copy-recursively
e65df6a6 65 delete-file-recursively
1968262a 66 file-name-predicate
4c261f41 67 find-files
5c6391b3 68 false-if-file-not-found
4c261f41 69
bd2fc4d8 70 search-path-as-list
b0e0d0e9 71 set-path-environment-variable
ebe2f31f
LC
72 search-path-as-string->list
73 list->search-path-as-string
7584f822
LC
74 which
75
57aff647 76 every*
b0e0d0e9
LC
77 alist-cons-before
78 alist-cons-after
79 alist-replace
8ddc41e1 80 modify-phases
57aff647 81
dcd72906 82 with-atomic-file-replacement
10c87717 83 substitute
ebe2f31f
LC
84 substitute*
85 dump-port
bc5bf85f 86 set-file-time
91133c2d 87 patch-shebang
c0895112 88 patch-makefile-SHELL
4eb01e54 89 patch-/usr/bin/file
91133c2d 90 fold-port-matches
30db6af1 91 remove-store-references
89e7f90d 92 wrapper?
251e8b2e 93 wrap-program
0fb9a8df
RW
94 wrap-script
95
96 wrap-error?
97 wrap-error-program
98 wrap-error-type
cbdfa50d 99
3f65c190 100 invoke
cbdfa50d
MW
101 invoke-error?
102 invoke-error-program
103 invoke-error-arguments
104 invoke-error-exit-status
105 invoke-error-term-signal
106 invoke-error-stop-signal
f380f9d5 107 report-invoke-error
251e8b2e 108
45d46223
LC
109 invoke/quiet
110
5db7df2e
PN
111 make-desktop-entry-file
112
251e8b2e 113 locale-category->string))
b0e0d0e9 114
782f1ea9
LC
115\f
116;;;
117;;; Guile 2.0 compatibility later.
118;;;
11996d85 119
782f1ea9
LC
120;; The bootstrap Guile is Guile 2.0, so provide a compatibility layer.
121(cond-expand
122 ((and guile-2 (not guile-2.2))
123 (define (setvbuf port mode . rest)
124 (apply (@ (guile) setvbuf) port
125 (match mode
126 ('line _IOLBF)
127 ('block _IOFBF)
128 ('none _IONBF)
129 (_ mode)) ;an _IO* integer
130 rest))
131
132 (module-replace! (current-module) '(setvbuf)))
133 (else #f))
134
135\f
b0e0d0e9
LC
136;;;
137;;; Directories.
138;;;
c36db98c 139
8be3b8a3
LC
140(define (%store-directory)
141 "Return the directory name of the store."
142 (or (getenv "NIX_STORE")
143 "/gnu/store"))
144
dacd5d2c
LC
145(define (store-file-name? file)
146 "Return true if FILE is in the store."
147 (string-prefix? (%store-directory) file))
148
b7c7c03e
LC
149(define (strip-store-file-name file)
150 "Strip the '/gnu/store' and hash from FILE, a store file name. The result
151is typically a \"PACKAGE-VERSION\" string."
152 (string-drop file
153 (+ 34 (string-length (%store-directory)))))
154
8c578a60
LC
155(define (package-name->name+version name)
156 "Given NAME, a package name like \"foo-0.9.1b\", return two values:
157\"foo\" and \"0.9.1b\". When the version part is unavailable, NAME and
158#f are returned. The first hyphen followed by a digit is considered to
159introduce the version part."
160 ;; See also `DrvName' in Nix.
161
162 (define number?
163 (cut char-set-contains? char-set:digit <>))
164
165 (let loop ((chars (string->list name))
166 (prefix '()))
167 (match chars
168 (()
169 (values name #f))
170 ((#\- (? number? n) rest ...)
171 (values (list->string (reverse prefix))
172 (list->string (cons n rest))))
173 ((head tail ...)
174 (loop tail (cons head prefix))))))
175
1d1fa932
LC
176(define parallel-job-count
177 ;; Number of processes to be passed next to GNU Make's `-j' argument.
178 (make-parameter
179 (match (getenv "NIX_BUILD_CORES") ;set by the daemon
180 (#f 1)
181 ("0" (current-processor-count))
182 (x (or (string->number x) 1)))))
e6039b9c 183
c36db98c
LC
184(define (directory-exists? dir)
185 "Return #t if DIR exists and is a directory."
9f55cf8d
LC
186 (let ((s (stat dir #f)))
187 (and s
188 (eq? 'directory (stat:type s)))))
c36db98c 189
d0084152
LC
190(define (executable-file? file)
191 "Return #t if FILE exists and is executable."
192 (let ((s (stat file #f)))
193 (and s
194 (not (zero? (logand (stat:mode s) #o100))))))
195
9741aca9
LC
196(define (symbolic-link? file)
197 "Return #t if FILE is a symbolic link (aka. \"symlink\".)"
198 (eq? (stat:type (lstat file)) 'symlink))
199
c0895112
LC
200(define (call-with-ascii-input-file file proc)
201 "Open FILE as an ASCII or binary file, and pass the resulting port to
202PROC. FILE is closed when PROC's dynamic extent is left. Return the
203return values of applying PROC to the port."
204 (let ((port (with-fluids ((%default-port-encoding #f))
205 ;; Use "b" so that `open-file' ignores `coding:' cookies.
206 (open-file file "rb"))))
207 (dynamic-wind
208 (lambda ()
209 #t)
210 (lambda ()
211 (proc port))
212 (lambda ()
213 (close-input-port port)))))
214
2bbc6db5
LC
215(define (file-header-match header)
216 "Return a procedure that returns true when its argument is a file starting
217with the bytes in HEADER, a bytevector."
218 (define len
219 (bytevector-length header))
99533da5 220
2bbc6db5
LC
221 (lambda (file)
222 "Return true if FILE starts with the right magic bytes."
223 (define (get-header)
224 (call-with-input-file file
225 (lambda (port)
226 (get-bytevector-n port len))
227 #:binary #t #:guess-encoding #f))
228
c23d1709
LC
229 (catch 'system-error
230 (lambda ()
231 (equal? (get-header) header))
232 (lambda args
233 (if (= EISDIR (system-error-errno args))
234 #f ;FILE is a directory
235 (apply throw args))))))
2bbc6db5
LC
236
237(define %elf-magic-bytes
238 ;; Magic bytes of ELF files. See <elf.h>.
239 (u8-list->bytevector (map char->integer (string->list "\x7FELF"))))
240
241(define elf-file?
242 (file-header-match %elf-magic-bytes))
99533da5 243
91ee959b
LC
244(define %ar-magic-bytes
245 ;; Magic bytes of archives created by 'ar'. See <ar.h>.
246 (u8-list->bytevector (map char->integer (string->list "!<arch>\n"))))
247
2bbc6db5
LC
248(define ar-file?
249 (file-header-match %ar-magic-bytes))
91ee959b 250
95e7be97
LC
251(define %gzip-magic-bytes
252 ;; Magic bytes of gzip file. Beware, it's a small header so there could be
253 ;; false positives.
254 #vu8(#x1f #x8b))
255
256(define gzip-file?
257 (file-header-match %gzip-magic-bytes))
258
259(define* (reset-gzip-timestamp file #:key (keep-mtime? #t))
260 "If FILE is a gzip file, reset its embedded timestamp (as with 'gzip
261--no-name') and return true. Otherwise return #f. When KEEP-MTIME? is true,
262preserve FILE's modification time."
263 (let ((stat (stat file))
264 (port (open file O_RDWR)))
265 (dynamic-wind
266 (const #t)
267 (lambda ()
268 (and (= 4 (seek port 4 SEEK_SET))
269 (put-bytevector port #vu8(0 0 0 0))))
270 (lambda ()
271 (close-port port)
272 (set-file-time file stat)))))
273
b0e0d0e9
LC
274(define-syntax-rule (with-directory-excursion dir body ...)
275 "Run BODY with DIR as the process's current directory."
276 (let ((init (getcwd)))
277 (dynamic-wind
278 (lambda ()
279 (chdir dir))
280 (lambda ()
281 body ...)
282 (lambda ()
283 (chdir init)))))
284
7da95264
LC
285(define (mkdir-p dir)
286 "Create directory DIR and all its ancestors."
287 (define absolute?
288 (string-prefix? "/" dir))
289
290 (define not-slash
291 (char-set-complement (char-set #\/)))
292
293 (let loop ((components (string-tokenize dir not-slash))
294 (root (if absolute?
295 ""
296 ".")))
297 (match components
298 ((head tail ...)
299 (let ((path (string-append root "/" head)))
300 (catch 'system-error
301 (lambda ()
302 (mkdir path)
303 (loop tail path))
304 (lambda args
305 (if (= EEXIST (system-error-errno args))
306 (loop tail path)
307 (apply throw args))))))
308 (() #t))))
309
4cc2ed98
LC
310(define (install-file file directory)
311 "Create DIRECTORY if it does not exist and copy FILE in there under the same
312name."
313 (mkdir-p directory)
314 (copy-file file (string-append directory "/" (basename file))))
315
5a64a791
MB
316(define (make-file-writable file)
317 "Make FILE writable for its owner."
318 (let ((stat (lstat file))) ;XXX: symlinks
319 (chmod file (logior #o600 (stat:perms stat)))))
320
c0746cc9 321(define* (copy-recursively source destination
12761f48
LC
322 #:key
323 (log (current-output-port))
8d846470
LC
324 (follow-symlinks? #f)
325 keep-mtime?)
12761f48 326 "Copy SOURCE directory to DESTINATION. Follow symlinks if FOLLOW-SYMLINKS?
8d846470
LC
327is true; otherwise, just preserve them. When KEEP-MTIME? is true, keep the
328modification time of the files in SOURCE on those of DESTINATION. Write
329verbose output to the LOG port."
c0746cc9
LC
330 (define strip-source
331 (let ((len (string-length source)))
332 (lambda (file)
333 (substring file len))))
334
335 (file-system-fold (const #t) ; enter?
336 (lambda (file stat result) ; leaf
337 (let ((dest (string-append destination
338 (strip-source file))))
339 (format log "`~a' -> `~a'~%" file dest)
12761f48
LC
340 (case (stat:type stat)
341 ((symlink)
342 (let ((target (readlink file)))
343 (symlink target dest)))
344 (else
8d846470
LC
345 (copy-file file dest)
346 (when keep-mtime?
347 (set-file-time dest stat))))))
c0746cc9 348 (lambda (dir stat result) ; down
8d846470
LC
349 (let ((target (string-append destination
350 (strip-source dir))))
351 (mkdir-p target)
352 (when keep-mtime?
353 (set-file-time target stat))))
c0746cc9
LC
354 (lambda (dir stat result) ; up
355 result)
356 (const #t) ; skip
357 (lambda (file stat errno result)
358 (format (current-error-port) "i/o error: ~a: ~a~%"
359 file (strerror errno))
360 #f)
361 #t
12761f48
LC
362 source
363
364 (if follow-symlinks?
365 stat
366 lstat)))
c0746cc9 367
d84a7be6
LC
368(define* (delete-file-recursively dir
369 #:key follow-mounts?)
370 "Delete DIR recursively, like `rm -rf', without following symlinks. Don't
371follow mount points either, unless FOLLOW-MOUNTS? is true. Report but ignore
372errors."
373 (let ((dev (stat:dev (lstat dir))))
374 (file-system-fold (lambda (dir stat result) ; enter?
375 (or follow-mounts?
376 (= dev (stat:dev stat))))
377 (lambda (file stat result) ; leaf
378 (delete-file file))
379 (const #t) ; down
380 (lambda (dir stat result) ; up
381 (rmdir dir))
382 (const #t) ; skip
383 (lambda (file stat errno result)
384 (format (current-error-port)
385 "warning: failed to delete ~a: ~a~%"
386 file (strerror errno)))
387 #t
388 dir
389
390 ;; Don't follow symlinks.
391 lstat)))
e65df6a6 392
1968262a
LC
393(define (file-name-predicate regexp)
394 "Return a predicate that returns true when passed a file name whose base
395name matches REGEXP."
396 (let ((file-rx (if (regexp? regexp)
397 regexp
398 (make-regexp regexp))))
399 (lambda (file stat)
400 (regexp-exec file-rx (basename file)))))
401
347f54ed 402(define* (find-files dir #:optional (pred (const #t))
f4ae827e
MW
403 #:key (stat lstat)
404 directories?
405 fail-on-error?)
1968262a
LC
406 "Return the lexicographically sorted list of files under DIR for which PRED
407returns true. PRED is passed two arguments: the absolute file name, and its
4ba3a84d
LC
408stat buffer; the default predicate always returns true. PRED can also be a
409regular expression, in which case it is equivalent to (file-name-predicate
347f54ed 410PRED). STAT is used to obtain file information; using 'lstat' means that
f4ae827e
MW
411symlinks are not followed. If DIRECTORIES? is true, then directories will
412also be included. If FAIL-ON-ERROR? is true, raise an exception upon error."
1968262a
LC
413 (let ((pred (if (procedure? pred)
414 pred
415 (file-name-predicate pred))))
416 ;; Sort the result to get deterministic results.
417 (sort (file-system-fold (const #t)
418 (lambda (file stat result) ; leaf
419 (if (pred file stat)
420 (cons file result)
421 result))
422 (lambda (dir stat result) ; down
f4ae827e
MW
423 (if (and directories?
424 (pred dir stat))
425 (cons dir result)
426 result))
1968262a
LC
427 (lambda (dir stat result) ; up
428 result)
429 (lambda (file stat result) ; skip
430 result)
431 (lambda (file stat errno result)
432 (format (current-error-port) "find-files: ~a: ~a~%"
433 file (strerror errno))
f4ae827e
MW
434 (when fail-on-error?
435 (error "find-files failed"))
1968262a
LC
436 result)
437 '()
347f54ed
LC
438 dir
439 stat)
1968262a 440 string<?)))
4c261f41 441
5c6391b3
LC
442(define-syntax-rule (false-if-file-not-found exp)
443 "Evaluate EXP but return #f if it raises to 'system-error with ENOENT."
444 (catch 'system-error
445 (lambda () exp)
446 (lambda args
447 (if (= ENOENT (system-error-errno args))
448 #f
449 (apply throw args)))))
450
b0e0d0e9
LC
451\f
452;;;
453;;; Search paths.
454;;;
455
6aa47e38 456(define* (search-path-as-list files input-dirs
7ec02d37 457 #:key (type 'directory) pattern)
6aa47e38
LC
458 "Return the list of directories among FILES of the given TYPE (a symbol as
459returned by 'stat:type') that exist in INPUT-DIRS. Example:
c36db98c
LC
460
461 (search-path-as-list '(\"share/emacs/site-lisp\" \"share/emacs/24.1\")
462 (list \"/package1\" \"/package2\" \"/package3\"))
463 => (\"/package1/share/emacs/site-lisp\"
464 \"/package3/share/emacs/site-lisp\")
465
7ec02d37
LC
466When PATTERN is true, it is a regular expression denoting file names to look
467for under the directories designated by FILES. For example:
468
469 (search-path-as-list '(\"xml\") (list docbook-xml docbook-xsl)
470 #:type 'regular
471 #:pattern \"^catalog\\\\.xml$\")
472 => (\"/…/xml/dtd/docbook/catalog.xml\"
473 \"/…/xml/xsl/docbook-xsl-1.78.1/catalog.xml\")
c36db98c
LC
474"
475 (append-map (lambda (input)
7ec02d37
LC
476 (append-map (lambda (file)
477 (let ((file (string-append input "/" file)))
7ec02d37 478 (if pattern
c6e030b2
EB
479 (find-files file (lambda (file stat)
480 (and stat
481 (eq? type (stat:type stat))
482 ((file-name-predicate pattern) file stat)))
483 #:stat stat
484 #:directories? #t)
7ec02d37
LC
485 (let ((stat (stat file #f)))
486 (if (and stat (eq? type (stat:type stat)))
487 (list file)
488 '())))))
6aa47e38 489 files))
d5b3de6a 490 (delete-duplicates input-dirs)))
c36db98c
LC
491
492(define (list->search-path-as-string lst separator)
fcd75bdb
LC
493 (if separator
494 (string-join lst separator)
495 (match lst
496 ((head rest ...) head)
497 (() ""))))
c36db98c 498
ebe2f31f 499(define* (search-path-as-string->list path #:optional (separator #\:))
fcd75bdb
LC
500 (if separator
501 (string-tokenize path
502 (char-set-complement (char-set separator)))
503 (list path)))
ebe2f31f 504
6aa47e38
LC
505(define* (set-path-environment-variable env-var files input-dirs
506 #:key
507 (separator ":")
7ec02d37
LC
508 (type 'directory)
509 pattern)
6aa47e38
LC
510 "Look for each of FILES of the given TYPE (a symbol as returned by
511'stat:type') in INPUT-DIRS. Set ENV-VAR to a SEPARATOR-separated path
512accordingly. Example:
c36db98c
LC
513
514 (set-path-environment-variable \"PKG_CONFIG\"
515 '(\"lib/pkgconfig\")
516 (list package1 package2))
7ec02d37
LC
517
518When PATTERN is not #f, it must be a regular expression (really a string)
519denoting file names to look for under the directories designated by FILES:
520
521 (set-path-environment-variable \"XML_CATALOG_FILES\"
522 '(\"xml\")
523 (list docbook-xml docbook-xsl)
524 #:type 'regular
525 #:pattern \"^catalog\\\\.xml$\")
c36db98c 526"
6aa47e38 527 (let* ((path (search-path-as-list files input-dirs
7ec02d37
LC
528 #:type type
529 #:pattern pattern))
9d9ef458 530 (value (list->search-path-as-string path separator)))
b15669f3
LC
531 (if (string-null? value)
532 (begin
533 ;; Never set ENV-VAR to an empty string because often, the empty
534 ;; string is equivalent to ".". This is the case for
535 ;; GUILE_LOAD_PATH in Guile 2.0, for instance.
536 (unsetenv env-var)
537 (format #t "environment variable `~a' unset~%" env-var))
538 (begin
539 (setenv env-var value)
540 (format #t "environment variable `~a' set to `~a'~%"
541 env-var value)))))
b0e0d0e9 542
7584f822
LC
543(define (which program)
544 "Return the complete file name for PROGRAM as found in $PATH, or #f if
545PROGRAM could not be found."
546 (search-path (search-path-as-string->list (getenv "PATH"))
547 program))
548
b0e0d0e9
LC
549\f
550;;;
551;;; Phases.
552;;;
553;;; In (guix build gnu-build-system), there are separate phases (configure,
554;;; build, test, install). They are represented as a list of name/procedure
555;;; pairs. The following procedures make it easy to change the list of
556;;; phases.
557;;;
558
57aff647
LC
559(define (every* pred lst)
560 "This is like 'every', but process all the elements of LST instead of
561stopping as soon as PRED returns false. This is useful when PRED has side
562effects, such as displaying warnings or error messages."
563 (let loop ((lst lst)
564 (result #t))
565 (match lst
566 (()
567 result)
568 ((head . tail)
569 (loop tail (and (pred head) result))))))
570
b0e0d0e9
LC
571(define* (alist-cons-before reference key value alist
572 #:optional (key=? equal?))
573 "Insert the KEY/VALUE pair before the first occurrence of a pair whose key
574is REFERENCE in ALIST. Use KEY=? to compare keys."
575 (let-values (((before after)
576 (break (match-lambda
577 ((k . _)
578 (key=? k reference)))
579 alist)))
580 (append before (alist-cons key value after))))
581
582(define* (alist-cons-after reference key value alist
583 #:optional (key=? equal?))
584 "Insert the KEY/VALUE pair after the first occurrence of a pair whose key
585is REFERENCE in ALIST. Use KEY=? to compare keys."
586 (let-values (((before after)
587 (break (match-lambda
588 ((k . _)
589 (key=? k reference)))
590 alist)))
591 (match after
592 ((reference after ...)
593 (append before (cons* reference `(,key . ,value) after)))
594 (()
595 (append before `((,key . ,value)))))))
596
597(define* (alist-replace key value alist #:optional (key=? equal?))
598 "Replace the first pair in ALIST whose car is KEY with the KEY/VALUE pair.
599An error is raised when no such pair exists."
600 (let-values (((before after)
601 (break (match-lambda
602 ((k . _)
603 (key=? k key)))
604 alist)))
605 (match after
606 ((_ after ...)
607 (append before (alist-cons key value after))))))
608
8ddc41e1
LC
609(define-syntax-rule (modify-phases phases mod-spec ...)
610 "Modify PHASES sequentially as per each MOD-SPEC, which may have one of the
611following forms:
612
613 (delete <old-phase-name>)
614 (replace <old-phase-name> <new-phase>)
615 (add-before <old-phase-name> <new-phase-name> <new-phase>)
616 (add-after <old-phase-name> <new-phase-name> <new-phase>)
617
7ee5a694
TUBK
618Where every <*-phase-name> is an expression evaluating to a symbol, and
619<new-phase> an expression evaluating to a procedure."
8ddc41e1
LC
620 (let* ((phases* phases)
621 (phases* (%modify-phases phases* mod-spec))
622 ...)
623 phases*))
624
625(define-syntax %modify-phases
626 (syntax-rules (delete replace add-before add-after)
627 ((_ phases (delete old-phase-name))
f8503e2b 628 (alist-delete old-phase-name phases))
8ddc41e1 629 ((_ phases (replace old-phase-name new-phase))
f8503e2b 630 (alist-replace old-phase-name new-phase phases))
8ddc41e1 631 ((_ phases (add-before old-phase-name new-phase-name new-phase))
f8503e2b 632 (alist-cons-before old-phase-name new-phase-name new-phase phases))
8ddc41e1 633 ((_ phases (add-after old-phase-name new-phase-name new-phase))
f8503e2b 634 (alist-cons-after old-phase-name new-phase-name new-phase phases))))
8ddc41e1 635
f380f9d5
LC
636\f
637;;;
638;;; Program invocation.
639;;;
640
cbdfa50d
MW
641(define-condition-type &invoke-error &error
642 invoke-error?
643 (program invoke-error-program)
644 (arguments invoke-error-arguments)
645 (exit-status invoke-error-exit-status)
646 (term-signal invoke-error-term-signal)
647 (stop-signal invoke-error-stop-signal))
648
3f65c190 649(define (invoke program . args)
cbdfa50d
MW
650 "Invoke PROGRAM with the given ARGS. Raise an exception
651if the exit code is non-zero; otherwise return #t."
652 (let ((code (apply system* program args)))
653 (unless (zero? code)
654 (raise (condition (&invoke-error
655 (program program)
656 (arguments args)
657 (exit-status (status:exit-val code))
658 (term-signal (status:term-sig code))
659 (stop-signal (status:stop-sig code))))))
3f65c190
DM
660 #t))
661
f380f9d5
LC
662(define* (report-invoke-error c #:optional (port (current-error-port)))
663 "Report to PORT about C, an '&invoke-error' condition, in a human-friendly
664way."
665 (format port "command~{ ~s~} failed with ~:[signal~;status~] ~a~%"
666 (cons (invoke-error-program c)
667 (invoke-error-arguments c))
668 (invoke-error-exit-status c)
669 (or (invoke-error-exit-status c)
670 (invoke-error-term-signal c)
671 (invoke-error-stop-signal c))))
672
45d46223
LC
673(define (open-pipe-with-stderr program . args)
674 "Run PROGRAM with ARGS in an input pipe, but, unlike 'open-pipe*', redirect
675both its standard output and standard error to the pipe. Return two value:
676the pipe to read PROGRAM's data from, and the PID of the child process running
677PROGRAM."
678 ;; 'open-pipe*' doesn't attempt to capture stderr in any way, which is why
679 ;; we need to roll our own.
680 (match (pipe)
681 ((input . output)
682 (match (primitive-fork)
683 (0
684 (dynamic-wind
685 (const #t)
686 (lambda ()
687 (close-port input)
688 (dup2 (fileno output) 1)
689 (dup2 (fileno output) 2)
690 (apply execlp program program args))
691 (lambda ()
692 (primitive-exit 127))))
693 (pid
694 (close-port output)
695 (values input pid))))))
696
697(define (invoke/quiet program . args)
698 "Invoke PROGRAM with ARGS and capture PROGRAM's standard output and standard
699error. If PROGRAM succeeds, print nothing and return the unspecified value;
700otherwise, raise a '&message' error condition that includes the status code
701and the output of PROGRAM."
702 (let-values (((pipe pid)
703 (apply open-pipe-with-stderr program args)))
704 (let loop ((lines '()))
705 (match (read-line pipe)
706 ((? eof-object?)
707 (close-port pipe)
708 (match (waitpid pid)
709 ((_ . status)
710 (unless (zero? status)
711 (let-syntax ((G_ (syntax-rules () ;for xgettext
712 ((_ str) str))))
713 (raise (condition
714 (&message
715 (message (format #f (G_ "'~a~{ ~a~}' exited \
716with status ~a; output follows:~%~%~{ ~a~%~}")
717 program args
718 (or (status:exit-val status)
719 status)
720 (reverse lines)))))))))))
721 (line
722 (loop (cons line lines)))))))
723
b0e0d0e9
LC
724\f
725;;;
726;;; Text substitution (aka. sed).
727;;;
728
dcd72906
LC
729(define (with-atomic-file-replacement file proc)
730 "Call PROC with two arguments: an input port for FILE, and an output
731port for the file that is going to replace FILE. Upon success, FILE is
732atomically replaced by what has been written to the output port, and
733PROC's result is returned."
734 (let* ((template (string-append file ".XXXXXX"))
d9dbab18
LC
735 (out (mkstemp! template))
736 (mode (stat:mode (stat file))))
b0e0d0e9
LC
737 (with-throw-handler #t
738 (lambda ()
739 (call-with-input-file file
740 (lambda (in)
dcd72906
LC
741 (let ((result (proc in out)))
742 (close out)
743 (chmod template mode)
744 (rename-file template file)
745 result))))
b0e0d0e9
LC
746 (lambda (key . args)
747 (false-if-exception (delete-file template))))))
748
dcd72906 749(define (substitute file pattern+procs)
094b2efc
TUBK
750 "PATTERN+PROCS is a list of regexp/two-argument-procedure pairs. For each
751line of FILE, and for each PATTERN that it matches, call the corresponding
752PROC as (PROC LINE MATCHES); PROC must return the line that will be written as
753a substitution of the original line. Be careful about using '$' to match the
754end of a line; by itself it won't match the terminating newline of a line."
dcd72906
LC
755 (let ((rx+proc (map (match-lambda
756 (((? regexp? pattern) . proc)
757 (cons pattern proc))
758 ((pattern . proc)
759 (cons (make-regexp pattern regexp/extended)
760 proc)))
761 pattern+procs)))
762 (with-atomic-file-replacement file
763 (lambda (in out)
764 (let loop ((line (read-line in 'concat)))
765 (if (eof-object? line)
766 #t
767 (let ((line (fold (lambda (r+p line)
768 (match r+p
769 ((regexp . proc)
770 (match (list-matches regexp line)
771 ((and m+ (_ _ ...))
772 (proc line m+))
773 (_ line)))))
774 line
775 rx+proc)))
776 (display line out)
777 (loop (read-line in 'concat)))))))))
778
10c87717
LC
779
780(define-syntax let-matches
781 ;; Helper macro for `substitute*'.
782 (syntax-rules (_)
783 ((let-matches index match (_ vars ...) body ...)
784 (let-matches (+ 1 index) match (vars ...)
785 body ...))
786 ((let-matches index match (var vars ...) body ...)
787 (let ((var (match:substring match index)))
788 (let-matches (+ 1 index) match (vars ...)
789 body ...)))
790 ((let-matches index match () body ...)
791 (begin body ...))))
792
8773648e
LC
793(define-syntax substitute*
794 (syntax-rules ()
795 "Substitute REGEXP in FILE by the string returned by BODY. BODY is
10c87717
LC
796evaluated with each MATCH-VAR bound to the corresponding positional regexp
797sub-expression. For example:
798
4fa697e9 799 (substitute* file
20d83444
LC
800 ((\"hello\")
801 \"good morning\\n\")
802 ((\"foo([a-z]+)bar(.*)$\" all letters end)
803 (string-append \"baz\" letter end)))
4fa697e9
LC
804
805Here, anytime a line of FILE contains \"hello\", it is replaced by \"good
806morning\". Anytime a line of FILE matches the second regexp, ALL is bound to
807the complete match, LETTERS is bound to the first sub-expression, and END is
808bound to the last one.
809
810When one of the MATCH-VAR is `_', no variable is bound to the corresponding
20d83444
LC
811match substring.
812
813Alternatively, FILE may be a list of file names, in which case they are
094b2efc
TUBK
814all subject to the substitutions.
815
816Be careful about using '$' to match the end of a line; by itself it won't
817match the terminating newline of a line."
8773648e 818 ((substitute* file ((regexp match-var ...) body ...) ...)
20d83444
LC
819 (let ()
820 (define (substitute-one-file file-name)
821 (substitute
822 file-name
823 (list (cons regexp
824 (lambda (l m+)
825 ;; Iterate over matches M+ and return the
826 ;; modified line based on L.
827 (let loop ((m* m+) ; matches
828 (o 0) ; offset in L
829 (r '())) ; result
830 (match m*
831 (()
832 (let ((r (cons (substring l o) r)))
833 (string-concatenate-reverse r)))
834 ((m . rest)
835 (let-matches 0 m (match-var ...)
836 (loop rest
837 (match:end m)
838 (cons*
839 (begin body ...)
840 (substring l o (match:start m))
841 r))))))))
842 ...)))
843
844 (match file
845 ((files (... ...))
846 (for-each substitute-one-file files))
847 ((? string? f)
848 (substitute-one-file f)))))))
10c87717 849
ebe2f31f
LC
850\f
851;;;
8be3b8a3 852;;; Patching shebangs---e.g., /bin/sh -> /gnu/store/xyz...-bash/bin/sh.
ebe2f31f
LC
853;;;
854
a18b4d08
LC
855(define* (dump-port in out
856 #:key (buffer-size 16384)
857 (progress (lambda (t k) (k))))
2c1fb353
LC
858 "Read as much data as possible from IN and write it to OUT, using chunks of
859BUFFER-SIZE bytes. Call PROGRESS at the beginning and after each successful
860transfer of BUFFER-SIZE bytes or less, passing it the total number of bytes
861transferred and the continuation of the transfer as a thunk."
ebe2f31f
LC
862 (define buffer
863 (make-bytevector buffer-size))
864
2c1fb353 865 (define (loop total bytes)
ebe2f31f 866 (or (eof-object? bytes)
a18b4d08 867 (let ((total (+ total bytes)))
ebe2f31f 868 (put-bytevector out buffer 0 bytes)
a18b4d08
LC
869 (progress total
870 (lambda ()
871 (loop total
2c1fb353
LC
872 (get-bytevector-n! in buffer 0 buffer-size)))))))
873
874 ;; Make sure PROGRESS is called when we start so that it can measure
875 ;; throughput.
876 (progress 0
877 (lambda ()
878 (loop 0 (get-bytevector-n! in buffer 0 buffer-size)))))
ebe2f31f 879
bc5bf85f
LC
880(define (set-file-time file stat)
881 "Set the atime/mtime of FILE to that specified by STAT."
882 (utime file
883 (stat:atime stat)
884 (stat:mtime stat)
885 (stat:atimensec stat)
886 (stat:mtimensec stat)))
887
ca1e3ad2
LC
888(define (get-char* p)
889 ;; We call it `get-char', but that's really a binary version
890 ;; thereof. (The real `get-char' cannot be used here because our
891 ;; bootstrap Guile is hacked to always use UTF-8.)
892 (match (get-u8 p)
893 ((? integer? x) (integer->char x))
894 (x x)))
895
ebe2f31f 896(define patch-shebang
88f85494 897 (let ((shebang-rx (make-regexp "^[[:blank:]]*(/[[:graph:]]+)[[:blank:]]*([[:graph:]]*)(.*)$")))
525a59d6 898 (lambda* (file
bc5bf85f
LC
899 #:optional
900 (path (search-path-as-string->list (getenv "PATH")))
901 #:key (keep-mtime? #t))
525a59d6
LC
902 "Replace the #! interpreter file name in FILE by a valid one found in
903PATH, when FILE actually starts with a shebang. Return #t when FILE was
bc5bf85f
LC
904patched, #f otherwise. When KEEP-MTIME? is true, the atime/mtime of
905FILE are kept unchanged."
ebe2f31f
LC
906 (define (patch p interpreter rest-of-line)
907 (let* ((template (string-append file ".XXXXXX"))
908 (out (mkstemp! template))
bc5bf85f
LC
909 (st (stat file))
910 (mode (stat:mode st)))
ebe2f31f
LC
911 (with-throw-handler #t
912 (lambda ()
913 (format out "#!~a~a~%"
914 interpreter rest-of-line)
915 (dump-port p out)
916 (close out)
917 (chmod template mode)
918 (rename-file template file)
bc5bf85f
LC
919 (when keep-mtime?
920 (set-file-time file st))
ebe2f31f
LC
921 #t)
922 (lambda (key . args)
923 (format (current-error-port)
924 "patch-shebang: ~a: error: ~a ~s~%"
925 file key args)
926 (false-if-exception (delete-file template))
927 #f))))
928
c0895112
LC
929 (call-with-ascii-input-file file
930 (lambda (p)
ca1e3ad2
LC
931 (and (eq? #\# (get-char* p))
932 (eq? #\! (get-char* p))
c0895112
LC
933 (let ((line (false-if-exception (read-line p))))
934 (and=> (and line (regexp-exec shebang-rx line))
935 (lambda (m)
11996d85
AE
936 (let* ((interp (match:substring m 1))
937 (arg1 (match:substring m 2))
938 (rest (match:substring m 3))
939 (has-env (string-suffix? "/env" interp))
940 (cmd (if has-env arg1 (basename interp)))
941 (bin (search-path path cmd)))
c0895112 942 (if bin
11996d85 943 (if (string=? bin interp)
c0895112 944 #f ; nothing to do
11996d85
AE
945 (if has-env
946 (begin
947 (format (current-error-port)
948 "patch-shebang: ~a: changing `~a' to `~a'~%"
949 file (string-append interp " " arg1) bin)
950 (patch p bin rest))
951 (begin
952 (format (current-error-port)
953 "patch-shebang: ~a: changing `~a' to `~a'~%"
954 file interp bin)
955 (patch p bin
ca8def6e
AE
956 (if (string-null? arg1)
957 ""
958 (string-append " " arg1 rest))))))
c0895112
LC
959 (begin
960 (format (current-error-port)
961 "patch-shebang: ~a: warning: no binary for interpreter `~a' found in $PATH~%"
962 file (basename cmd))
963 #f))))))))))))
964
bc5bf85f
LC
965(define* (patch-makefile-SHELL file #:key (keep-mtime? #t))
966 "Patch the `SHELL' variable in FILE, which is supposedly a makefile.
967When KEEP-MTIME? is true, the atime/mtime of FILE are kept unchanged."
c0895112
LC
968
969 ;; For instance, Gettext-generated po/Makefile.in.in do not honor $SHELL.
970
971 ;; XXX: Unlike with `patch-shebang', FILE is always touched.
972
973 (define (find-shell name)
5e5deea9 974 (let ((shell (which name)))
c0895112
LC
975 (unless shell
976 (format (current-error-port)
977 "patch-makefile-SHELL: warning: no binary for shell `~a' found in $PATH~%"
978 name))
979 shell))
980
bc5bf85f 981 (let ((st (stat file)))
dd0a8ef1
LC
982 ;; Consider FILE is using an 8-bit encoding to avoid errors.
983 (with-fluids ((%default-port-encoding #f))
984 (substitute* file
985 (("^ *SHELL[[:blank:]]*:?=[[:blank:]]*([[:graph:]]*/)([[:graph:]]+)(.*)$"
986 _ dir shell args)
987 (let* ((old (string-append dir shell))
988 (new (or (find-shell shell) old)))
989 (unless (string=? new old)
990 (format (current-error-port)
991 "patch-makefile-SHELL: ~a: changing `SHELL' from `~a' to `~a'~%"
992 file old new))
993 (string-append "SHELL = " new args)))))
bc5bf85f
LC
994
995 (when keep-mtime?
996 (set-file-time file st))))
10c87717 997
4eb01e54
LC
998(define* (patch-/usr/bin/file file
999 #:key
1000 (file-command (which "file"))
1001 (keep-mtime? #t))
1002 "Patch occurrences of \"/usr/bin/file\" in FILE, replacing them with
1003FILE-COMMAND. When KEEP-MTIME? is true, keep FILE's modification time
1004unchanged."
1005 (if (not file-command)
1006 (format (current-error-port)
1007 "patch-/usr/bin/file: warning: \
1008no replacement 'file' command, doing nothing~%")
1009 (let ((st (stat file)))
dd0a8ef1
LC
1010 ;; Consider FILE is using an 8-bit encoding to avoid errors.
1011 (with-fluids ((%default-port-encoding #f))
1012 (substitute* file
1013 (("/usr/bin/file")
1014 (begin
1015 (format (current-error-port)
1016 "patch-/usr/bin/file: ~a: changing `~a' to `~a'~%"
1017 file "/usr/bin/file" file-command)
1018 file-command))))
4eb01e54
LC
1019
1020 (when keep-mtime?
1021 (set-file-time file st)))))
1022
91133c2d
LC
1023(define* (fold-port-matches proc init pattern port
1024 #:optional (unmatched (lambda (_ r) r)))
1025 "Read from PORT character-by-character; for each match against
1026PATTERN, call (PROC MATCH RESULT), where RESULT is seeded with INIT.
1027PATTERN is a list of SRFI-14 char-sets. Call (UNMATCHED CHAR RESULT)
1028for each unmatched character."
1029 (define initial-pattern
1030 ;; The poor developer's regexp.
1031 (if (string? pattern)
1032 (map char-set (string->list pattern))
1033 pattern))
1034
1035 ;; Note: we're not really striving for performance here...
1036 (let loop ((chars '())
1037 (pattern initial-pattern)
1038 (matched '())
1039 (result init))
1040 (cond ((null? chars)
ca1e3ad2 1041 (loop (list (get-char* port))
91133c2d
LC
1042 pattern
1043 matched
1044 result))
1045 ((null? pattern)
1046 (loop chars
1047 initial-pattern
1048 '()
1049 (proc (list->string (reverse matched)) result)))
1050 ((eof-object? (car chars))
1051 (fold-right unmatched result matched))
1052 ((char-set-contains? (car pattern) (car chars))
1053 (loop (cdr chars)
1054 (cdr pattern)
1055 (cons (car chars) matched)
1056 result))
1057 ((null? matched) ; common case
1058 (loop (cdr chars)
1059 pattern
1060 matched
1061 (unmatched (car chars) result)))
1062 (else
1063 (let ((matched (reverse matched)))
1064 (loop (append (cdr matched) chars)
1065 initial-pattern
1066 '()
1067 (unmatched (car matched) result)))))))
1068
1069(define* (remove-store-references file
8be3b8a3 1070 #:optional (store (%store-directory)))
91133c2d
LC
1071 "Remove from FILE occurrences of file names in STORE; return #t when
1072store paths were encountered in FILE, #f otherwise. This procedure is
1073known as `nuke-refs' in Nixpkgs."
1074 (define pattern
1075 (let ((nix-base32-chars
1076 '(#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9
1077 #\a #\b #\c #\d #\f #\g #\h #\i #\j #\k #\l #\m #\n
1078 #\p #\q #\r #\s #\v #\w #\x #\y #\z)))
1079 `(,@(map char-set (string->list store))
1080 ,(char-set #\/)
1081 ,@(make-list 32 (list->char-set nix-base32-chars))
1082 ,(char-set #\-))))
1083
1084 (with-fluids ((%default-port-encoding #f))
1085 (with-atomic-file-replacement file
1086 (lambda (in out)
1087 ;; We cannot use `regexp-exec' here because it cannot deal with
1088 ;; strings containing NUL characters.
1089 (format #t "removing store references from `~a'...~%" file)
782f1ea9
LC
1090 (setvbuf in 'block 65536)
1091 (setvbuf out 'block 65536)
91133c2d 1092 (fold-port-matches (lambda (match result)
93b03575
LC
1093 (put-bytevector out (string->utf8 store))
1094 (put-u8 out (char->integer #\/))
1095 (put-bytevector out
1096 (string->utf8
1097 "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-"))
91133c2d
LC
1098 #t)
1099 #f
1100 pattern
1101 in
1102 (lambda (char result)
93b03575 1103 (put-u8 out (char->integer char))
91133c2d
LC
1104 result))))))
1105
0fb9a8df
RW
1106(define-condition-type &wrap-error &error
1107 wrap-error?
1108 (program wrap-error-program)
1109 (type wrap-error-type))
1110
89e7f90d
AI
1111(define (wrapper? prog)
1112 "Return #t if PROG is a wrapper as produced by 'wrap-program'."
1113 (and (file-exists? prog)
1114 (let ((base (basename prog)))
1115 (and (string-prefix? "." base)
1116 (string-suffix? "-real" base)))))
1117
30db6af1 1118(define* (wrap-program prog #:rest vars)
de611138 1119 "Make a wrapper for PROG. VARS should look like this:
30db6af1
NK
1120
1121 '(VARIABLE DELIMITER POSITION LIST-OF-DIRECTORIES)
1122
1123where DELIMITER is optional. ':' will be used if DELIMITER is not given.
1124
1125For example, this command:
1126
1127 (wrap-program \"foo\"
de611138
EB
1128 '(\"PATH\" \":\" = (\"/gnu/.../bar/bin\"))
1129 '(\"CERT_PATH\" suffix (\"/gnu/.../baz/certs\"
30db6af1
NK
1130 \"/qux/certs\")))
1131
1132will copy 'foo' to '.foo-real' and create the file 'foo' with the following
1133contents:
1134
1135 #!location/of/bin/bash
de611138
EB
1136 export PATH=\"/gnu/.../bar/bin\"
1137 export CERT_PATH=\"$CERT_PATH${CERT_PATH:+:}/gnu/.../baz/certs:/qux/certs\"
b01f8967 1138 exec -a $0 location/of/.foo-real \"$@\"
30db6af1
NK
1139
1140This is useful for scripts that expect particular programs to be in $PATH, for
1141programs that expect particular shared libraries to be in $LD_LIBRARY_PATH, or
de611138
EB
1142modules in $GUILE_LOAD_PATH, etc.
1143
b14a8385
LC
1144If PROG has previously been wrapped by 'wrap-program', the wrapper is extended
1145with definitions for VARS."
1146 (define wrapped-file
1147 (string-append (dirname prog) "/." (basename prog) "-real"))
1148
1149 (define already-wrapped?
1150 (file-exists? wrapped-file))
1151
1152 (define (last-line port)
1153 ;; Return the last line read from PORT and leave PORT's cursor right
1154 ;; before it.
1155 (let loop ((previous-line-offset 0)
1156 (previous-line "")
1157 (position (seek port 0 SEEK_CUR)))
1158 (match (read-line port 'concat)
1159 ((? eof-object?)
1160 (seek port previous-line-offset SEEK_SET)
1161 previous-line)
1162 ((? string? line)
1163 (loop position line (+ (string-length line) position))))))
1164
1165 (define (export-variable lst)
1166 ;; Return a string that exports an environment variable.
1167 (match lst
1168 ((var sep '= rest)
1169 (format #f "export ~a=\"~a\""
1170 var (string-join rest sep)))
1171 ((var sep 'prefix rest)
e6c4e411
JL
1172 (format #f "export ~a=\"~a${~a:+~a}$~a\""
1173 var (string-join rest sep) var sep var))
b14a8385 1174 ((var sep 'suffix rest)
e6c4e411
JL
1175 (format #f "export ~a=\"$~a${~a+~a}~a\""
1176 var var var sep (string-join rest sep)))
b14a8385
LC
1177 ((var '= rest)
1178 (format #f "export ~a=\"~a\""
1179 var (string-join rest ":")))
1180 ((var 'prefix rest)
1181 (format #f "export ~a=\"~a${~a:+:}$~a\""
1182 var (string-join rest ":") var var))
1183 ((var 'suffix rest)
1184 (format #f "export ~a=\"$~a${~a:+:}~a\""
1185 var var var (string-join rest ":")))))
1186
1187 (if already-wrapped?
1188
1189 ;; PROG is already a wrapper: add the new "export VAR=VALUE" lines just
1190 ;; before the last line.
1191 (let* ((port (open-file prog "r+"))
1192 (last (last-line port)))
1193 (for-each (lambda (var)
1194 (display (export-variable var) port)
1195 (newline port))
1196 vars)
1197 (display last port)
1198 (close-port port))
1199
1200 ;; PROG is not wrapped yet: create a shell script that sets VARS.
1201 (let ((prog-tmp (string-append wrapped-file "-tmp")))
1202 (link prog wrapped-file)
1203
1204 (call-with-output-file prog-tmp
1205 (lambda (port)
1206 (format port
1207 "#!~a~%~a~%exec -a \"$0\" \"~a\" \"$@\"~%"
1208 (which "bash")
1209 (string-join (map export-variable vars) "\n")
1210 (canonicalize-path wrapped-file))))
1211
1212 (chmod prog-tmp #o755)
1213 (rename-file prog-tmp prog))))
30db6af1 1214
0fb9a8df
RW
1215(define wrap-script
1216 (let ((interpreter-regex
1217 (make-regexp
1218 (string-append "^#! ?(/[^ ]+/bin/("
1219 (string-join '("python[^ ]*"
1220 "Rscript"
1221 "perl"
1222 "ruby"
1223 "bash"
1224 "sh") "|")
1225 "))( ?.*)")))
1226 (coding-line-regex
1227 (make-regexp
1228 ".*#.*coding[=:][[:space:]]*([-a-zA-Z_0-9.]+)")))
1229 (lambda* (prog #:key (guile (which "guile")) #:rest vars)
1230 "Wrap the script PROG such that VARS are set first. The format of VARS
1231is the same as in the WRAP-PROGRAM procedure. This procedure differs from
1232WRAP-PROGRAM in that it does not create a separate shell script. Instead,
1233PROG is modified directly by prepending a Guile script, which is interpreted
1234as a comment in the script's language.
1235
1236Special encoding comments as supported by Python are recreated on the second
1237line.
1238
1239Note that this procedure can only be used once per file as Guile scripts are
1240not supported."
1241 (define update-env
1242 (match-lambda
1243 ((var sep '= rest)
1244 `(setenv ,var ,(string-join rest sep)))
1245 ((var sep 'prefix rest)
1246 `(let ((current (getenv ,var)))
1247 (setenv ,var (if current
1248 (string-append ,(string-join rest sep)
1249 ,sep current)
1250 ,(string-join rest sep)))))
1251 ((var sep 'suffix rest)
1252 `(let ((current (getenv ,var)))
1253 (setenv ,var (if current
1254 (string-append current ,sep
1255 ,(string-join rest sep))
1256 ,(string-join rest sep)))))
1257 ((var '= rest)
1258 `(setenv ,var ,(string-join rest ":")))
1259 ((var 'prefix rest)
1260 `(let ((current (getenv ,var)))
1261 (setenv ,var (if current
1262 (string-append ,(string-join rest ":")
1263 ":" current)
1264 ,(string-join rest ":")))))
1265 ((var 'suffix rest)
1266 `(let ((current (getenv ,var)))
1267 (setenv ,var (if current
1268 (string-append current ":"
1269 ,(string-join rest ":"))
1270 ,(string-join rest ":")))))))
1271 (let-values (((interpreter args coding-line)
1272 (call-with-ascii-input-file prog
1273 (lambda (p)
1274 (let ((first-match
1275 (false-if-exception
1276 (regexp-exec interpreter-regex (read-line p)))))
1277 (values (and first-match (match:substring first-match 1))
1278 (and first-match (match:substring first-match 3))
1279 (false-if-exception
1280 (and=> (regexp-exec coding-line-regex (read-line p))
1281 (lambda (m) (match:substring m 0))))))))))
1282 (if interpreter
1283 (let* ((header (format #f "\
1284#!~a --no-auto-compile
1285#!#; ~a
1286#\\-~s
1287#\\-~s
1288"
1289 guile
1290 (or coding-line "Guix wrapper")
1291 (cons 'begin (map update-env
1292 (match vars
1293 ((#:guile _ . vars) vars)
1294 (_ vars))))
1295 `(let ((cl (command-line)))
1296 (apply execl ,interpreter
1297 (car cl)
1298 (cons (car cl)
1299 (append
1300 ',(string-split args #\space)
1301 cl))))))
1302 (template (string-append prog ".XXXXXX"))
1303 (out (mkstemp! template))
1304 (st (stat prog))
1305 (mode (stat:mode st)))
1306 (with-throw-handler #t
1307 (lambda ()
1308 (call-with-ascii-input-file prog
1309 (lambda (p)
1310 (format out header)
1311 (dump-port p out)
1312 (close out)
1313 (chmod template mode)
1314 (rename-file template prog)
1315 (set-file-time prog st))))
1316 (lambda (key . args)
1317 (format (current-error-port)
1318 "wrap-script: ~a: error: ~a ~s~%"
1319 prog key args)
1320 (false-if-exception (delete-file template))
1321 (raise (condition
1322 (&wrap-error (program prog)
1323 (type key))))
1324 #f)))
1325 (raise (condition
1326 (&wrap-error (program prog)
1327 (type 'no-interpreter-found)))))))))
1328
10bb4e16
PN
1329(define* (make-desktop-entry-file destination #:key
1330 (type "Application") ; One of "Application", "Link" or "Directory".
1331 (version "1.1")
1332 name
1333 (generic-name name)
1334 (no-display #f)
1335 comment
1336 icon
1337 (hidden #f)
1338 only-show-in
1339 not-show-in
1340 (d-bus-activatable #f)
1341 try-exec
1342 exec
1343 path
1344 (terminal #f)
1345 actions
1346 mime-type
1347 (categories "Application")
1348 implements
1349 keywords
1350 (startup-notify #t)
1351 startup-w-m-class
1352 #:rest all-args)
1353 "Create a desktop entry file at DESTINATION.
1354You must specify NAME.
1355
1356Values can be booleans, numbers, strings or list of strings.
1357
1358Additionally, locales can be specified with an alist where the key is the
1359locale. The #f key specifies the default. Example:
1360
1361 #:name '((#f \"I love Guix\") (\"fr\" \"J'aime Guix\"))
1362
1363produces
1364
1365 Name=I love Guix
1366 Name[fr]=J'aime Guix
1367
1368For a complete description of the format, see the specifications at
1369https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html."
1370 (define (escape-semicolon s)
1371 (string-join (string-split s #\;) "\\;"))
1372 (define* (parse key value #:optional locale)
1373 (set! value (match value
1374 (#t "true")
1375 (#f "false")
1376 ((? number? n) n)
1377 ((? string? s) (escape-semicolon s))
1378 ((? list? value)
1379 (catch 'wrong-type-arg
1380 (lambda () (string-join (map escape-semicolon value) ";"))
1381 (lambda args (error "List arguments can only contain strings: ~a" args))))
1382 (_ (error "Value must be a boolean, number, string or list of strings"))))
1383 (format #t "~a=~a~%"
1384 (if locale
1385 (format #f "~a[~a]" key locale)
1386 key)
1387 value))
1388
1389 (define key-error-message "This procedure only takes key arguments beside DESTINATION")
1390
1391 (unless name
1392 (error "Missing NAME key argument"))
1393 (unless (member #:type all-args)
1394 (set! all-args (append (list #:type type) all-args)))
1395 (mkdir-p (dirname destination))
1396
1397 (with-output-to-file destination
1398 (lambda ()
1399 (format #t "[Desktop Entry]~%")
1400 (let loop ((args all-args))
1401 (match args
1402 (() #t)
1403 ((_) (error key-error-message))
1404 ((key value . ...)
1405 (unless (keyword? key)
1406 (error key-error-message))
1407 (set! key
1408 (string-join (map string-titlecase
1409 (string-split (symbol->string
1410 (keyword->symbol key))
1411 #\-))
1412 ""))
1413 (match value
1414 (((_ . _) . _)
1415 (for-each (lambda (locale-subvalue)
1416 (parse key
1417 (if (and (list? (cdr locale-subvalue))
1418 (= 1 (length (cdr locale-subvalue))))
1419 ;; Support both proper and improper lists for convenience.
1420 (cadr locale-subvalue)
1421 (cdr locale-subvalue))
1422 (car locale-subvalue)))
1423 value))
1424 (_
1425 (parse key value)))
1426 (loop (cddr args))))))))
1427
251e8b2e
LC
1428\f
1429;;;
1430;;; Locales.
1431;;;
1432
1433(define (locale-category->string category)
1434 "Return the name of locale category CATEGORY, one of the 'LC_' constants.
1435If CATEGORY is a bitwise or of several 'LC_' constants, an approximation is
1436returned."
1437 (letrec-syntax ((convert (syntax-rules ()
1438 ((_)
1439 (number->string category))
1440 ((_ first rest ...)
1441 (if (= first category)
1442 (symbol->string 'first)
1443 (convert rest ...))))))
1444 (convert LC_ADDRESS LC_ALL LC_COLLATE LC_CTYPE
1445 LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES LC_MONETARY
1446 LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE
1447 LC_TIME)))
1448
b0e0d0e9
LC
1449;;; Local Variables:
1450;;; eval: (put 'call-with-output-file/atomic 'scheme-indent-function 1)
99533da5 1451;;; eval: (put 'call-with-ascii-input-file 'scheme-indent-function 1)
b0e0d0e9 1452;;; eval: (put 'with-throw-handler 'scheme-indent-function 1)
8197c978 1453;;; eval: (put 'let-matches 'scheme-indent-function 3)
dcd72906 1454;;; eval: (put 'with-atomic-file-replacement 'scheme-indent-function 1)
b0e0d0e9 1455;;; End: