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:
Henrik Lissner 2018-05-24 19:00:41 +02:00
parent 3dd291a675
commit 8746c12fae
No known key found for this signature in database
GPG key ID: 5F6C0EA160557395
4 changed files with 413 additions and 259 deletions

View file

@ -1,149 +1,255 @@
;;; core/autoload/modules.el -*- lexical-binding: t; -*-
;;;###autoload
(defun doom//reload ()
"Reload your private Doom config. Experimental!"
(interactive)
(when (file-exists-p doom-packages-file)
(delete-file doom-packages-file))
(cond ((and noninteractive (not (daemonp)))
(doom-initialize)
(doom//reload-autoloads)
(require 'server)
(when (server-running-p)
(message "Reloading active Emacs session...")
(server-eval-at server-name '(doom//reload))))
(autoload 'print! "autoload/message" nil 'macro)
(autoload 'printerr! "autoload/message" nil 'macro)
((let ((load-prefer-newer t)
doom-init-p)
(setq doom-modules (make-hash-table :test #'equal :size 100 :rehash-threshold 1.0))
(doom-initialize t)
(message "%d packages reloaded" (length package-alist))
(run-hooks 'doom-reload-hook)))))
(defun doom--server-eval (body)
(require 'server)
(when (server-running-p)
(server-eval-at server-name body)))
;;;###autoload
(defun doom//reload (&optional force-p)
"Reloads your config. This is experimental!
If called from a noninteractive session, this will try to communicate with a
live server (if one is found) to tell it to run this function.
If called from an interactive session, tries to reload autoloads files (if
necessary), reinistalize doom (via `doom-initialize') and reloads your private
init.el and config.el. Then runs `doom-reload-hook'."
(interactive)
(unless doom--inhibit-reload
(cond ((and noninteractive (not (daemonp)))
(require 'server)
(if (not (server-running-p))
(doom//reload-autoloads force-p)
(print! "Reloading active Emacs session...")
(print!
(bold "%%s")
(if (server-eval-at server-name '(doom//reload))
(green "Done!")
(red "There were issues!")))))
((let ((load-prefer-newer t))
(doom//reload-autoloads force-p)
(doom-initialize t)
(doom-initialize-modules t)
(print! (green "%d packages reloaded" (length package-alist)))
(run-hooks 'doom-reload-hook))))))
;;
;; Autoload file generation
;;
(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.")
(defun doom--byte-compile (file)
(let ((short-name (file-name-nondirectory file)))
(condition-case-unless-debug ex
(when (byte-compile-file file)
(load (byte-compile-dest-file file) nil t)
(unless noninteractive
(message "Finished compiling %s" short-name)))
('error
(doom-delete-autoloads-file file)
(error "Error in %s: %s -- %s"
short-name
(car ex) (error-message-string ex))))))
;;;###autoload
(defun doom-delete-autoloads-file (file)
"Delete FILE (an autoloads file), and delete the accompanying *.elc file, if
it exists."
(or (stringp file)
(signal 'wrong-type-argument (list 'stringp file)))
(when (file-exists-p file)
(delete-file file)
(ignore-errors (delete-file (byte-compile-dest-file file)))
(print! "Deleted old %s" (file-name-nondirectory file))))
;;;###autoload
(defun doom//reload-autoloads (&optional file force-p)
"Reloads FILE (an autoload file), if it needs reloading.
FILE should be one of `doom-autoload-file' or `doom-package-autoload-file'. If
it is nil, it will try to reload both. If FORCE-P (universal argument) do it
even if it doesn't need reloading!"
(interactive
(list nil current-prefix-arg))
(or (null file)
(stringp file)
(signal 'wrong-type-argument (list 'stringp file)))
(cond ((equal file doom-autoload-file)
(doom//reload-doom-autoloads force-p))
((equal file doom-package-autoload-file)
(doom//reload-package-autoloads force-p))
((progn
(doom//reload-doom-autoloads force-p)
(doom//reload-package-autoloads force-p)))))
(defvar generated-autoload-load-name)
;;;###autoload
(defun doom//reload-autoloads (&optional force)
"Refreshes the autoloads.el file, specified by `doom-autoload-file'.
(defun doom//reload-doom-autoloads (&optional force-p)
"Refreshes the autoloads.el file, specified by `doom-autoload-file', if
necessary (or if FORCE-P is non-nil).
It scans and reads core/autoload/*.el, modules/*/*/autoload.el and
modules/*/*/autoload/*.el, and generates an autoloads file at the path specified
by `doom-autoload-file'. This file tells Emacs where to find lazy-loaded
functions.
modules/*/*/autoload/*.el, and generates `doom-autoload-file'. This file tells
Emacs where to find lazy-loaded functions.
This should be run whenever init.el or an autoload file is modified. Running
'make autoloads' from the commandline executes this command."
This should be run whenever your `doom!' block, or a module autoload file, is
modified."
(interactive)
;; This function must not use autoloaded functions or external dependencies.
;; It must assume nothing is set up!
(let ((default-directory doom-emacs-dir)
(let ((doom-modules (doom-module-table))
(default-directory doom-emacs-dir)
(targets
(file-expand-wildcards
(expand-file-name "autoload/*.el" doom-core-dir)))
(generate-autoload-section-continuation "")
(generate-autoload-section-header "")
(generate-autoload-section-trailer "")
(doom--stage 'autoloads)
outdated)
(expand-file-name "autoload/*.el" doom-core-dir))))
(dolist (path (doom-module-load-path))
(let ((auto-dir (expand-file-name "autoload" path))
(auto-file (expand-file-name "autoload.el" path)))
(when (file-exists-p auto-file)
(push auto-file targets))
(when (file-directory-p auto-dir)
(dolist (file (doom-files-under auto-dir :match "\\.el$"))
(dolist (file (doom-files-in auto-dir :match "\\.el$" :full t))
(push file targets)))))
(when (file-exists-p doom-autoload-file)
(delete-file doom-autoload-file)
(ignore-errors (delete-file (byte-compile-dest-file doom-autoload-file)))
(message "Deleted old autoloads.el"))
(message "Generating new autoloads.el")
(dolist (file (mapcar #'file-truename (reverse targets)))
(let ((generated-autoload-load-name (file-name-sans-extension file)))
(message
(cond ((not (doom-file-cookie-p file))
"⚠ Ignoring %s")
((update-file-autoloads file nil doom-autoload-file)
"✕ Nothing in %s")
("✓ Scanned %s"))
(if (file-in-directory-p file default-directory)
(file-relative-name file)
(abbreviate-file-name file)))))
(make-directory (file-name-directory doom-autoload-file) t)
(let ((buf (find-file-noselect doom-autoload-file t))
(load-path (append doom-psuedo-module-dirs
doom-modules-dirs
load-path))
case-fold-search)
;; FIXME Make me faster
(unwind-protect
(with-current-buffer buf
(goto-char (point-min))
(insert ";;; -*- lexical-binding:t -*-\n"
";; This file is autogenerated by `doom//reload-autoloads', DO NOT EDIT !!\n\n")
(if (and (not force-p)
(file-exists-p doom-autoload-file)
(not (cl-loop for file in targets
if (file-newer-than-file-p file doom-autoload-file)
return t)))
(ignore (print! (green "Doom core autoloads is up-to-date"))
(doom-initialize-autoloads doom-autoload-file))
(doom-delete-autoloads-file doom-autoload-file)
;; in case the buffer is open somewhere and modified
(when-let* ((buf (find-buffer-visiting doom-autoload-file)))
(with-current-buffer buf
(set-buffer-modified-p nil))
(kill-buffer buf))
(message "Generating new autoloads.el")
(dolist (file (nreverse targets))
(let* ((file (file-truename file))
(generated-autoload-load-name (file-name-sans-extension file))
(noninteractive (not doom-debug-mode)))
(print!
(cond ((not (doom-file-cookie-p file))
"⚠ Ignoring %s")
((update-file-autoloads file nil doom-autoload-file)
(yellow "✕ Nothing in %%s"))
((green "✓ Scanned %%s")))
(if (file-in-directory-p file default-directory)
(file-relative-name file)
(abbreviate-file-name file)))))
(make-directory (file-name-directory doom-autoload-file) t)
(let ((buf (find-file-noselect doom-autoload-file t))
case-fold-search)
(unwind-protect
(with-current-buffer buf
(goto-char (point-min))
(insert ";;; -*- lexical-binding:t -*-\n"
";; This file is autogenerated by `doom//reload-doom-autoloads', DO NOT EDIT !!\n\n")
(save-excursion
;; Replace autoload paths (only for module autoloads) with
;; absolute paths for faster resolution during load and
;; simpler `load-path'
(let ((load-path (append doom-psuedo-module-dirs
doom-modules-dirs
load-path))
cache)
(save-excursion
(while (re-search-forward "^\\s-*(autoload\\s-+'[^ ]+\\s-+\"\\([^\"]*\\)\"" nil t)
(let ((path (match-string 1)))
(replace-match
(or (cdr (assoc path cache))
(when-let* ((libpath (locate-library path))
(libpath (file-name-sans-extension libpath)))
(push (cons path (abbreviate-file-name libpath)) cache)
libpath)
path)
t t nil 1)))
(print! (green "✓ Autoload paths expanded")))))
;; Remove byte-compile inhibiting file variables so we can
;; byte-compile the file.
(when (re-search-forward "^;; no-byte-compile: t\n" nil t)
(replace-match "" t t))
;; Byte compile it to give the file a chance to reveal errors.
(save-buffer)
(doom--byte-compile doom-autoload-file)
(when (and noninteractive (not (daemonp)))
(doom--server-eval `(load-file ,doom-autoload-file)))
t)
(kill-buffer buf))))))
;; Replace autoload paths (only for module autoloads) with
;; absolute paths for faster resolution during load and simpler
;; `load-path'
(save-excursion
(let (cache)
(while (re-search-forward "^\\s-*(autoload\\s-+'[^ ]+\\s-+\"\\([^\"]*\\)\"" nil t)
(let ((path (match-string 1)))
(replace-match
(or (cdr (assoc path cache))
(when-let* ((libpath (locate-library path))
(libpath (file-name-sans-extension libpath)))
(push (cons path (abbreviate-file-name libpath)) cache)
libpath)
(progn
(warn "Couldn't find absolute path for: %s" path)
path))
t t nil 1))))
(message "✓ Autoload paths expanded"))
;;;###autoload
(defun doom//reload-package-autoloads (&optional force-p)
"Compiles `doom-package-autoload-file' from the autoloads files of all
installed packages. It also caches `load-path', `Info-directory-list',
`doom-disabled-packages', `package-activated-list' and `auto-mode-alist'.
;; insert package autoloads
(save-excursion
(dolist (spec package-alist)
(let ((pkg (car spec)))
(unless (memq pkg doom-autoload-excluded-packages)
(let ((file
(abbreviate-file-name
(concat (package--autoloads-file-name (cadr spec)) ".el"))))
(insert "(let ((load-file-name " (prin1-to-string file) "))\n")
(insert-file-contents file)
(while (re-search-forward "^\\(?:;;\\(.*\n\\)\\|\n\\)" nil t)
(unless (nth 8 (syntax-ppss))
(replace-match "" t t)))
(unless (bolp) (insert "\n"))
(insert ")\n")))))
(message "✓ Package autoloads included"))
Will do nothing if none of your installed packages have been modified. If
FORCE-P (universal argument) is non-nil, regenerate it anyway.
;; Remove `load-path' and `auto-mode-alist' modifications (most
;; of them, at least); they are cached elsewhere, so these are
;; unnecessary overhead.
(while (re-search-forward (concat "^\\s-*(\\(add-to-list\\s-+'\\(?:load-path\\|auto-mode-alist\\)\\)")
nil t)
(beginning-of-line)
(skip-chars-forward " \t")
(kill-sexp))
(message "✓ load-path/auto-mode-alist entries removed")
This should be run whenever your `doom!' block or update your packages."
(interactive)
(if (and (not force-p)
(file-exists-p doom-package-autoload-file)
(not (cl-loop initially do (doom-ensure-packages-initialized t)
for (_pkg desc) in package-alist
for autoload-file = (concat (package--autoloads-file-name desc) ".el")
if (file-newer-than-file-p autoload-file doom-package-autoload-file)
return t)))
(ignore (print! (green "Doom package autoloads is up-to-date"))
(doom-initialize-autoloads doom-package-autoload-file))
(doom-delete-autoloads-file doom-package-autoload-file)
(with-temp-file doom-package-autoload-file
(insert ";;; -*- lexical-binding:t -*-\n"
";; This file is autogenerated by `doom//reload-package-autoloads', DO NOT EDIT !!\n\n")
;; insert package autoloads
(save-excursion
(dolist (spec package-alist)
(cl-destructuring-bind (pkg desc) spec
(unless (memq pkg doom-autoload-excluded-packages)
(let ((file
(abbreviate-file-name
(concat (package--autoloads-file-name desc) ".el"))))
(when (file-exists-p file)
(insert "(let ((load-file-name " (prin1-to-string file) "))\n")
(insert-file-contents file)
(while (re-search-forward "^\\(?:;;\\(.*\n\\)\\|\n\\)" nil t)
(unless (nth 8 (syntax-ppss))
(replace-match "" t t)))
(unless (bolp) (insert "\n"))
(insert ")\n"))))))
(print! (green "✓ Package autoloads included"))
;; Cache the important and expensive-to-initialize state here.
(doom-initialize-packages 'internal)
(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))
(print! (green "✓ Cached package state")))
;; Remove `load-path' and `auto-mode-alist' modifications (most of them,
;; at least); they are cached later, so all those membership checks are
;; unnecessary overhead.
(while (re-search-forward "^\\s-*\\((\\(?:add-to-list\\|when (boundp \\)\\s-+'\\(?:load-path\\|auto-mode-alist\\)\\)" nil t)
(goto-char (match-beginning 1))
(kill-sexp))
(print! (green "✓ Removed load-path/auto-mode-alist entries")))
(doom--byte-compile doom-package-autoload-file)
(when (and noninteractive (not (daemonp)))
(doom--server-eval `(load-file ,doom-package-autoload-file)))
t))
;; Remove byte-compile inhibiting file variables so we can
;; byte-compile the file.
(when (re-search-forward "^;; no-byte-compile: t\n$" nil t)
(replace-match "" t t))
(save-buffer)
;; Byte compile it to give the file a chance to reveal errors.
(condition-case-unless-debug ex
(quiet! (byte-compile-file doom-autoload-file 'load))
('error
(delete-file doom-autoload-file)
(message "Deleting autoloads file!")
(error "Error in autoloads.el: %s -- %s"
(car ex) (error-message-string ex))))
(message "Done!"))
(kill-buffer buf)))))
;;
;; Byte compilation
;;
;;;###autoload
(defun doom//byte-compile (&optional modules recompile-p)

View file

@ -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'."

View file

@ -58,6 +58,20 @@ Use this for files that change often, like cache files.")
"Where your private customizations are placed. Must end in a slash. Respects
XDG directory conventions if ~/.config/doom exists.")
;; Doom hooks
(defvar doom-pre-init-hook nil
"Hooks run after Doom is first initialized; after Doom's core files are
loaded, but before your private init.el file or anything else is loaded.")
(defvar doom-init-hook nil
"Hooks run after all init.el files are loaded, including your private and all
module init.el files, but before their config.el files are loaded.")
(defvar doom-post-init-hook nil
"A list of hooks run when Doom is fully initialized. Fires at the end of
`emacs-startup-hook', as late as possible. Guaranteed to run after everything
else (except for `window-setup-hook').")
;;;
;; UTF-8 as the default coding system
@ -106,22 +120,6 @@ XDG directory conventions if ~/.config/doom exists.")
url-configuration-directory (concat doom-etc-dir "url/"))
;; Custom init hooks; clearer than `after-init-hook', `emacs-startup-hook', and
;; `window-setup-hook'.
(defvar doom-pre-init-hook nil
"Hooks run after Doom is first initialized; after Doom's core files are
loaded, but before your private init.el file or anything else is loaded.")
(defvar doom-init-hook nil
"Hooks run after all init.el files are loaded, including your private and all
module init.el files, but before their config.el files are loaded.")
(defvar doom-post-init-hook nil
"A list of hooks run when Doom is fully initialized. Fires at the end of
`emacs-startup-hook', as late as possible. Guaranteed to run after everything
else (except for `window-setup-hook').")
;;
;; Emacs fixes/hacks
;;
@ -163,7 +161,7 @@ with functions that require it (like modeline segments)."
(advice-add #'make-indirect-buffer :around #'doom*set-indirect-buffer-filename)
;; Truly silence startup message
(advice-add #'display-startup-echo-area-message :override #'ignore)
(fset #'display-startup-echo-area-message #'ignore)
;;
@ -202,8 +200,10 @@ this, you'll get stuttering and random freezes) and resets
(require 'core-packages)
(require 'core-os)
(unless noninteractive
(doom-initialize))
(doom-initialize noninteractive)
(if noninteractive
(require 'core-dispatcher)
(doom-initialize-modules))
(provide 'core)
;;; core.el ends here

View file

@ -31,5 +31,3 @@
load-prefer-newer noninteractive)
(require 'core (concat user-emacs-directory "core/core"))
(when noninteractive
(require 'core-dispatcher))