hydra: Pass the directory name of the Nix checkout.
[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")
e14c3929
LC
123
124 ;; Ignore SIGPIPE. If the daemon closes the connection, we prefer to be
125 ;; notified via an EPIPE later.
126 (sigaction SIGPIPE SIG_IGN)
127
e49951eb
MW
128 (setvbuf (current-output-port) _IOLBF)
129 (setvbuf (current-error-port) _IOLBF))
130
cdd5d6f9
LC
131(define* (show-version-and-exit #:optional (command (car (command-line))))
132 "Display version information for COMMAND and `(exit 0)'."
133 (simple-format #t "~a (~a) ~a~%"
134 command %guix-package-name %guix-version)
135 (exit 0))
136
3441e164
LC
137(define (show-bug-report-information)
138 (format #t (_ "
139Report bugs to: ~a.") %guix-bug-report-address)
140 (format #t (_ "
141~a home page: <~a>") %guix-package-name %guix-home-page-url)
142 (display (_ "
143General help using GNU software: <http://www.gnu.org/gethelp/>"))
144 (newline))
145
969e678e
LC
146(define (string->number* str)
147 "Like `string->number', but error out with an error message on failure."
148 (or (string->number str)
149 (leave (_ "~a: invalid number~%") str)))
150
073c34d7
LC
151(define (call-with-error-handling thunk)
152 "Call THUNK within a user-friendly error handler."
153 (guard (c ((package-input-error? c)
154 (let* ((package (package-error-package c))
155 (input (package-error-invalid-input c))
156 (location (package-location package))
157 (file (location-file location))
158 (line (location-line location))
159 (column (location-column location)))
98eb8cbe 160 (leave (_ "~a:~a:~a: package `~a' has an invalid input: ~s~%")
073c34d7
LC
161 file line column
162 (package-full-name package) input)))
9b222abe
LC
163 ((package-cross-build-system-error? c)
164 (let* ((package (package-error-package c))
165 (loc (package-location package))
166 (system (package-build-system package)))
167 (leave (_ "~a: ~a: build system `~a' does not support cross builds~%")
168 (location->string loc)
169 (package-full-name package)
170 (build-system-name system))))
ef86c39f 171 ((nix-connection-error? c)
98eb8cbe 172 (leave (_ "failed to connect to `~a': ~a~%")
ef86c39f
LC
173 (nix-connection-error-file c)
174 (strerror (nix-connection-error-code c))))
073c34d7
LC
175 ((nix-protocol-error? c)
176 ;; FIXME: Server-provided error messages aren't i18n'd.
98eb8cbe 177 (leave (_ "build failed: ~a~%")
073c34d7 178 (nix-protocol-error-message c))))
e14c3929
LC
179 ;; Catch EPIPE and the likes.
180 (catch 'system-error
181 thunk
182 (lambda args
183 (leave (_ "~a~%")
184 (strerror (system-error-errno args)))))))
073c34d7 185
eb0880e7
LC
186(define (read/eval-package-expression str)
187 "Read and evaluate STR and return the package it refers to, or exit an
188error."
189 (let ((exp (catch #t
190 (lambda ()
191 (call-with-input-string str read))
192 (lambda args
193 (leave (_ "failed to read expression ~s: ~s~%")
194 str args)))))
195 (let ((p (catch #t
196 (lambda ()
197 (eval exp the-scm-module))
198 (lambda args
199 (leave (_ "failed to evaluate expression `~a': ~s~%")
200 exp args)))))
201 (if (package? p)
202 p
203 (leave (_ "expression `~s' does not evaluate to a package~%")
204 exp)))))
205
dd36b51b
LC
206(define* (show-what-to-build store drv
207 #:key dry-run? (use-substitutes? #t))
9bb2b96a 208 "Show what will or would (depending on DRY-RUN?) be built in realizing the
4d60610a 209derivations listed in DRV. Return #t if there's something to build, #f
dd36b51b
LC
210otherwise. When USE-SUBSTITUTES?, check and report what is prerequisites are
211available for download."
212 (let*-values (((build download)
59688fc4
LC
213 (fold2 (lambda (drv build download)
214 (let-values (((b d)
215 (derivation-prerequisites-to-build
216 store drv
217 #:use-substitutes?
218 use-substitutes?)))
219 (values (append b build)
220 (append d download))))
dd36b51b
LC
221 '() '()
222 drv))
223 ((build) ; add the DRV themselves
224 (delete-duplicates
59688fc4
LC
225 (append (map derivation-file-name
226 (remove (lambda (drv)
227 (let ((out (derivation->output-path
228 drv)))
229 (or (valid-path? store out)
230 (and use-substitutes?
231 (has-substitutes? store
232 out)))))
233 drv))
dd36b51b
LC
234 (map derivation-input-path build))))
235 ((download) ; add the references of DOWNLOAD
1a8b7834
LC
236 (if use-substitutes?
237 (delete-duplicates
238 (append download
239 (remove (cut valid-path? store <>)
240 (append-map
241 substitutable-references
242 (substitutable-path-info store
243 download)))))
244 download)))
00554b2a 245 ;; TODO: Show the installed size of DOWNLOAD.
9bb2b96a 246 (if dry-run?
dd36b51b
LC
247 (begin
248 (format (current-error-port)
83e61a73
LC
249 (N_ "~:[The following derivation would be built:~%~{ ~a~%~}~;~]"
250 "~:[The following derivations would be built:~%~{ ~a~%~}~;~]"
dd36b51b
LC
251 (length build))
252 (null? build) build)
253 (format (current-error-port)
83e61a73
LC
254 (N_ "~:[The following file would be downloaded:~%~{ ~a~%~}~;~]"
255 "~:[The following files would be downloaded:~%~{ ~a~%~}~;~]"
dd36b51b
LC
256 (length download))
257 (null? download) download))
258 (begin
259 (format (current-error-port)
83e61a73
LC
260 (N_ "~:[The following derivation will be built:~%~{ ~a~%~}~;~]"
261 "~:[The following derivations will be built:~%~{ ~a~%~}~;~]"
dd36b51b
LC
262 (length build))
263 (null? build) build)
264 (format (current-error-port)
83e61a73
LC
265 (N_ "~:[The following file will be downloaded:~%~{ ~a~%~}~;~]"
266 "~:[The following files will be downloaded:~%~{ ~a~%~}~;~]"
dd36b51b
LC
267 (length download))
268 (null? download) download)))
269 (pair? build)))
9bb2b96a 270
073c34d7
LC
271(define-syntax with-error-handling
272 (syntax-rules ()
273 "Run BODY within a user-friendly error condition handler."
274 ((_ body ...)
275 (call-with-error-handling
276 (lambda ()
277 body ...)))))
278
64fc89b6
LC
279(define (location->string loc)
280 "Return a human-friendly, GNU-standard representation of LOC."
281 (match loc
282 (#f (_ "<unknown location>"))
283 (($ <location> file line column)
284 (format #f "~a:~a:~a" file line column))))
285
c61b026e
LC
286(define (switch-symlinks link target)
287 "Atomically switch LINK, a symbolic link, to point to TARGET. Works
288both when LINK already exists and when it does not."
289 (let ((pivot (string-append link ".new")))
290 (symlink target pivot)
291 (rename-file pivot link)))
292
f651b477
LC
293(define (config-directory)
294 "Return the name of the configuration directory, after making sure that it
295exists. Honor the XDG specs,
296<http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html>."
297 (let ((dir (and=> (or (getenv "XDG_CONFIG_HOME")
298 (and=> (getenv "HOME")
299 (cut string-append <> "/.config")))
300 (cut string-append <> "/guix"))))
301 (catch 'system-error
302 (lambda ()
303 (mkdir dir)
304 dir)
305 (lambda args
306 (match (system-error-errno args)
307 ((or EEXIST 0)
308 dir)
309 (err
310 (leave (_ "failed to create configuration directory `~a': ~a~%")
311 dir (strerror err))))))))
312
299112d3
LC
313(define* (fill-paragraph str width #:optional (column 0))
314 "Fill STR such that each line contains at most WIDTH characters, assuming
315that the first character is at COLUMN.
316
317When STR contains a single line break surrounded by other characters, it is
318converted to a space; sequences of more than one line break are preserved."
319 (define (maybe-break chr result)
320 (match result
321 ((column newlines chars)
322 (case chr
323 ((#\newline)
324 `(,column ,(+ 1 newlines) ,chars))
325 (else
326 (let ((chars (case newlines
327 ((0) chars)
328 ((1) (cons #\space chars))
329 (else
330 (append (make-list newlines #\newline) chars))))
331 (column (case newlines
332 ((0) column)
333 ((1) (+ 1 column))
334 (else 0))))
335 (let ((chars (cons chr chars))
336 (column (+ 1 column)))
337 (if (> column width)
338 (let*-values (((before after)
339 (break (cut eqv? #\space <>) chars))
340 ((len)
341 (length before)))
342 (if (<= len width)
343 `(,len
344 0
345 ,(if (null? after)
346 before
347 (append before (cons #\newline (cdr after)))))
348 `(,column 0 ,chars))) ; unbreakable
349 `(,column 0 ,chars)))))))))
350
351 (match (string-fold maybe-break
352 `(,column 0 ())
353 str)
354 ((_ _ chars)
355 (list->string (reverse chars)))))
356
357(define (string->recutils str)
358 "Return a version of STR where newlines have been replaced by newlines
359followed by \"+ \", which makes for a valid multi-line field value in the
360`recutils' syntax."
361 (list->string
362 (string-fold-right (lambda (chr result)
363 (if (eqv? chr #\newline)
364 (cons* chr #\+ #\space result)
365 (cons chr result)))
366 '()
367 str)))
368
369(define* (package->recutils p port
370 #:optional (width (or (and=> (getenv "WIDTH")
371 string->number)
372 80)))
373 "Write to PORT a `recutils' record of package P, arranging to fit within
374WIDTH columns."
375 (define (description->recutils str)
376 (let ((str (_ str)))
377 (string->recutils
378 (fill-paragraph str width
379 (string-length "description: ")))))
380
381 ;; Note: Don't i18n field names so that people can post-process it.
382 (format port "name: ~a~%" (package-name p))
383 (format port "version: ~a~%" (package-version p))
384 (format port "location: ~a~%"
385 (or (and=> (package-location p) location->string)
386 (_ "unknown")))
387 (format port "home-page: ~a~%" (package-home-page p))
388 (format port "license: ~a~%"
389 (match (package-license p)
390 (((? license? licenses) ...)
391 (string-join (map license-name licenses)
392 ", "))
393 ((? license? license)
394 (license-name license))
395 (x
396 (_ "unknown"))))
397 (format port "synopsis: ~a~%"
398 (string-map (match-lambda
399 (#\newline #\space)
400 (chr chr))
401 (or (and=> (package-synopsis p) _)
402 "")))
403 (format port "description: ~a~%"
404 (and=> (package-description p) description->recutils))
405 (newline port))
406
a5975ced
LC
407(define (args-fold* options unrecognized-option-proc operand-proc . seeds)
408 "A wrapper on top of `args-fold' that does proper user-facing error
409reporting."
410 (catch 'misc-error
411 (lambda ()
412 (apply args-fold options unrecognized-option-proc
413 operand-proc seeds))
414 (lambda (key proc msg args . rest)
415 ;; XXX: MSG is not i18n'd.
416 (leave (_ "invalid argument: ~a~%")
417 (apply format #f msg args)))))
418
e49951eb 419(define (show-guix-usage)
e49951eb 420 (format (current-error-port)
25c93676
LC
421 (_ "Try `guix --help' for more information.~%"))
422 (exit 1))
e49951eb 423
e31ff8b8
LC
424(define (command-files)
425 "Return the list of source files that define Guix sub-commands."
426 (define directory
427 (and=> (search-path %load-path "guix.scm")
428 (compose (cut string-append <> "/guix/scripts")
429 dirname)))
430
2b8cf44f
LC
431 (define dot-scm?
432 (cut string-suffix? ".scm" <>))
433
434 ;; In Guile 2.0.5 `scandir' would return "." and ".." regardless even though
435 ;; they don't match `dot-scm?'. Work around it by doing additional
436 ;; filtering.
e31ff8b8 437 (if directory
2b8cf44f 438 (filter dot-scm? (scandir directory dot-scm?))
e31ff8b8
LC
439 '()))
440
441(define (commands)
442 "Return the list of Guix command names."
443 (map (compose (cut string-drop-right <> 4)
444 basename)
445 (command-files)))
446
447(define (show-guix-help)
448 (format #t (_ "Usage: guix COMMAND ARGS...
449Run COMMAND with ARGS.\n"))
450 (newline)
451 (format #t (_ "COMMAND must be one of the sub-commands listed below:\n"))
452 (newline)
453 ;; TODO: Display a synopsis of each command.
b30b13dc 454 (format #t "~{ ~a~%~}" (sort (commands) string<?))
e31ff8b8
LC
455 (show-bug-report-information))
456
a2011be5
LC
457(define program-name
458 ;; Name of the command-line program currently executing, or #f.
459 (make-parameter #f))
460
ec5d0a85
LC
461(define (run-guix-command command . args)
462 "Run COMMAND with the given ARGS. Report an error when COMMAND is not
463found."
464 (define module
465 (catch 'misc-error
466 (lambda ()
467 (resolve-interface `(guix scripts ,command)))
468 (lambda -
25c93676
LC
469 (format (current-error-port)
470 (_ "guix: ~a: command not found~%") command)
471 (show-guix-usage))))
ec5d0a85
LC
472
473 (let ((command-main (module-ref module
474 (symbol-append 'guix- command))))
475 (parameterize ((program-name command))
476 (apply command-main args))))
477
a2011be5
LC
478(define guix-warning-port
479 (make-parameter (current-warning-port)))
480
e49951eb
MW
481(define (guix-main arg0 . args)
482 (initialize-guix)
483 (let ()
484 (define (option? str) (string-prefix? "-" str))
485 (match args
25c93676
LC
486 (()
487 (format (current-error-port)
488 (_ "guix: missing command name~%"))
489 (show-guix-usage))
e12b3eb9 490 ((or ("-h") ("--help"))
25c93676
LC
491 (show-guix-help))
492 (("--version")
493 (show-version-and-exit "guix"))
494 (((? option? o) args ...)
495 (format (current-error-port)
496 (_ "guix: unrecognized option '~a'~%") o)
497 (show-guix-usage))
e49951eb 498 ((command args ...)
ec5d0a85
LC
499 (apply run-guix-command
500 (string->symbol command)
501 args)))))
e49951eb 502
073c34d7 503;;; ui.scm ends here