💥 Replace package.el/quelpa with straight #374

There are a few kinks to iron out, but for the most part it's done. Doom
Emacs, powered by straight. Goodbye gnutls and elpa/quelpa issues.

This update doesn't come with rollback or lockfile support yet, but I
will eventually include one with Doom, and packages will be (by default,
anyway) updated in sync with Doom.

Relevant threads: #1577 #1566 #1473
This commit is contained in:
Henrik Lissner 2019-07-21 15:39:45 +02:00
parent 492f2dea1e
commit b90dede1ab
No known key found for this signature in database
GPG key ID: 5F6C0EA160557395
35 changed files with 1542 additions and 1771 deletions

View file

@ -1,15 +1,18 @@
;;; core/autoload/cli.el -*- lexical-binding: t; -*-
;; Externs
(defvar evil-collection-mode-list)
(require 'core-cli)
;;;###autoload
(defun doom-cli-run (command &rest _args)
(defun doom--cli-run (command &rest _args)
(when (featurep 'general)
(general-auto-unbind-keys))
(let* ((evil-collection-mode-list nil)
(default-directory doom-emacs-dir)
(buf (get-buffer-create " *bin/doom*"))
(doom-message-backend 'ansi)
(doom-format-backend 'ansi)
(ignore-window-parameters t)
(noninteractive t)
(standard-output
@ -39,21 +42,21 @@
"TODO"
(interactive "P")
(let ((doom-auto-accept yes))
(doom-cli-run "autoloads")))
(doom--cli-run "autoloads")))
;;;###autoload
(defun doom//update (&optional yes)
"TODO"
(interactive "P")
(let ((doom-auto-accept yes))
(doom-cli-run "update")))
(doom--cli-run "update")))
;;;###autoload
(defun doom//upgrade (&optional yes)
"TODO"
(interactive "P")
(let ((doom-auto-accept yes))
(doom-cli-run "upgrade"))
(doom--cli-run "upgrade"))
(when (y-or-n-p "You must restart Emacs for the upgrade to take effect. Restart?")
(doom/restart-and-restore)))
@ -62,18 +65,18 @@
"TODO"
(interactive "P")
(let ((doom-auto-accept yes))
(doom-cli-run "install")))
(doom--cli-run "install")))
;;;###autoload
(defun doom//autoremove (&optional yes)
"TODO"
(interactive "P")
(let ((doom-auto-accept yes))
(doom-cli-run "autoremove")))
(doom--cli-run "autoremove")))
;;;###autoload
(defun doom//refresh (&optional yes)
"TODO"
(interactive "P")
(let ((doom-auto-accept yes))
(doom-cli-run "refresh")))
(doom--cli-run "refresh")))

View file

@ -16,65 +16,54 @@ ready to be pasted in a bug report on github."
(require 'vc-git)
(let ((default-directory doom-emacs-dir)
(doom-modules (doom-modules)))
(format
(concat "- OS: %s (%s)\n"
"- Shell: %s\n"
"- Emacs: %s (%s)\n"
"- Doom: %s (%s)\n"
"- Graphic display: %s (daemon: %s)\n"
"- System features: %s\n"
"- Details:\n"
" ```elisp\n"
" env bootstrapper: %s\n"
" elc count: %s\n"
" uname -a: %s\n"
" modules: %s\n"
" packages: %s\n"
" exec-path: %s\n"
" ```")
system-type system-configuration
shell-file-name
emacs-version (format-time-string "%b %d, %Y" emacs-build-time)
doom-version
(or (string-trim (shell-command-to-string "git log -1 --format=\"%D %h %ci\""))
"n/a")
(display-graphic-p) (daemonp)
(bound-and-true-p system-configuration-features)
(cond ((file-exists-p doom-env-file) 'envvar-file)
((featurep 'exec-path-from-shell) 'exec-path-from-shell))
;; details
(length (doom-files-in `(,@doom-modules-dirs
,doom-core-dir
,doom-private-dir)
:type 'files :match "\\.elc$" :sort nil))
(if IS-WINDOWS
"n/a"
(with-temp-buffer
(unless (zerop (call-process "uname" nil t nil "-msrv"))
(insert (format "%s" system-type)))
(string-trim (buffer-string))))
(or (cl-loop with cat = nil
for key being the hash-keys of doom-modules
if (or (not cat) (not (eq cat (car key))))
do (setq cat (car key))
and collect cat
and collect (cdr key)
else collect
(let ((flags (doom-module-get cat (cdr key) :flags)))
(if flags
`(,(cdr key) ,@flags)
(cdr key))))
"n/a")
(or (ignore-errors
(require 'use-package)
(cl-loop for (name . plist) in (doom-find-packages :private t)
if (use-package-plist-delete (copy-sequence plist) :modules)
collect (format "%s" (cons name it))
else
collect (symbol-name name)))
"n/a")
;; abbreviate $HOME to hide username
(mapcar #'abbreviate-file-name exec-path))))
(cl-letf
(((symbol-function 'sh)
(lambda (format)
(string-trim
(shell-command-to-string format)))))
`((emacs
(version . ,emacs-version)
(features ,@system-configuration-features)
(build . ,(format-time-string "%b %d, %Y" emacs-build-time))
(buildopts ,system-configuration-options))
(doom
(version . ,doom-version)
(build . ,(sh "git log -1 --format=\"%D %h %ci\"")))
(system
(type . ,system-type)
(config . ,system-configuration)
(shell . ,shell-file-name)
(uname . ,(if IS-WINDOWS
"n/a"
(sh "uname -msrv")))
(path . ,(mapcar #'abbreviate-file-name exec-path)))
(config
(envfile . ,(cond ((file-exists-p doom-env-file) 'envvar-file)
((featurep 'exec-path-from-shell) 'exec-path-from-shell)))
(elc-files . ,(length (doom-files-in `(,@doom-modules-dirs
,doom-core-dir
,doom-private-dir)
:type 'files :match "\\.elc$" :sort nil)))
(modules ,@(or (cl-loop with cat = nil
for key being the hash-keys of doom-modules
if (or (not cat) (not (eq cat (car key))))
do (setq cat (car key))
and collect cat
and collect (cdr key)
else collect
(let ((flags (doom-module-get cat (cdr key) :flags)))
(if flags
`(,(cdr key) ,@flags)
(cdr key))))
'("n/a")))
(packages ,@(or (ignore-errors
(require 'use-package)
(cl-loop for (name . plist) in (doom-find-packages :private t)
if (use-package-plist-delete (copy-sequence plist) :modules)
collect (format "%s" (cons name it))
else
collect (symbol-name name)))
'("n/a"))))))))
;;
@ -86,24 +75,55 @@ ready to be pasted in a bug report on github."
branch and commit."
(interactive)
(require 'vc-git)
(print! "Doom v%s (Emacs v%s)\nBranch: %s\nCommit: %s"
(print! "Doom v%s (Emacs v%s)\nBranch: %s\nCommit: %s\nBuild date: %s"
doom-version
emacs-version
(or (vc-git--symbolic-ref doom-core-dir)
"n/a")
(or (vc-git-working-revision doom-core-dir)
"n/a")
(or (string-trim (shell-command-to-string "git log -1 --format=%ci"))
"n/a")))
;;;###autoload
(defun doom/info ()
(defun doom/info (&optional raw)
"Collects some debug information about your Emacs session, formats it into
markdown and copies it to your clipboard, ready to be pasted into bug reports!"
(interactive)
(message "Generating Doom info...")
(if noninteractive
(print! (doom-info))
(kill-new (doom-info))
(message "Done! Copied to clipboard.")))
(interactive "P")
(let ((buffer (get-buffer-create "*doom-info*"))
(info (doom-info)))
(with-current-buffer buffer
(unless (or noninteractive
(eq major-mode 'markdown-mode)
(not (fboundp 'markdown-mode)))
(markdown-mode))
(erase-buffer)
(if raw
(progn
(save-excursion
(pp info (current-buffer)))
(when (re-search-forward "(modules " nil t)
(goto-char (match-beginning 0))
(cl-destructuring-bind (beg . end)
(bounds-of-thing-at-point 'sexp)
(let ((sexp (prin1-to-string (sexp-at-point))))
(delete-region beg end)
(insert sexp)))))
(insert "<details>\n\n```\n")
(dolist (group info)
(insert! "%-8s%-10s %s\n"
((car group)
(caadr group)
(cdadr group)))
(dolist (spec (cddr group))
(insert! (indent 8 "%-10s %s\n")
((car spec) (cdr spec)))))
(insert "```\n</details>"))
(if noninteractive
(print! (buffer-string))
(switch-to-buffer buffer)
(kill-new (buffer-string))
(print! (green "Copied markdown to clipboard"))))))
;;;###autoload
(defun doom/am-i-secure ()
@ -144,11 +164,11 @@ markdown and copies it to your clipboard, ready to be pasted into bug reports!"
(macroexp-progn
(append `((setq noninteractive nil
doom-debug-mode t
load-path ',load-path
package--init-file-ensured t
package-user-dir ,package-user-dir
package-archives ',package-archives
user-emacs-directory ,doom-emacs-dir
doom--modules-cache nil)
user-emacs-directory ,doom-emacs-dir)
(with-eval-after-load 'undo-tree
;; undo-tree throws errors because `buffer-undo-tree' isn't
;; corrrectly initialized

View file

@ -365,10 +365,14 @@ current file is in, or d) the module associated with the current major mode (see
(recenter)
(message "Couldn't find the config block"))))))))
(defvar doom--help-packages-list nil)
(defun doom--help-packages-list (&optional refresh)
(or (unless refresh
(doom-cache-get 'help-packages))
(doom-cache-set 'help-packages (doom-package-list 'all))))
doom--help-packages-list)
(setq doom--help-packages-list
(append (cl-loop for package in doom-core-packages
collect (list package :modules '((:core internal))))
(doom-package-list 'all)))))
(defun doom--help-package-configs (package)
;; TODO Add git checks, in case ~/.emacs.d isn't a git repo

View file

@ -1,4 +1,4 @@
;;; core/autoload/hydras.el -*- lexical-binding: t; -*-
;;; core/autoload/hydras.el -*- lexical-binding: t; no-byte-compile: t; -*-
;;;###autoload (autoload 'doom-text-zoom-hydra/body "core/autoload/hydras" nil t)
(defhydra doom-text-zoom-hydra (:hint t :color red)

View file

@ -1,128 +1,67 @@
;;; core/autoload/packages.el -*- lexical-binding: t; -*-
(require 'core-packages)
(load! "cache") ; in case autoloads haven't been generated yet
(defun doom--packages-choose (prompt)
(let ((table (cl-loop for pkg in package-alist
unless (doom-package-built-in-p (cdr pkg))
collect (cons (package-desc-full-name (cdr pkg))
(cdr pkg)))))
(cdr (assoc (completing-read prompt
(mapcar #'car table)
nil t)
table))))
(defun doom--refresh-pkg-cache ()
"Clear the cache for `doom-refresh-packages-maybe'."
(setq doom--refreshed-p nil)
(doom-cache-set 'last-pkg-refresh nil))
;;;###autoload
(defun doom-refresh-packages-maybe (&optional force-p)
"Refresh ELPA packages, if it hasn't been refreshed recently."
(when force-p
(doom--refresh-pkg-cache))
(unless (or (doom-cache-get 'last-pkg-refresh)
doom--refreshed-p)
(condition-case e
(progn
(message "Refreshing package archives")
(package-refresh-contents)
(doom-cache-set 'last-pkg-refresh t 1200))
((debug error)
(doom--refresh-pkg-cache)
(signal 'doom-error e)))))
;;
;;; Package metadata
;;;###autoload
(defun doom-package-plist (package)
(defun doom-package-get (package &optional prop nil-value)
"Returns PACKAGE's `package!' recipe from `doom-packages'."
(cdr (assq package doom-packages)))
(let ((plist (cdr (assq package doom-packages))))
(if prop
(if (plist-member plist prop)
(plist-get plist prop)
nil-value)
plist)))
;;;###autoload
(defun doom-package-desc (package)
"Returns PACKAGE's desc struct from `package-alist'."
(cadr (assq (or (car (doom-package-prop package :recipe))
package)
package-alist)))
(defun doom-package-recipe (package &optional prop nil-value)
"Returns the `straight' recipe PACKAGE was registered with."
(let ((plist (gethash (symbol-name package) straight--recipe-cache)))
(if prop
(if (plist-member plist prop)
(plist-get plist prop)
nil-value)
plist)))
;;;###autoload
(defun doom-package-true-name (package)
"Return PACKAGE's true name.
It is possible for quelpa packages to be given a psuedonym (the first argument
of `package!'). Its real name is the car of package's :recipe. e.g.
(package! X :recipe (Y :fetcher github :repo \"abc/def\"))
X's real name is Y."
(let ((sym (car (doom-package-prop package :recipe))))
(or (and (symbolp sym)
(not (keywordp sym))
sym)
package)))
(defun doom-package-build-recipe (package &optional prop nil-value)
"Returns the `straight' recipe PACKAGE was installed with."
(let ((plist (nth 2 (gethash (symbol-name package) straight--build-cache))))
(if prop
(if (plist-member plist prop)
(plist-get plist prop)
nil-value)
plist)))
;;;###autoload
(defun doom-package-psuedo-name (package)
(defun doom-package-build-time (package)
"TODO"
(or (cl-loop for (package . plist) in doom-packages
for recipe-name = (car (plist-get plist :recipe))
if (eq recipe-name package)
return recipe-name)
package))
(car (gethash (symbol-name package) straight--build-cache)))
;;;###autoload
(defun doom-package-backend (package &optional noerror)
"Return backend that PACKAGE was installed with.
(defun doom-package-dependencies (package &optional recursive noerror)
"Return a list of dependencies for a package."
(let ((deps (nth 1 (gethash (symbol-name package) straight--build-cache))))
(if recursive
(nconc deps (mapcan (lambda (dep) (doom-package-dependencies dep t t))
deps))
deps)))
Can either be elpa, quelpa or emacs (built-in). Throws an error if NOERROR is
nil and the package isn't installed.
See `doom-package-recipe-backend' to get the backend PACKAGE is registered with
\(as opposed to what it is was installed with)."
(cl-check-type package symbol)
(let ((package-truename (doom-package-true-name package)))
(cond ((assq package-truename quelpa-cache) 'quelpa)
((assq package-truename package-alist) 'elpa)
((doom-package-built-in-p package) 'emacs)
((not noerror) (error "%s package is not installed" package)))))
;;;###autoload
(defun doom-package-recipe-backend (package &optional noerror)
"Return backend that PACKAGE is registered with.
See `doom-package-backend' to get backend for currently installed package."
(cl-check-type package symbol)
(cond ((not (doom-package-registered-p package))
(unless noerror
(error "%s package is not registered" package)))
((let ((builtin (eval (doom-package-prop package :built-in) t)))
(or (and (eq builtin 'prefer)
(locate-library (symbol-name package) nil doom-site-load-path))
(eq builtin 't)))
'emacs)
((doom-package-prop package :recipe)
'quelpa)
('elpa)))
;;;###autoload
(defun doom-package-prop (package prop &optional nil-value)
"Return PROPerty in PACKAGE's plist.
Otherwise returns NIL-VALUE if package isn't registered or PROP doesn't
exist/isn't specified."
(cl-check-type package symbol)
(cl-check-type prop keyword)
(if-let (plist (doom-package-plist package))
(if (plist-member plist prop)
(plist-get plist prop)
nil-value)
nil-value))
(defun doom-package-depending-on (package &optional noerror)
"Return a list of packages that depend on the package named NAME."
(cl-check-type name symbol)
;; can't get dependencies for built-in packages
(unless (or (doom-package-build-recipe name)
noerror)
(error "Couldn't find %s, is it installed?" name))
(cl-loop for pkg in (hash-table-keys straight--build-cache)
for deps = (doom-package-dependencies pkg)
if (memq package deps)
collect pkg
and append (doom-package-depending-on pkg t)))
;;
@ -131,99 +70,52 @@ exist/isn't specified."
;;;###autoload
(defun doom-package-built-in-p (package)
"Return non-nil if PACKAGE (a symbol) is built-in."
(unless (doom-package-installed-p package)
(or (package-built-in-p (doom-package-true-name package))
(locate-library (symbol-name package) nil doom-site-load-path))))
(eq (doom-package-build-recipe package :type)
'built-in))
;;;###autoload
(defun doom-package-installed-p (package)
"Return non-nil if PACKAGE (a symbol) is installed."
(when-let (desc (doom-package-desc package))
(and (package-installed-p desc)
(file-directory-p (package-desc-dir desc)))))
(file-directory-p (straight--build-dir (symbol-name package))))
;;;###autoload
(defun doom-package-registered-p (package)
"Return non-nil if PACKAGE (a symbol) has been registered with `package!'.
Excludes packages that have a non-nil :built-in property."
(let ((package (or (cl-loop for (pkg . plist) in doom-packages
for newname = (car (plist-get plist :recipe))
if (and (symbolp newname)
(eq newname package))
return pkg)
package)))
(when-let (plist (doom-package-plist package))
(not (eval (plist-get plist :ignore))))))
(when-let (plist (doom-package-get package))
(not (eval (plist-get plist :ignore) t))))
;;;###autoload
(defun doom-package-private-p (package)
"Return non-nil if PACKAGE was installed by the user's private config."
(doom-package-prop package :private))
(doom-package-get package :private))
;;;###autoload
(defun doom-package-protected-p (package)
"Return non-nil if PACKAGE is protected.
A protected package cannot be deleted and will be auto-installed if missing."
(memq (doom-package-true-name package) doom-core-packages))
(memq package doom-core-packages))
;;;###autoload
(defun doom-package-core-p (package)
"Return non-nil if PACKAGE is a core Doom package."
(or (doom-package-protected-p package)
(assq :core (doom-package-prop package :modules))))
;;;###autoload
(defun doom-package-different-backend-p (package)
"Return t if a PACKAGE (a symbol) has a new backend than what it was installed
with. Returns nil otherwise, or if package isn't installed."
(cl-check-type package symbol)
(and (doom-package-installed-p package)
(not (doom-get-depending-on package)) ; not a dependency
(not (eq (doom-package-backend package 'noerror)
(doom-package-recipe-backend package 'noerror)))))
(assq :core (doom-package-get package :modules))))
;;;###autoload
(defun doom-package-different-recipe-p (name)
"Return t if a package named NAME (a symbol) has a different recipe than it
was installed with."
(cl-check-type name symbol)
(when (doom-package-installed-p name)
(let ((package-truename (doom-package-true-name name)))
(when-let* ((quelpa-recipe (assq package-truename quelpa-cache))
(doom-recipe (assq package-truename doom-packages)))
(not (equal (cdr quelpa-recipe)
(cdr (plist-get (cdr doom-recipe) :recipe))))))))
(defvar quelpa-upgrade-p)
;;;###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."
(cl-check-type name symbol)
(when-let (desc (doom-package-desc name))
(let* ((old-version (package-desc-version desc))
(new-version
(pcase (doom-package-backend name)
(`quelpa
(let ((recipe (doom-package-prop name :recipe))
(dir (expand-file-name (symbol-name name) quelpa-build-dir))
(inhibit-message (not doom-debug-mode))
(quelpa-upgrade-p t))
(if-let (ver (quelpa-checkout recipe dir))
(version-to-list ver)
old-version)))
(`elpa
(let ((desc (cadr (assq name package-archive-contents))))
(when (package-desc-p desc)
(package-desc-version desc)))))))
(unless (and (listp old-version) (listp new-version))
(error "Couldn't get version for %s" name))
(when (version-list-< old-version new-version)
(list name old-version new-version)))))
;; TODO
;; (when (doom-package-installed-p name)
;; (when-let* ((doom-recipe (assq name doom-packages))
;; (install-recipe (doom-package-recipe)))
;; (not (equal (cdr quelpa-recipe)
;; (cdr (plist-get (cdr doom-recipe) :recipe))))))
)
;;
@ -307,32 +199,37 @@ files."
collect (cons sym plist)
and if (and deps (not (doom-package-built-in-p sym)))
nconc
(cl-loop for pkg in (doom-get-dependencies-for sym 'recursive 'noerror)
(cl-loop for pkg in (doom-package-dependencies sym 'recursive 'noerror)
if (or (eq installed 'any)
(if installed
(doom-package-installed-p pkg)
(not (doom-package-installed-p pkg))))
collect (cons pkg (cdr (assq pkg doom-packages)))))))
(defun doom--read-module-packages-file (file &optional raw noerror)
(defun doom--read-module-packages-file (file &optional eval noerror)
(with-temp-buffer ; prevent buffer-local settings from propagating
(condition-case e
(if (not raw)
(if (not eval)
(load file noerror t t)
(when (file-readable-p file)
(insert-file-contents file)
(delay-mode-hooks (emacs-lisp-mode))
(while (re-search-forward "(package! " nil t)
(save-excursion
(goto-char (match-beginning 0))
(cl-destructuring-bind (name . plist) (cdr (sexp-at-point))
(push (cons name
(plist-put plist :modules
(cond ((file-in-directory-p file doom-private-dir)
'((:private)))
((file-in-directory-p file doom-core-dir)
'((:core)))
((doom-module-from-path file)))))
doom-packages))))))
(unless (string-match-p
"^.*;" (buffer-substring-no-properties
(line-beginning-position)
(point)))
(cl-destructuring-bind (name . plist) (cdr (sexp-at-point))
(push (cons name
(plist-put plist :modules
(cond ((file-in-directory-p file doom-private-dir)
'((:private)))
((file-in-directory-p file doom-core-dir)
'((:core)))
((doom-module-from-path file)))))
doom-packages)))))))
((debug error)
(signal 'doom-package-error
(list (or (doom-module-from-path file)
@ -350,15 +247,16 @@ ones."
(let ((noninteractive t)
(doom-modules (doom-modules))
doom-packages
doom-disabled-packages
package-pinned-packages)
(doom--read-module-packages-file (expand-file-name "packages.el" doom-core-dir) all-p)
doom-disabled-packages)
(doom--read-module-packages-file
(expand-file-name "packages.el" doom-core-dir)
all-p t)
(let ((private-packages (expand-file-name "packages.el" doom-private-dir)))
(unless all-p
;; We load the private packages file twice to ensure disabled packages
;; are seen ASAP, and a second time to ensure privately overridden
;; packages are properly overwritten.
(doom--read-module-packages-file private-packages nil t))
(doom--read-module-packages-file private-packages t t))
(if all-p
(mapc #'doom--read-module-packages-file
(doom-files-in doom-modules-dir
@ -371,192 +269,11 @@ ones."
for doom--current-module = key
do (doom--read-module-packages-file path nil t)))
(doom--read-module-packages-file private-packages all-p t))
(append (cl-loop for package in doom-core-packages
collect (list package :modules '((:core internal))))
(nreverse doom-packages))))
;;;###autoload
(defun doom-get-package-alist ()
"Returns a list of all desired packages, their dependencies and their desc
objects, in the order of their `package! blocks.'"
(cl-remove-duplicates
(cl-loop for name in (mapcar #'car doom-packages)
if (assq name package-alist)
nconc (cl-loop for dep in (package--get-deps name)
if (assq dep package-alist)
collect (cons dep (cadr it)))
and collect (cons name (cadr it)))
:key #'car
:from-end t))
;;;###autoload
(defun doom-get-depending-on (name &optional noerror)
"Return a list of packages that depend on the package named NAME."
(cl-check-type name symbol)
(setq name (or (car (doom-package-prop name :recipe)) name))
(unless (doom-package-built-in-p name)
(if-let (desc (cadr (assq name package-alist)))
(mapcar #'package-desc-name (package--used-elsewhere-p desc nil t))
(unless noerror
(error "Couldn't find %s, is it installed?" name)))))
;;;###autoload
(defun doom-get-dependencies-for (name &optional recursive noerror)
"Return a list of dependencies for a package."
(cl-check-type name symbol)
;; can't get dependencies for built-in packages
(unless (doom-package-built-in-p name)
(if-let (desc (doom-package-desc name))
(let* ((deps (mapcar #'car (package-desc-reqs desc)))
(deps (cl-remove-if #'doom-package-built-in-p deps)))
(if recursive
(nconc deps (mapcan (lambda (dep) (doom-get-dependencies-for dep t t))
deps))
deps))
(unless noerror
(error "Couldn't find %s, is it installed?" name)))))
;;;###autoload
(defun doom-get-outdated-packages (&optional include-frozen-p)
"Return a list of packages that are out of date. Each element is a list,
containing (PACKAGE-SYMBOL OLD-VERSION-LIST NEW-VERSION-LIST).
If INCLUDE-FROZEN-P is non-nil, check frozen packages as well.
Used by `doom-packages-update'."
(doom-refresh-packages-maybe doom-debug-mode)
(cl-loop for package in (mapcar #'car package-alist)
when (and (or (not (eval (doom-package-prop package :freeze)))
include-frozen-p)
(not (eval (doom-package-prop package :ignore)))
(not (doom-package-different-backend-p package))
(doom-package-outdated-p package))
collect it))
;;;###autoload
(defun doom-get-orphaned-packages ()
"Return a list of symbols representing packages that are no longer needed or
depended on.
Used by `doom-packages-autoremove'."
(let ((package-selected-packages
(mapcar #'car (doom-find-packages :ignored nil :disabled nil))))
(append (cl-remove-if #'doom-package-registered-p (package--removable-packages))
(cl-loop for pkg in package-selected-packages
if (and (doom-package-different-backend-p pkg)
(not (doom-package-built-in-p pkg)))
collect pkg))))
;;;###autoload
(defun doom-get-missing-packages ()
"Return a list of requested packages that aren't installed or built-in, but
are enabled (with a `package!' directive). Each element is a list whose CAR is
the package symbol, and whose CDR is a plist taken from that package's
`package!' declaration.
Used by `doom-packages-install'."
(cl-loop for (name . plist)
in (doom-find-packages :ignored nil
:disabled nil
:deps t)
if (and (equal (plist-get plist :pin)
(ignore-errors
(package-desc-archive
(cadr (assq name package-alist)))))
(or (not (doom-package-installed-p name))
(doom-package-different-backend-p name)
(doom-package-different-recipe-p name)))
collect (cons name plist)))
(nreverse doom-packages)))
;;
;; Main functions
(defun doom--delete-package-files (name-or-desc)
(let ((pkg-build-dir
(if (package-desc-p name-or-desc)
(package-desc-dir name-or-desc)
(expand-file-name (symbol-name name-or-desc) quelpa-build-dir))))
(when (file-directory-p pkg-build-dir)
(delete-directory pkg-build-dir t))))
;;;###autoload
(defun doom-install-package (name &optional plist)
"Installs package NAME with optional quelpa RECIPE (see `quelpa-recipe' for an
example; the package name can be omitted)."
(cl-check-type name symbol)
(when (and (doom-package-installed-p name)
(not (doom-package-built-in-p name)))
(if (or (doom-package-different-backend-p name)
(doom-package-different-recipe-p name))
(doom-delete-package name t)
(user-error "%s is already installed" name)))
(let* ((inhibit-message (not doom-debug-mode))
(plist (or plist (doom-package-plist name))))
(if-let (recipe (plist-get plist :recipe))
(condition-case e
(let (quelpa-upgrade-p)
(quelpa recipe))
((debug error)
(doom--delete-package-files name)
(signal (car e) (cdr e))))
(package-install name))
(if (not (doom-package-installed-p name))
(doom--delete-package-files name)
(add-to-list 'package-selected-packages name nil 'eq)
(setf (alist-get name doom-packages) plist)
name)))
;;;###autoload
(defun doom-update-package (name &optional force-p)
"Updates package NAME (a symbol) if it is out of date, using quelpa or
package.el as appropriate."
(cl-check-type name symbol)
(unless (doom-package-installed-p name)
(error "%s isn't installed" name))
(when (doom-package-different-backend-p name)
(user-error "%s's backend has changed and must be uninstalled first" name))
(when (or force-p (doom-package-outdated-p name))
(let ((inhibit-message (not doom-debug-mode))
(desc (doom-package-desc name)))
(pcase (doom-package-backend name)
(`quelpa
(let ((name (doom-package-true-name name)))
(condition-case e
(let ((quelpa-upgrade-p t))
(quelpa (assq name quelpa-cache)))
((debug error)
(doom--delete-package-files name)
(signal (car e) (cdr e))))))
(`elpa
(let* ((archive (cadr (assq name package-archive-contents)))
(packages
(if (package-desc-p archive)
(package-compute-transaction (list archive) (package-desc-reqs archive))
(package-compute-transaction () (list (list archive))))))
(package-download-transaction packages))))
(unless (doom-package-outdated-p name)
(doom--delete-package-files desc)
t))))
;;;###autoload
(defun doom-delete-package (name &optional force-p)
"Uninstalls package NAME if it exists, and clears it from `quelpa-cache'."
(cl-check-type name symbol)
(unless (doom-package-installed-p name)
(user-error "%s isn't installed" name))
(let ((inhibit-message (not doom-debug-mode))
(name (doom-package-true-name name)))
(when-let (spec (assq name quelpa-cache))
(delq! spec quelpa-cache)
(quelpa-save-cache))
(package-delete (doom-package-desc name) force-p)
(doom--delete-package-files name)
(not (doom-package-installed-p name))))
;;
;; Interactive commands
;;; Main functions
;;;###autoload
(defun doom/reload-packages ()
@ -565,61 +282,3 @@ package.el as appropriate."
(message "Reloading packages")
(doom-initialize-packages t)
(message "Reloading packages...DONE"))
;;;###autoload
(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))
(selection (if packages
(completing-read "Update package: "
(mapcar #'car packages)
nil t)
(user-error "All packages are up to date")))
(name (car (assoc (intern selection) package-alist))))
(unless name
(user-error "'%s' is already up-to-date" selection))
(list (assq name packages))))
(cl-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 t) "Updated" "Failed to update")
package old-v-str new-v-str)
(message "Aborted")))
(message "%s is up-to-date" package))))
;;
;; Advice
;;;###autoload
(defun doom*package-delete (desc &rest _)
"Update `quelpa-cache' upon a successful `package-delete'."
(let ((name (package-desc-name desc)))
(unless (doom-package-installed-p name)
(when-let (spec (assq name quelpa-cache))
(setq quelpa-cache (delq spec quelpa-cache))
(quelpa-save-cache)
(doom--delete-package-files name)))))
;;
;; Make package.el cooperate with Doom
;; Updates QUELPA after deleting a package
;;;###autoload
(advice-add #'package-delete :after #'doom*package-delete)
;; Replace with Doom variants
;;;###autoload
(advice-add #'package-autoremove :override #'doom//autoremove)
;;;###autoload
(advice-add #'package-install-selected-packages :override #'doom//install)