From 323b2f6c2fce8c3efc3b69da22eaa068a4b05378 Mon Sep 17 00:00:00 2001 From: Henrik Lissner Date: Fri, 19 May 2017 02:57:39 +0200 Subject: [PATCH] Refactor package management: better feedback & bug fixes --- core/autoload/packages.el | 174 +++++++++++++++++++++++--------------- core/core-packages.el | 62 +++++++------- 2 files changed, 140 insertions(+), 96 deletions(-) diff --git a/core/autoload/packages.el b/core/autoload/packages.el index e757c26ae..bb5b33614 100644 --- a/core/autoload/packages.el +++ b/core/autoload/packages.el @@ -45,9 +45,9 @@ list of the package." (new-version (pcase (doom-package-backend name) ('quelpa - (let ((recipe (assq name quelpa-cache)) + (let ((recipe (plist-get (cdr (assq 'rotate-text doom-packages)) :recipe)) (dir (expand-file-name (symbol-name name) quelpa-build-dir)) - (inhibit-message t)) + (inhibit-message (not doom-debug-mode))) (if-let (ver (quelpa-checkout recipe dir)) (version-to-list ver) old-version))) @@ -127,22 +127,36 @@ Used by `doom/packages-install'." (doom-get-packages))) ;;;###autoload -(defun doom*package-delete (name &rest _) +(defun doom*package-delete (desc &rest _) "Update `quelpa-cache' upon a successful `package-delete'." - (when (and (not (package-installed-p name)) - (quelpa-setup-p) - (assq name quelpa-cache)) - (setq quelpa-cache (assq-delete-all name quelpa-cache)) - (quelpa-save-cache) - (let ((path (expand-file-name (symbol-name name) quelpa-build-dir))) - (when (file-exists-p path) - (delete-directory path t))))) + (let ((name (package-desc-name desc))) + (when (and (not (package-installed-p name)) + (quelpa-setup-p) + (assq name quelpa-cache)) + (setq quelpa-cache (assq-delete-all name quelpa-cache)) + (quelpa-save-cache) + (let ((path (expand-file-name (symbol-name name) quelpa-build-dir))) + (when (file-exists-p path) + (delete-directory path t)))))) ;;; Private functions (defsubst doom--sort-alpha (it other) (string-lessp (symbol-name (car it)) (symbol-name (car other)))) +(defun doom--packages-choose (prompt) + (doom-initialize) + (let* ((table (mapcar + (lambda (p) (cons (package-desc-full-name p) p)) + (delq nil + (mapcar (lambda (p) (unless (package-built-in-p p) p)) + (apply #'append (mapcar #'cdr package-alist)))))) + (name (completing-read + prompt + (mapcar #'car table) + nil t))) + (cdr (assoc name table)))) + ;; ;; Main functions @@ -160,8 +174,9 @@ example; the package name can be omitted)." (recipe (plist-get plist :recipe))) (cond (recipe (quelpa recipe)) (t (package-install name)))) - (cl-pushnew (cons name plist) doom-packages :test #'eq :key #'car) - (package-installed-p name)) + (when (package-installed-p name) + (cl-pushnew (cons name plist) doom-packages :test #'eq :key #'car) + t)) (defun doom-update-package (name) "Updates package NAME if it is out of date, using quelpa or package.el as @@ -188,6 +203,11 @@ appropriate." (unless (package-installed-p name) (user-error "%s isn't installed" name)) (let ((inhibit-message (not doom-debug-mode))) + (unless (quelpa-setup-p) + (error "Could not initialize QUELPA")) + (when (assq name quelpa-cache) + (setq quelpa-cache (assq-delete-all name quelpa-cache)) + (quelpa-save-cache)) (package-delete (cadr (assq name package-alist)) force-p)) (not (package-installed-p name))) @@ -219,26 +239,29 @@ appropriate." (message! (yellow "Aborted!"))) (t + (doom-refresh-packages) (dolist (pkg packages) + (message! "Installing %s" (car pkg)) (condition-case ex - (message! - (cond ((package-installed-p (car pkg)) - (dark (white "Skipped %%%%s (already installed)"))) - ((doom-install-package (car pkg) (cdr pkg)) - (green "Installed %%s (%%s)")) - (t - (red "Failed to install %%s (%%s)"))) - (concat (symbol-name (car pkg)) - (when (plist-member (cdr pkg) :pin) - (format " [pinned: %s]" (plist-get (cdr pkg) :pin)))) - (pcase (doom-package-backend (car pkg)) - ('quelpa "QUELPA") - ('elpa "ELPA"))) + (progn + (message! + " %s%s" + (cond ((package-installed-p (car pkg)) + (dark (white "ALREADY INSTALLED"))) + ((doom-install-package (car pkg) (cdr pkg)) + (green "DONE")) + (t + (red "FAILED"))) + (if (plist-member (cdr pkg) :pin) + (format " [pinned: %s]" (plist-get (cdr pkg) :pin)) + ""))) ('user-error - (message! (bold (red "Error installing %s: %s" (car pkg) ex)))))) + (message! (bold (red " ERROR: %s" ex )))) + ('error + (message! (bold (red " FATAL ERROR: %s" ex ))))))) - (message! (bold (green "Finished!"))) - (doom/reload))))) + (message! (bold (green "Finished!"))) + (doom/reload)))) ;;;###autoload (defun doom/packages-update () @@ -267,16 +290,19 @@ appropriate." (message! (yellow "Aborted!"))) (t + (doom-refresh-packages) (dolist (pkg packages) + (message! "Updating %s" (car pkg)) (condition-case ex (message! (let ((result (doom-update-package (car pkg)))) (color (if result 'green 'red) - "%s %s" - (if result "Updated" "Failed to update") - (car pkg)))) + " %s" + (if result "DONE" "FAILED")))) ('user-error - (message! (bold (red "Error updating %s: %s" (car pkg) ex)))))) + (message! (bold (red " ERROR: %s" ex)))) + ('error + (message! (bold (red " FATAL ERROR: %s" ex)))))) (message! (bold (green "Finished!"))) (doom/reload))))) @@ -293,7 +319,10 @@ appropriate." (y-or-n-p (format "%s packages will be deleted:\n\n%s\n\nProceed?" (length packages) - (mapconcat (lambda (sym) (format "+ %s" sym)) + (mapconcat (lambda (sym) (format "+ %s (%s)" sym + (pcase (doom-package-backend sym) + ('quelpa "QUELPA") + ('elpa "ELPA")))) (sort (cl-copy-list packages) #'string-lessp) "\n"))))) (message! (yellow "Aborted!"))) @@ -305,10 +334,12 @@ appropriate." (let ((result (doom-delete-package pkg t))) (color (if result 'green 'red) "%s %s" - (if result "Deleted" "Failed to delete") + (if result "Removed" "Failed to remove") pkg))) ('user-error - (message! (bold (red "Error deleting %s: %s" pkg ex)))))) + (message! (bold (red " ERROR: %s" ex)))) + ('error + (message! (bold (red " FATAL ERROR: %s" ex)))))) (message! (bold (green "Finished!"))) (doom/reload))))) @@ -317,45 +348,54 @@ appropriate." (defalias 'doom/install-package #'package-install) ;;;###autoload -(defun doom/delete-package (package) +(defun doom/reinstall-package (desc) + "Reinstalls package package with optional quelpa RECIPE (see `quelpa-recipe' for +an example; the package package can be omitted)." + (declare (interactive-only t)) + (interactive + (list (doom--packages-choose "Reinstall package: "))) + (let ((package (package-desc-name desc))) + (doom-delete-package package t) + (doom-install-package package (cdr (assq package doom-packages))))) + +;;;###autoload +(defun doom/delete-package (desc) "Prompts the user with a list of packages and deletes the selected package. Use this interactively. Use `doom-delete-package' for direct calls." (declare (interactive-only t)) (interactive - (progn - (doom-initialize) - (list (completing-read - "Delete package: " - (cl-remove-if #'package-built-in-p package-alist :key #'car) - nil t)))) - (if (package-installed-p package) - (if (y-or-n-p (format "%s will be deleted. Confirm?" package)) - (message "%s %s" - (if (doom-delete-package package) "Deleted" "Failed to delete") - pkg) - (message "Aborted")) - (message "%s isn't installed" package))) + (list (doom--packages-choose "Delete package: "))) + (let ((package (package-desc-name desc))) + (if (package-installed-p package) + (if (y-or-n-p (format "%s will be deleted. Confirm?" package)) + (message "%s %s" + (if (doom-delete-package package t) "Deleted" "Failed to delete") + package) + (message "Aborted")) + (message "%s isn't installed" package)))) ;;;###autoload -(defun doom/update-package (package) +(defun doom/update-package (pkg) "Prompts the user with a list of outdated packages and updates the selected package. Use this interactively. Use `doom-update-package' for direct calls." (declare (interactive-only t)) (interactive - (let ((packages (doom-get-outdated-packages))) - (list - (if packages - (completing-read "Update package: " - (mapcar #'symbol-name (mapcar #'car packages))) - (user-error "All packages are up-to-date"))))) - (if-let (desc (doom-package-outdated-p (intern package))) - (if (y-or-n-p (format "%s will be updated from %s to %s. Update?" - (car desc) - (package-version-join (cadr desc)) - (package-version-join (cl-caddr desc)))) - (message "%s %s" - (if (doom-update-package package) "Updated" "Failed to update") - pkg) - (message "Aborted")) - (message "%s is up-to-date" package))) + (let* ((packages (doom-get-outdated-packages)) + (package (if packages + (completing-read "Update package: " + (mapcar #'car packages) + nil t) + (user-error "All packages are up to date")))) + (list (cdr (assq (car (assoc package package-alist)) packages))))) + (destructuring-bind (package old-version new-version) pkg + (if-let (desc (doom-package-outdated-p package)) + (let ((old-v-str (package-version-join old-version)) + (new-v-str (package-version-join new-version))) + (if (y-or-n-p (format "%s will be updated from %s to %s. Update?" + package old-v-str new-v-str)) + (message "%s %s (%s => %s)" + (if (doom-update-package package) "Updated" "Failed to update") + package old-v-str new-v-str) + (message "Aborted"))) + (message "%s is up-to-date" package)))) diff --git a/core/core-packages.el b/core/core-packages.el index 3fed86e4b..4b396b496 100644 --- a/core/core-packages.el +++ b/core/core-packages.el @@ -1,39 +1,40 @@ ;;; core-packages.el -;; Emacs package management is opinionated. Unfortunately, so am I. With the -;; help of `use-package', `quelpa' and package.el, DOOM Emacs manages its -;; plugins and their dependencies through `doom/packages-install', -;; `doom/packages-update' and `doom/packages-autoremove', which can be called -;; via `make' on the command line (make {install,update,autoremove}). +;; Emacs package management is opinionated. Unfortunately, so am I. I've bound +;; together `use-package', `quelpa' and package.el to create my own, +;; rolling-release, lazily-loaded package management system for Emacs. ;; -;; My config is divided into two parts: configuration and packages.el files. -;; You'll find packages.el files in each DOOM module (in `doom-modules-dir') and -;; one in `doom-core-dir'. When executed, they fill `doom-packages' and -;; `doom-modules', which are necessary for DOOM to extrapolate all kinds of -;; information about plugins. +;; The three key commands are `doom/packages-install', `doom/packages-update' +;; and `doom/packages-autoremove', which can be called via `make' on the command +;; line (make {install,update,autoremove}). These read packages.el files in each +;; activated module in `doom-modules-dir' (and one in `doom-core-dir') which +;; tell DOOM what plugins to install and where from. ;; ;; Why all the trouble? Because: -;; 1. Scriptability: I want an alternative to package.el's list-packages -;; interface for updating and installing packages. -;; 2. Flexibility: I sometimes want packages from sources other than ELPA. Like -;; github or the Emacs wiki, because certain plugins are out-of-date through -;; official channels, have changed hands unofficially, or simply haven't been -;; submitted to an ELPA repo yet. -;; 3. Stability: I don't want to worry that each time I use my package -;; manager something might inexplicably go wrong. Cask, I'm looking at you. -;; 4. Performance: A minor point, but it helps startup performance not to do -;; package.el initialization and package installation checks at startup. -;; 5. Simplicity: Without Cask, I have no external dependencies to worry about -;; (unless you count make). DOOM handles itself. Arguably, my emacs.d is -;; overcomplicated, but configuring is simpler as a result (well, for me -;; anyway :P). +;; 1. Scriptability: I live in the command line. I want a programmable +;; alternative to `list-packages' for updating and installing packages. +;; 2. Flexibility: I want packages from sources other than ELPA. Like github or +;; the Emacs wiki, because certain plugins are out-of-date through official +;; channels, have changed hands, or simply aren't in any ELPA repo. +;; 3. Stability: I used Cask before this. It would error out with cyrptic errors +;; depending on the version of Emacs I used and the alignment of the planets. +;; No more. +;; 4. Performance: A minor point, but this system is lazy-loaded (more so if you +;; byte-compile). Not having to initialize package.el (or check that your +;; packages are installed) every time you start up Emacs affords us precious +;; seconds. +;; 5. Simplicity: No Cask, no external dependencies (unless you count make). +;; Arguably, this means my config is overcomplicated, but shhh, it's alright. +;; Everything is fine. ;; -;; It should be safe to use package.el functionality, however, avoid -;; `package-autoremove' as it will not reliably select the correct packages to -;; delete. +;; Technically, package.el commands should still work. To be absolutely sure, +;; use the doom alternatives: ;; -;; For complete certainty, I've provided DOOM alternatives of package commands, -;; like `doom/install-package', `doom/delete-package' & `doom/update-packages'. +;; + `package-install': `doom/install-package' +;; + `package-reinstall': `doom/reinstall-package' +;; + `package-delete': `doom/delete-package' +;; + `package-update': `doom/update-package' +;; + `package-autoremove': `doom/packages-autoremove' ;; ;; See core/autoload/packages.el for more functions. @@ -537,5 +538,8 @@ package files." ;; Updates QUELPA after deleting a package (advice-add #'package-delete :after #'doom*package-delete) +;; It isn't safe to use `package-autoremove', so get rid of it +(advice-add #'package-autoremove :override #'doom/packages-autoremove) + (provide 'core-packages) ;;; core-packages.el ends here