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