Redesign Doom bootstrap, caching & autoload generation logic
The autoloads file has been split into doom-autoload-file and doom-package-autoload-file. The former is for Doom's modules and standard library; the latter is for compiling all package autoloads like load-path and auto-mode-alist (among other things). This reduced my startup speed from ~1s to ~0.5s
This commit is contained in:
parent
3dd291a675
commit
8746c12fae
4 changed files with 413 additions and 259 deletions
|
@ -40,53 +40,47 @@
|
|||
;; See core/autoload/packages.el for more functions.
|
||||
|
||||
(defvar doom-init-p nil
|
||||
"Non-nil if doom is done initializing (once `doom-post-init-hook' is done). If
|
||||
this is nil after Emacs has started something is wrong.")
|
||||
"Non-nil if `doom-initialize' has run.")
|
||||
|
||||
(defvar doom-init-modules-p nil
|
||||
"Non-nil if `doom-initialize-modules' has run.")
|
||||
|
||||
(defvar doom-init-time nil
|
||||
"The time it took, in seconds, for DOOM Emacs to initialize.")
|
||||
|
||||
(defvar doom-modules
|
||||
(make-hash-table :test #'equal :size 100 :rehash-threshold 1.0)
|
||||
(defvar doom-modules ()
|
||||
"A hash table of enabled modules. Set by `doom-initialize-modules'.")
|
||||
|
||||
(defvar doom-modules-dirs
|
||||
(list (expand-file-name "modules/" doom-private-dir) doom-modules-dir)
|
||||
"A list of module root directories. Order determines priority.")
|
||||
|
||||
(defvar doom-psuedo-module-dirs
|
||||
(list doom-private-dir)
|
||||
(defvar doom-psuedo-module-dirs (list doom-private-dir)
|
||||
"Additional paths for modules that are outside of `doom-modules-dirs'.
|
||||
`doom//reload-autoloads', `doom//byte-compile' and `doom-initialize-packages'
|
||||
will include the directories in this list.")
|
||||
`doom//reload-doom-autoloads', `doom//byte-compile' and
|
||||
`doom-initialize-packages' will include the directories in this list.")
|
||||
|
||||
(defvar doom-packages ()
|
||||
"A list of enabled packages. Each element is a sublist, whose CAR is the
|
||||
package's name as a symbol, and whose CDR is the plist supplied to its
|
||||
`package!' declaration. Set by `doom-initialize-packages'.")
|
||||
|
||||
(defvar doom-core-packages
|
||||
'(persistent-soft use-package quelpa async)
|
||||
(defvar doom-core-packages '(persistent-soft use-package quelpa async)
|
||||
"A list of packages that must be installed (and will be auto-installed if
|
||||
missing) and shouldn't be deleted.")
|
||||
|
||||
(defvar doom-disabled-packages ()
|
||||
"A list of packages that should be ignored by `def-package!'.")
|
||||
|
||||
(defvar doom-autoload-excluded-packages '(marshal gh)
|
||||
"Packages that have silly or destructive autoload files that try to load
|
||||
everyone in the universe and their dog, causing errors that make babies cry. No
|
||||
one wants that.")
|
||||
|
||||
(defvar doom-site-load-path load-path
|
||||
"The starting load-path, before it is altered by `doom-initialize'.")
|
||||
|
||||
(defvar doom-autoload-file (concat doom-local-dir "autoloads.el")
|
||||
"Where `doom//reload-autoloads' will generate its autoloads file.")
|
||||
"Where `doom//reload-doom-autoloads' will generate its core autoloads file.")
|
||||
|
||||
(defvar doom-packages-file (concat doom-cache-dir "packages.el")
|
||||
"Where to cache `load-path', `Info-directory-list', `doom-disabled-packages'
|
||||
and `auto-mode-alist'.")
|
||||
(defvar doom-package-autoload-file (concat doom-local-dir "autoloads.pkg.el")
|
||||
"Where `doom//reload-package-autoloads' will generate its package.el autoloads
|
||||
file.")
|
||||
|
||||
(defvar doom-reload-hook nil
|
||||
"A list of hooks to run when `doom//reload-load-path' is called.")
|
||||
|
@ -150,22 +144,6 @@ and `auto-mode-alist'.")
|
|||
(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
|
||||
auto-mode-alist ',auto-mode-alist
|
||||
Info-directory-list ',Info-directory-list
|
||||
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.
|
||||
|
@ -195,17 +173,59 @@ session, with a different init.el, like so:
|
|||
'window-setup-hook))
|
||||
|
||||
|
||||
;;
|
||||
;; Bootstrap helpers
|
||||
;;
|
||||
|
||||
(defun doom-ensure-packages-initialized (&optional force-p)
|
||||
"Make sure package.el is initialized."
|
||||
(when (or force-p (not package--initialized))
|
||||
(require 'package)
|
||||
(setq package-activated-list nil
|
||||
package--initialized nil)
|
||||
(let (byte-compile-warnings)
|
||||
(condition-case _
|
||||
(quiet! (package-initialize))
|
||||
('error (package-refresh-contents)
|
||||
(setq doom--refreshed-p t)
|
||||
(package-initialize))))))
|
||||
|
||||
(defun doom-ensure-core-packages ()
|
||||
"Make sure `doom-core-packages' are installed."
|
||||
(when-let* ((core-packages (cl-remove-if #'package-installed-p doom-core-packages)))
|
||||
(message "Installing core packages")
|
||||
(unless doom--refreshed-p
|
||||
(package-refresh-contents))
|
||||
(dolist (package core-packages)
|
||||
(let ((inhibit-message t))
|
||||
(package-install package))
|
||||
(if (package-installed-p package)
|
||||
(message "✓ Installed %s" package)
|
||||
(error "✕ Couldn't install %s" package)))
|
||||
(message "Installing core packages...done")))
|
||||
|
||||
(defun doom-ensure-core-directories ()
|
||||
"Make sure all Doom's essential local directories (in and including
|
||||
`doom-local-dir') exist."
|
||||
(dolist (dir (list doom-local-dir doom-etc-dir doom-cache-dir doom-packages-dir))
|
||||
(unless (file-directory-p dir)
|
||||
(make-directory dir t))))
|
||||
|
||||
|
||||
;;
|
||||
;; Bootstrap API
|
||||
;;
|
||||
|
||||
(autoload 'doom//reload-doom-autoloads "autoload/modules" nil t)
|
||||
(autoload 'doom//reload-package-autoloads "autoload/modules" nil t)
|
||||
|
||||
(defun doom-initialize (&optional force-p)
|
||||
"Bootstrap Doom, if it hasn't already (or if FORCE-P is non-nil).
|
||||
|
||||
The bootstrap process involves making sure the essential directories exist, core
|
||||
packages are installed, `doom-autoload-file' is loaded, `doom-packages-file'
|
||||
cache exists (and is loaded) and, finally, loads your private init.el (which
|
||||
should contain your `doom!' block).
|
||||
The bootstrap process involves making sure 1) the essential directories exist,
|
||||
2) the core packages are installed, 3) `doom-autoload-file' and
|
||||
`doom-package-autoload-file' exist and have been loaded, and 4) Doom's core
|
||||
files are loaded.
|
||||
|
||||
If the cache exists, much of this function isn't run, which substantially
|
||||
reduces startup time.
|
||||
|
@ -228,71 +248,54 @@ Module load order is determined by your `doom!' block. See `doom-modules-dirs'
|
|||
for a list of all recognized module trees. Order defines precedence (from most
|
||||
to least)."
|
||||
(when (or force-p (not doom-init-p))
|
||||
(when (and (or force-p noninteractive)
|
||||
(file-exists-p doom-packages-file))
|
||||
(message "Deleting packages.el cache")
|
||||
(delete-file doom-packages-file))
|
||||
(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)
|
||||
(make-directory dir t)))
|
||||
;; Ensure plugins have been initialized
|
||||
(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))))
|
||||
;; Ensure core packages are installed
|
||||
(when-let* ((core-packages (cl-remove-if #'package-installed-p doom-core-packages)))
|
||||
(message "Installing core packages")
|
||||
(unless doom--refreshed-p
|
||||
(package-refresh-contents))
|
||||
(dolist (package core-packages)
|
||||
(let ((inhibit-message t))
|
||||
(package-install package))
|
||||
(if (package-installed-p package)
|
||||
(message "✓ Installed %s" package)
|
||||
(error "✕ Couldn't install %s" package)))
|
||||
(message "Installing core packages...done"))
|
||||
(unless noninteractive
|
||||
(add-hook 'doom-pre-init-hook #'doom|refresh-cache)))
|
||||
;; Load autoloads file
|
||||
(doom-initialize-autoloads))
|
||||
;; Set this to prevent infinite recursive calls to `doom-initialize'
|
||||
(setq doom-init-p t)
|
||||
;; `doom-autoload-file' tells Emacs where to load all its autoloaded
|
||||
;; functions from. This includes everything in core/autoload/*.el and all
|
||||
;; the autoload files in your enabled modules.
|
||||
(unless (doom-initialize-autoloads doom-autoload-file force-p)
|
||||
(doom-ensure-core-directories)
|
||||
(doom-ensure-packages-initialized force-p)
|
||||
(doom-ensure-core-packages)
|
||||
;; Regenerate `doom-autoload-file', which tells Doom where to find all its
|
||||
;; module autoloaded functions.
|
||||
(unless (or force-p noninteractive)
|
||||
(doom//reload-doom-autoloads)))
|
||||
;; Loads `doom-package-autoload-file', which caches `load-path',
|
||||
;; `auto-mode-alist', `Info-directory-list', `doom-disabled-packages' and
|
||||
;; `package-activated-list'. A big reduction in startup time.
|
||||
(unless (doom-initialize-autoloads doom-package-autoload-file force-p)
|
||||
(unless (or force-p noninteractive)
|
||||
(doom//reload-package-autoloads))))
|
||||
;; Initialize Doom core
|
||||
(unless noninteractive
|
||||
(require 'core-ui)
|
||||
(require 'core-editor)
|
||||
(require 'core-projects)
|
||||
(require 'core-keybinds))
|
||||
;; Bootstrap Doom
|
||||
(unless doom-init-p
|
||||
(require 'core-keybinds)))
|
||||
|
||||
(defun doom-initialize-modules (&optional force-p)
|
||||
"Loads the init.el in `doom-private-dir' and sets up hooks for a healthy
|
||||
session of Dooming. Will noop if used more than once, unless FORCE-P is
|
||||
non-nil."
|
||||
(when (or force-p (not doom-init-modules-p))
|
||||
;; Set `doom-init-modules-p' early, so `doom-pre-init-hook' won't infinitely
|
||||
;; recurse by accident if any of them need `doom-initialize-modules'.
|
||||
(setq doom-init-modules-p t)
|
||||
(unless noninteractive
|
||||
(add-hook! 'doom-reload-hook
|
||||
#'(doom|refresh-cache doom|display-benchmark))
|
||||
(add-hook! 'emacs-startup-hook
|
||||
#'(doom|post-init doom|display-benchmark)))
|
||||
(run-hooks 'doom-pre-init-hook)
|
||||
(when doom-private-dir
|
||||
(load (concat doom-private-dir "init") t t)))
|
||||
(setq doom-init-p t))
|
||||
(let ((load-prefer-newer t))
|
||||
(load (expand-file-name "init" doom-private-dir)
|
||||
'noerror 'nomessage)))))
|
||||
|
||||
(defun doom-initialize-autoloads ()
|
||||
"Tries to load `doom-autoload-file', otherwise throws an error (unless in a
|
||||
noninteractive session)."
|
||||
(unless
|
||||
(condition-case-unless-debug e
|
||||
(load (substring doom-autoload-file 0 -3) 'noerror 'nomessage)
|
||||
(error
|
||||
(funcall (if noninteractive #'warn #'error)
|
||||
"Autoload error: %s -> %s"
|
||||
(car e) (error-message-string e))))
|
||||
(unless noninteractive
|
||||
(error "No autoloads file! Run make autoloads"))))
|
||||
(defun doom-initialize-autoloads (file &optional clear-p)
|
||||
"Tries to load FILE (an autoloads file). Otherwise tries to regenerate it. If
|
||||
CLEAR-P is non-nil, regenerate it anyway."
|
||||
(unless clear-p
|
||||
(load (file-name-sans-extension file) 'noerror 'nomessage)))
|
||||
|
||||
(defun doom-initialize-packages (&optional force-p)
|
||||
"Ensures that Doom's package management system, package.el and quelpa are
|
||||
|
@ -306,8 +309,8 @@ 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
|
||||
;; Prefer uncompiled files to reduce stale code issues
|
||||
(let ((load-prefer-newer t))
|
||||
(let ((load-prefer-newer t) ; reduce stale code issues
|
||||
(doom-modules (doom-module-table)))
|
||||
;; 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
|
||||
|
@ -454,6 +457,37 @@ added, if the file exists."
|
|||
collect (plist-get plist :path))
|
||||
(cl-remove-if-not #'file-directory-p doom-psuedo-module-dirs)))
|
||||
|
||||
(defun doom-module-table (&optional modules)
|
||||
"Converts MODULES (a malformed plist) into a hash table of modules, fit for
|
||||
`doom-modules'. If MODULES is omitted, it will fetch your module mplist from the
|
||||
`doom!' block in your private init.el file."
|
||||
(let* ((doom-modules (make-hash-table :test #'equal
|
||||
:size (if modules (length modules) 100)
|
||||
:rehash-threshold 1.0)))
|
||||
(when (null modules)
|
||||
(let ((init-file (expand-file-name "init.el" doom-private-dir)))
|
||||
(if (not (file-exists-p init-file))
|
||||
(error "%s doesn't exist" (abbreviate-file-name init-file))
|
||||
(with-temp-buffer
|
||||
(insert-file-contents init-file)
|
||||
(when (re-search-forward "^\\s-*\\((doom! \\)" nil t)
|
||||
(goto-char (match-beginning 1))
|
||||
(setq modules (cdr (sexp-at-point))))))
|
||||
(unless modules
|
||||
(error "Couldn't gather module list from %s" init-file))))
|
||||
(if (eq modules t) (setq modules nil))
|
||||
(let (category)
|
||||
(dolist (m modules)
|
||||
(cond ((keywordp m) (setq category m))
|
||||
((not category) (error "No module category specified for %s" m))
|
||||
((let ((module (if (listp m) (car m) m))
|
||||
(flags (if (listp m) (cdr m))))
|
||||
(if-let* ((path (doom-module-locate-path category module)))
|
||||
(doom-module-set category module :flags flags :path path)
|
||||
(when doom-debug-mode
|
||||
(message "Couldn't find the %s %s module" category module))))))))
|
||||
doom-modules))
|
||||
|
||||
|
||||
;;
|
||||
;; Use-package modifications
|
||||
|
@ -506,25 +540,40 @@ added, if the file exists."
|
|||
(defmacro doom! (&rest modules)
|
||||
"Bootstraps DOOM Emacs and its modules.
|
||||
|
||||
MODULES is an malformed plist of modules to load."
|
||||
(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-locate-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))))))))
|
||||
The bootstrap process involves making sure the essential directories exist, core
|
||||
packages are installed, `doom-autoload-file' is loaded, `doom-packages-file'
|
||||
cache exists (and is loaded) and, finally, loads your private init.el (which
|
||||
should contain your `doom!' block).
|
||||
|
||||
If the cache exists, much of this function isn't run, which substantially
|
||||
reduces startup time.
|
||||
|
||||
The overall load order of Doom is as follows:
|
||||
|
||||
~/.emacs.d/init.el
|
||||
~/.emacs.d/core/core.el
|
||||
`doom-pre-init-hook'
|
||||
~/.doom.d/init.el
|
||||
Module init.el files
|
||||
`doom-init-hook'
|
||||
Module config.el files
|
||||
~/.doom.d/config.el
|
||||
`after-init-hook'
|
||||
`emacs-startup-hook'
|
||||
`doom-post-init-hook' (at end of `emacs-startup-hook')
|
||||
|
||||
Module load order is determined by your `doom!' block. See `doom-modules-dirs'
|
||||
for a list of all recognized module trees. Order defines precedence (from most
|
||||
to least)."
|
||||
(let ((doom-modules (doom-module-table (or modules t)))
|
||||
init-forms config-forms file-name-handler-alist)
|
||||
(maphash (lambda (key value)
|
||||
(let ((path (plist-get value :path)))
|
||||
(push `(let ((doom--current-module ',key)) (load! init ,path t))
|
||||
init-forms)
|
||||
(push `(let ((doom--current-module ',key)) (load! config ,path t))
|
||||
config-forms)))
|
||||
doom-modules)
|
||||
`(let (file-name-handler-alist)
|
||||
(setq doom-modules ',doom-modules)
|
||||
,@(nreverse init-forms)
|
||||
|
@ -533,8 +582,9 @@ MODULES is an malformed plist of modules to load."
|
|||
(let ((doom--stage 'config))
|
||||
,@(nreverse config-forms)
|
||||
(when doom-private-dir
|
||||
(load ,(concat doom-private-dir "config")
|
||||
t (not doom-debug-mode))))))))
|
||||
(let ((load-prefer-newer t))
|
||||
(load ,(expand-file-name "config" doom-private-dir)
|
||||
t (not doom-debug-mode)))))))))
|
||||
|
||||
(defmacro def-package! (name &rest plist)
|
||||
"A thin wrapper around `use-package'."
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue