system: Add /etc/ssl symlink; set needed variables in /etc/profile.
[jackhill/guix/guix.git] / emacs / guix-base.el
index eb88f37..5129c87 100644 (file)
 ;;; Code:
 
 (require 'cl-lib)
+(require 'guix-profiles)
 (require 'guix-backend)
 (require 'guix-utils)
 (require 'guix-history)
 (require 'guix-messages)
 
 \f
-;;; Profiles
-
-(defvar guix-user-profile
-  (expand-file-name "~/.guix-profile")
-  "User profile.")
-
-(defvar guix-default-profile
-  (concat (or (getenv "NIX_STATE_DIR") "/var/guix")
-          "/profiles/per-user/"
-          (getenv "USER")
-          "/guix-profile")
-  "Default Guix profile.")
-
-(defvar guix-current-profile guix-default-profile
-  "Current profile.")
-
-(defun guix-profile-prompt (&optional default)
-  "Prompt for profile and return it.
-Use DEFAULT as a start directory.  If it is nil, use
-`guix-current-profile'."
-  (let* ((path (read-file-name "Profile: "
-                               (file-name-directory
-                                (or default guix-current-profile))))
-         (path (directory-file-name (expand-file-name path))))
-    (if (string= path guix-user-profile)
-        guix-default-profile
-      path)))
-
-(defun guix-set-current-profile (path)
-  "Set `guix-current-profile' to PATH.
-Interactively, prompt for PATH.  With prefix, use
-`guix-default-profile'."
-  (interactive
-   (list (if current-prefix-arg
-             guix-default-profile
-           (guix-profile-prompt))))
-  (setq guix-current-profile path)
-  (message "Current profile has been set to '%s'."
-           guix-current-profile))
-
-\f
 ;;; Parameters of the entries
 
 (defvar guix-param-titles
@@ -82,6 +42,7 @@ Interactively, prompt for PATH.  With prefix, use
      (id                . "ID")
      (name              . "Name")
      (version           . "Version")
+     (source            . "Source")
      (license           . "License")
      (synopsis          . "Synopsis")
      (description       . "Description")
@@ -100,6 +61,7 @@ Interactively, prompt for PATH.  With prefix, use
      (id                . "ID")
      (name              . "Name")
      (version           . "Version")
+     (source            . "Source")
      (license           . "License")
      (synopsis          . "Synopsis")
      (description       . "Description")
@@ -640,14 +602,152 @@ See `revert-buffer' for the meaning of NOCONFIRM."
       (guix-set-buffer guix-profile entries guix-buffer-type guix-entry-type
                        search-type search-vals t t))))
 
-(defun guix-redisplay-buffer ()
-  "Redisplay current information.
+(cl-defun guix-redisplay-buffer (&key buffer profile entries buffer-type
+                                      entry-type search-type search-vals)
+  "Redisplay a Guix BUFFER.
+Restore the point and window positions after redisplaying if possible.
+
 This function will not update the information, use
-\"\\[revert-buffer]\" if you want the full update."
+\"\\[revert-buffer]\" if you want the full update.
+
+If BUFFER is nil, use the current buffer.  For the meaning of the
+rest arguments, see `guix-set-buffer'."
   (interactive)
-  (guix-show-entries guix-entries guix-buffer-type guix-entry-type)
-  (guix-result-message guix-profile guix-entries guix-entry-type
-                       guix-search-type guix-search-vals))
+  (or buffer (setq buffer (current-buffer)))
+  (with-current-buffer buffer
+    (or (derived-mode-p 'guix-info-mode 'guix-list-mode)
+        (error "%S is not a Guix buffer" buffer))
+    (let* ((point (point))
+           (was-at-button (button-at point))
+           ;; For simplicity, ignore an unlikely case when multiple
+           ;; windows display the same BUFFER.
+           (window (car (get-buffer-window-list buffer nil t)))
+           (window-start (and window (window-start window))))
+      (guix-set-buffer (or profile     guix-profile)
+                       (or entries     guix-entries)
+                       (or buffer-type guix-buffer-type)
+                       (or entry-type  guix-entry-type)
+                       (or search-type guix-search-type)
+                       (or search-vals guix-search-vals)
+                       t t)
+      (goto-char point)
+      (and was-at-button
+           (not (button-at (point)))
+           (forward-button 1))
+      (when window
+        (set-window-point window (point))
+        (set-window-start window window-start)))))
+
+\f
+;;; Generations
+
+(defcustom guix-generation-packages-buffer-name-function
+  #'guix-generation-packages-buffer-name-default
+  "Function used to define name of a buffer with generation packages.
+This function is called with 2 arguments: PROFILE (string) and
+GENERATION (number)."
+  :type '(choice (function-item guix-generation-packages-buffer-name-default)
+                 (function-item guix-generation-packages-buffer-name-long)
+                 (function :tag "Other function"))
+  :group 'guix)
+
+(defcustom guix-generation-packages-update-buffer t
+  "If non-nil, always update list of packages during comparing generations.
+If nil, generation packages are received only once.  So when you
+compare generation 1 and generation 2, the packages for both
+generations will be received.  Then if you compare generation 1
+and generation 3, only the packages for generation 3 will be
+received.  Thus if you use comparing of different generations a
+lot, you may set this variable to nil to improve the
+performance."
+  :type 'boolean
+  :group 'guix)
+
+(defvar guix-output-name-width 30
+  "Width of an output name \"column\".
+This variable is used in auxiliary buffers for comparing generations.")
+
+(defun guix-generation-file (profile generation)
+  "Return the file name of a PROFILE's GENERATION."
+  (format "%s-%s-link" profile generation))
+
+(defun guix-manifest-file (profile &optional generation)
+  "Return the file name of a PROFILE's manifest.
+If GENERATION number is specified, return manifest file name for
+this generation."
+  (expand-file-name "manifest"
+                    (if generation
+                        (guix-generation-file profile generation)
+                      profile)))
+
+(defun guix-generation-packages (profile generation)
+  "Return a list of sorted packages installed in PROFILE's GENERATION.
+Each element of the list is a list of the package specification and its path."
+  (let ((names+paths (guix-eval-read
+                      (guix-make-guile-expression
+                       'generation-package-specifications+paths
+                       profile generation))))
+    (sort names+paths
+          (lambda (a b)
+            (string< (car a) (car b))))))
+
+(defun guix-generation-packages-buffer-name-default (profile generation)
+  "Return name of a buffer for displaying GENERATION's package outputs.
+Use base name of PROFILE path."
+  (let ((profile-name (file-name-base (directory-file-name profile))))
+    (format "*Guix %s: generation %s*"
+            profile-name generation)))
+
+(defun guix-generation-packages-buffer-name-long (profile generation)
+  "Return name of a buffer for displaying GENERATION's package outputs.
+Use the full PROFILE path."
+  (format "*Guix generation %s (%s)*"
+          generation profile))
+
+(defun guix-generation-packages-buffer-name (profile generation)
+  "Return name of a buffer for displaying GENERATION's package outputs."
+  (let ((fun (if (functionp guix-generation-packages-buffer-name-function)
+                 guix-generation-packages-buffer-name-function
+               #'guix-generation-packages-buffer-name-default)))
+    (funcall fun profile generation)))
+
+(defun guix-generation-insert-package (name path)
+  "Insert package output NAME and PATH at point."
+  (insert name)
+  (indent-to guix-output-name-width 2)
+  (insert path "\n"))
+
+(defun guix-generation-insert-packages (buffer profile generation)
+  "Insert package outputs installed in PROFILE's GENERATION in BUFFER."
+  (with-current-buffer buffer
+    (setq buffer-read-only nil
+          indent-tabs-mode nil)
+    (erase-buffer)
+    (mapc (lambda (name+path)
+            (guix-generation-insert-package
+             (car name+path) (cadr name+path)))
+          (guix-generation-packages profile generation))))
+
+(defun guix-generation-packages-buffer (profile generation)
+  "Return buffer with package outputs installed in PROFILE's GENERATION.
+Create the buffer if needed."
+  (let ((buf-name (guix-generation-packages-buffer-name
+                   profile generation)))
+    (or (and (null guix-generation-packages-update-buffer)
+             (get-buffer buf-name))
+        (let ((buf (get-buffer-create buf-name)))
+          (guix-generation-insert-packages buf profile generation)
+          buf))))
+
+(defun guix-profile-generation-manifest-file (generation)
+  "Return the file name of a GENERATION's manifest.
+GENERATION is a generation number of `guix-profile' profile."
+  (guix-manifest-file guix-profile generation))
+
+(defun guix-profile-generation-packages-buffer (generation)
+  "Insert GENERATION's package outputs in a buffer and return it.
+GENERATION is a generation number of `guix-profile' profile."
+  (guix-generation-packages-buffer guix-profile generation))
 
 \f
 ;;; Actions on packages and generations
@@ -816,13 +916,14 @@ ENTRIES is a list of package entries to get info about packages."
           strings)
     (insert "\n")))
 
-(defun guix-operation-prompt ()
-  "Prompt a user for continuing the current package operation.
-Return non-nil, if the operation should be continued; nil otherwise."
+(defun guix-operation-prompt (&optional prompt)
+  "Prompt a user for continuing the current operation.
+Return non-nil, if the operation should be continued; nil otherwise.
+Ask a user with PROMPT for continuing an operation."
   (let* ((option-keys (mapcar #'guix-operation-option-key
                               guix-operation-options))
          (keys (append '(?y ?n) option-keys))
-         (prompt (concat (propertize "Continue operation?"
+         (prompt (concat (propertize (or prompt "Continue operation?")
                                      'face 'minibuffer-prompt)
                          " ("
                          (mapconcat
@@ -832,9 +933,11 @@ Return non-nil, if the operation should be continued; nil otherwise."
                           keys
                           ", ")
                          ") ")))
-    (prog1 (guix-operation-prompt-1 prompt keys)
-      ;; Clear the minibuffer after prompting.
-      (message ""))))
+    (let ((mode-line mode-line-format))
+      (prog1 (guix-operation-prompt-1 prompt keys)
+        (setq mode-line-format mode-line)
+        ;; Clear the minibuffer after prompting.
+        (message "")))))
 
 (defun guix-operation-prompt-1 (prompt keys)
   "This function is internal for `guix-operation-prompt'."
@@ -895,6 +998,30 @@ Each element from GENERATIONS is a generation number."
       'switch-to-generation profile generation)
      operation-buffer)))
 
+(defun guix-package-source-path (package-id)
+  "Return a store file path to a source of a package PACKAGE-ID."
+  (message "Calculating the source derivation ...")
+  (guix-eval-read
+   (guix-make-guile-expression
+    'package-source-path package-id)))
+
+(defvar guix-after-source-download-hook nil
+  "Hook run after successful performing a 'source-download' operation.")
+
+(defun guix-package-source-build-derivation (package-id &optional prompt)
+  "Build source derivation of a package PACKAGE-ID.
+Ask a user with PROMPT for continuing an operation."
+  (when (or (not guix-operation-confirm)
+            (guix-operation-prompt (or prompt
+                                       "Build the source derivation?")))
+    (guix-eval-in-repl
+     (guix-make-guile-expression
+      'package-source-build-derivation
+      package-id
+      :use-substitutes? (or guix-use-substitutes 'f)
+      :dry-run? (or guix-dry-run 'f))
+     nil 'source-download)))
+
 \f
 ;;; Pull