Polish package management system; no infinite recursion; smarter autoload refresh

This commit is contained in:
Henrik Lissner 2017-02-03 19:20:47 -05:00
parent 70a1fb52cc
commit e80df3c03c
4 changed files with 122 additions and 84 deletions

View file

@ -58,7 +58,7 @@ the quelpa recipe (if any).
BACKEND can be 'quelpa or 'elpa, and will instruct this function to return only BACKEND can be 'quelpa or 'elpa, and will instruct this function to return only
the packages relevant to that backend." the packages relevant to that backend."
(doom-initialize) (doom-reload)
(unless (quelpa-setup-p) (unless (quelpa-setup-p)
(error "Could not initialize quelpa")) (error "Could not initialize quelpa"))
(--map (cons it (assq it quelpa-cache)) (--map (cons it (assq it quelpa-cache))
@ -76,18 +76,31 @@ be fed to `doom/packages-update'."
(defun doom-get-orphaned-packages () (defun doom-get-orphaned-packages ()
"Return a list of packages that are no longer needed or depended on. Can be "Return a list of packages that are no longer needed or depended on. Can be
fed to `doom/packages-delete'." fed to `doom/packages-delete'."
(doom-initialize) (doom-reload)
(-difference (package--removable-packages) (let ((package-selected-packages
doom-protected-packages)) (append (mapcar 'car doom-packages) doom-protected-packages)))
(package--removable-packages)))
;;;###autoload ;;;###autoload
(defun doom-get-packages-to-install () (defun doom-get-packages-to-install ()
"Return a list of packages that aren't installed, but need to be. Used by "Return a list of packages that aren't installed, but need to be. Used by
`doom/packages-install'." `doom/packages-install'."
(doom-refresh-self) (doom-reload)
(--remove (assq (car it) package-alist) (--remove (assq (car it) package-alist)
(append doom-packages (-map 'list doom-protected-packages)))) (append doom-packages (-map 'list doom-protected-packages))))
;;;###autoload
(defun doom*package-delete (name)
"Makes `package-delete' update `quelpa-cache'."
(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 (f-expand (symbol-name name) quelpa-build-dir)))
(when (f-exists-p path)
(delete-directory path t)))))
;; ;;
;; Main functions ;; Main functions
@ -133,15 +146,8 @@ appropriate."
(doom-initialize) (doom-initialize)
(unless (package-installed-p name) (unless (package-installed-p name)
(error "%s isn't installed" name)) (error "%s isn't installed" name))
(let ((desc (cadr (assq package package-alist)))) (let ((desc (cadr (assq name package-alist))))
(package-delete desc)) (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))) (not (package-installed-p name)))
@ -152,6 +158,7 @@ appropriate."
;;;###autoload ;;;###autoload
(defun doom/packages-install () (defun doom/packages-install ()
"Interactive command for installing missing packages."
(interactive) (interactive)
(let ((packages (doom-get-packages-to-install))) (let ((packages (doom-get-packages-to-install)))
(cond ((not packages) (cond ((not packages)
@ -187,6 +194,7 @@ appropriate."
;;;###autoload ;;;###autoload
(defun doom/packages-update () (defun doom/packages-update ()
"Interactive command for updating packages."
(interactive) (interactive)
(let ((packages (doom-get-outdated-packages))) (let ((packages (doom-get-outdated-packages)))
(cond ((not packages) (cond ((not packages)
@ -219,6 +227,7 @@ appropriate."
;;;###autoload ;;;###autoload
(defun doom/packages-autoremove () (defun doom/packages-autoremove ()
"Interactive command for auto-removing orphaned packages."
(interactive) (interactive)
(let ((packages (doom-get-orphaned-packages))) (let ((packages (doom-get-orphaned-packages)))
(cond ((not packages) (cond ((not packages)
@ -243,7 +252,6 @@ appropriate."
(doom-message "Finished!"))))) (doom-message "Finished!")))))
;;;###autoload ;;;###autoload
(defalias 'doom/package-install 'package-install) (defalias 'doom/package-install 'package-install)

View file

@ -60,14 +60,15 @@ If HOOK is omitted, then default to `__PACKAGE__' to determine HOOK."
(-list hook))))) (-list hook)))))
funcs)))) funcs))))
(defmacro associate! (mode &rest rest) (defmacro associate! (&rest rest)
"Associate a major or minor mode to certain patterns and project files." "Associate a major or minor mode to certain patterns and project files."
(declare (indent 1)) (declare (indent 1))
(let ((minor (plist-get rest :minor)) (let* ((mode (if (keywordp (car rest)) __PACKAGE__ (pop rest)))
(in (plist-get rest :in)) (minor (plist-get rest :minor))
(match (plist-get rest :match)) (in (plist-get rest :in))
(files (plist-get rest :files)) (match (plist-get rest :match))
(pred (plist-get rest :when))) (files (plist-get rest :files))
(pred (plist-get rest :when)))
(cond ((or files in pred) (cond ((or files in pred)
(when (and files (not (or (listp files) (stringp files)))) (when (and files (not (or (listp files) (stringp files))))
(user-error "associate! :files expects a string or list of strings")) (user-error "associate! :files expects a string or list of strings"))

View file

@ -37,6 +37,10 @@ if you have byte-compiled your configuration (as intended).")
anything, just track what should be loaded. Useful for scanning packages and anything, just track what should be loaded. Useful for scanning packages and
loaded modules.") loaded modules.")
(defvar doom-reloading-p nil
"If non-nil, DOOM is reloading itself. Use this to determine to prevent
infinite recursion.")
(defvar doom-prefer-el-p noninteractive (defvar doom-prefer-el-p noninteractive
"If non-nil, load uncompiled .el config files.") "If non-nil, load uncompiled .el config files.")
@ -62,7 +66,6 @@ loaded modules.")
use-package-verbose doom-debug-mode use-package-verbose doom-debug-mode
quelpa-checkout-melpa-p nil quelpa-checkout-melpa-p nil
quelpa-update-melpa-p nil quelpa-update-melpa-p nil
quelpa-use-package-inhibit-loading-quelpa t
quelpa-dir (expand-file-name "quelpa" doom-packages-dir) quelpa-dir (expand-file-name "quelpa" doom-packages-dir)
;; ssh, no tears. Only compiling. ;; ssh, no tears. Only compiling.
byte-compile-warnings byte-compile-warnings
@ -107,7 +110,13 @@ avoided to speed up startup."
(unless (or doom-init-p force-p) (unless (or doom-init-p force-p)
(setq load-path doom--base-load-path (setq load-path doom--base-load-path
package-activated-list nil) package-activated-list nil)
(package-initialize) (package-initialize t)
;; Sure, package-initialize fills the load-path, but it will error out on
;; missing packages. UNACCEPTAABBLLLE!
(setq load-path
(append load-path
(directory-files package-user-dir t "^[a-zA-Z0-9]" t)))
(unless (and (file-exists-p doom-packages-dir) (unless (and (file-exists-p doom-packages-dir)
(require 'use-package nil t) (require 'use-package nil t)
(require 'quelpa nil t)) (require 'quelpa nil t))
@ -125,7 +134,7 @@ avoided to speed up startup."
(require 'quelpa) (require 'quelpa)
(require 'use-package) (require 'use-package)
(advice-add 'package-delete :around 'doom*package-delete) (advice-add 'package-delete :after 'doom*package-delete)
;; Remove package management keywords, I'll deal with the myself ;; Remove package management keywords, I'll deal with the myself
(mapc (lambda (keyword) (setq use-package-keywords (delq keyword use-package-keywords))) (mapc (lambda (keyword) (setq use-package-keywords (delq keyword use-package-keywords)))
'(:ensure :pin)) '(:ensure :pin))
@ -134,12 +143,21 @@ avoided to speed up startup."
(defun doom-reload () (defun doom-reload ()
"Rereads the Emacs config, reloading `doom-packages' and "Rereads the Emacs config, reloading `doom-packages' and
`doom-enabled-modules'." `doom-enabled-modules'."
(doom-initialize) (unless doom-reloading-p
(let ((doom-prefer-el-p t) (doom-initialize)
(doom-dont-load-p t)) (let ((doom-prefer-el-p t)
(setq doom-modules nil (doom-dont-load-p t)
doom-packages nil) (doom-reloading-p t)
(load (concat doom-emacs-dir "init.el") :noerror (not doom-debug-mode) :nosuffix))) noninteractive)
(setq doom-enabled-modules nil
doom-packages nil)
(let ((load-fn (lambda (file) (load file :noerror (not doom-debug-mode) :nosuffix))))
(mapc load-fn
(list (f-expand "init.el" doom-emacs-dir)
(f-expand "core.el" doom-core-dir)))
(mapc load-fn
(--map (doom-module-path (car it) (cdr it) "packages.el")
doom-enabled-modules))))))
;; ;;
@ -155,43 +173,48 @@ avoided to speed up startup."
conventions of DOOM emacs, as well as let-bind `__PACKAGE__' for the containing conventions of DOOM emacs, as well as let-bind `__PACKAGE__' for the containing
forms. This is helpful for macros like `set!' and `add-hook!'. Note that forms. This is helpful for macros like `set!' and `add-hook!'. Note that
packages are deferred by default." packages are deferred by default."
(declare (indent defun))
`(let ((__PACKAGE__ ',name)) `(let ((__PACKAGE__ ',name))
(use-package ,name ,@plist))) (use-package ,name ,@plist)))
(defmacro package! (name &rest plist) (defmacro package! (name &rest plist)
"Wraps around `use-package' (with `quelpa-use-package') and takes the same "Wraps around `use-package' to declare a deferred package (unless otherwise
arguments. Ensures the package named NAME is installed and available. If indicated), takes the same arguments, but adds the :recipe property, which takes
`doom--auto-install-p' is nil, then strip out :ensure and :quelpa properties, a MELPA recipe. Also binds `__PACKAGE__` for PLIST forms to optionally use."
which is the case if you've byte-compiled DOOM Emacs.
Note that packages are deferred by default."
(declare (indent defun)) (declare (indent defun))
(let ((recipe (cadr (memq :recipe plist))) (let ((recipe (cadr (memq :recipe plist)))
(pin (cadr (memq :pin plist)))) (pin (cadr (memq :pin plist)))
(when recipe (lpath (cadr (memq :load-path plist))))
(when (= 0 (mod (length recipe) 2)) (when (and recipe (= 0 (mod (length recipe) 2)))
(push name recipe)) (push name recipe))
(setq plist (use-package-plist-delete plist :recipe))) (if (not lpath)
(cl-pushnew (cons name recipe) doom-packages :key 'car)
(cl-pushnew lpath doom--base-load-path)
(setq recipe nil
pin nil))
(when pin (when pin
(add-to-list 'package-pinned-packages (cons package (plist-get plist :pin))) (cl-pushnew (cons package (plist-get plist :pin)) package-pinned-packages :key 'car))
(setq plist (use-package-plist-delete plist :pin))) (setq plist (use-package-plist-delete plist :recipe))
(pushnew (cons name recipe) doom-packages :key 'car) (setq plist (use-package-plist-delete plist :pin))
(unless doom-dont-load-p (unless doom-dont-load-p
`(use-package! ,name ,@plist)))) `(use-package! ,name ,@plist))))
(defmacro require! (feature) (defmacro require! (feature)
"Like `require', but prefers uncompiled files when `doom-prefer-el-p' is "Like `require', but will prefer uncompiled files if `doom-prefer-el-p' is
non-nil or in a noninteractive session." non-nil or this is a noninteractive session."
(let ((prefer-el-p (or doom-prefer-el-p noninteractive))) (let ((prefer-el-p (or doom-prefer-el-p noninteractive)))
`(require ',feature `(require ',feature
,(locate-file (concat (symbol-name feature) (if prefer-el-p ".el")) ,(locate-file (concat (symbol-name feature) (if prefer-el-p ".el"))
load-path)))) load-path))))
(defmacro load! (file-or-module-sym &optional submodule file) (defmacro load! (module &optional submodule file)
"Load a module from `doom-modules-dir'. Plays the same role as "Load a module from `doom-modules-dir' when both MODULE and SUBMODULE is
`load-relative', but is specific to DOOM emacs modules and submodules. If provided (both symbols). If FILE is non-nil, append it to the resulting path. If
`doom-prefer-el-p' is non-nil or in an noninteractive session, prefer the SUBMODULE is nil, MODULE is loaded relative to the current file (see `__DIR__').
un-compiled elisp file. When SUBMODULE is nil, FILE isn't used.
Will prefer uncompiled elisp sources if `doom-prefer-el-p' is non-nil or this is
an noninteractive session.
Examples: Examples:
(load! :lang emacs-lisp) (load! :lang emacs-lisp)
@ -205,13 +228,13 @@ Examples:
path file) path file)
(cond ((null submodule) (cond ((null submodule)
(setq path __DIR__ (setq path __DIR__
file (concat (symbol-name file-or-module-sym) ".el"))) file (concat (symbol-name module) ".el")))
(t (t
(pushnew (cons file-or-module-sym submodule) (cl-pushnew (cons module submodule)
doom-enabled-modules doom-enabled-modules
:test (lambda (x y) (and (eq (car x) (car y)) :test (lambda (x y) (and (eq (car x) (car y))
(eq (cdr x) (cdr y))))) (eq (cdr x) (cdr y)))))
(setq path (doom-module-path file-or-module-sym submodule) (setq path (doom-module-path module submodule)
file (or file "config.el")))) file (or file "config.el"))))
(setq path (f-slash path) (setq path (f-slash path)
file (concat path file)) file (concat path file))
@ -249,39 +272,42 @@ or make clean outside of Emacs."
"Refreshes the autoloads.el file, which tells Emacs where to find all the "Refreshes the autoloads.el file, which tells Emacs where to find all the
autoloaded functions in the modules you use or among the core libraries. autoloaded functions in the modules you use or among the core libraries.
In modules, checks for modules/*/autoload.el and modules/*/autoload/*.el.
Rerun this whenever you modify your init.el (or use `make autoloads` from the Rerun this whenever you modify your init.el (or use `make autoloads` from the
command line)." command line)."
(interactive) (interactive)
(doom-reload) (unless doom-reloading-p
(let ((generated-autoload-file (concat doom-local-dir "autoloads.el")) (doom-reload)
(autoload-files (let ((generated-autoload-file (concat doom-local-dir "autoloads.el"))
(append (-flatten (mapcar (lambda (dir) (autoload-files
(let ((auto-dir (f-expand "autoload" dir)) (append (-flatten (mapcar (lambda (dir)
(auto-file (f-expand "autoload.el" dir))) (let ((auto-dir (f-expand "autoload" dir))
(cond ((f-directory-p auto-dir) (auto-file (f-expand "autoload.el" dir)))
(f-glob "*.el" auto-dir)) (cond ((f-directory-p auto-dir)
((f-exists-p auto-file) (f-glob "*.el" auto-dir))
auto-file)))) ((f-exists-p auto-file)
(--map (doom-module-path (car it) (cdr it)) doom-enabled-modules))) auto-file))))
(f-glob "autoload/*.el" doom-core-dir)))) (--map (doom-module-path (car it) (cdr it)) doom-enabled-modules)))
(when (f-exists-p generated-autoload-file) (f-glob "autoload/*.el" doom-core-dir))))
(f-delete generated-autoload-file) (when (f-exists-p generated-autoload-file)
(message "Deleted old autoloads.el")) (f-delete generated-autoload-file)
(dolist (file autoload-files) (message "Deleted old autoloads.el"))
(update-file-autoloads file) (dolist (file autoload-files)
(message "Detected: %s" (f-relative file doom-emacs-dir))) (update-file-autoloads file)
(with-current-buffer (get-file-buffer generated-autoload-file) (message "Detected: %s" (f-relative file doom-emacs-dir)))
(save-buffer)) (with-current-buffer (get-file-buffer generated-autoload-file)
(load generated-autoload-file nil t t) (save-buffer)
(message "Done!"))) (eval-buffer))
(message "Done!"))))
(defun doom/byte-compile (&optional comprehensive-p) (defun doom/byte-compile (&optional comprehensive-p)
"Byte (re)compile the important files in your emacs configuration (i.e. "Byte (re)compile the important files in your emacs configuration (i.e.
init.el, core/*.el and modules/*/*/config.el) DOOM Emacs was designed to benefit init.el, core/*.el and modules/*/*/config.el) DOOM Emacs was designed to benefit
a lot from this. a lot from this.
If COMPREHENSIVE-P or the envar ALL is non-nil, also compile all autoload files. If COMPREHENSIVE-P is non-nil, then compile modules/*/*/*.el (except for
The benefit from this is minimal and may take more time." packages.el files). The benefit from this is minimal and may take more time."
(interactive) (interactive)
(doom-reload) (doom-reload)
(let ((targets (append (let ((targets (append

View file

@ -133,9 +133,7 @@ enable multiple minor modes for the same regexp.")
;;; Let 'er rip ;;; Let 'er rip
(require 'core-lib) (require 'core-lib)
(unless (require 'autoloads (concat doom-local-dir "autoloads.el") t) (require 'autoloads (concat doom-local-dir "autoloads.el") t)
(doom/refresh-autoloads))
(unless noninteractive (unless noninteractive
(package! anaphora (package! anaphora
:commands (awhen aif acond awhile)) :commands (awhen aif acond awhile))
@ -160,7 +158,12 @@ enable multiple minor modes for the same regexp.")
(require! core-ui) ; draw me like one of your French editors (require! core-ui) ; draw me like one of your French editors
(require! core-popups) ; taming sudden yet inevitable windows (require! core-popups) ; taming sudden yet inevitable windows
(require! core-editor) ; baseline configuration for text editing (require! core-editor) ; baseline configuration for text editing
(require! core-projects))) ; making Emacs project-aware (require! core-projects) ; making Emacs project-aware
;; We check last as a promise that the core files won't use autoloaded
;; functions. If they do, it shouldn't be autoloaded!
(unless (featurep 'autoloads)
(doom/refresh-autoloads))))
(provide 'core) (provide 'core)
;;; core.el ends here ;;; core.el ends here