Rewrite package management to be less hackish (untested)

This commit is contained in:
Henrik Lissner 2017-02-03 07:58:16 -05:00
parent 74aa0ab6a7
commit f2a31e9d87
7 changed files with 535 additions and 266 deletions

View file

@ -1,151 +1,272 @@
;;; packages.el
(defvar doom-packages-last-refresh nil
"A timestamp indicating the last time `package-refresh-contents' was run.")
;;;###autoload
(defun doom-package-outdated-p (package)
"Determine whether PACKAGE (a symbol) is outdated or not. If outdated, returns
a cons cell, whose car is the current version string of PACKAGE (a symbol), and
whose cdr is the latest version of the package. Be sure to run
`package-refresh-contents' beforehand, or the return value could be out of
date."
(unless package-selected-packages
(doom-initialize))
(when (and (memq package package-selected-packages)
(package-installed-p package)
(quelpa-setup-p))
(let* ((pkg-recipe (cdr (assq 'quelpa quelpa-cache)))
(cur-desc (cadr (or (assq package package-alist)
(assq package package--builtins))))
(cur-version (package-desc-version cur-desc))
(inhibit-message t)
new-version)
(setq new-version
(if pkg-recipe
(let ((ver (quelpa-checkout
pkg-recipe
(f-expand (symbol-name package) quelpa-build-dir))))
(or (and ver (version-to-list ver)) cur-version))
(package-desc-version (cadr (assq package package-archive-contents)))))
(unless (version-list-<= new-version cur-version)
(cons cur-version new-version)))))
(defun doom-refresh-packages ()
"Refresh ELPA packages."
(when (or (not doom-packages-last-refresh)
(> (nth 1 (time-since doom-packages-last-refresh)) 3600))
(doom-initialize)
(package-refresh-contents)
(setq doom-packages-last-refresh (current-time))))
;;;###autoload
(defun doom-package-elpa-p (name)
"Returns non-nil if NAME was a package installed with elpa."
(doom-initialize)
(and (assq name package-alist)
(not (doom-package-quelpa-p name))))
;;;###autoload
(defun doom-package-quelpa-p (name)
"Returns non-nil if NAME was a package installed with quelpa."
(unless (quelpa-setup-p)
(error "Could not initialize quelpa"))
(assq name quelpa-cache))
;;;###autoload
(defun doom-package-outdated-p (name)
"Determine whether NAME (a symbol) is outdated or not. If outdated, returns a
list, whose car is NAME, and cdr the current version list and latest version
list of the package."
(doom-refresh-packages)
(package-read-all-archive-contents)
(when (assq name package-alist)
(let* ((old-version
(package-desc-version (cadr (or (assq name package-alist)
(assq name package--builtins)))))
(new-version
(cond ((doom-package-quelpa-p name)
(let ((recipe (assq name quelpa-cache))
(dir (f-expand (symbol-name name) quelpa-build-dir))
(inhibit-message t))
(or (quelpa-checkout recipe dir)
old-version)))
((doom-package-elpa-p name)
(package-desc-version (cadr (assq name package-archive-contents)))))))
(unless (version-list-<= new-version old-version)
(cons name old-version new-version)))))
;;;###autoload
(defun doom-get-packages (&optional backend)
"Retrieves a list of explicitly installed packages (i.e. non-dependencies).
Each element is a cons cell, whose car is the package symbol and whose cdr is
the quelpa recipe (if any).
BACKEND can be 'quelpa or 'elpa, and will instruct this function to return only
the packages relevant to that backend."
(doom-initialize)
(unless (quelpa-setup-p)
(error "Could not initialize quelpa"))
(--map (cons it (assq it quelpa-cache))
(-intersection (package--find-non-dependencies)
(append (mapcar 'car doom-packages) doom-protected-packages))))
;;;###autoload
(defun doom-get-outdated-packages ()
"Return a list of packages that are out of date. Each element is a sublist,
containing (list package-symbol current-version-string new-version-string). Can
be fed to `doom/packages-update'."
(-non-nil (--map (doom-package-outdated-p (car it)) (doom-get-packages))))
;;;###autoload
(defun doom-get-orphaned-packages ()
"Return a list of packages that are no longer needed or depended on. Can be
fed to `doom/packages-delete'."
(doom-initialize)
(-difference (package--removable-packages)
doom-protected-packages))
;;;###autoload
(defun doom-get-packages-to-install ()
"Return a list of packages that aren't installed, but need to be. Used by
`doom/packages-install'."
(doom-refresh-self)
(--remove (assq (car it) package-alist)
(append doom-packages (-map 'list doom-protected-packages))))
;;
;; Main functions
;;
(defun doom-install-package (name &optional recipe)
"Installs package NAME with optional quelpa RECIPE (see `quelpa-recipe' for an
example; the package name can be omitted)."
(doom-refresh-packages)
(when (package-installed-p name)
(error "%s is already installed" name))
(cond (recipe (quelpa (plist-get plist :recipe)))
(t (package-install name)))
(add-to-list 'doom-packages (cons name recipe))
(package-installed-p name))
(defun doom-update-package (name)
"Updates package NAME if it is out of date, using quelpa or package.el as
appropriate."
(doom-refresh-packages)
(unless (package-installed-p name)
(error "%s isn't installed" name))
(when (doom-package-outdated-p name)
(let (quelpa-modified-p)
(cond ((doom-package-quelpa-p name)
(let ((quelpa-upgrade-p t))
(quelpa it)
(setq quelpa-modified-p t)))
(t
(let ((desc (cadr (assq name package-alist)))
(archive (cadr (assq name package-archive-contents))))
(package-install-from-archive archive)
(delete-directory (package-desc-dir desc) t))
(package-install name))))
(when quelpa-modified-p
(quelpa-save-cache))
(version-list-=
(package-desc-version (cadr (assq name package-alist)))
(package-desc-version (cadr (assq name package-archive-contents))))))
(defun doom-delete-package (name)
"Uninstalls package NAME if it exists, and clears it from `quelpa-cache'."
(doom-initialize)
(unless (package-installed-p name)
(error "%s isn't installed" name))
(let ((desc (cadr (assq package package-alist))))
(package-delete desc))
(when (and (quelpa-setup-p)
(assq name quelpa-cache))
(setq quelpa-cache (delq name quelpa-cache))
(let ((path (f-expand (symbol-name name) quelpa-build-dir)))
(when (f-exists-p path)
(delete-directory path t)))
(quelpa-save-cache))
(not (package-installed-p name)))
;;
;; Interactive commands
;;
;;;###autoload
(defun doom/packages-install ()
"Install missing packages."
(interactive)
(let ((pkg-n (doom-reload-packages :install)))
(if (= pkg-n 0)
(message "Nothing to install")
(message "\nInstalled %s packages:\n%s" pkg-n
(mapconcat (lambda (pkg) (concat "+ " (symbol-name pkg)))
doom-installed-packages "\n")))))
(let ((packages (doom-get-packages-to-install)))
(cond ((not packages)
(message "No packages to install!"))
((not (y-or-n-p
(format "%s packages will be installed:\n%s\n\nProceed?"
(length packages)
(mapconcat (lambda (pkg) (format "+ %s (%s)"
(symbol-name (car pkg))
(cond ((cdr pkg) "QUELPA")
(t "ELPA"))))
packages "\n"))))
(message "Aborted!"))
(t
(doom-message "Installing %s packages" (length packages))
(dolist (pkg packages)
(condition-case ex
(doom-message "%s %s (%s)"
(let ((plist (cdr pkg)))
(if (doom-install-package (car pkg) (cdr pkg))
"Installed"
"Failed to install"))
pkg
(cond ((cdr pkg) "QUELPA")
(t "ELPA")))
('error
(doom-message "Error installing %s: %s" (car pkg) ex))))
(doom-message "Finished!")))))
;;;###autoload
(defun doom/packages-update ()
"Update outdated packages. This includes quelpa-installed packages and ELPA
packages. This will delete old versions of packages as well."
(interactive)
(message "Refreshing packages...")
(doom-initialize t)
(if (not package-alist)
(message "No packages are installed")
(require 'quelpa)
(when (quelpa-setup-p)
(setq quelpa-cache (--filter (package-installed-p (car it)) quelpa-cache)))
(let* ((err 0)
(quelpa-packages (-map 'car quelpa-cache))
(elpa-packages (-difference (package--find-non-dependencies) quelpa-packages))
quelpa-modified-p
outdated-packages)
(message "ELPA\n%s\n\nQUELPA\n%s" elpa-packages quelpa-packages)
(dolist (pkg (append quelpa-packages elpa-packages))
(awhen (doom-package-outdated-p pkg)
(push (list pkg it) outdated-packages)))
(message "\nOUTDATED\n%s" outdated-packages)
;; (cond ((not outdated-packages)
;; (message "Everything is up-to-date"))
(let ((packages (doom-get-outdated-packages)))
(cond ((not packages)
(message "Everything is up-to-date"))
;; ((not (y-or-n-p
;; (format "%s packages will be updated:\n%s\n\nProceed?"
;; (length outdated-packages)
;; (mapconcat (lambda (pkg) (format "%s: %s -> %s"
;; (car pkg)
;; (car (cdr pkg))
;; (cdr (cdr pkg))))
;; (--sort (string-lessp (symbol-name (car it))
;; (symbol-name (car other)))
;; outdated-packages) ", "))))
;; (message "Aborted"))
((not (y-or-n-p
(format "%s packages will be updated:\n%s\n\nProceed?"
(length packages)
(mapconcat (lambda (pkg) (format "%s: %s -> %s"
(car pkg)
(car (cdr pkg))
(cdr (cdr pkg))))
(--sort (string-lessp (symbol-name (car it))
(symbol-name (car other)))
outdated-packages) ", "))))
(message "Aborted!"))
;; (t
;; (dolist (pkg outdated-packages)
;; (condition-case ex
;; (cond ((assq pkg quelpa-outdated-packages)
;; (let ((inhibit-message t))
;; (quelpa package)
;; (setq quelpa-modified-p t)))
;; ((memq pkg elpa-outdated-packages)
;; (let ((desc (cadr (assq pkg package-alist)))
;; (archive (cadr (assoc pkg package-archive-contents))))
;; (package-install-from-archive archive)
;; (delete-directory (package-desc-dir desc) t)))
;; (t (error "Not a valid package")))
;; ('error
;; (setq err (1+ err))
;; (message "ERROR (%s): %s" pkg ex))))))
;; (when quelpa-modified-p
;; (quelpa-save-cache))
;; (if (> err 0)
;; (message "Done, but with %s errors" err)
;; (message "Done"))
)))
(t
(dolist (pkg packages)
(condition-case ex
(doom-message "%s %s"
(if (doom-update-package pkg)
"Updated"
"Failed to update")
pkg)
('error
(doom-message "Error installing %s: %s" pkg ex))))
(doom-message "Finished!")))))
;;;###autoload
(defun doom/packages-clean ()
"Delete packages that are no longer used or depended on."
(defun doom/packages-autoremove ()
(interactive)
(doom-reload-packages)
(let* ((package-selected-packages (-intersection (package--find-non-dependencies)
(append doom-packages doom-protected-packages)))
(packages-to-delete (-difference (package--removable-packages) doom-protected-packages))
quelpa-modified-p)
(cond ((not package-selected-packages)
(message "No packages installed!"))
((not packages-to-delete)
(message "No unused packages to remove."))
(let ((packages (doom-get-orphaned-packages)))
(cond ((not packages)
(message "No unused packages to remove"))
((not (y-or-n-p
(format "%s packages will be deleted:\n%s\n\nProceed?"
(length packages-to-delete)
(mapconcat 'symbol-name (-sort 'string-lessp packages-to-delete) ", "))))
(message "Aborted."))
(length packages)
(mapconcat 'symbol-name (-sort 'string-lessp packages) ", "))))
(message "Aborted!"))
(t
(require 'quelpa)
(quelpa-setup-p)
(dolist (p packages-to-delete)
(package-delete (cadr (assq p package-alist)) t)
(when (and quelpa-cache (assq p quelpa-cache))
(setq quelpa-cache (assq-delete-all p quelpa-cache)
quelpa-modified-p t)))
(when quelpa-modified-p
(quelpa-save-cache))))))
(dolist (pkg packages)
(condition-case ex
(doom-message "%s %s"
(if (doom-delete-package pkg)
"Deleted"
"Failed to delete")
pkg)
('error
(doom-message "Error deleting %s: %s" pkg ex))))
(doom-message "Finished!")))))
;;;###autoload
(defun doom/packages-reload ()
"Reload `load-path' by scanning all packages. Run this if you ran make update
or make clean outside of Emacs."
(interactive)
(doom-initialize t)
(message "Reloaded %s packages" (length package-alist)))
(defalias 'doom/package-install 'package-install)
;;;###autoload
(defun doom/packages-delete (&optional package)
"Attempt to delete PACKAGE. Wraps around `package-delete'."
(interactive)
(doom-reload-packages)
(let* ((pkg (or package (completing-read "Delete package: " doom-packages nil t)))
(pkg-desc (cdr (assq pkg package-alist))))
(unless pkg-desc
(error "Couldn't find the package %s" package))
(package-delete pkg-desc)))
(defun doom/package-delete (&optional package)
(interactive
(list (completing-read "Delete package: " (doom-get-packages))))
(if (package-installed-p package)
(message "%s %s"
(if (doom-delete-package package)
"Deleted"
"Failed to delete")
pkg)
(message "%s isn't installed" package)))
;;;###autoload
(defun doom/package-update (&optional package)
(interactive
(list (completing-read "Update package: " (doom-get-packages))))
(if (doom-package-outdated-p package)
(message "%s %s"
(if (doom-update-package package)
"Updated"
"Failed to update")
pkg)
(message "%s is up-to-date" package)))