From e80df3c03ce72e9b24a220b6eb8c2807ef4edff4 Mon Sep 17 00:00:00 2001 From: Henrik Lissner Date: Fri, 3 Feb 2017 19:20:47 -0500 Subject: [PATCH] Polish package management system; no infinite recursion; smarter autoload refresh --- core/autoload/packages.el | 36 ++++++---- core/core-lib.el | 13 ++-- core/core-packages.el | 146 ++++++++++++++++++++++---------------- core/core.el | 11 +-- 4 files changed, 122 insertions(+), 84 deletions(-) diff --git a/core/autoload/packages.el b/core/autoload/packages.el index ec1bddb12..da63609b7 100644 --- a/core/autoload/packages.el +++ b/core/autoload/packages.el @@ -58,7 +58,7 @@ 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) + (doom-reload) (unless (quelpa-setup-p) (error "Could not initialize quelpa")) (--map (cons it (assq it quelpa-cache)) @@ -76,18 +76,31 @@ be fed to `doom/packages-update'." (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)) + (doom-reload) + (let ((package-selected-packages + (append (mapcar 'car doom-packages) doom-protected-packages))) + (package--removable-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) + (doom-reload) (--remove (assq (car it) package-alist) (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 @@ -133,15 +146,8 @@ appropriate." (doom-initialize) (unless (package-installed-p name) (error "%s isn't installed" name)) - (let ((desc (cadr (assq package package-alist)))) + (let ((desc (cadr (assq name 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))) @@ -152,6 +158,7 @@ appropriate." ;;;###autoload (defun doom/packages-install () + "Interactive command for installing missing packages." (interactive) (let ((packages (doom-get-packages-to-install))) (cond ((not packages) @@ -187,6 +194,7 @@ appropriate." ;;;###autoload (defun doom/packages-update () + "Interactive command for updating packages." (interactive) (let ((packages (doom-get-outdated-packages))) (cond ((not packages) @@ -219,6 +227,7 @@ appropriate." ;;;###autoload (defun doom/packages-autoremove () + "Interactive command for auto-removing orphaned packages." (interactive) (let ((packages (doom-get-orphaned-packages))) (cond ((not packages) @@ -243,7 +252,6 @@ appropriate." (doom-message "Finished!"))))) - ;;;###autoload (defalias 'doom/package-install 'package-install) diff --git a/core/core-lib.el b/core/core-lib.el index 43e480769..1836a41d6 100644 --- a/core/core-lib.el +++ b/core/core-lib.el @@ -60,14 +60,15 @@ If HOOK is omitted, then default to `__PACKAGE__' to determine HOOK." (-list hook))))) funcs)))) -(defmacro associate! (mode &rest rest) +(defmacro associate! (&rest rest) "Associate a major or minor mode to certain patterns and project files." (declare (indent 1)) - (let ((minor (plist-get rest :minor)) - (in (plist-get rest :in)) - (match (plist-get rest :match)) - (files (plist-get rest :files)) - (pred (plist-get rest :when))) + (let* ((mode (if (keywordp (car rest)) __PACKAGE__ (pop rest))) + (minor (plist-get rest :minor)) + (in (plist-get rest :in)) + (match (plist-get rest :match)) + (files (plist-get rest :files)) + (pred (plist-get rest :when))) (cond ((or files in pred) (when (and files (not (or (listp files) (stringp files)))) (user-error "associate! :files expects a string or list of strings")) diff --git a/core/core-packages.el b/core/core-packages.el index f7cca22e2..83f31f711 100644 --- a/core/core-packages.el +++ b/core/core-packages.el @@ -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 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 "If non-nil, load uncompiled .el config files.") @@ -62,7 +66,6 @@ loaded modules.") use-package-verbose doom-debug-mode quelpa-checkout-melpa-p nil quelpa-update-melpa-p nil - quelpa-use-package-inhibit-loading-quelpa t quelpa-dir (expand-file-name "quelpa" doom-packages-dir) ;; ssh, no tears. Only compiling. byte-compile-warnings @@ -107,7 +110,13 @@ avoided to speed up startup." (unless (or doom-init-p force-p) (setq load-path doom--base-load-path 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) (require 'use-package nil t) (require 'quelpa nil t)) @@ -125,7 +134,7 @@ avoided to speed up startup." (require 'quelpa) (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 (mapc (lambda (keyword) (setq use-package-keywords (delq keyword use-package-keywords))) '(:ensure :pin)) @@ -134,12 +143,21 @@ avoided to speed up startup." (defun doom-reload () "Rereads the Emacs config, reloading `doom-packages' and `doom-enabled-modules'." - (doom-initialize) - (let ((doom-prefer-el-p t) - (doom-dont-load-p t)) - (setq doom-modules nil - doom-packages nil) - (load (concat doom-emacs-dir "init.el") :noerror (not doom-debug-mode) :nosuffix))) + (unless doom-reloading-p + (doom-initialize) + (let ((doom-prefer-el-p t) + (doom-dont-load-p t) + (doom-reloading-p t) + 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 forms. This is helpful for macros like `set!' and `add-hook!'. Note that packages are deferred by default." + (declare (indent defun)) `(let ((__PACKAGE__ ',name)) (use-package ,name ,@plist))) (defmacro package! (name &rest plist) - "Wraps around `use-package' (with `quelpa-use-package') and takes the same -arguments. Ensures the package named NAME is installed and available. If -`doom--auto-install-p' is nil, then strip out :ensure and :quelpa properties, -which is the case if you've byte-compiled DOOM Emacs. - -Note that packages are deferred by default." + "Wraps around `use-package' to declare a deferred package (unless otherwise +indicated), takes the same arguments, but adds the :recipe property, which takes +a MELPA recipe. Also binds `__PACKAGE__` for PLIST forms to optionally use." (declare (indent defun)) (let ((recipe (cadr (memq :recipe plist))) - (pin (cadr (memq :pin plist)))) - (when recipe - (when (= 0 (mod (length recipe) 2)) - (push name recipe)) - (setq plist (use-package-plist-delete plist :recipe))) + (pin (cadr (memq :pin plist))) + (lpath (cadr (memq :load-path plist)))) + (when (and recipe (= 0 (mod (length recipe) 2))) + (push name 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 - (add-to-list 'package-pinned-packages (cons package (plist-get plist :pin))) - (setq plist (use-package-plist-delete plist :pin))) - (pushnew (cons name recipe) doom-packages :key 'car) + (cl-pushnew (cons package (plist-get plist :pin)) package-pinned-packages :key 'car)) + (setq plist (use-package-plist-delete plist :recipe)) + (setq plist (use-package-plist-delete plist :pin)) (unless doom-dont-load-p `(use-package! ,name ,@plist)))) (defmacro require! (feature) - "Like `require', but prefers uncompiled files when `doom-prefer-el-p' is -non-nil or in a noninteractive session." + "Like `require', but will prefer uncompiled files if `doom-prefer-el-p' is +non-nil or this is a noninteractive session." (let ((prefer-el-p (or doom-prefer-el-p noninteractive))) `(require ',feature ,(locate-file (concat (symbol-name feature) (if prefer-el-p ".el")) load-path)))) -(defmacro load! (file-or-module-sym &optional submodule file) - "Load a module from `doom-modules-dir'. Plays the same role as -`load-relative', but is specific to DOOM emacs modules and submodules. If -`doom-prefer-el-p' is non-nil or in an noninteractive session, prefer the -un-compiled elisp file. +(defmacro load! (module &optional submodule file) + "Load a module from `doom-modules-dir' when both MODULE and SUBMODULE is +provided (both symbols). If FILE is non-nil, append it to the resulting path. If +SUBMODULE is nil, MODULE is loaded relative to the current file (see `__DIR__'). +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: (load! :lang emacs-lisp) @@ -205,13 +228,13 @@ Examples: path file) (cond ((null submodule) (setq path __DIR__ - file (concat (symbol-name file-or-module-sym) ".el"))) + file (concat (symbol-name module) ".el"))) (t - (pushnew (cons file-or-module-sym submodule) - doom-enabled-modules - :test (lambda (x y) (and (eq (car x) (car y)) - (eq (cdr x) (cdr y))))) - (setq path (doom-module-path file-or-module-sym submodule) + (cl-pushnew (cons module submodule) + doom-enabled-modules + :test (lambda (x y) (and (eq (car x) (car y)) + (eq (cdr x) (cdr y))))) + (setq path (doom-module-path module submodule) file (or file "config.el")))) (setq path (f-slash path) 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 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 command line)." (interactive) - (doom-reload) - (let ((generated-autoload-file (concat doom-local-dir "autoloads.el")) - (autoload-files - (append (-flatten (mapcar (lambda (dir) - (let ((auto-dir (f-expand "autoload" dir)) - (auto-file (f-expand "autoload.el" dir))) - (cond ((f-directory-p auto-dir) - (f-glob "*.el" auto-dir)) - ((f-exists-p auto-file) - auto-file)))) - (--map (doom-module-path (car it) (cdr it)) doom-enabled-modules))) - (f-glob "autoload/*.el" doom-core-dir)))) - (when (f-exists-p generated-autoload-file) - (f-delete generated-autoload-file) - (message "Deleted old autoloads.el")) - (dolist (file autoload-files) - (update-file-autoloads file) - (message "Detected: %s" (f-relative file doom-emacs-dir))) - (with-current-buffer (get-file-buffer generated-autoload-file) - (save-buffer)) - (load generated-autoload-file nil t t) - (message "Done!"))) + (unless doom-reloading-p + (doom-reload) + (let ((generated-autoload-file (concat doom-local-dir "autoloads.el")) + (autoload-files + (append (-flatten (mapcar (lambda (dir) + (let ((auto-dir (f-expand "autoload" dir)) + (auto-file (f-expand "autoload.el" dir))) + (cond ((f-directory-p auto-dir) + (f-glob "*.el" auto-dir)) + ((f-exists-p auto-file) + auto-file)))) + (--map (doom-module-path (car it) (cdr it)) doom-enabled-modules))) + (f-glob "autoload/*.el" doom-core-dir)))) + (when (f-exists-p generated-autoload-file) + (f-delete generated-autoload-file) + (message "Deleted old autoloads.el")) + (dolist (file autoload-files) + (update-file-autoloads file) + (message "Detected: %s" (f-relative file doom-emacs-dir))) + (with-current-buffer (get-file-buffer generated-autoload-file) + (save-buffer) + (eval-buffer)) + (message "Done!")))) (defun doom/byte-compile (&optional comprehensive-p) "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 a lot from this. -If COMPREHENSIVE-P or the envar ALL is non-nil, also compile all autoload files. -The benefit from this is minimal and may take more time." +If COMPREHENSIVE-P is non-nil, then compile modules/*/*/*.el (except for +packages.el files). The benefit from this is minimal and may take more time." (interactive) (doom-reload) (let ((targets (append diff --git a/core/core.el b/core/core.el index 3b35fae12..9eb04d3e4 100644 --- a/core/core.el +++ b/core/core.el @@ -133,9 +133,7 @@ enable multiple minor modes for the same regexp.") ;;; Let 'er rip (require 'core-lib) - (unless (require 'autoloads (concat doom-local-dir "autoloads.el") t) - (doom/refresh-autoloads)) - + (require 'autoloads (concat doom-local-dir "autoloads.el") t) (unless noninteractive (package! anaphora :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-popups) ; taming sudden yet inevitable windows (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) ;;; core.el ends here