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