Major refactor of Doom bootstrap process
+ New `input` and `buffer` support for :defer in def-package! can now defer packages until the first command invoked after startup or first interactive buffer switch, respectively + Exploit these new :defer techniques to lazy-load many core packages, netting Doom a 20-30% decrease in startup time + Various userland macros (like package!, def-package-hook!, packages!, and disable-packages!) will now throw an error if used incorrectly (i.e. outside of their intended files; e.g. package! should be used in packages.el files) + Removed support for multiple/nested doom! calls. There should only be THE ONE in ~/.doom.d/init.el (or ~/.config/doom/init.el) + Fix an issue where load-path and auto-mode-list modifications would not persist because doom-packages-file was cached too late. + Added package-activated-list to cached variables in doom-packages-file, thus we no longer need custom-file. + Load Doom core files from doom-initialize. Now doom-initialize can be called from state-dependent non-interactive functions, instead of reloading core/core.el, which was clumsy + Removed the doom-post-init-hook hook. There was no reason for it to exist when doom-init-hook can simply be appended to
This commit is contained in:
parent
bb4a8e98e6
commit
bec79a3d4c
6 changed files with 268 additions and 229 deletions
|
@ -73,6 +73,17 @@ missing) and shouldn't be deleted.")
|
|||
(defvar doom-disabled-packages ()
|
||||
"A list of packages that should be ignored by `def-package!'.")
|
||||
|
||||
(defvar doom-deferred-packages
|
||||
'((input)
|
||||
(buffer))
|
||||
"A alist of packages that have been deferred. The CAR is the type of deferral
|
||||
for the package, the CDR is the list of packages.
|
||||
|
||||
input will be loaded on the first action the user invokes
|
||||
after startup.
|
||||
buffer will be loaded on the first new buffer to be opened
|
||||
interactively.")
|
||||
|
||||
(defvar doom-reload-hook nil
|
||||
"A list of hooks to run when `doom/reload-load-path' is called.")
|
||||
|
||||
|
@ -87,10 +98,8 @@ missing) and shouldn't be deleted.")
|
|||
and `auto-mode-alist'.")
|
||||
|
||||
(defvar doom--current-module nil)
|
||||
(defvar doom--init-cache-p nil)
|
||||
(defvar doom--initializing nil)
|
||||
(defvar doom--refreshed-p nil)
|
||||
(defvar generated-autoload-load-name)
|
||||
(defvar doom--stage nil)
|
||||
|
||||
;;
|
||||
(setq autoload-compute-prefixes nil
|
||||
|
@ -134,37 +143,57 @@ and `auto-mode-alist'.")
|
|||
|
||||
|
||||
;;
|
||||
;; Startup benchmark
|
||||
;; Helpers 'n hooks
|
||||
;;
|
||||
|
||||
(defun doom-packages--benchmark ()
|
||||
(format "Doom loaded %s packages across %d modules in %.03fs"
|
||||
;; Certainly imprecise, especially where custom additions to
|
||||
;; load-path are concerned, but I don't mind a [small] margin of
|
||||
;; error in the plugin count in exchange for faster startup.
|
||||
(- (length load-path) (length doom-site-load-path))
|
||||
(hash-table-count doom-modules)
|
||||
(or doom-init-time
|
||||
(setq doom-init-time (float-time (time-subtract (current-time) before-init-time))))))
|
||||
(defun doom--assert-stage-p (stage macro)
|
||||
(cl-assert (eq stage doom--stage)
|
||||
nil
|
||||
"Found %s call in non-%s.el file (%s)"
|
||||
macro (symbol-name stage)
|
||||
(if (file-in-directory-p load-file-name doom-emacs-dir)
|
||||
(file-relative-name load-file-name doom-emacs-dir)
|
||||
(abbreviate-file-name load-file-name))))
|
||||
|
||||
(defun doom|refresh-cache ()
|
||||
"Refresh `doom-packages-file', which caches `load-path',
|
||||
`Info-directory-list', `doom-disabled-packages', `auto-mode-alist' and
|
||||
`package-activated-list'."
|
||||
(doom-initialize-packages 'internal)
|
||||
(let ((coding-system-for-write 'emacs-internal))
|
||||
(with-temp-file doom-packages-file
|
||||
(insert ";;; -*- lexical-binding:t -*-\n"
|
||||
";; This file was autogenerated by `doom|refresh-cache', DO NOT EDIT!\n")
|
||||
(prin1 `(setq load-path ',load-path
|
||||
Info-directory-list ',Info-directory-list
|
||||
auto-mode-alist ',auto-mode-alist
|
||||
doom-disabled-packages ',doom-disabled-packages
|
||||
package-activated-list ',package-activated-list)
|
||||
(current-buffer)))))
|
||||
|
||||
(defun doom|display-benchmark (&optional return-p)
|
||||
"Display a benchmark, showing number of packages and modules, and how quickly
|
||||
they were loaded at startup.
|
||||
|
||||
If RETURN-P, return the message as a string instead of displaying it."
|
||||
(funcall (if return-p #'format #'message)
|
||||
"Doom loaded %s packages across %d modules in %.03fs"
|
||||
;; Certainly imprecise, especially where custom additions to
|
||||
;; load-path are concerned, but I don't mind a [small] margin of
|
||||
;; error in the plugin count in exchange for faster startup.
|
||||
(- (length load-path) (length doom-site-load-path))
|
||||
(hash-table-count doom-modules)
|
||||
(or doom-init-time
|
||||
(setq doom-init-time (float-time (time-subtract (current-time) before-init-time))))))
|
||||
|
||||
(add-hook 'emacs-startup-hook #'doom|display-benchmark)
|
||||
(add-hook 'doom-reload-hook #'doom|display-benchmark)
|
||||
|
||||
|
||||
;;
|
||||
;; Bootstrap API
|
||||
;;
|
||||
|
||||
(defun doom--refresh-cache ()
|
||||
"TODO"
|
||||
(when doom--init-cache-p
|
||||
(doom-initialize-packages 'internal)
|
||||
(unless noninteractive
|
||||
(with-temp-buffer
|
||||
(prin1 `(setq load-path ',load-path
|
||||
Info-directory-list ',Info-directory-list
|
||||
doom-disabled-packages ',doom-disabled-packages)
|
||||
(current-buffer))
|
||||
(write-file doom-packages-file))
|
||||
(setq doom--init-cache-p nil))))
|
||||
|
||||
(defun doom-initialize (&optional force-p)
|
||||
"Bootstrap the bare essentials to get Doom running, if it hasn't already. If
|
||||
FORCE-P is non-nil, do it anyway.
|
||||
|
@ -180,12 +209,14 @@ FORCE-P is non-nil, do it anyway.
|
|||
(require 'cl-lib)
|
||||
(require 'map))
|
||||
(when (or force-p (not doom-init-p))
|
||||
(unless (load doom-autoload-file t t t)
|
||||
;; autoloads file
|
||||
(unless (load doom-autoload-file 'noerror 'nomessage 'nosuffix)
|
||||
(unless noninteractive
|
||||
(error "No autoloads file! Run make autoloads")))
|
||||
(when (and noninteractive (file-exists-p doom-packages-file))
|
||||
;; packages.el cache
|
||||
(when (and force-p (file-exists-p doom-packages-file))
|
||||
(delete-file doom-packages-file))
|
||||
(when (or force-p (not (load doom-packages-file t t t)))
|
||||
(unless (load doom-packages-file 'noerror 'nomessage 'nosuffix)
|
||||
;; Ensure core folders exist, otherwise we get errors
|
||||
(dolist (dir (list doom-local-dir doom-etc-dir doom-cache-dir doom-packages-dir))
|
||||
(unless (file-directory-p dir)
|
||||
|
@ -195,7 +226,8 @@ FORCE-P is non-nil, do it anyway.
|
|||
(setq package-activated-list nil
|
||||
package--initialized nil)
|
||||
(let (byte-compile-warnings)
|
||||
(condition-case _ (package-initialize)
|
||||
(condition-case _
|
||||
(package-initialize)
|
||||
('error (package-refresh-contents)
|
||||
(setq doom--refreshed-p t)
|
||||
(package-initialize))))
|
||||
|
@ -213,8 +245,23 @@ FORCE-P is non-nil, do it anyway.
|
|||
(error "✕ Couldn't install %s" package)))
|
||||
(message "Installing core packages...done")))
|
||||
(cl-pushnew doom-core-dir load-path :test #'string=)
|
||||
(setq doom--init-cache-p t))
|
||||
(setq doom-init-p t)))
|
||||
(add-hook 'doom-internal-init-hook #'doom|refresh-cache))
|
||||
(when doom-debug-mode
|
||||
(message "Doom initialized")))
|
||||
;; initialize Doom core
|
||||
(require 'core-lib)
|
||||
(require 'core-os)
|
||||
(unless noninteractive
|
||||
(require 'core-ui)
|
||||
(require 'core-editor)
|
||||
(require 'core-projects)
|
||||
(require 'core-keybinds))
|
||||
;; load input-deferred packages on first `pre-command-hook'
|
||||
(add-transient-hook! 'pre-command-hook
|
||||
(mapc #'require (cdr (assq 'input doom-deferred-packages))))
|
||||
(add-transient-hook! 'doom-after-switch-buffer-hook
|
||||
(when (get-buffer-window)
|
||||
(mapc #'require (cdr (assq 'buffer doom-deferred-packages))))))
|
||||
|
||||
(defun doom-initialize-autoloads ()
|
||||
"Ensures that `doom-autoload-file' exists and is loaded. Otherwise run
|
||||
|
@ -222,14 +269,6 @@ FORCE-P is non-nil, do it anyway.
|
|||
(unless (file-exists-p doom-autoload-file)
|
||||
(quiet! (doom//reload-autoloads))))
|
||||
|
||||
(defun doom-initialize-modules ()
|
||||
"Bootstraps all enabled modules by loading their config.el files."
|
||||
(maphash (lambda (key plist)
|
||||
(let ((doom--current-module key))
|
||||
(load (expand-file-name "config" (plist-get plist :path))
|
||||
'noerror (not doom-debug-mode))))
|
||||
doom-modules))
|
||||
|
||||
(defun doom-initialize-packages (&optional force-p)
|
||||
"Ensures that `doom-packages', `packages-alist' and `quelpa-cache' are
|
||||
populated.
|
||||
|
@ -242,51 +281,57 @@ Use this before any of package.el, quelpa or Doom's package management's API to
|
|||
ensure all the necessary package metadata is initialized and available for
|
||||
them."
|
||||
(with-temp-buffer ; prevent buffer-local settings from propagating
|
||||
(cl-flet
|
||||
((_load
|
||||
(file &optional noerror interactive)
|
||||
(condition-case-unless-debug ex
|
||||
(let ((load-prefer-newer t)
|
||||
(noninteractive (not interactive)))
|
||||
(load file noerror 'nomessage 'nosuffix))
|
||||
('error
|
||||
(lwarn 'doom-initialize-packages :warning
|
||||
"%s in %s: %s"
|
||||
(car ex)
|
||||
(file-relative-name file doom-emacs-dir)
|
||||
(error-message-string ex))))))
|
||||
(let ((load-prefer-newer t))
|
||||
;; package.el and quelpa handle themselves if their state changes during
|
||||
;; the current session, but if you change an packages.el file in a module,
|
||||
;; there's no non-trivial way to detect that, so we give you a way to
|
||||
;; reload only doom-packages.
|
||||
;; reload only doom-packages (by passing 'internal as FORCE-P).
|
||||
;; `doom-packages'
|
||||
(when (or force-p (not doom-packages))
|
||||
(unless (eq force-p 'internal)
|
||||
;; `package-alist'
|
||||
(when (or force-p (not (bound-and-true-p package-alist)))
|
||||
(setq load-path doom-site-load-path)
|
||||
(require 'package)
|
||||
(setq package-activated-list nil
|
||||
package--initialized nil)
|
||||
(let (byte-compile-warnings)
|
||||
(condition-case _
|
||||
(package-initialize)
|
||||
('error (package-refresh-contents)
|
||||
(setq doom--refreshed-p t)
|
||||
(package-initialize)))))
|
||||
|
||||
;; `quelpa-cache'
|
||||
(when (or force-p (not (bound-and-true-p quelpa-cache)))
|
||||
(require 'quelpa)
|
||||
(setq quelpa-initialized-p nil)
|
||||
(or (quelpa-setup-p)
|
||||
(error "Could not initialize quelpa"))))
|
||||
|
||||
(setq doom-packages nil)
|
||||
(_load (expand-file-name "packages.el" doom-core-dir))
|
||||
(cl-loop for key being the hash-keys of doom-modules
|
||||
for path = (doom-module-expand-file (car key) (cdr key) "packages.el")
|
||||
if (file-exists-p path)
|
||||
do (let ((doom--current-module key)) (_load path)))
|
||||
(cl-loop for dir in doom-psuedo-module-dirs
|
||||
for path = (expand-file-name "packages.el" dir)
|
||||
if (file-exists-p path)
|
||||
do (_load path)))
|
||||
|
||||
(unless (eq force-p 'internal)
|
||||
;; `package-alist'
|
||||
(when (or force-p (not (bound-and-true-p package-alist)))
|
||||
(setq load-path doom-site-load-path)
|
||||
(require 'package)
|
||||
(setq package-activated-list nil)
|
||||
(let (byte-compile-warnings)
|
||||
(package-initialize)))
|
||||
|
||||
;; `quelpa-cache'
|
||||
(when (or force-p (not (bound-and-true-p quelpa-cache)))
|
||||
(require 'quelpa)
|
||||
(setq quelpa-initialized-p nil)
|
||||
(or (quelpa-setup-p)
|
||||
(error "Could not initialize quelpa")))))))
|
||||
(cl-flet
|
||||
((_load
|
||||
(file &optional noerror interactive)
|
||||
(condition-case-unless-debug ex
|
||||
(let ((noninteractive (not interactive)))
|
||||
(load file noerror 'nomessage 'nosuffix))
|
||||
('error
|
||||
(lwarn 'doom-initialize-packages :warning
|
||||
"%s in %s: %s"
|
||||
(car ex)
|
||||
(file-relative-name file doom-emacs-dir)
|
||||
(error-message-string ex))))))
|
||||
(let ((doom--stage 'packages))
|
||||
(_load (expand-file-name "packages.el" doom-core-dir))
|
||||
(cl-loop for key being the hash-keys of doom-modules
|
||||
for path = (doom-module-expand-file (car key) (cdr key) "packages.el")
|
||||
if (file-exists-p path)
|
||||
do (let ((doom--current-module key)) (_load path)))
|
||||
(cl-loop for dir in doom-psuedo-module-dirs
|
||||
for path = (expand-file-name "packages.el" dir)
|
||||
if (file-exists-p path)
|
||||
do (_load path))))))))
|
||||
|
||||
|
||||
;;
|
||||
|
@ -387,28 +432,34 @@ added, if the file exists."
|
|||
"Bootstraps DOOM Emacs and its modules.
|
||||
|
||||
MODULES is an malformed plist of modules to load."
|
||||
(let (load-forms module file-name-handler-alist)
|
||||
(dolist (m modules)
|
||||
(cond ((keywordp m) (setq module m))
|
||||
((not module) (error "No namespace specified in `doom!' for %s" m))
|
||||
((let ((submodule (if (listp m) (car m) m))
|
||||
(flags (if (listp m) (cdr m))))
|
||||
(let ((path (doom-module-find-path module submodule)))
|
||||
(if (not path)
|
||||
(when doom-debug-mode
|
||||
(message "Couldn't find the %s %s module" module submodule))
|
||||
(doom-module-set module submodule :flags flags :path path)
|
||||
(push `(let ((doom--current-module ',(cons module submodule)))
|
||||
(load! init ,path t))
|
||||
load-forms)))))))
|
||||
(let (init-forms config-forms file-name-handler-alist)
|
||||
(let (module)
|
||||
(dolist (m modules)
|
||||
(cond ((keywordp m) (setq module m))
|
||||
((not module) (error "No namespace specified in `doom!' for %s" m))
|
||||
((let ((submodule (if (listp m) (car m) m))
|
||||
(flags (if (listp m) (cdr m))))
|
||||
(let ((path (doom-module-find-path module submodule)))
|
||||
(if (not path)
|
||||
(when doom-debug-mode
|
||||
(message "Couldn't find the %s %s module" module submodule))
|
||||
(doom-module-set module submodule :flags flags :path path)
|
||||
(push `(let ((doom--current-module ',(cons module submodule)))
|
||||
(load! init ,path t))
|
||||
init-forms)
|
||||
(push `(let ((doom--current-module ',(cons module submodule)))
|
||||
(load! config ,path t))
|
||||
config-forms))))))))
|
||||
`(let (file-name-handler-alist)
|
||||
(setq doom-modules ',doom-modules)
|
||||
(let ((doom--initializing t))
|
||||
,@(nreverse load-forms))
|
||||
,(unless doom--initializing
|
||||
'(unless noninteractive
|
||||
(doom--refresh-cache)
|
||||
(doom-initialize-modules))))))
|
||||
(let ((doom--stage 'init))
|
||||
,@(nreverse init-forms))
|
||||
(unless noninteractive
|
||||
(run-hooks 'doom-internal-init-hook)
|
||||
(let ((doom--stage 'config))
|
||||
,@(nreverse config-forms)
|
||||
(when doom-private-dir
|
||||
(load ,(concat doom-private-dir "config") t t)))))))
|
||||
|
||||
(defmacro def-package! (name &rest plist)
|
||||
"A thin wrapper around `use-package'."
|
||||
|
@ -419,10 +470,17 @@ MODULES is an malformed plist of modules to load."
|
|||
;; If byte-compiling, ignore this package if it doesn't meet the condition.
|
||||
;; This avoids false-positive load errors.
|
||||
(unless (and (bound-and-true-p byte-compile-current-file)
|
||||
(or (and (plist-member plist :if) (not (eval (plist-get plist :if))))
|
||||
(and (plist-member plist :when) (not (eval (plist-get plist :when))))
|
||||
(and (plist-member plist :unless) (eval (plist-get plist :unless)))))
|
||||
`(use-package ,name ,@plist)))
|
||||
(or (and (plist-member plist :if) (not (eval (plist-get plist :if) t)))
|
||||
(and (plist-member plist :when) (not (eval (plist-get plist :when) t)))
|
||||
(and (plist-member plist :unless) (eval (plist-get plist :unless) t))))
|
||||
`(progn
|
||||
,(when-let* ((defer (plist-get plist :defer))
|
||||
(type (or (car-safe defer) defer)))
|
||||
(setq plist (plist-put plist :defer (or (cdr-safe defer) t)))
|
||||
(when (and (not doom-init-p)
|
||||
(assq type doom-deferred-packages))
|
||||
`(push ',name (cdr (assq ',type doom-deferred-packages)))))
|
||||
(use-package ,name ,@plist))))
|
||||
|
||||
(defmacro def-package-hook! (package when &rest body)
|
||||
"Reconfigures a package's `def-package!' block.
|
||||
|
@ -439,6 +497,7 @@ WARNING: If :pre-init or :pre-config hooks return nil, the original
|
|||
`def-package!''s :init/:config block (respectively) is overwritten, so remember
|
||||
to have them return non-nil (or exploit that to overwrite Doom's config)."
|
||||
(declare (indent defun))
|
||||
(doom--assert-stage-p 'init #'package!)
|
||||
(cond ((eq when :disable)
|
||||
(message "Using :disable with `def-package-hook!' is deprecated. Use :disable in `package!' instead.")
|
||||
(ignore (push package doom-disabled-packages)))
|
||||
|
@ -533,16 +592,6 @@ omitted. eg. (featurep! +flag1)"
|
|||
;; Module package macros
|
||||
;;
|
||||
|
||||
(defun doom--assert-file-p (file-name macro)
|
||||
(cl-assert (string= (file-name-nondirectory load-file-name) file-name)
|
||||
nil
|
||||
"Found %s call in non-%s file (%s)"
|
||||
macro
|
||||
file-name
|
||||
(if (file-in-directory-p load-file-name doom-emacs-dir)
|
||||
(file-relative-name load-file-name doom-emacs-dir)
|
||||
(abbreviate-file-name load-file-name))))
|
||||
|
||||
(defmacro package! (name &rest plist)
|
||||
"Declares a package and how to install it (if applicable).
|
||||
|
||||
|
@ -568,7 +617,7 @@ Accepts the following properties:
|
|||
:freeze FORM
|
||||
Do not update this package if FORM is non-nil."
|
||||
(declare (indent defun))
|
||||
(doom--assert-file-p "packages.el" #'package!)
|
||||
(doom--assert-stage-p 'packages #'package!)
|
||||
(cond ((memq name doom-disabled-packages) nil)
|
||||
((let ((disable (plist-get plist :disable)))
|
||||
(and disable (eval disable)))
|
||||
|
@ -599,7 +648,7 @@ Accepts the following properties:
|
|||
packages at once.
|
||||
|
||||
Only use this macro in a module's packages.el file."
|
||||
(doom--assert-file-p "packages.el" #'packages!)
|
||||
(doom--assert-stage-p 'packages #'packages!)
|
||||
`(progn ,@(cl-loop for desc in packages collect `(package! ,@desc))))
|
||||
|
||||
(defmacro disable-packages! (&rest packages)
|
||||
|
@ -607,7 +656,7 @@ Only use this macro in a module's packages.el file."
|
|||
packages at once.
|
||||
|
||||
Only use this macro in a module's packages.el file."
|
||||
(doom--assert-file-p "packages.el" #'disable-packages!)
|
||||
(doom--assert-stage-p 'packages #'disable-packages!)
|
||||
`(setq doom-disabled-packages (append ',packages doom-disabled-packages)))
|
||||
|
||||
(defmacro depends-on! (module submodule &optional flags)
|
||||
|
@ -617,7 +666,7 @@ Only use this macro in a module's packages.el file.
|
|||
|
||||
MODULE is a keyword, and SUBMODULE is a symbol. Under the hood, this simply
|
||||
loads MODULE SUBMODULE's packages.el file."
|
||||
(doom--assert-file-p "packages.el" #'depends-on!)
|
||||
(doom--assert-stage-p 'packages #'depends-on!)
|
||||
`(let ((doom-modules ,doom-modules)
|
||||
(flags ,flags))
|
||||
(when flags
|
||||
|
@ -680,6 +729,7 @@ call `doom//reload-load-path' remotely (through emacsclient)."
|
|||
(message "%d packages reloaded" (length package-alist))
|
||||
(run-hooks 'doom-reload-hook))))
|
||||
|
||||
(defvar generated-autoload-load-name)
|
||||
(defun doom//reload-autoloads ()
|
||||
"Refreshes the autoloads.el file, specified by `doom-autoload-file'.
|
||||
|
||||
|
@ -805,12 +855,11 @@ If RECOMPILE-P is non-nil, only recompile out-of-date files."
|
|||
;; pertinent to files compiled later.
|
||||
(let (noninteractive)
|
||||
;; Core libraries aren't fully loaded in a noninteractive session, so
|
||||
;; we reload it with `noninteractive' set to nil to force them to.
|
||||
(load (expand-file-name "core.el" doom-core-dir) nil t t)
|
||||
;; we pretend to be interactive and reinitialize
|
||||
(doom-initialize)
|
||||
;; In case autoloads.el hasn't been properly generated at this point.
|
||||
(dolist (file (file-expand-wildcards (expand-file-name "autoload/*.el" doom-core-dir)))
|
||||
(load file t t t)))
|
||||
(doom-initialize-modules)
|
||||
(unless (file-exists-p doom-autoload-file)
|
||||
(mapc #'load (file-expand-wildcards (expand-file-name "autoload/*.el" doom-core-dir)))))
|
||||
;; Assemble el files we want to compile; taking into account that
|
||||
;; MODULES may be a list of MODULE/SUBMODULE strings from the command
|
||||
;; line.
|
||||
|
@ -819,6 +868,7 @@ If RECOMPILE-P is non-nil, only recompile out-of-date files."
|
|||
in (or modules (append (list doom-core-dir) (doom-module-load-path)))
|
||||
if (equal target "core")
|
||||
nconc (nreverse (doom-packages--files doom-core-dir "\\.el$"))
|
||||
and collect (expand-file-name "init.el" doom-private-dir)
|
||||
else if (file-directory-p target)
|
||||
nconc (nreverse (doom-packages--files target "\\.el$"))
|
||||
else if (cl-member target doom-psuedo-module-dirs :test #'file-in-directory-p)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue