system: Make /gnu/store a read-only bind mount by default.
[jackhill/guix/guix.git] / guix / ui.scm
CommitLineData
233e7676 1;;; GNU Guix --- Functional package management for GNU
e9651e39 2;;; Copyright © 2012, 2013, 2014, 2015 Ludovic Courtès <ludo@gnu.org>
e49951eb 3;;; Copyright © 2013 Mark H Weaver <mhw@netris.org>
98eb8cbe 4;;; Copyright © 2013 Nikita Karetnikov <nikita@karetnikov.org>
c0c018f1 5;;; Copyright © 2014 Alex Kost <alezost@gmail.com>
e9c1e22f 6;;; Copyright © 2014 Deck Pickard <deck.r.pickard@gmail.com>
073c34d7 7;;;
233e7676 8;;; This file is part of GNU Guix.
073c34d7 9;;;
233e7676 10;;; GNU Guix is free software; you can redistribute it and/or modify it
073c34d7
LC
11;;; under the terms of the GNU General Public License as published by
12;;; the Free Software Foundation; either version 3 of the License, or (at
13;;; your option) any later version.
14;;;
233e7676 15;;; GNU Guix is distributed in the hope that it will be useful, but
073c34d7
LC
16;;; WITHOUT ANY WARRANTY; without even the implied warranty of
17;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;;; GNU General Public License for more details.
19;;;
20;;; You should have received a copy of the GNU General Public License
233e7676 21;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
073c34d7
LC
22
23(define-module (guix ui)
24 #:use-module (guix utils)
25 #:use-module (guix store)
cdd5d6f9 26 #:use-module (guix config)
073c34d7 27 #:use-module (guix packages)
5d7a8584 28 #:use-module (guix profiles)
9bb2b96a 29 #:use-module (guix derivations)
b7071bc5
LC
30 #:use-module (guix build-system)
31 #:use-module (guix serialization)
59758816 32 #:use-module ((guix build utils) #:select (mkdir-p))
299112d3
LC
33 #:use-module ((guix licenses) #:select (license? license-name))
34 #:use-module (srfi srfi-1)
35 #:use-module (srfi srfi-11)
2cd09108 36 #:use-module (srfi srfi-19)
073c34d7
LC
37 #:use-module (srfi srfi-26)
38 #:use-module (srfi srfi-34)
c1d52c71 39 #:use-module (srfi srfi-35)
a5975ced 40 #:use-module (srfi srfi-37)
e31ff8b8 41 #:autoload (ice-9 ftw) (scandir)
64fc89b6 42 #:use-module (ice-9 match)
9bb2b96a 43 #:use-module (ice-9 format)
2cd09108 44 #:use-module (ice-9 regex)
073c34d7
LC
45 #:export (_
46 N_
ee764179 47 P_
70e629f5 48 report-error
073c34d7 49 leave
1151f6ae 50 report-load-error
4ae7559f 51 warn-about-load-error
cdd5d6f9 52 show-version-and-exit
3441e164 53 show-bug-report-information
969e678e 54 string->number*
1d6243cf 55 size->number
9bb2b96a 56 show-what-to-build
5d7a8584 57 show-manifest-transaction
073c34d7 58 call-with-error-handling
64fc89b6 59 with-error-handling
ac5de156 60 read/eval
eb0880e7 61 read/eval-package-expression
299112d3 62 location->string
c61b026e 63 switch-symlinks
f651b477 64 config-directory
299112d3
LC
65 fill-paragraph
66 string->recutils
e49951eb 67 package->recutils
2876b989 68 package-specification->name+version+output
2cd09108
NK
69 string->generations
70 string->duration
a5975ced 71 args-fold*
b3f21389 72 parse-command-line
e49951eb 73 run-guix-command
a2011be5
LC
74 program-name
75 guix-warning-port
76 warning
e49951eb 77 guix-main))
073c34d7
LC
78
79;;; Commentary:
80;;;
81;;; User interface facilities for command-line tools.
82;;;
83;;; Code:
84
85(define %gettext-domain
ee764179 86 ;; Text domain for strings used in the tools.
073c34d7
LC
87 "guix")
88
ee764179
LC
89(define %package-text-domain
90 ;; Text domain for package synopses and descriptions.
91 "guix-packages")
92
073c34d7
LC
93(define _ (cut gettext <> %gettext-domain))
94(define N_ (cut ngettext <> <> <> %gettext-domain))
ee764179 95(define P_ (cut gettext <> %package-text-domain))
073c34d7 96
b2a886f6
LC
97(define-syntax-rule (define-diagnostic name prefix)
98 "Create a diagnostic macro (i.e., NAME), which will prepend PREFIX to all
99messages."
100 (define-syntax name
101 (lambda (x)
102 (define (augmented-format-string fmt)
103 (string-append "~:[~*~;guix ~a: ~]~a" (syntax->datum fmt)))
104
89d02b98
LC
105 (syntax-case x ()
106 ((name (underscore fmt) args (... ...))
107 (and (string? (syntax->datum #'fmt))
108 (free-identifier=? #'underscore #'_))
b2a886f6
LC
109 (with-syntax ((fmt* (augmented-format-string #'fmt))
110 (prefix (datum->syntax x prefix)))
111 #'(format (guix-warning-port) (gettext fmt*)
112 (program-name) (program-name) prefix
113 args (... ...))))
89d02b98 114 ((name (N-underscore singular plural n) args (... ...))
b2a886f6 115 (and (string? (syntax->datum #'singular))
89d02b98
LC
116 (string? (syntax->datum #'plural))
117 (free-identifier=? #'N-underscore #'N_))
b2a886f6
LC
118 (with-syntax ((s (augmented-format-string #'singular))
119 (p (augmented-format-string #'plural))
120 (prefix (datum->syntax x prefix)))
121 #'(format (guix-warning-port)
122 (ngettext s p n %gettext-domain)
123 (program-name) (program-name) prefix
124 args (... ...))))))))
125
126(define-diagnostic warning "warning: ") ; emit a warning
127
128(define-diagnostic report-error "error: ")
129(define-syntax-rule (leave args ...)
130 "Emit an error message and exit."
131 (begin
132 (report-error args ...)
133 (exit 1)))
134
1151f6ae
LC
135(define (report-load-error file args)
136 "Report the failure to load FILE, a user-provided Scheme file, and exit.
137ARGS is the list of arguments received by the 'throw' handler."
138 (match args
139 (('system-error . _)
140 (let ((err (system-error-errno args)))
141 (leave (_ "failed to load '~a': ~a~%") file (strerror err))))
142 (('syntax-error proc message properties form . rest)
143 (let ((loc (source-properties->location properties)))
144 (format (current-error-port) (_ "~a: error: ~a~%")
145 (location->string loc) message)
146 (exit 1)))
147 ((error args ...)
148 (report-error (_ "failed to load '~a':~%") file)
149 (apply display-error #f (current-error-port) args)
150 (exit 1))))
151
4ae7559f
LC
152(define (warn-about-load-error file args) ;FIXME: factorize with ↑
153 "Report the failure to load FILE, a user-provided Scheme file, without
154exiting. ARGS is the list of arguments received by the 'throw' handler."
155 (match args
156 (('system-error . _)
157 (let ((err (system-error-errno args)))
158 (warning (_ "failed to load '~a': ~a~%") file (strerror err))))
159 (('syntax-error proc message properties form . rest)
160 (let ((loc (source-properties->location properties)))
161 (format (current-error-port) (_ "~a: warning: ~a~%")
162 (location->string loc) message)))
163 ((error args ...)
164 (warning (_ "failed to load '~a':~%") file)
165 (apply display-error #f (current-error-port) args))))
166
b2a886f6
LC
167(define (install-locale)
168 "Install the current locale settings."
169 (catch 'system-error
170 (lambda _
171 (setlocale LC_ALL ""))
172 (lambda args
173 (warning (_ "failed to install locale: ~a~%")
174 (strerror (system-error-errno args))))))
175
e49951eb 176(define (initialize-guix)
633f045f 177 "Perform the usual initialization for stand-alone Guix commands."
e49951eb 178 (install-locale)
39e9f95d 179 (textdomain %gettext-domain)
e14c3929
LC
180
181 ;; Ignore SIGPIPE. If the daemon closes the connection, we prefer to be
182 ;; notified via an EPIPE later.
183 (sigaction SIGPIPE SIG_IGN)
184
e49951eb
MW
185 (setvbuf (current-output-port) _IOLBF)
186 (setvbuf (current-error-port) _IOLBF))
187
cdd5d6f9
LC
188(define* (show-version-and-exit #:optional (command (car (command-line))))
189 "Display version information for COMMAND and `(exit 0)'."
190 (simple-format #t "~a (~a) ~a~%"
191 command %guix-package-name %guix-version)
9250b304 192 (display (_ "Copyright (C) 2015 the Guix authors
64a967cc
LC
193License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
194This is free software: you are free to change and redistribute it.
195There is NO WARRANTY, to the extent permitted by law.
196"))
cdd5d6f9
LC
197 (exit 0))
198
3441e164
LC
199(define (show-bug-report-information)
200 (format #t (_ "
201Report bugs to: ~a.") %guix-bug-report-address)
202 (format #t (_ "
203~a home page: <~a>") %guix-package-name %guix-home-page-url)
204 (display (_ "
205General help using GNU software: <http://www.gnu.org/gethelp/>"))
206 (newline))
207
969e678e
LC
208(define (string->number* str)
209 "Like `string->number', but error out with an error message on failure."
210 (or (string->number str)
211 (leave (_ "~a: invalid number~%") str)))
212
1d6243cf
LC
213(define (size->number str)
214 "Convert STR, a storage measurement representation such as \"1024\" or
215\"1MiB\", to a number of bytes. Raise an error if STR could not be
216interpreted."
217 (define unit-pos
218 (string-rindex str char-set:digit))
219
220 (define unit
221 (and unit-pos (substring str (+ 1 unit-pos))))
222
223 (let* ((numstr (if unit-pos
224 (substring str 0 (+ 1 unit-pos))
225 str))
226 (num (string->number numstr)))
227 (unless num
228 (leave (_ "invalid number: ~a~%") numstr))
229
230 ((compose inexact->exact round)
231 (* num
232 (match unit
4a44d7bb
LC
233 ((or "KiB" "K" "k") (expt 2 10))
234 ((or "MiB" "M") (expt 2 20))
235 ((or "GiB" "G") (expt 2 30))
236 ((or "TiB" "T") (expt 2 40))
237 ((or "PiB" "P") (expt 2 50))
238 ((or "EiB" "E") (expt 2 60))
239 ((or "ZiB" "Z") (expt 2 70))
240 ((or "YiB" "Y") (expt 2 80))
241 ("kB" (expt 10 3))
1d6243cf
LC
242 ("MB" (expt 10 6))
243 ("GB" (expt 10 9))
244 ("TB" (expt 10 12))
4a44d7bb
LC
245 ("PB" (expt 10 15))
246 ("EB" (expt 10 18))
247 ("ZB" (expt 10 21))
248 ("YB" (expt 10 24))
1d6243cf
LC
249 ("" 1)
250 (_
251 (leave (_ "unknown unit: ~a~%") unit)))))))
252
073c34d7
LC
253(define (call-with-error-handling thunk)
254 "Call THUNK within a user-friendly error handler."
255 (guard (c ((package-input-error? c)
256 (let* ((package (package-error-package c))
257 (input (package-error-invalid-input c))
258 (location (package-location package))
259 (file (location-file location))
260 (line (location-line location))
261 (column (location-column location)))
98eb8cbe 262 (leave (_ "~a:~a:~a: package `~a' has an invalid input: ~s~%")
073c34d7
LC
263 file line column
264 (package-full-name package) input)))
9b222abe
LC
265 ((package-cross-build-system-error? c)
266 (let* ((package (package-error-package c))
267 (loc (package-location package))
268 (system (package-build-system package)))
269 (leave (_ "~a: ~a: build system `~a' does not support cross builds~%")
270 (location->string loc)
271 (package-full-name package)
272 (build-system-name system))))
c0c018f1
AK
273 ((profile-not-found-error? c)
274 (leave (_ "profile '~a' does not exist~%")
275 (profile-error-profile c)))
276 ((missing-generation-error? c)
277 (leave (_ "generation ~a of profile '~a' does not exist~%")
278 (missing-generation-error-generation c)
279 (profile-error-profile c)))
b7071bc5
LC
280 ((nar-error? c)
281 (let ((file (nar-error-file c))
282 (port (nar-error-port c)))
283 (if file
284 (leave (_ "corrupt input while restoring '~a' from ~s~%")
285 file (or (port-filename port) port))
286 (leave (_ "corrupt input while restoring archive from ~s~%")
287 (or (port-filename port) port)))))
ef86c39f 288 ((nix-connection-error? c)
98eb8cbe 289 (leave (_ "failed to connect to `~a': ~a~%")
ef86c39f
LC
290 (nix-connection-error-file c)
291 (strerror (nix-connection-error-code c))))
073c34d7
LC
292 ((nix-protocol-error? c)
293 ;; FIXME: Server-provided error messages aren't i18n'd.
98eb8cbe 294 (leave (_ "build failed: ~a~%")
c1d52c71 295 (nix-protocol-error-message c)))
f304c9c2
LC
296 ((derivation-missing-output-error? c)
297 (leave (_ "reference to invalid output '~a' of derivation '~a'~%")
298 (derivation-missing-output c)
299 (derivation-file-name (derivation-error-derivation c))))
c1d52c71
LC
300 ((message-condition? c)
301 ;; Normally '&message' error conditions have an i18n'd message.
12703d08
LC
302 (leave (_ "~a~%")
303 (gettext (condition-message c) %gettext-domain))))
e14c3929
LC
304 ;; Catch EPIPE and the likes.
305 (catch 'system-error
306 thunk
bde8c0e6
LC
307 (lambda (key proc format-string format-args . rest)
308 (leave (_ "~a: ~a~%") proc
309 (apply format #f format-string format-args))))))
073c34d7 310
56b82106
LC
311(define %guix-user-module
312 ;; Module in which user expressions are evaluated.
07254feb
LC
313 ;; Compute lazily to avoid circularity with (guix gexp).
314 (delay
315 (let ((module (make-module)))
316 (beautify-user-module! module)
317 ;; Use (guix gexp) so that one can use #~ & co.
318 (module-use! module (resolve-interface '(guix gexp)))
319 module)))
56b82106 320
ac5de156
LC
321(define (read/eval str)
322 "Read and evaluate STR, raising an error if something goes wrong."
eb0880e7
LC
323 (let ((exp (catch #t
324 (lambda ()
325 (call-with-input-string str read))
326 (lambda args
327 (leave (_ "failed to read expression ~s: ~s~%")
328 str args)))))
ac5de156
LC
329 (catch #t
330 (lambda ()
07254feb 331 (eval exp (force %guix-user-module)))
ac5de156
LC
332 (lambda args
333 (leave (_ "failed to evaluate expression `~a': ~s~%")
334 exp args)))))
335
336(define (read/eval-package-expression str)
337 "Read and evaluate STR and return the package it refers to, or exit an
338error."
339 (match (read/eval str)
340 ((? package? p) p)
341 (_
342 (leave (_ "expression ~s does not evaluate to a package~%")
343 str))))
eb0880e7 344
dd36b51b
LC
345(define* (show-what-to-build store drv
346 #:key dry-run? (use-substitutes? #t))
9bb2b96a 347 "Show what will or would (depending on DRY-RUN?) be built in realizing the
4d60610a 348derivations listed in DRV. Return #t if there's something to build, #f
dd36b51b
LC
349otherwise. When USE-SUBSTITUTES?, check and report what is prerequisites are
350available for download."
e9651e39
LC
351 (define substitutable?
352 ;; Call 'substitutation-oracle' upfront so we don't end up launching the
353 ;; substituter many times. This makes a big difference, especially when
354 ;; DRV is a long list as is the case with 'guix environment'.
355 (if use-substitutes?
356 (substitution-oracle store drv)
357 (const #f)))
358
52ddf2ae 359 (define (built-or-substitutable? drv)
f304c9c2
LC
360 (or (null? (derivation-outputs drv))
361 (let ((out (derivation->output-path drv))) ;XXX: assume "out" exists
52ddf2ae 362 (or (valid-path? store out)
e9651e39 363 (substitutable? out)))))
52ddf2ae 364
dd36b51b 365 (let*-values (((build download)
59688fc4
LC
366 (fold2 (lambda (drv build download)
367 (let-values (((b d)
368 (derivation-prerequisites-to-build
369 store drv
e9651e39 370 #:substitutable? substitutable?)))
59688fc4
LC
371 (values (append b build)
372 (append d download))))
dd36b51b
LC
373 '() '()
374 drv))
375 ((build) ; add the DRV themselves
376 (delete-duplicates
59688fc4 377 (append (map derivation-file-name
52ddf2ae 378 (remove built-or-substitutable? drv))
dd36b51b
LC
379 (map derivation-input-path build))))
380 ((download) ; add the references of DOWNLOAD
1a8b7834
LC
381 (if use-substitutes?
382 (delete-duplicates
383 (append download
384 (remove (cut valid-path? store <>)
385 (append-map
386 substitutable-references
387 (substitutable-path-info store
388 download)))))
389 download)))
00554b2a 390 ;; TODO: Show the installed size of DOWNLOAD.
9bb2b96a 391 (if dry-run?
dd36b51b
LC
392 (begin
393 (format (current-error-port)
83e61a73
LC
394 (N_ "~:[The following derivation would be built:~%~{ ~a~%~}~;~]"
395 "~:[The following derivations would be built:~%~{ ~a~%~}~;~]"
dd36b51b
LC
396 (length build))
397 (null? build) build)
398 (format (current-error-port)
83e61a73
LC
399 (N_ "~:[The following file would be downloaded:~%~{ ~a~%~}~;~]"
400 "~:[The following files would be downloaded:~%~{ ~a~%~}~;~]"
dd36b51b
LC
401 (length download))
402 (null? download) download))
403 (begin
404 (format (current-error-port)
83e61a73
LC
405 (N_ "~:[The following derivation will be built:~%~{ ~a~%~}~;~]"
406 "~:[The following derivations will be built:~%~{ ~a~%~}~;~]"
dd36b51b
LC
407 (length build))
408 (null? build) build)
409 (format (current-error-port)
83e61a73
LC
410 (N_ "~:[The following file will be downloaded:~%~{ ~a~%~}~;~]"
411 "~:[The following files will be downloaded:~%~{ ~a~%~}~;~]"
dd36b51b
LC
412 (length download))
413 (null? download) download)))
414 (pair? build)))
9bb2b96a 415
5d7a8584
AK
416(define (right-arrow port)
417 "Return either a string containing the 'RIGHT ARROW' character, or an ASCII
418replacement if PORT is not Unicode-capable."
419 (with-fluids ((%default-port-encoding (port-encoding port)))
420 (let ((arrow "→"))
421 (catch 'encoding-error
422 (lambda ()
423 (call-with-output-string
424 (lambda (port)
425 (set-port-conversion-strategy! port 'error)
426 (display arrow port))))
427 (lambda (key . args)
428 "->")))))
429
430(define* (show-manifest-transaction store manifest transaction
431 #:key dry-run?)
432 "Display what will/would be installed/removed from MANIFEST by TRANSACTION."
433 (define (package-strings name version output item)
434 (map (lambda (name version output item)
435 (format #f " ~a~:[:~a~;~*~]\t~a\t~a"
436 name
437 (equal? output "out") output version
438 (if (package? item)
439 (package-output store item output)
440 item)))
441 name version output item))
442
443 (define → ;an arrow that can be represented on stderr
444 (right-arrow (current-error-port)))
445
446 (define (upgrade-string name old-version new-version output item)
447 (format #f " ~a~:[:~a~;~*~]\t~a ~a ~a\t~a"
448 name (equal? output "out") output
449 old-version → new-version
450 (if (package? item)
451 (package-output store item output)
452 item)))
453
46b23e1a 454 (let-values (((remove install upgrade downgrade)
5d7a8584
AK
455 (manifest-transaction-effects manifest transaction)))
456 (match remove
457 ((($ <manifest-entry> name version output item) ..1)
458 (let ((len (length name))
459 (remove (package-strings name version output item)))
460 (if dry-run?
461 (format (current-error-port)
462 (N_ "The following package would be removed:~%~{~a~%~}~%"
463 "The following packages would be removed:~%~{~a~%~}~%"
464 len)
465 remove)
466 (format (current-error-port)
467 (N_ "The following package will be removed:~%~{~a~%~}~%"
468 "The following packages will be removed:~%~{~a~%~}~%"
469 len)
470 remove))))
471 (_ #f))
46b23e1a
LC
472 (match downgrade
473 (((($ <manifest-entry> name old-version)
474 . ($ <manifest-entry> _ new-version output item)) ..1)
475 (let ((len (length name))
476 (downgrade (map upgrade-string
477 name old-version new-version output item)))
478 (if dry-run?
479 (format (current-error-port)
480 (N_ "The following package would be downgraded:~%~{~a~%~}~%"
481 "The following packages would be downgraded:~%~{~a~%~}~%"
482 len)
483 downgrade)
484 (format (current-error-port)
485 (N_ "The following package will be downgraded:~%~{~a~%~}~%"
486 "The following packages will be downgraded:~%~{~a~%~}~%"
487 len)
488 downgrade))))
489 (_ #f))
5d7a8584
AK
490 (match upgrade
491 (((($ <manifest-entry> name old-version)
492 . ($ <manifest-entry> _ new-version output item)) ..1)
493 (let ((len (length name))
494 (upgrade (map upgrade-string
495 name old-version new-version output item)))
496 (if dry-run?
497 (format (current-error-port)
498 (N_ "The following package would be upgraded:~%~{~a~%~}~%"
499 "The following packages would be upgraded:~%~{~a~%~}~%"
500 len)
501 upgrade)
502 (format (current-error-port)
503 (N_ "The following package will be upgraded:~%~{~a~%~}~%"
504 "The following packages will be upgraded:~%~{~a~%~}~%"
505 len)
506 upgrade))))
507 (_ #f))
508 (match install
509 ((($ <manifest-entry> name version output item _) ..1)
510 (let ((len (length name))
511 (install (package-strings name version output item)))
512 (if dry-run?
513 (format (current-error-port)
514 (N_ "The following package would be installed:~%~{~a~%~}~%"
515 "The following packages would be installed:~%~{~a~%~}~%"
516 len)
517 install)
518 (format (current-error-port)
519 (N_ "The following package will be installed:~%~{~a~%~}~%"
520 "The following packages will be installed:~%~{~a~%~}~%"
521 len)
522 install))))
523 (_ #f))))
524
073c34d7
LC
525(define-syntax with-error-handling
526 (syntax-rules ()
527 "Run BODY within a user-friendly error condition handler."
528 ((_ body ...)
529 (call-with-error-handling
530 (lambda ()
531 body ...)))))
532
64fc89b6
LC
533(define (location->string loc)
534 "Return a human-friendly, GNU-standard representation of LOC."
535 (match loc
536 (#f (_ "<unknown location>"))
537 (($ <location> file line column)
538 (format #f "~a:~a:~a" file line column))))
539
c61b026e
LC
540(define (switch-symlinks link target)
541 "Atomically switch LINK, a symbolic link, to point to TARGET. Works
542both when LINK already exists and when it does not."
543 (let ((pivot (string-append link ".new")))
544 (symlink target pivot)
545 (rename-file pivot link)))
546
f651b477
LC
547(define (config-directory)
548 "Return the name of the configuration directory, after making sure that it
549exists. Honor the XDG specs,
550<http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html>."
551 (let ((dir (and=> (or (getenv "XDG_CONFIG_HOME")
552 (and=> (getenv "HOME")
553 (cut string-append <> "/.config")))
554 (cut string-append <> "/guix"))))
555 (catch 'system-error
556 (lambda ()
59758816 557 (mkdir-p dir)
f651b477
LC
558 dir)
559 (lambda args
59758816
LC
560 (let ((err (system-error-errno args)))
561 ;; ERR is necessarily different from EEXIST.
562 (leave (_ "failed to create configuration directory `~a': ~a~%")
563 dir (strerror err)))))))
f651b477 564
299112d3
LC
565(define* (fill-paragraph str width #:optional (column 0))
566 "Fill STR such that each line contains at most WIDTH characters, assuming
567that the first character is at COLUMN.
568
569When STR contains a single line break surrounded by other characters, it is
570converted to a space; sequences of more than one line break are preserved."
571 (define (maybe-break chr result)
572 (match result
573 ((column newlines chars)
574 (case chr
575 ((#\newline)
576 `(,column ,(+ 1 newlines) ,chars))
577 (else
3a09e1d2
CS
578 (let* ((spaces (if (and (pair? chars) (eqv? (car chars) #\.)) 2 1))
579 (chars (case newlines
580 ((0) chars)
581 ((1)
582 (append (make-list spaces #\space) chars))
583 (else
584 (append (make-list newlines #\newline) chars))))
585 (column (case newlines
586 ((0) column)
587 ((1) (+ spaces column))
588 (else 0))))
299112d3
LC
589 (let ((chars (cons chr chars))
590 (column (+ 1 column)))
591 (if (> column width)
592 (let*-values (((before after)
593 (break (cut eqv? #\space <>) chars))
594 ((len)
595 (length before)))
596 (if (<= len width)
597 `(,len
598 0
599 ,(if (null? after)
600 before
3a09e1d2
CS
601 (append before
602 (cons #\newline
603 (drop-while (cut eqv? #\space <>)
604 after)))))
299112d3
LC
605 `(,column 0 ,chars))) ; unbreakable
606 `(,column 0 ,chars)))))))))
607
608 (match (string-fold maybe-break
609 `(,column 0 ())
610 str)
611 ((_ _ chars)
612 (list->string (reverse chars)))))
613
2876b989
LC
614\f
615;;;
616;;; Packages.
617;;;
618
299112d3
LC
619(define (string->recutils str)
620 "Return a version of STR where newlines have been replaced by newlines
621followed by \"+ \", which makes for a valid multi-line field value in the
622`recutils' syntax."
623 (list->string
624 (string-fold-right (lambda (chr result)
625 (if (eqv? chr #\newline)
626 (cons* chr #\+ #\space result)
627 (cons chr result)))
628 '()
629 str)))
630
631(define* (package->recutils p port
632 #:optional (width (or (and=> (getenv "WIDTH")
633 string->number)
634 80)))
635 "Write to PORT a `recutils' record of package P, arranging to fit within
636WIDTH columns."
637 (define (description->recutils str)
ee764179 638 (let ((str (P_ str)))
299112d3
LC
639 (string->recutils
640 (fill-paragraph str width
641 (string-length "description: ")))))
642
20ffce82
LC
643 (define (dependencies->recutils packages)
644 (let ((list (string-join (map package-full-name
645 (sort packages package<?)) " ")))
646 (string->recutils
647 (fill-paragraph list width
648 (string-length "dependencies: ")))))
649
9c0fc279
CR
650 (define (package<? p1 p2)
651 (string<? (package-full-name p1) (package-full-name p2)))
652
299112d3
LC
653 ;; Note: Don't i18n field names so that people can post-process it.
654 (format port "name: ~a~%" (package-name p))
655 (format port "version: ~a~%" (package-version p))
5763ad92
LC
656 (format port "systems: ~a~%"
657 (string-join (package-transitive-supported-systems p)))
9c0fc279
CR
658 (format port "dependencies: ~a~%"
659 (match (package-direct-inputs p)
5e6feee6
EB
660 (((labels inputs . _) ...)
661 (dependencies->recutils (filter package? inputs)))))
299112d3
LC
662 (format port "location: ~a~%"
663 (or (and=> (package-location p) location->string)
664 (_ "unknown")))
8fa3e6b3
LC
665
666 ;; Note: Starting from version 1.6 or recutils, hyphens are not allowed in
667 ;; field identifiers.
668 (format port "homepage: ~a~%" (package-home-page p))
669
299112d3
LC
670 (format port "license: ~a~%"
671 (match (package-license p)
672 (((? license? licenses) ...)
673 (string-join (map license-name licenses)
674 ", "))
675 ((? license? license)
676 (license-name license))
677 (x
678 (_ "unknown"))))
679 (format port "synopsis: ~a~%"
680 (string-map (match-lambda
681 (#\newline #\space)
682 (chr chr))
ee764179 683 (or (and=> (package-synopsis p) P_)
299112d3
LC
684 "")))
685 (format port "description: ~a~%"
686 (and=> (package-description p) description->recutils))
687 (newline port))
688
2cd09108
NK
689(define (string->generations str)
690 "Return the list of generations matching a pattern in STR. This function
691accepts the following patterns: \"1\", \"1,2,3\", \"1..9\", \"1..\", \"..9\"."
692 (define (maybe-integer)
693 (let ((x (string->number str)))
694 (and (integer? x)
695 x)))
696
697 (define (maybe-comma-separated-integers)
698 (let ((lst (delete-duplicates
699 (map string->number
700 (string-split str #\,)))))
701 (and (every integer? lst)
702 lst)))
703
704 (cond ((maybe-integer)
705 =>
706 list)
707 ((maybe-comma-separated-integers)
708 =>
709 identity)
710 ((string-match "^([0-9]+)\\.\\.([0-9]+)$" str)
711 =>
712 (lambda (match)
713 (let ((s (string->number (match:substring match 1)))
714 (e (string->number (match:substring match 2))))
715 (and (every integer? (list s e))
716 (<= s e)
717 (iota (1+ (- e s)) s)))))
718 ((string-match "^([0-9]+)\\.\\.$" str)
719 =>
720 (lambda (match)
721 (let ((s (string->number (match:substring match 1))))
722 (and (integer? s)
723 `(>= ,s)))))
724 ((string-match "^\\.\\.([0-9]+)$" str)
725 =>
726 (lambda (match)
727 (let ((e (string->number (match:substring match 1))))
728 (and (integer? e)
729 `(<= ,e)))))
730 (else #f)))
731
732(define (string->duration str)
733 "Return the duration matching a pattern in STR. This function accepts the
734following patterns: \"1d\", \"1w\", \"1m\"."
735 (define (hours->duration hours match)
736 (make-time time-duration 0
737 (* 3600 hours (string->number (match:substring match 1)))))
738
739 (cond ((string-match "^([0-9]+)d$" str)
740 =>
741 (lambda (match)
742 (hours->duration 24 match)))
743 ((string-match "^([0-9]+)w$" str)
744 =>
745 (lambda (match)
746 (hours->duration (* 24 7) match)))
747 ((string-match "^([0-9]+)m$" str)
748 =>
749 (lambda (match)
750 (hours->duration (* 24 30) match)))
751 (else #f)))
752
2876b989
LC
753(define* (package-specification->name+version+output spec
754 #:optional (output "out"))
755 "Parse package specification SPEC and return three value: the specified
756package name, version number (or #f), and output name (or OUTPUT). SPEC may
757optionally contain a version number and an output name, as in these examples:
758
759 guile
760 guile-2.0.9
761 guile:debug
762 guile-2.0.9:debug
763"
764 (let*-values (((name sub-drv)
765 (match (string-rindex spec #\:)
766 (#f (values spec output))
767 (colon (values (substring spec 0 colon)
768 (substring spec (+ 1 colon))))))
769 ((name version)
770 (package-name->name+version name)))
771 (values name version sub-drv)))
772
773\f
774;;;
775;;; Command-line option processing.
776;;;
777
a5975ced
LC
778(define (args-fold* options unrecognized-option-proc operand-proc . seeds)
779 "A wrapper on top of `args-fold' that does proper user-facing error
780reporting."
781 (catch 'misc-error
782 (lambda ()
783 (apply args-fold options unrecognized-option-proc
784 operand-proc seeds))
785 (lambda (key proc msg args . rest)
786 ;; XXX: MSG is not i18n'd.
787 (leave (_ "invalid argument: ~a~%")
788 (apply format #f msg args)))))
789
16eb115e
DP
790(define (environment-build-options)
791 "Return additional build options passed as environment variables."
792 (arguments-from-environment-variable "GUIX_BUILD_OPTIONS"))
793
b3f21389
LC
794(define %default-argument-handler
795 ;; The default handler for non-option command-line arguments.
796 (lambda (arg result)
797 (alist-cons 'argument arg result)))
798
799(define* (parse-command-line args options seeds
800 #:key
801 (argument-handler %default-argument-handler))
802 "Parse the command-line arguments ARGS as well as arguments passed via the
803'GUIX_BUILD_OPTIONS' environment variable according to OPTIONS (a list of
804SRFI-37 options) and return the result, seeded by SEEDS.
805Command-line options take precedence those passed via 'GUIX_BUILD_OPTIONS'.
806
807ARGUMENT-HANDLER is called for non-option arguments, like the 'operand-proc'
808parameter of 'args-fold'."
cf6ce3e6 809 (define (parse-options-from args seeds)
b3f21389
LC
810 ;; Actual parsing takes place here.
811 (apply args-fold* args options
812 (lambda (opt name arg . rest)
813 (leave (_ "~A: unrecognized option~%") name))
814 argument-handler
815 seeds))
816
cf6ce3e6
LC
817 (call-with-values
818 (lambda ()
819 (parse-options-from (environment-build-options) seeds))
820 (lambda seeds
821 ;; ARGS take precedence over what the environment variable specifies.
822 (parse-options-from args seeds))))
b3f21389 823
e49951eb 824(define (show-guix-usage)
e49951eb 825 (format (current-error-port)
25c93676
LC
826 (_ "Try `guix --help' for more information.~%"))
827 (exit 1))
e49951eb 828
e31ff8b8
LC
829(define (command-files)
830 "Return the list of source files that define Guix sub-commands."
831 (define directory
832 (and=> (search-path %load-path "guix.scm")
833 (compose (cut string-append <> "/guix/scripts")
834 dirname)))
835
2b8cf44f
LC
836 (define dot-scm?
837 (cut string-suffix? ".scm" <>))
838
839 ;; In Guile 2.0.5 `scandir' would return "." and ".." regardless even though
840 ;; they don't match `dot-scm?'. Work around it by doing additional
841 ;; filtering.
e31ff8b8 842 (if directory
2b8cf44f 843 (filter dot-scm? (scandir directory dot-scm?))
e31ff8b8
LC
844 '()))
845
846(define (commands)
847 "Return the list of Guix command names."
848 (map (compose (cut string-drop-right <> 4)
849 basename)
850 (command-files)))
851
852(define (show-guix-help)
59f734f3 853 (define (internal? command)
2c74fde0 854 (member command '("substitute" "authenticate" "offload")))
59f734f3 855
e31ff8b8
LC
856 (format #t (_ "Usage: guix COMMAND ARGS...
857Run COMMAND with ARGS.\n"))
858 (newline)
859 (format #t (_ "COMMAND must be one of the sub-commands listed below:\n"))
860 (newline)
861 ;; TODO: Display a synopsis of each command.
59f734f3
LC
862 (format #t "~{ ~a~%~}" (sort (remove internal? (commands))
863 string<?))
e31ff8b8
LC
864 (show-bug-report-information))
865
a2011be5
LC
866(define program-name
867 ;; Name of the command-line program currently executing, or #f.
868 (make-parameter #f))
869
ec5d0a85
LC
870(define (run-guix-command command . args)
871 "Run COMMAND with the given ARGS. Report an error when COMMAND is not
872found."
873 (define module
874 (catch 'misc-error
875 (lambda ()
876 (resolve-interface `(guix scripts ,command)))
877 (lambda -
25c93676
LC
878 (format (current-error-port)
879 (_ "guix: ~a: command not found~%") command)
880 (show-guix-usage))))
ec5d0a85
LC
881
882 (let ((command-main (module-ref module
883 (symbol-append 'guix- command))))
884 (parameterize ((program-name command))
885 (apply command-main args))))
886
a2011be5
LC
887(define guix-warning-port
888 (make-parameter (current-warning-port)))
889
e49951eb
MW
890(define (guix-main arg0 . args)
891 (initialize-guix)
892 (let ()
893 (define (option? str) (string-prefix? "-" str))
894 (match args
25c93676
LC
895 (()
896 (format (current-error-port)
897 (_ "guix: missing command name~%"))
898 (show-guix-usage))
e12b3eb9 899 ((or ("-h") ("--help"))
25c93676
LC
900 (show-guix-help))
901 (("--version")
902 (show-version-and-exit "guix"))
903 (((? option? o) args ...)
904 (format (current-error-port)
905 (_ "guix: unrecognized option '~a'~%") o)
906 (show-guix-usage))
849eebbb
LC
907 (("help" args ...)
908 (show-guix-help))
e49951eb 909 ((command args ...)
ec5d0a85
LC
910 (apply run-guix-command
911 (string->symbol command)
912 args)))))
e49951eb 913
073c34d7 914;;; ui.scm ends here