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