guix package: Reuse FTP connections for subsequent `latest-release' calls.
[jackhill/guix/guix.git] / guix / ui.scm
CommitLineData
233e7676
LC
1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2012, 2013 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>
073c34d7 5;;;
233e7676 6;;; This file is part of GNU Guix.
073c34d7 7;;;
233e7676 8;;; GNU Guix is free software; you can redistribute it and/or modify it
073c34d7
LC
9;;; under the terms of the GNU General Public License as published by
10;;; the Free Software Foundation; either version 3 of the License, or (at
11;;; your option) any later version.
12;;;
233e7676 13;;; GNU Guix is distributed in the hope that it will be useful, but
073c34d7
LC
14;;; WITHOUT ANY WARRANTY; without even the implied warranty of
15;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16;;; GNU General Public License for more details.
17;;;
18;;; You should have received a copy of the GNU General Public License
233e7676 19;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
073c34d7
LC
20
21(define-module (guix ui)
22 #:use-module (guix utils)
23 #:use-module (guix store)
cdd5d6f9 24 #:use-module (guix config)
073c34d7 25 #:use-module (guix packages)
9b222abe 26 #:use-module (guix build-system)
9bb2b96a 27 #:use-module (guix derivations)
299112d3
LC
28 #:use-module ((guix licenses) #:select (license? license-name))
29 #:use-module (srfi srfi-1)
30 #:use-module (srfi srfi-11)
073c34d7
LC
31 #:use-module (srfi srfi-26)
32 #:use-module (srfi srfi-34)
a5975ced 33 #:use-module (srfi srfi-37)
e31ff8b8 34 #:autoload (ice-9 ftw) (scandir)
64fc89b6 35 #:use-module (ice-9 match)
9bb2b96a 36 #:use-module (ice-9 format)
073c34d7
LC
37 #:export (_
38 N_
39 leave
cdd5d6f9 40 show-version-and-exit
3441e164 41 show-bug-report-information
969e678e 42 string->number*
9bb2b96a 43 show-what-to-build
073c34d7 44 call-with-error-handling
64fc89b6 45 with-error-handling
eb0880e7 46 read/eval-package-expression
299112d3 47 location->string
c61b026e 48 switch-symlinks
f651b477 49 config-directory
299112d3
LC
50 fill-paragraph
51 string->recutils
e49951eb 52 package->recutils
a5975ced 53 args-fold*
e49951eb 54 run-guix-command
a2011be5
LC
55 program-name
56 guix-warning-port
57 warning
e49951eb 58 guix-main))
073c34d7
LC
59
60;;; Commentary:
61;;;
62;;; User interface facilities for command-line tools.
63;;;
64;;; Code:
65
66(define %gettext-domain
67 "guix")
68
69(define _ (cut gettext <> %gettext-domain))
70(define N_ (cut ngettext <> <> <> %gettext-domain))
71
b2a886f6
LC
72(define-syntax-rule (define-diagnostic name prefix)
73 "Create a diagnostic macro (i.e., NAME), which will prepend PREFIX to all
74messages."
75 (define-syntax name
76 (lambda (x)
77 (define (augmented-format-string fmt)
78 (string-append "~:[~*~;guix ~a: ~]~a" (syntax->datum fmt)))
79
89d02b98
LC
80 (syntax-case x ()
81 ((name (underscore fmt) args (... ...))
82 (and (string? (syntax->datum #'fmt))
83 (free-identifier=? #'underscore #'_))
b2a886f6
LC
84 (with-syntax ((fmt* (augmented-format-string #'fmt))
85 (prefix (datum->syntax x prefix)))
86 #'(format (guix-warning-port) (gettext fmt*)
87 (program-name) (program-name) prefix
88 args (... ...))))
89d02b98 89 ((name (N-underscore singular plural n) args (... ...))
b2a886f6 90 (and (string? (syntax->datum #'singular))
89d02b98
LC
91 (string? (syntax->datum #'plural))
92 (free-identifier=? #'N-underscore #'N_))
b2a886f6
LC
93 (with-syntax ((s (augmented-format-string #'singular))
94 (p (augmented-format-string #'plural))
95 (prefix (datum->syntax x prefix)))
96 #'(format (guix-warning-port)
97 (ngettext s p n %gettext-domain)
98 (program-name) (program-name) prefix
99 args (... ...))))))))
100
101(define-diagnostic warning "warning: ") ; emit a warning
102
103(define-diagnostic report-error "error: ")
104(define-syntax-rule (leave args ...)
105 "Emit an error message and exit."
106 (begin
107 (report-error args ...)
108 (exit 1)))
109
110(define (install-locale)
111 "Install the current locale settings."
112 (catch 'system-error
113 (lambda _
114 (setlocale LC_ALL ""))
115 (lambda args
116 (warning (_ "failed to install locale: ~a~%")
117 (strerror (system-error-errno args))))))
118
e49951eb 119(define (initialize-guix)
633f045f 120 "Perform the usual initialization for stand-alone Guix commands."
e49951eb
MW
121 (install-locale)
122 (textdomain "guix")
123 (setvbuf (current-output-port) _IOLBF)
124 (setvbuf (current-error-port) _IOLBF))
125
cdd5d6f9
LC
126(define* (show-version-and-exit #:optional (command (car (command-line))))
127 "Display version information for COMMAND and `(exit 0)'."
128 (simple-format #t "~a (~a) ~a~%"
129 command %guix-package-name %guix-version)
130 (exit 0))
131
3441e164
LC
132(define (show-bug-report-information)
133 (format #t (_ "
134Report bugs to: ~a.") %guix-bug-report-address)
135 (format #t (_ "
136~a home page: <~a>") %guix-package-name %guix-home-page-url)
137 (display (_ "
138General help using GNU software: <http://www.gnu.org/gethelp/>"))
139 (newline))
140
969e678e
LC
141(define (string->number* str)
142 "Like `string->number', but error out with an error message on failure."
143 (or (string->number str)
144 (leave (_ "~a: invalid number~%") str)))
145
073c34d7
LC
146(define (call-with-error-handling thunk)
147 "Call THUNK within a user-friendly error handler."
148 (guard (c ((package-input-error? c)
149 (let* ((package (package-error-package c))
150 (input (package-error-invalid-input c))
151 (location (package-location package))
152 (file (location-file location))
153 (line (location-line location))
154 (column (location-column location)))
98eb8cbe 155 (leave (_ "~a:~a:~a: package `~a' has an invalid input: ~s~%")
073c34d7
LC
156 file line column
157 (package-full-name package) input)))
9b222abe
LC
158 ((package-cross-build-system-error? c)
159 (let* ((package (package-error-package c))
160 (loc (package-location package))
161 (system (package-build-system package)))
162 (leave (_ "~a: ~a: build system `~a' does not support cross builds~%")
163 (location->string loc)
164 (package-full-name package)
165 (build-system-name system))))
ef86c39f 166 ((nix-connection-error? c)
98eb8cbe 167 (leave (_ "failed to connect to `~a': ~a~%")
ef86c39f
LC
168 (nix-connection-error-file c)
169 (strerror (nix-connection-error-code c))))
073c34d7
LC
170 ((nix-protocol-error? c)
171 ;; FIXME: Server-provided error messages aren't i18n'd.
98eb8cbe 172 (leave (_ "build failed: ~a~%")
073c34d7
LC
173 (nix-protocol-error-message c))))
174 (thunk)))
175
eb0880e7
LC
176(define (read/eval-package-expression str)
177 "Read and evaluate STR and return the package it refers to, or exit an
178error."
179 (let ((exp (catch #t
180 (lambda ()
181 (call-with-input-string str read))
182 (lambda args
183 (leave (_ "failed to read expression ~s: ~s~%")
184 str args)))))
185 (let ((p (catch #t
186 (lambda ()
187 (eval exp the-scm-module))
188 (lambda args
189 (leave (_ "failed to evaluate expression `~a': ~s~%")
190 exp args)))))
191 (if (package? p)
192 p
193 (leave (_ "expression `~s' does not evaluate to a package~%")
194 exp)))))
195
dd36b51b
LC
196(define* (show-what-to-build store drv
197 #:key dry-run? (use-substitutes? #t))
9bb2b96a 198 "Show what will or would (depending on DRY-RUN?) be built in realizing the
4d60610a 199derivations listed in DRV. Return #t if there's something to build, #f
dd36b51b
LC
200otherwise. When USE-SUBSTITUTES?, check and report what is prerequisites are
201available for download."
202 (let*-values (((build download)
203 (fold2 (lambda (drv-path build download)
204 (let ((drv (call-with-input-file drv-path
205 read-derivation)))
206 (let-values (((b d)
207 (derivation-prerequisites-to-build
208 store drv
209 #:use-substitutes?
210 use-substitutes?)))
211 (values (append b build)
212 (append d download)))))
213 '() '()
214 drv))
215 ((build) ; add the DRV themselves
216 (delete-duplicates
217 (append (remove (compose (lambda (out)
218 (or (valid-path? store out)
219 (and use-substitutes?
220 (has-substitutes? store
221 out))))
222 derivation-path->output-path)
223 drv)
224 (map derivation-input-path build))))
225 ((download) ; add the references of DOWNLOAD
1a8b7834
LC
226 (if use-substitutes?
227 (delete-duplicates
228 (append download
229 (remove (cut valid-path? store <>)
230 (append-map
231 substitutable-references
232 (substitutable-path-info store
233 download)))))
234 download)))
9bb2b96a 235 (if dry-run?
dd36b51b
LC
236 (begin
237 (format (current-error-port)
83e61a73
LC
238 (N_ "~:[The following derivation would be built:~%~{ ~a~%~}~;~]"
239 "~:[The following derivations would be built:~%~{ ~a~%~}~;~]"
dd36b51b
LC
240 (length build))
241 (null? build) build)
242 (format (current-error-port)
83e61a73
LC
243 (N_ "~:[The following file would be downloaded:~%~{ ~a~%~}~;~]"
244 "~:[The following files would be downloaded:~%~{ ~a~%~}~;~]"
dd36b51b
LC
245 (length download))
246 (null? download) download))
247 (begin
248 (format (current-error-port)
83e61a73
LC
249 (N_ "~:[The following derivation will be built:~%~{ ~a~%~}~;~]"
250 "~:[The following derivations will be built:~%~{ ~a~%~}~;~]"
dd36b51b
LC
251 (length build))
252 (null? build) build)
253 (format (current-error-port)
83e61a73
LC
254 (N_ "~:[The following file will be downloaded:~%~{ ~a~%~}~;~]"
255 "~:[The following files will be downloaded:~%~{ ~a~%~}~;~]"
dd36b51b
LC
256 (length download))
257 (null? download) download)))
258 (pair? build)))
9bb2b96a 259
073c34d7
LC
260(define-syntax with-error-handling
261 (syntax-rules ()
262 "Run BODY within a user-friendly error condition handler."
263 ((_ body ...)
264 (call-with-error-handling
265 (lambda ()
266 body ...)))))
267
64fc89b6
LC
268(define (location->string loc)
269 "Return a human-friendly, GNU-standard representation of LOC."
270 (match loc
271 (#f (_ "<unknown location>"))
272 (($ <location> file line column)
273 (format #f "~a:~a:~a" file line column))))
274
c61b026e
LC
275(define (switch-symlinks link target)
276 "Atomically switch LINK, a symbolic link, to point to TARGET. Works
277both when LINK already exists and when it does not."
278 (let ((pivot (string-append link ".new")))
279 (symlink target pivot)
280 (rename-file pivot link)))
281
f651b477
LC
282(define (config-directory)
283 "Return the name of the configuration directory, after making sure that it
284exists. Honor the XDG specs,
285<http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html>."
286 (let ((dir (and=> (or (getenv "XDG_CONFIG_HOME")
287 (and=> (getenv "HOME")
288 (cut string-append <> "/.config")))
289 (cut string-append <> "/guix"))))
290 (catch 'system-error
291 (lambda ()
292 (mkdir dir)
293 dir)
294 (lambda args
295 (match (system-error-errno args)
296 ((or EEXIST 0)
297 dir)
298 (err
299 (leave (_ "failed to create configuration directory `~a': ~a~%")
300 dir (strerror err))))))))
301
299112d3
LC
302(define* (fill-paragraph str width #:optional (column 0))
303 "Fill STR such that each line contains at most WIDTH characters, assuming
304that the first character is at COLUMN.
305
306When STR contains a single line break surrounded by other characters, it is
307converted to a space; sequences of more than one line break are preserved."
308 (define (maybe-break chr result)
309 (match result
310 ((column newlines chars)
311 (case chr
312 ((#\newline)
313 `(,column ,(+ 1 newlines) ,chars))
314 (else
315 (let ((chars (case newlines
316 ((0) chars)
317 ((1) (cons #\space chars))
318 (else
319 (append (make-list newlines #\newline) chars))))
320 (column (case newlines
321 ((0) column)
322 ((1) (+ 1 column))
323 (else 0))))
324 (let ((chars (cons chr chars))
325 (column (+ 1 column)))
326 (if (> column width)
327 (let*-values (((before after)
328 (break (cut eqv? #\space <>) chars))
329 ((len)
330 (length before)))
331 (if (<= len width)
332 `(,len
333 0
334 ,(if (null? after)
335 before
336 (append before (cons #\newline (cdr after)))))
337 `(,column 0 ,chars))) ; unbreakable
338 `(,column 0 ,chars)))))))))
339
340 (match (string-fold maybe-break
341 `(,column 0 ())
342 str)
343 ((_ _ chars)
344 (list->string (reverse chars)))))
345
346(define (string->recutils str)
347 "Return a version of STR where newlines have been replaced by newlines
348followed by \"+ \", which makes for a valid multi-line field value in the
349`recutils' syntax."
350 (list->string
351 (string-fold-right (lambda (chr result)
352 (if (eqv? chr #\newline)
353 (cons* chr #\+ #\space result)
354 (cons chr result)))
355 '()
356 str)))
357
358(define* (package->recutils p port
359 #:optional (width (or (and=> (getenv "WIDTH")
360 string->number)
361 80)))
362 "Write to PORT a `recutils' record of package P, arranging to fit within
363WIDTH columns."
364 (define (description->recutils str)
365 (let ((str (_ str)))
366 (string->recutils
367 (fill-paragraph str width
368 (string-length "description: ")))))
369
370 ;; Note: Don't i18n field names so that people can post-process it.
371 (format port "name: ~a~%" (package-name p))
372 (format port "version: ~a~%" (package-version p))
373 (format port "location: ~a~%"
374 (or (and=> (package-location p) location->string)
375 (_ "unknown")))
376 (format port "home-page: ~a~%" (package-home-page p))
377 (format port "license: ~a~%"
378 (match (package-license p)
379 (((? license? licenses) ...)
380 (string-join (map license-name licenses)
381 ", "))
382 ((? license? license)
383 (license-name license))
384 (x
385 (_ "unknown"))))
386 (format port "synopsis: ~a~%"
387 (string-map (match-lambda
388 (#\newline #\space)
389 (chr chr))
390 (or (and=> (package-synopsis p) _)
391 "")))
392 (format port "description: ~a~%"
393 (and=> (package-description p) description->recutils))
394 (newline port))
395
a5975ced
LC
396(define (args-fold* options unrecognized-option-proc operand-proc . seeds)
397 "A wrapper on top of `args-fold' that does proper user-facing error
398reporting."
399 (catch 'misc-error
400 (lambda ()
401 (apply args-fold options unrecognized-option-proc
402 operand-proc seeds))
403 (lambda (key proc msg args . rest)
404 ;; XXX: MSG is not i18n'd.
405 (leave (_ "invalid argument: ~a~%")
406 (apply format #f msg args)))))
407
e49951eb 408(define (show-guix-usage)
e49951eb 409 (format (current-error-port)
25c93676
LC
410 (_ "Try `guix --help' for more information.~%"))
411 (exit 1))
e49951eb 412
e31ff8b8
LC
413(define (command-files)
414 "Return the list of source files that define Guix sub-commands."
415 (define directory
416 (and=> (search-path %load-path "guix.scm")
417 (compose (cut string-append <> "/guix/scripts")
418 dirname)))
419
2b8cf44f
LC
420 (define dot-scm?
421 (cut string-suffix? ".scm" <>))
422
423 ;; In Guile 2.0.5 `scandir' would return "." and ".." regardless even though
424 ;; they don't match `dot-scm?'. Work around it by doing additional
425 ;; filtering.
e31ff8b8 426 (if directory
2b8cf44f 427 (filter dot-scm? (scandir directory dot-scm?))
e31ff8b8
LC
428 '()))
429
430(define (commands)
431 "Return the list of Guix command names."
432 (map (compose (cut string-drop-right <> 4)
433 basename)
434 (command-files)))
435
436(define (show-guix-help)
437 (format #t (_ "Usage: guix COMMAND ARGS...
438Run COMMAND with ARGS.\n"))
439 (newline)
440 (format #t (_ "COMMAND must be one of the sub-commands listed below:\n"))
441 (newline)
442 ;; TODO: Display a synopsis of each command.
b30b13dc 443 (format #t "~{ ~a~%~}" (sort (commands) string<?))
e31ff8b8
LC
444 (show-bug-report-information))
445
a2011be5
LC
446(define program-name
447 ;; Name of the command-line program currently executing, or #f.
448 (make-parameter #f))
449
ec5d0a85
LC
450(define (run-guix-command command . args)
451 "Run COMMAND with the given ARGS. Report an error when COMMAND is not
452found."
453 (define module
454 (catch 'misc-error
455 (lambda ()
456 (resolve-interface `(guix scripts ,command)))
457 (lambda -
25c93676
LC
458 (format (current-error-port)
459 (_ "guix: ~a: command not found~%") command)
460 (show-guix-usage))))
ec5d0a85
LC
461
462 (let ((command-main (module-ref module
463 (symbol-append 'guix- command))))
464 (parameterize ((program-name command))
465 (apply command-main args))))
466
a2011be5
LC
467(define guix-warning-port
468 (make-parameter (current-warning-port)))
469
e49951eb
MW
470(define (guix-main arg0 . args)
471 (initialize-guix)
472 (let ()
473 (define (option? str) (string-prefix? "-" str))
474 (match args
25c93676
LC
475 (()
476 (format (current-error-port)
477 (_ "guix: missing command name~%"))
478 (show-guix-usage))
479 (("--help")
480 (show-guix-help))
481 (("--version")
482 (show-version-and-exit "guix"))
483 (((? option? o) args ...)
484 (format (current-error-port)
485 (_ "guix: unrecognized option '~a'~%") o)
486 (show-guix-usage))
e49951eb 487 ((command args ...)
ec5d0a85
LC
488 (apply run-guix-command
489 (string->symbol command)
490 args)))))
e49951eb 491
073c34d7 492;;; ui.scm ends here