gnu: pinentry-qt: Build with qtbase.
[jackhill/guix/guix.git] / emacs / guix-ui-package.el
1 ;;; guix-ui-package.el --- Interface for displaying packages -*- lexical-binding: t -*-
2
3 ;; Copyright © 2014, 2015, 2016 Alex Kost <alezost@gmail.com>
4
5 ;; This file is part of GNU Guix.
6
7 ;; GNU Guix is free software; you can redistribute it and/or modify
8 ;; it under the terms of the GNU General Public License as published by
9 ;; the Free Software Foundation, either version 3 of the License, or
10 ;; (at your option) any later version.
11
12 ;; GNU Guix is distributed in the hope that it will be useful,
13 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 ;; GNU General Public License for more details.
16
17 ;; You should have received a copy of the GNU General Public License
18 ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20 ;;; Commentary:
21
22 ;; This file provides an interface for displaying packages and outputs
23 ;; in 'list' and 'info' buffers, and commands for working with them.
24
25 ;;; Code:
26
27 (require 'cl-lib)
28 (require 'guix-buffer)
29 (require 'guix-list)
30 (require 'guix-info)
31 (require 'guix-ui)
32 (require 'guix-base)
33 (require 'guix-backend)
34 (require 'guix-guile)
35 (require 'guix-entry)
36 (require 'guix-utils)
37 (require 'guix-hydra)
38 (require 'guix-hydra-build)
39 (require 'guix-read)
40 (require 'guix-license)
41 (require 'guix-location)
42 (require 'guix-profiles)
43
44 (guix-ui-define-entry-type package)
45 (guix-ui-define-entry-type output)
46
47 (defcustom guix-package-list-type 'output
48 "Define how to display packages in 'list' buffer.
49 Should be a symbol `package' or `output' (if `output', display each
50 output on a separate line; if `package', display each package on
51 a separate line)."
52 :type '(choice (const :tag "List of packages" package)
53 (const :tag "List of outputs" output))
54 :group 'guix-package)
55
56 (defcustom guix-package-info-type 'package
57 "Define how to display packages in 'info' buffer.
58 Should be a symbol `package' or `output' (if `output', display
59 each output separately; if `package', display outputs inside
60 package data)."
61 :type '(choice (const :tag "Display packages" package)
62 (const :tag "Display outputs" output))
63 :group 'guix-package)
64
65 (defun guix-package-get-display (profile search-type &rest search-values)
66 "Search for packages/outputs and show results.
67
68 If PROFILE is nil, use `guix-current-profile'.
69
70 See `guix-ui-get-entries' for the meaning of SEARCH-TYPE and
71 SEARCH-VALUES.
72
73 Results are displayed in the list buffer, unless a single package
74 is found and `guix-package-list-single' is nil."
75 (let* ((args (cl-list* (or profile guix-current-profile)
76 search-type search-values))
77 (entries (guix-buffer-get-entries
78 'list guix-package-list-type args)))
79 (if (or guix-package-list-single
80 (null entries)
81 (cdr entries))
82 (guix-buffer-display-entries
83 entries 'list guix-package-list-type args 'add)
84 (guix-buffer-get-display-entries
85 'info guix-package-info-type args 'add))))
86
87 (defun guix-package-entry->name-specification (entry &optional output)
88 "Return name specification of the package ENTRY and OUTPUT."
89 (guix-package-name-specification
90 (guix-entry-value entry 'name)
91 (guix-entry-value entry 'version)
92 (or output (guix-entry-value entry 'output))))
93
94 (defun guix-package-entries->name-specifications (entries)
95 "Return name specifications by the package or output ENTRIES."
96 (cl-remove-duplicates (mapcar #'guix-package-entry->name-specification
97 entries)
98 :test #'string=))
99
100 (defun guix-package-installed-outputs (entry)
101 "Return a list of installed outputs for the package ENTRY."
102 (mapcar (lambda (installed-entry)
103 (guix-entry-value installed-entry 'output))
104 (guix-entry-value entry 'installed)))
105
106 (defun guix-package-id-and-output-by-output-id (output-id)
107 "Return a list (PACKAGE-ID OUTPUT) by OUTPUT-ID."
108 (cl-multiple-value-bind (package-id-str output)
109 (split-string output-id ":")
110 (let ((package-id (string-to-number package-id-str)))
111 (list (if (= 0 package-id) package-id-str package-id)
112 output))))
113
114 (defun guix-package-build-log-file (id)
115 "Return build log file name of a package defined by ID."
116 (guix-eval-read
117 (guix-make-guile-expression 'package-build-log-file id)))
118
119 (defun guix-package-find-build-log (id)
120 "Show build log of a package defined by ID."
121 (require 'guix-build-log)
122 (let ((file (guix-package-build-log-file id)))
123 (if file
124 (guix-build-log-find-file file)
125 (message "Couldn't find the package build log."))))
126
127 \f
128 ;;; Processing package actions
129
130 (defun guix-process-package-actions (profile actions
131 &optional operation-buffer)
132 "Process package ACTIONS on PROFILE.
133 Each action is a list of the form:
134
135 (ACTION-TYPE PACKAGE-SPEC ...)
136
137 ACTION-TYPE is one of the following symbols: `install',
138 `upgrade', `remove'/`delete'.
139 PACKAGE-SPEC should have the following form: (ID [OUTPUT] ...)."
140 (let (install upgrade remove)
141 (mapc (lambda (action)
142 (let ((action-type (car action))
143 (specs (cdr action)))
144 (cl-case action-type
145 (install (setq install (append install specs)))
146 (upgrade (setq upgrade (append upgrade specs)))
147 ((remove delete) (setq remove (append remove specs))))))
148 actions)
149 (when (guix-continue-package-operation-p
150 profile
151 :install install :upgrade upgrade :remove remove)
152 (guix-eval-in-repl
153 (guix-make-guile-expression
154 'process-package-actions profile
155 :install install :upgrade upgrade :remove remove
156 :use-substitutes? (or guix-use-substitutes 'f)
157 :dry-run? (or guix-dry-run 'f))
158 (and (not guix-dry-run) operation-buffer)))))
159
160 (cl-defun guix-continue-package-operation-p (profile
161 &key install upgrade remove)
162 "Return non-nil if a package operation should be continued.
163 Ask a user if needed (see `guix-operation-confirm').
164 INSTALL, UPGRADE, REMOVE are 'package action specifications'.
165 See `guix-process-package-actions' for details."
166 (or (null guix-operation-confirm)
167 (let* ((entries (guix-ui-get-entries
168 profile 'package 'id
169 (append (mapcar #'car install)
170 (mapcar #'car upgrade)
171 (mapcar #'car remove))
172 '(id name version location)))
173 (install-strings (guix-get-package-strings install entries))
174 (upgrade-strings (guix-get-package-strings upgrade entries))
175 (remove-strings (guix-get-package-strings remove entries)))
176 (if (or install-strings upgrade-strings remove-strings)
177 (let ((buf (get-buffer-create guix-temp-buffer-name)))
178 (with-current-buffer buf
179 (setq-local cursor-type nil)
180 (setq buffer-read-only nil)
181 (erase-buffer)
182 (insert "Profile: " profile "\n\n")
183 (guix-insert-package-strings install-strings "install")
184 (guix-insert-package-strings upgrade-strings "upgrade")
185 (guix-insert-package-strings remove-strings "remove")
186 (let ((win (temp-buffer-window-show
187 buf
188 '((display-buffer-reuse-window
189 display-buffer-at-bottom)
190 (window-height . fit-window-to-buffer)))))
191 (prog1 (guix-operation-prompt)
192 (quit-window nil win)))))
193 (message "Nothing to be done.
194 If Guix REPL was restarted, the data is not up-to-date.")
195 nil))))
196
197 (defun guix-get-package-strings (specs entries)
198 "Return short package descriptions for performing package actions.
199 See `guix-process-package-actions' for the meaning of SPECS.
200 ENTRIES is a list of package entries to get info about packages."
201 (delq nil
202 (mapcar
203 (lambda (spec)
204 (let* ((id (car spec))
205 (outputs (cdr spec))
206 (entry (guix-entry-by-id id entries)))
207 (when entry
208 (let ((location (guix-entry-value entry 'location)))
209 (concat (guix-package-entry->name-specification entry)
210 (when outputs
211 (concat ":"
212 (guix-concat-strings outputs ",")))
213 (when location
214 (concat "\t(" location ")")))))))
215 specs)))
216
217 (defun guix-insert-package-strings (strings action)
218 "Insert information STRINGS at point for performing package ACTION."
219 (when strings
220 (insert "Package(s) to " (propertize action 'face 'bold) ":\n")
221 (mapc (lambda (str)
222 (insert " " str "\n"))
223 strings)
224 (insert "\n")))
225
226 \f
227 ;;; Package 'info'
228
229 (guix-ui-info-define-interface package
230 :buffer-name "*Guix Package Info*"
231 :format '(guix-package-info-insert-heading
232 ignore
233 (synopsis ignore (simple guix-package-info-synopsis))
234 ignore
235 (description ignore (simple guix-package-info-description))
236 ignore
237 (outputs simple guix-package-info-insert-outputs)
238 guix-package-info-insert-misc
239 (source simple guix-package-info-insert-source)
240 (location simple guix-package-info-insert-location)
241 (home-url format (format guix-url))
242 (license format (format guix-package-license))
243 (systems format guix-package-info-insert-systems)
244 (inputs format (format guix-package-input))
245 (native-inputs format (format guix-package-native-input))
246 (propagated-inputs format
247 (format guix-package-propagated-input)))
248 :titles '((home-url . "Home page")
249 (systems . "Supported systems"))
250 :required '(id name version installed non-unique))
251
252 (guix-info-define-interface installed-output
253 :format '((path simple (indent guix-file))
254 (dependencies simple (indent guix-file)))
255 :titles '((path . "Store directory"))
256 :reduced? t)
257
258 (defface guix-package-info-heading
259 '((t :inherit guix-info-heading))
260 "Face for package name and version headings."
261 :group 'guix-package-info-faces)
262
263 (defface guix-package-info-name
264 '((t :inherit font-lock-keyword-face))
265 "Face used for a name of a package."
266 :group 'guix-package-info-faces)
267
268 (defface guix-package-info-name-button
269 '((t :inherit button))
270 "Face used for a full name that can be used to describe a package."
271 :group 'guix-package-info-faces)
272
273 (defface guix-package-info-version
274 '((t :inherit font-lock-builtin-face))
275 "Face used for a version of a package."
276 :group 'guix-package-info-faces)
277
278 (defface guix-package-info-synopsis
279 '((((type tty pc) (class color)) :weight bold)
280 (t :height 1.1 :weight bold :inherit variable-pitch))
281 "Face used for a synopsis of a package."
282 :group 'guix-package-info-faces)
283
284 (defface guix-package-info-description
285 '((t))
286 "Face used for a description of a package."
287 :group 'guix-package-info-faces)
288
289 (defface guix-package-info-license
290 '((t :inherit font-lock-string-face))
291 "Face used for a license of a package."
292 :group 'guix-package-info-faces)
293
294 (defface guix-package-info-location
295 '((t :inherit link))
296 "Face used for a location of a package."
297 :group 'guix-package-info-faces)
298
299 (defface guix-package-info-source
300 '((t :inherit link :underline nil))
301 "Face used for a source URL of a package."
302 :group 'guix-package-info-faces)
303
304 (defface guix-package-info-installed-outputs
305 '((default :weight bold)
306 (((class color) (min-colors 88) (background light))
307 :foreground "ForestGreen")
308 (((class color) (min-colors 88) (background dark))
309 :foreground "PaleGreen")
310 (((class color) (min-colors 8))
311 :foreground "green")
312 (t :underline t))
313 "Face used for installed outputs of a package."
314 :group 'guix-package-info-faces)
315
316 (defface guix-package-info-uninstalled-outputs
317 '((t :weight bold))
318 "Face used for uninstalled outputs of a package."
319 :group 'guix-package-info-faces)
320
321 (defface guix-package-info-obsolete
322 '((t :inherit error))
323 "Face used if a package is obsolete."
324 :group 'guix-package-info-faces)
325
326 (defcustom guix-package-info-auto-find-package t
327 "If non-nil, open store directory after pressing \"Show\" package button.
328 If nil, just display the store directory (or directories) without finding."
329 :type 'boolean
330 :group 'guix-package-info)
331
332 (defcustom guix-package-info-auto-find-source nil
333 "If non-nil, open source file after pressing \"Show\" source button.
334 If nil, just display the source file name without finding."
335 :type 'boolean
336 :group 'guix-package-info)
337
338 (defcustom guix-package-info-auto-download-source t
339 "If nil, do not automatically download a source file if it doesn't exist.
340 After pressing a \"Show\" button, a derivation of the package
341 source is calculated and a store file path is displayed. If this
342 variable is non-nil and the source file does not exist in the
343 store, it will be automatically downloaded (with a possible
344 prompt depending on `guix-operation-confirm' variable)."
345 :type 'boolean
346 :group 'guix-package-info)
347
348 (defcustom guix-package-info-button-functions
349 '(guix-package-info-insert-build-button
350 guix-package-info-insert-build-log-button)
351 "List of functions used to insert package buttons in Info buffer.
352 Each function is called with 2 arguments: package ID and full name."
353 :type '(repeat function)
354 :group 'guix-package-info)
355
356 (defvar guix-package-info-download-buffer nil
357 "Buffer from which a current download operation was performed.")
358
359 (defvar guix-package-info-output-format "%-10s"
360 "String used to format output names of the packages.
361 It should be a '%s'-sequence. After inserting an output name
362 formatted with this string, an action button is inserted.")
363
364 (defvar guix-package-info-obsolete-string "(This package is obsolete)"
365 "String used if a package is obsolete.")
366
367 (define-button-type 'guix-package-location
368 :supertype 'guix
369 'face 'guix-package-info-location
370 'help-echo "Find location of this package"
371 'action (lambda (btn)
372 (guix-find-location (button-label btn))))
373
374 (define-button-type 'guix-package-license
375 :supertype 'guix
376 'face 'guix-package-info-license
377 'help-echo "Display license info"
378 'action (lambda (btn)
379 (require 'guix-ui-license)
380 (guix-buffer-get-display-entries
381 'info 'license
382 (list 'name (button-label btn))
383 'add)))
384
385 (define-button-type 'guix-package-name
386 :supertype 'guix
387 'face 'guix-package-info-name-button
388 'help-echo "Describe this package"
389 'action (lambda (btn)
390 (guix-buffer-get-display-entries-current
391 'info guix-package-info-type
392 (list (guix-ui-current-profile)
393 'name (or (button-get btn 'spec)
394 (button-label btn)))
395 'add)))
396
397 (define-button-type 'guix-package-heading
398 :supertype 'guix-package-name
399 'face 'guix-package-info-heading)
400
401 (define-button-type 'guix-package-source
402 :supertype 'guix
403 'face 'guix-package-info-source
404 'help-echo ""
405 'action (lambda (_)
406 ;; As a source may not be a real URL (e.g., "mirror://..."),
407 ;; no action is bound to a source button.
408 (message "Yes, this is the source URL. What did you expect?")))
409
410 (defun guix-package-info-insert-heading (entry)
411 "Insert package ENTRY heading (name and version) at point."
412 (guix-insert-button
413 (concat (guix-entry-value entry 'name) " "
414 (guix-entry-value entry 'version))
415 'guix-package-heading
416 'spec (guix-package-entry->name-specification entry)))
417
418 (defun guix-package-info-insert-location (location &optional _)
419 "Insert package LOCATION at point."
420 (if (null location)
421 (guix-format-insert nil)
422 (let ((location-file (car (split-string location ":"))))
423 (guix-info-insert-value-indent location 'guix-package-location)
424 ;; Do not show "Packages" button if a package 'from file' is displayed.
425 (unless (eq (guix-ui-current-search-type) 'from-file)
426 (guix-info-insert-indent)
427 (guix-info-insert-action-button
428 "Packages"
429 (lambda (btn)
430 (guix-package-get-display (guix-ui-current-profile)
431 'location
432 (button-get btn 'location)))
433 (format "Display packages from location '%s'" location-file)
434 'location location-file)))))
435
436 (defun guix-package-info-insert-systems (systems entry)
437 "Insert supported package SYSTEMS at point."
438 (guix-info-insert-value-format
439 systems 'guix-hydra-build-system
440 'action (lambda (btn)
441 (let ((args (guix-hydra-build-latest-prompt-args
442 :job (button-get btn 'job-name)
443 :system (button-label btn))))
444 (apply #'guix-hydra-build-get-display
445 'latest args)))
446 'job-name (guix-hydra-job-name-specification
447 (guix-entry-value entry 'name)
448 (guix-entry-value entry 'version))))
449
450 (defmacro guix-package-info-define-insert-inputs (&optional type)
451 "Define a face and a function for inserting package inputs.
452 TYPE is a type of inputs.
453 Function name is `guix-package-info-insert-TYPE-inputs'.
454 Face name is `guix-package-info-TYPE-inputs'."
455 (let* ((type-str (symbol-name type))
456 (type-name (and type (concat type-str "-")))
457 (type-desc (and type (concat type-str " ")))
458 (face (intern (concat "guix-package-info-" type-name "inputs")))
459 (btn (intern (concat "guix-package-" type-name "input"))))
460 `(progn
461 (defface ,face
462 '((t :inherit guix-package-info-name-button))
463 ,(concat "Face used for " type-desc "inputs of a package.")
464 :group 'guix-package-info-faces)
465
466 (define-button-type ',btn
467 :supertype 'guix-package-name
468 'face ',face))))
469
470 (guix-package-info-define-insert-inputs)
471 (guix-package-info-define-insert-inputs native)
472 (guix-package-info-define-insert-inputs propagated)
473
474 (defun guix-package-info-insert-outputs (outputs entry)
475 "Insert OUTPUTS from package ENTRY at point."
476 (and (guix-entry-value entry 'obsolete)
477 (guix-package-info-insert-obsolete-text))
478 (and (guix-entry-value entry 'non-unique)
479 (guix-entry-value entry 'installed)
480 (guix-package-info-insert-non-unique-text
481 (guix-package-entry->name-specification entry)))
482 (insert "\n")
483 (dolist (output outputs)
484 (guix-package-info-insert-output output entry)))
485
486 (defun guix-package-info-insert-obsolete-text ()
487 "Insert a message about obsolete package at point."
488 (guix-info-insert-indent)
489 (guix-format-insert guix-package-info-obsolete-string
490 'guix-package-info-obsolete))
491
492 (defun guix-package-info-insert-non-unique-text (full-name)
493 "Insert a message about non-unique package with FULL-NAME at point."
494 (insert "\n")
495 (guix-info-insert-indent)
496 (insert "Installed outputs are displayed for a non-unique ")
497 (guix-insert-button full-name 'guix-package-name)
498 (insert " package."))
499
500 (defun guix-package-info-insert-output (output entry)
501 "Insert OUTPUT at point.
502 Make some fancy text with buttons and additional stuff if the
503 current OUTPUT is installed (if there is such output in
504 `installed' parameter of a package ENTRY)."
505 (let* ((installed (guix-entry-value entry 'installed))
506 (obsolete (guix-entry-value entry 'obsolete))
507 (installed-entry (cl-find-if
508 (lambda (entry)
509 (string= (guix-entry-value entry 'output)
510 output))
511 installed))
512 (action-type (if installed-entry 'delete 'install))
513 (profile (guix-ui-current-profile)))
514 (guix-info-insert-indent)
515 (guix-format-insert output
516 (if installed-entry
517 'guix-package-info-installed-outputs
518 'guix-package-info-uninstalled-outputs)
519 guix-package-info-output-format)
520 ;; Do not allow a user to install/delete anything to/from a system
521 ;; profile, so add action buttons only for non-system profiles.
522 (when (and profile
523 (not (guix-system-profile? profile)))
524 (guix-package-info-insert-action-button action-type entry output)
525 (when obsolete
526 (guix-info-insert-indent)
527 (guix-package-info-insert-action-button 'upgrade entry output)))
528 (insert "\n")
529 (when installed-entry
530 (guix-info-insert-entry installed-entry 'installed-output 2))))
531
532 (defun guix-package-info-insert-action-button (type entry output)
533 "Insert button to process an action on a package OUTPUT at point.
534 TYPE is one of the following symbols: `install', `delete', `upgrade'.
535 ENTRY is an alist with package info."
536 (let ((type-str (capitalize (symbol-name type)))
537 (full-name (guix-package-entry->name-specification entry output)))
538 (guix-info-insert-action-button
539 type-str
540 (lambda (btn)
541 (guix-process-package-actions
542 (guix-ui-current-profile)
543 `((,(button-get btn 'action-type) (,(button-get btn 'id)
544 ,(button-get btn 'output))))
545 (current-buffer)))
546 (concat type-str " '" full-name "'")
547 'action-type type
548 'id (or (guix-entry-value entry 'package-id)
549 (guix-entry-id entry))
550 'output output)))
551
552 (defun guix-package-info-show-store-path (entry-id package-id)
553 "Show store directories of the package outputs in the current buffer.
554 ENTRY-ID is an ID of the current entry (package or output).
555 PACKAGE-ID is an ID of the package which store path to show."
556 (let* ((entries (guix-buffer-current-entries))
557 (entry (guix-entry-by-id entry-id entries))
558 (dirs (guix-package-store-path package-id)))
559 (or dirs
560 (error "Couldn't define store directory of the package"))
561 (let* ((new-entry (cons (cons 'store-path dirs)
562 entry))
563 (new-entries (guix-replace-entry entry-id new-entry entries)))
564 (setf (guix-buffer-item-entries guix-buffer-item)
565 new-entries)
566 (guix-buffer-redisplay-goto-button)
567 (let ((dir (car dirs)))
568 (if (file-exists-p dir)
569 (if guix-package-info-auto-find-package
570 (find-file dir)
571 (message nil))
572 (message "'%s' does not exist.\nTry to build this package."
573 dir))))))
574
575 (defun guix-package-info-insert-misc (entry)
576 "Insert various buttons and other info for package ENTRY at point."
577 (if (guix-entry-value entry 'obsolete)
578 (guix-format-insert nil)
579 (let* ((entry-id (guix-entry-id entry))
580 (package-id (or (guix-entry-value entry 'package-id)
581 entry-id))
582 (full-name (guix-package-entry->name-specification entry))
583 (store-path (guix-entry-value entry 'store-path)))
584 (guix-info-insert-title-simple "Package")
585 (if store-path
586 (guix-info-insert-value-indent store-path 'guix-file)
587 (guix-info-insert-action-button
588 "Show"
589 (lambda (btn)
590 (guix-package-info-show-store-path
591 (button-get btn 'entry-id)
592 (button-get btn 'package-id)))
593 "Show the store directory of the current package"
594 'entry-id entry-id
595 'package-id package-id))
596 (when guix-package-info-button-functions
597 (insert "\n")
598 (guix-mapinsert (lambda (fun)
599 (funcall fun package-id full-name))
600 guix-package-info-button-functions
601 (guix-info-get-indent)
602 :indent guix-info-indent
603 :column (guix-info-fill-column))))))
604
605 (defun guix-package-info-insert-build-button (id full-name)
606 "Insert button to build a package defined by ID."
607 (guix-info-insert-action-button
608 "Build"
609 (lambda (btn)
610 (guix-build-package (button-get btn 'id)
611 (format "Build '%s' package?" full-name)))
612 (format "Build the current package")
613 'id id))
614
615 (defun guix-package-info-insert-build-log-button (id _name)
616 "Insert button to show build log of a package defined by ID."
617 (guix-info-insert-action-button
618 "Build Log"
619 (lambda (btn)
620 (guix-package-find-build-log (button-get btn 'id)))
621 "View build log of the current package"
622 'id id))
623
624 (defun guix-package-info-show-source (entry-id package-id)
625 "Show file name of a package source in the current info buffer.
626 Find the file if needed (see `guix-package-info-auto-find-source').
627 ENTRY-ID is an ID of the current entry (package or output).
628 PACKAGE-ID is an ID of the package which source to show."
629 (let* ((entries (guix-buffer-current-entries))
630 (entry (guix-entry-by-id entry-id entries))
631 (file (guix-package-source-path package-id)))
632 (or file
633 (error "Couldn't define file name of the package source"))
634 (let* ((new-entry (cons (cons 'source-file file)
635 entry))
636 (new-entries (guix-replace-entry entry-id new-entry entries)))
637 (setf (guix-buffer-item-entries guix-buffer-item)
638 new-entries)
639 (guix-buffer-redisplay-goto-button)
640 (if (file-exists-p file)
641 (if guix-package-info-auto-find-source
642 (guix-find-file file)
643 (message "The source store path is displayed."))
644 (if guix-package-info-auto-download-source
645 (guix-package-info-download-source package-id)
646 (message "The source does not exist in the store."))))))
647
648 (defun guix-package-info-download-source (package-id)
649 "Download a source of the package PACKAGE-ID."
650 (setq guix-package-info-download-buffer (current-buffer))
651 (guix-package-source-build-derivation
652 package-id
653 "The source does not exist in the store. Download it?"))
654
655 (defun guix-package-info-insert-source (source entry)
656 "Insert SOURCE from package ENTRY at point.
657 SOURCE is a list of URLs."
658 (if (null source)
659 (guix-format-insert nil)
660 (let* ((source-file (guix-entry-value entry 'source-file))
661 (entry-id (guix-entry-id entry))
662 (package-id (or (guix-entry-value entry 'package-id)
663 entry-id)))
664 (if (null source-file)
665 (guix-info-insert-action-button
666 "Show"
667 (lambda (btn)
668 (guix-package-info-show-source (button-get btn 'entry-id)
669 (button-get btn 'package-id)))
670 "Show the source store directory of the current package"
671 'entry-id entry-id
672 'package-id package-id)
673 (unless (file-exists-p source-file)
674 (guix-info-insert-action-button
675 "Download"
676 (lambda (btn)
677 (guix-package-info-download-source
678 (button-get btn 'package-id)))
679 "Download the source into the store"
680 'package-id package-id))
681 (guix-info-insert-value-indent source-file 'guix-file))
682 (guix-info-insert-value-indent source 'guix-package-source))))
683
684 (defun guix-package-info-redisplay-after-download ()
685 "Redisplay an 'info' buffer after downloading the package source.
686 This function is used to hide a \"Download\" button if needed."
687 (when (buffer-live-p guix-package-info-download-buffer)
688 (with-current-buffer guix-package-info-download-buffer
689 (guix-buffer-redisplay-goto-button))
690 (setq guix-package-info-download-buffer nil)))
691
692 (add-hook 'guix-after-source-download-hook
693 'guix-package-info-redisplay-after-download)
694
695 \f
696 ;;; Package 'list'
697
698 (guix-ui-list-define-interface package
699 :buffer-name "*Guix Package List*"
700 :format '((name guix-package-list-get-name 20 t)
701 (version nil 10 nil)
702 (outputs nil 13 t)
703 (installed guix-package-list-get-installed-outputs 13 t)
704 (synopsis guix-list-get-one-line 30 nil))
705 :sort-key '(name)
706 :marks '((install . ?I)
707 (upgrade . ?U)
708 (delete . ?D)))
709
710 (let ((map guix-package-list-mode-map))
711 (define-key map (kbd "B") 'guix-package-list-latest-builds)
712 (define-key map (kbd "e") 'guix-package-list-edit)
713 (define-key map (kbd "x") 'guix-package-list-execute)
714 (define-key map (kbd "i") 'guix-package-list-mark-install)
715 (define-key map (kbd "d") 'guix-package-list-mark-delete)
716 (define-key map (kbd "U") 'guix-package-list-mark-upgrade)
717 (define-key map (kbd "^") 'guix-package-list-mark-upgrades))
718
719 (defface guix-package-list-installed
720 '((t :inherit guix-package-info-installed-outputs))
721 "Face used if there are installed outputs for the current package."
722 :group 'guix-package-list-faces)
723
724 (defface guix-package-list-obsolete
725 '((t :inherit guix-package-info-obsolete))
726 "Face used if a package is obsolete."
727 :group 'guix-package-list-faces)
728
729 (defcustom guix-package-list-generation-marking-enabled nil
730 "If non-nil, allow putting marks in a list with 'generation packages'.
731
732 By default this is disabled, because it may be confusing. For
733 example, a package is installed in some generation, so a user can
734 mark it for deletion in the list of packages from this
735 generation, but the package may not be installed in the latest
736 generation, so actually it cannot be deleted.
737
738 If you managed to understand the explanation above or if you
739 really know what you do or if you just don't care, you can set
740 this variable to t. It should not do much harm anyway (most
741 likely)."
742 :type 'boolean
743 :group 'guix-package-list)
744
745 (defun guix-package-list-get-name (name entry)
746 "Return NAME of the package ENTRY.
747 Colorize it with `guix-package-list-installed' or
748 `guix-package-list-obsolete' if needed."
749 (guix-get-string name
750 (cond ((guix-entry-value entry 'obsolete)
751 'guix-package-list-obsolete)
752 ((guix-entry-value entry 'installed)
753 'guix-package-list-installed))))
754
755 (defun guix-package-list-get-installed-outputs (installed &optional _)
756 "Return string with outputs from INSTALLED entries."
757 (guix-get-string
758 (mapcar (lambda (entry)
759 (guix-entry-value entry 'output))
760 installed)))
761
762 (defun guix-package-list-marking-check ()
763 "Signal an error if marking is disabled for the current buffer."
764 (when (and (not guix-package-list-generation-marking-enabled)
765 (or (derived-mode-p 'guix-package-list-mode)
766 (derived-mode-p 'guix-output-list-mode))
767 (eq (guix-ui-current-search-type) 'generation))
768 (error "Action marks are disabled for lists of 'generation packages'")))
769
770 (defun guix-package-list-mark-outputs (mark default
771 &optional prompt available)
772 "Mark the current package with MARK and move to the next line.
773 If PROMPT is non-nil, use it to ask a user for outputs from
774 AVAILABLE list, otherwise mark all DEFAULT outputs."
775 (let ((outputs (if prompt
776 (guix-completing-read-multiple
777 prompt available nil t)
778 default)))
779 (apply #'guix-list--mark mark t outputs)))
780
781 (defun guix-package-list-mark-install (&optional arg)
782 "Mark the current package for installation and move to the next line.
783 With ARG, prompt for the outputs to install (several outputs may
784 be separated with \",\")."
785 (interactive "P")
786 (guix-package-list-marking-check)
787 (let* ((entry (guix-list-current-entry))
788 (all (guix-entry-value entry 'outputs))
789 (installed (guix-package-installed-outputs entry))
790 (available (cl-set-difference all installed :test #'string=)))
791 (or available
792 (user-error "This package is already installed"))
793 (guix-package-list-mark-outputs
794 'install '("out")
795 (and arg "Output(s) to install: ")
796 available)))
797
798 (defun guix-package-list-mark-delete (&optional arg)
799 "Mark the current package for deletion and move to the next line.
800 With ARG, prompt for the outputs to delete (several outputs may
801 be separated with \",\")."
802 (interactive "P")
803 (guix-package-list-marking-check)
804 (let* ((entry (guix-list-current-entry))
805 (installed (guix-package-installed-outputs entry)))
806 (or installed
807 (user-error "This package is not installed"))
808 (guix-package-list-mark-outputs
809 'delete installed
810 (and arg "Output(s) to delete: ")
811 installed)))
812
813 (defun guix-package-list-mark-upgrade (&optional arg)
814 "Mark the current package for upgrading and move to the next line.
815 With ARG, prompt for the outputs to upgrade (several outputs may
816 be separated with \",\")."
817 (interactive "P")
818 (guix-package-list-marking-check)
819 (let* ((entry (guix-list-current-entry))
820 (installed (guix-package-installed-outputs entry)))
821 (or installed
822 (user-error "This package is not installed"))
823 (when (or (guix-entry-value entry 'obsolete)
824 (y-or-n-p "This package is not obsolete. Try to upgrade it anyway? "))
825 (guix-package-list-mark-outputs
826 'upgrade installed
827 (and arg "Output(s) to upgrade: ")
828 installed))))
829
830 (defun guix-package-mark-upgrades (fun)
831 "Mark all obsolete packages for upgrading.
832 Use FUN to perform marking of the current line. FUN should
833 take an entry as argument."
834 (guix-package-list-marking-check)
835 (let ((obsolete (cl-remove-if-not
836 (lambda (entry)
837 (guix-entry-value entry 'obsolete))
838 (guix-buffer-current-entries))))
839 (guix-list-for-each-line
840 (lambda ()
841 (let* ((id (guix-list-current-id))
842 (entry (cl-find-if
843 (lambda (entry)
844 (equal id (guix-entry-id entry)))
845 obsolete)))
846 (when entry
847 (funcall fun entry)))))))
848
849 (defun guix-package-list-mark-upgrades ()
850 "Mark all obsolete packages for upgrading."
851 (interactive)
852 (guix-package-mark-upgrades
853 (lambda (entry)
854 (apply #'guix-list--mark
855 'upgrade nil
856 (guix-package-installed-outputs entry)))))
857
858 (defun guix-package-assert-non-system-profile ()
859 "Verify that the current profile is not a system one.
860 The current profile is the one used by the current buffer."
861 (let ((profile (guix-ui-current-profile)))
862 (and profile
863 (guix-system-profile? profile)
864 (user-error "Packages cannot be installed or removed to/from \
865 profile '%s'.
866 Use 'guix system reconfigure' shell command to modify a system profile."
867 profile))))
868
869 (defun guix-package-execute-actions (fun)
870 "Perform actions on the marked packages.
871 Use FUN to define actions suitable for `guix-process-package-actions'.
872 FUN should take action-type as argument."
873 (guix-package-assert-non-system-profile)
874 (let ((actions (delq nil
875 (mapcar fun '(install delete upgrade)))))
876 (if actions
877 (guix-process-package-actions (guix-ui-current-profile)
878 actions (current-buffer))
879 (user-error "No operations specified"))))
880
881 (defun guix-package-list-execute ()
882 "Perform actions on the marked packages."
883 (interactive)
884 (guix-package-execute-actions #'guix-package-list-make-action))
885
886 (defun guix-package-list-make-action (action-type)
887 "Return action specification for the packages marked with ACTION-TYPE.
888 Return nil, if there are no packages marked with ACTION-TYPE.
889 The specification is suitable for `guix-process-package-actions'."
890 (let ((specs (guix-list-get-marked-args action-type)))
891 (and specs (cons action-type specs))))
892
893 (defun guix-package-list-edit (&optional directory)
894 "Go to the location of the current package.
895 See `guix-find-location' for the meaning of DIRECTORY."
896 (interactive (list (guix-read-directory)))
897 (guix-edit (guix-list-current-id) directory))
898
899 (defun guix-package-list-latest-builds (number &rest args)
900 "Display latest NUMBER of Hydra builds of the current package.
901 Interactively, prompt for NUMBER. With prefix argument, prompt
902 for all ARGS."
903 (interactive
904 (let ((entry (guix-list-current-entry)))
905 (guix-hydra-build-latest-prompt-args
906 :job (guix-hydra-job-name-specification
907 (guix-entry-value entry 'name)
908 (guix-entry-value entry 'version)))))
909 (apply #'guix-hydra-latest-builds number args))
910
911 \f
912 ;;; Output 'info'
913
914 (guix-ui-info-define-interface output
915 :buffer-name "*Guix Package Info*"
916 :format '((name format (format guix-package-info-name))
917 (version format guix-output-info-insert-version)
918 (output format guix-output-info-insert-output)
919 (synopsis simple (indent guix-package-info-synopsis))
920 guix-package-info-insert-misc
921 (source simple guix-package-info-insert-source)
922 (path simple (indent guix-file))
923 (dependencies simple (indent guix-file))
924 (location simple guix-package-info-insert-location)
925 (home-url format (format guix-url))
926 (license format (format guix-package-license))
927 (systems format guix-package-info-insert-systems)
928 (inputs format (format guix-package-input))
929 (native-inputs format (format guix-package-native-input))
930 (propagated-inputs format
931 (format guix-package-propagated-input))
932 (description simple (indent guix-package-info-description)))
933 :titles guix-package-info-titles
934 :required '(id package-id installed non-unique))
935
936 (defun guix-output-info-insert-version (version entry)
937 "Insert output VERSION and obsolete text if needed at point."
938 (guix-info-insert-value-format version
939 'guix-package-info-version)
940 (and (guix-entry-value entry 'obsolete)
941 (guix-package-info-insert-obsolete-text)))
942
943 (defun guix-output-info-insert-output (output entry)
944 "Insert OUTPUT and action buttons at point."
945 (let* ((installed (guix-entry-value entry 'installed))
946 (obsolete (guix-entry-value entry 'obsolete))
947 (action-type (if installed 'delete 'install)))
948 (guix-info-insert-value-format
949 output
950 (if installed
951 'guix-package-info-installed-outputs
952 'guix-package-info-uninstalled-outputs))
953 (guix-info-insert-indent)
954 (guix-package-info-insert-action-button action-type entry output)
955 (when obsolete
956 (guix-info-insert-indent)
957 (guix-package-info-insert-action-button 'upgrade entry output))))
958
959 \f
960 ;;; Output 'list'
961
962 (guix-ui-list-define-interface output
963 :buffer-name "*Guix Package List*"
964 :describe-function 'guix-output-list-describe
965 :format '((name guix-package-list-get-name 20 t)
966 (version nil 10 nil)
967 (output nil 9 t)
968 (installed nil 12 t)
969 (synopsis guix-list-get-one-line 30 nil))
970 :required '(id package-id)
971 :sort-key '(name)
972 :marks '((install . ?I)
973 (upgrade . ?U)
974 (delete . ?D)))
975
976 (let ((map guix-output-list-mode-map))
977 (define-key map (kbd "B") 'guix-package-list-latest-builds)
978 (define-key map (kbd "e") 'guix-output-list-edit)
979 (define-key map (kbd "x") 'guix-output-list-execute)
980 (define-key map (kbd "i") 'guix-output-list-mark-install)
981 (define-key map (kbd "d") 'guix-output-list-mark-delete)
982 (define-key map (kbd "U") 'guix-output-list-mark-upgrade)
983 (define-key map (kbd "^") 'guix-output-list-mark-upgrades))
984
985 (defun guix-output-list-mark-install ()
986 "Mark the current output for installation and move to the next line."
987 (interactive)
988 (guix-package-list-marking-check)
989 (let* ((entry (guix-list-current-entry))
990 (installed (guix-entry-value entry 'installed)))
991 (if installed
992 (user-error "This output is already installed")
993 (guix-list--mark 'install t))))
994
995 (defun guix-output-list-mark-delete ()
996 "Mark the current output for deletion and move to the next line."
997 (interactive)
998 (guix-package-list-marking-check)
999 (let* ((entry (guix-list-current-entry))
1000 (installed (guix-entry-value entry 'installed)))
1001 (if installed
1002 (guix-list--mark 'delete t)
1003 (user-error "This output is not installed"))))
1004
1005 (defun guix-output-list-mark-upgrade ()
1006 "Mark the current output for upgrading and move to the next line."
1007 (interactive)
1008 (guix-package-list-marking-check)
1009 (let* ((entry (guix-list-current-entry))
1010 (installed (guix-entry-value entry 'installed)))
1011 (or installed
1012 (user-error "This output is not installed"))
1013 (when (or (guix-entry-value entry 'obsolete)
1014 (y-or-n-p "This output is not obsolete. Try to upgrade it anyway? "))
1015 (guix-list--mark 'upgrade t))))
1016
1017 (defun guix-output-list-mark-upgrades ()
1018 "Mark all obsolete package outputs for upgrading."
1019 (interactive)
1020 (guix-package-mark-upgrades
1021 (lambda (_) (guix-list--mark 'upgrade))))
1022
1023 (defun guix-output-list-execute ()
1024 "Perform actions on the marked outputs."
1025 (interactive)
1026 (guix-package-execute-actions #'guix-output-list-make-action))
1027
1028 (defun guix-output-list-make-action (action-type)
1029 "Return action specification for the outputs marked with ACTION-TYPE.
1030 Return nil, if there are no outputs marked with ACTION-TYPE.
1031 The specification is suitable for `guix-process-output-actions'."
1032 (let ((ids (guix-list-get-marked-id-list action-type)))
1033 (and ids (cons action-type
1034 (mapcar #'guix-package-id-and-output-by-output-id
1035 ids)))))
1036
1037 (defun guix-output-list-describe (ids)
1038 "Describe outputs with IDS (list of output identifiers).
1039 See `guix-package-info-type'."
1040 (if (eq guix-package-info-type 'output)
1041 (guix-buffer-get-display-entries
1042 'info 'output
1043 (cl-list* (guix-ui-current-profile) 'id ids)
1044 'add)
1045 (let ((pids (mapcar (lambda (oid)
1046 (car (guix-package-id-and-output-by-output-id
1047 oid)))
1048 ids)))
1049 (guix-buffer-get-display-entries
1050 'info 'package
1051 (cl-list* (guix-ui-current-profile)
1052 'id (cl-remove-duplicates pids))
1053 'add))))
1054
1055 (defun guix-output-list-edit (&optional directory)
1056 "Go to the location of the current package.
1057 See `guix-find-location' for the meaning of DIRECTORY."
1058 (interactive (list (guix-read-directory)))
1059 (guix-edit (guix-entry-value (guix-list-current-entry)
1060 'package-id)
1061 directory))
1062
1063 \f
1064 ;;; Interactive commands
1065
1066 (defvar guix-package-search-params '(name synopsis description)
1067 "Default list of package parameters for searching by regexp.")
1068
1069 (defvar guix-package-search-history nil
1070 "A history of minibuffer prompts.")
1071
1072 ;;;###autoload
1073 (defun guix-packages-by-name (name &optional profile)
1074 "Display Guix packages with NAME.
1075 NAME is a string with name specification. It may optionally contain
1076 a version number. Examples: \"guile\", \"guile@2.0.11\".
1077
1078 If PROFILE is nil, use `guix-current-profile'.
1079 Interactively with prefix, prompt for PROFILE."
1080 (interactive
1081 (list (guix-read-package-name)
1082 (guix-ui-read-profile)))
1083 (guix-package-get-display profile 'name name))
1084
1085 ;;;###autoload
1086 (defun guix-packages-by-license (license &optional profile)
1087 "Display Guix packages with LICENSE.
1088 LICENSE is a license name string.
1089 If PROFILE is nil, use `guix-current-profile'.
1090 Interactively with prefix, prompt for PROFILE."
1091 (interactive
1092 (list (guix-read-license-name)
1093 (guix-ui-read-profile)))
1094 (guix-package-get-display profile 'license license))
1095
1096 ;;;###autoload
1097 (defun guix-packages-by-location (location &optional profile)
1098 "Display Guix packages placed in LOCATION file.
1099 If PROFILE is nil, use `guix-current-profile'.
1100 Interactively with prefix, prompt for PROFILE."
1101 (interactive
1102 (list (guix-read-package-location)
1103 (guix-ui-read-profile)))
1104 (guix-package-get-display profile 'location location))
1105
1106 ;;;###autoload
1107 (defun guix-package-from-file (file &optional profile)
1108 "Display Guix package that the code from FILE evaluates to.
1109 If PROFILE is nil, use `guix-current-profile'.
1110 Interactively with prefix, prompt for PROFILE."
1111 (interactive
1112 (list (read-file-name "File with package: ")
1113 (guix-ui-read-profile)))
1114 (guix-buffer-get-display-entries
1115 'info 'package
1116 (list (or profile guix-current-profile) 'from-file file)
1117 'add))
1118
1119 ;;;###autoload
1120 (defun guix-search-by-regexp (regexp &optional params profile)
1121 "Search for Guix packages by REGEXP.
1122 PARAMS are package parameters that should be searched.
1123 If PARAMS are not specified, use `guix-package-search-params'.
1124
1125 If PROFILE is nil, use `guix-current-profile'.
1126 Interactively with prefix, prompt for PROFILE."
1127 (interactive
1128 (list (read-regexp "Regexp: " nil 'guix-package-search-history)
1129 nil (guix-ui-read-profile)))
1130 (guix-package-get-display profile 'regexp regexp
1131 (or params guix-package-search-params)))
1132
1133 ;;;###autoload
1134 (defun guix-search-by-name (regexp &optional profile)
1135 "Search for Guix packages matching REGEXP in a package name.
1136 If PROFILE is nil, use `guix-current-profile'.
1137 Interactively with prefix, prompt for PROFILE."
1138 (interactive
1139 (list (read-string "Package name by regexp: "
1140 nil 'guix-package-search-history)
1141 (guix-ui-read-profile)))
1142 (guix-search-by-regexp regexp '(name) profile))
1143
1144 ;;;###autoload
1145 (defun guix-installed-packages (&optional profile)
1146 "Display information about installed Guix packages.
1147 If PROFILE is nil, use `guix-current-profile'.
1148 Interactively with prefix, prompt for PROFILE."
1149 (interactive (list (guix-ui-read-profile)))
1150 (guix-package-get-display profile 'installed))
1151
1152 ;;;###autoload
1153 (defun guix-installed-user-packages ()
1154 "Display information about Guix packages installed in a user profile."
1155 (interactive)
1156 (guix-installed-packages guix-user-profile))
1157
1158 ;;;###autoload
1159 (defun guix-installed-system-packages ()
1160 "Display information about Guix packages installed in a system profile."
1161 (interactive)
1162 (guix-installed-packages
1163 (guix-packages-profile guix-system-profile nil t)))
1164
1165 ;;;###autoload
1166 (defun guix-obsolete-packages (&optional profile)
1167 "Display information about obsolete Guix packages.
1168 If PROFILE is nil, use `guix-current-profile'.
1169 Interactively with prefix, prompt for PROFILE."
1170 (interactive (list (guix-ui-read-profile)))
1171 (guix-package-get-display profile 'obsolete))
1172
1173 ;;;###autoload
1174 (defun guix-all-available-packages (&optional profile)
1175 "Display information about all available Guix packages.
1176 If PROFILE is nil, use `guix-current-profile'.
1177 Interactively with prefix, prompt for PROFILE."
1178 (interactive (list (guix-ui-read-profile)))
1179 (guix-package-get-display profile 'all-available))
1180
1181 ;;;###autoload
1182 (defun guix-newest-available-packages (&optional profile)
1183 "Display information about the newest available Guix packages.
1184 If PROFILE is nil, use `guix-current-profile'.
1185 Interactively with prefix, prompt for PROFILE."
1186 (interactive (list (guix-ui-read-profile)))
1187 (guix-package-get-display profile 'newest-available))
1188
1189 (provide 'guix-ui-package)
1190
1191 ;;; guix-ui-package.el ends here