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; -*- ;;; core/autoload/modules.el -*- lexical-binding: t; -*-
;;;###autoload (autoload 'print! "autoload/message" nil 'macro)
(defun doom//reload () (autoload 'printerr! "autoload/message" nil 'macro)
"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))))
((let ((load-prefer-newer t) (defun doom--server-eval (body)
doom-init-p) (require 'server)
(setq doom-modules (make-hash-table :test #'equal :size 100 :rehash-threshold 1.0)) (when (server-running-p)
(doom-initialize t) (server-eval-at server-name body)))
(message "%d packages reloaded" (length package-alist))
(run-hooks 'doom-reload-hook))))) ;;;###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) (defvar generated-autoload-load-name)
;;;###autoload ;;;###autoload
(defun doom//reload-autoloads (&optional force) (defun doom//reload-doom-autoloads (&optional force-p)
"Refreshes the autoloads.el file, specified by `doom-autoload-file'. "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 It scans and reads core/autoload/*.el, modules/*/*/autoload.el and
modules/*/*/autoload/*.el, and generates an autoloads file at the path specified modules/*/*/autoload/*.el, and generates `doom-autoload-file'. This file tells
by `doom-autoload-file'. This file tells Emacs where to find lazy-loaded Emacs where to find lazy-loaded functions.
functions.
This should be run whenever init.el or an autoload file is modified. Running This should be run whenever your `doom!' block, or a module autoload file, is
'make autoloads' from the commandline executes this command." modified."
(interactive) (interactive)
;; This function must not use autoloaded functions or external dependencies. (let ((doom-modules (doom-module-table))
;; It must assume nothing is set up! (default-directory doom-emacs-dir)
(let ((default-directory doom-emacs-dir)
(targets (targets
(file-expand-wildcards (file-expand-wildcards
(expand-file-name "autoload/*.el" doom-core-dir))) (expand-file-name "autoload/*.el" doom-core-dir))))
(generate-autoload-section-continuation "")
(generate-autoload-section-header "")
(generate-autoload-section-trailer "")
(doom--stage 'autoloads)
outdated)
(dolist (path (doom-module-load-path)) (dolist (path (doom-module-load-path))
(let ((auto-dir (expand-file-name "autoload" path)) (let ((auto-dir (expand-file-name "autoload" path))
(auto-file (expand-file-name "autoload.el" path))) (auto-file (expand-file-name "autoload.el" path)))
(when (file-exists-p auto-file) (when (file-exists-p auto-file)
(push auto-file targets)) (push auto-file targets))
(when (file-directory-p auto-dir) (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))))) (push file targets)))))
(when (file-exists-p doom-autoload-file) (if (and (not force-p)
(delete-file doom-autoload-file) (file-exists-p doom-autoload-file)
(ignore-errors (delete-file (byte-compile-dest-file doom-autoload-file))) (not (cl-loop for file in targets
(message "Deleted old autoloads.el")) if (file-newer-than-file-p file doom-autoload-file)
(message "Generating new autoloads.el") return t)))
(dolist (file (mapcar #'file-truename (reverse targets))) (ignore (print! (green "Doom core autoloads is up-to-date"))
(let ((generated-autoload-load-name (file-name-sans-extension file))) (doom-initialize-autoloads doom-autoload-file))
(message (doom-delete-autoloads-file doom-autoload-file)
(cond ((not (doom-file-cookie-p file)) ;; in case the buffer is open somewhere and modified
"⚠ Ignoring %s") (when-let* ((buf (find-buffer-visiting doom-autoload-file)))
((update-file-autoloads file nil doom-autoload-file) (with-current-buffer buf
"✕ Nothing in %s") (set-buffer-modified-p nil))
("✓ Scanned %s")) (kill-buffer buf))
(if (file-in-directory-p file default-directory) (message "Generating new autoloads.el")
(file-relative-name file) (dolist (file (nreverse targets))
(abbreviate-file-name file))))) (let* ((file (file-truename file))
(make-directory (file-name-directory doom-autoload-file) t) (generated-autoload-load-name (file-name-sans-extension file))
(let ((buf (find-file-noselect doom-autoload-file t)) (noninteractive (not doom-debug-mode)))
(load-path (append doom-psuedo-module-dirs (print!
doom-modules-dirs (cond ((not (doom-file-cookie-p file))
load-path)) "⚠ Ignoring %s")
case-fold-search) ((update-file-autoloads file nil doom-autoload-file)
;; FIXME Make me faster (yellow "✕ Nothing in %%s"))
(unwind-protect ((green "✓ Scanned %%s")))
(with-current-buffer buf (if (file-in-directory-p file default-directory)
(goto-char (point-min)) (file-relative-name file)
(insert ";;; -*- lexical-binding:t -*-\n" (abbreviate-file-name file)))))
";; This file is autogenerated by `doom//reload-autoloads', DO NOT EDIT !!\n\n") (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 ;;;###autoload
;; absolute paths for faster resolution during load and simpler (defun doom//reload-package-autoloads (&optional force-p)
;; `load-path' "Compiles `doom-package-autoload-file' from the autoloads files of all
(save-excursion installed packages. It also caches `load-path', `Info-directory-list',
(let (cache) `doom-disabled-packages', `package-activated-list' and `auto-mode-alist'.
(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"))
;; insert package autoloads Will do nothing if none of your installed packages have been modified. If
(save-excursion FORCE-P (universal argument) is non-nil, regenerate it anyway.
(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"))
;; Remove `load-path' and `auto-mode-alist' modifications (most This should be run whenever your `doom!' block or update your packages."
;; of them, at least); they are cached elsewhere, so these are (interactive)
;; unnecessary overhead. (if (and (not force-p)
(while (re-search-forward (concat "^\\s-*(\\(add-to-list\\s-+'\\(?:load-path\\|auto-mode-alist\\)\\)") (file-exists-p doom-package-autoload-file)
nil t) (not (cl-loop initially do (doom-ensure-packages-initialized t)
(beginning-of-line) for (_pkg desc) in package-alist
(skip-chars-forward " \t") for autoload-file = (concat (package--autoloads-file-name desc) ".el")
(kill-sexp)) if (file-newer-than-file-p autoload-file doom-package-autoload-file)
(message "✓ load-path/auto-mode-alist entries removed") 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 ;; Byte compilation
(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)))))
;;;###autoload ;;;###autoload
(defun doom//byte-compile (&optional modules recompile-p) (defun doom//byte-compile (&optional modules recompile-p)

View file

@ -40,53 +40,47 @@
;; See core/autoload/packages.el for more functions. ;; See core/autoload/packages.el for more functions.
(defvar doom-init-p nil (defvar doom-init-p nil
"Non-nil if doom is done initializing (once `doom-post-init-hook' is done). If "Non-nil if `doom-initialize' has run.")
this is nil after Emacs has started something is wrong.")
(defvar doom-init-modules-p nil
"Non-nil if `doom-initialize-modules' has run.")
(defvar doom-init-time nil (defvar doom-init-time nil
"The time it took, in seconds, for DOOM Emacs to initialize.") "The time it took, in seconds, for DOOM Emacs to initialize.")
(defvar doom-modules (defvar doom-modules ()
(make-hash-table :test #'equal :size 100 :rehash-threshold 1.0)
"A hash table of enabled modules. Set by `doom-initialize-modules'.") "A hash table of enabled modules. Set by `doom-initialize-modules'.")
(defvar doom-modules-dirs (defvar doom-modules-dirs
(list (expand-file-name "modules/" doom-private-dir) doom-modules-dir) (list (expand-file-name "modules/" doom-private-dir) doom-modules-dir)
"A list of module root directories. Order determines priority.") "A list of module root directories. Order determines priority.")
(defvar doom-psuedo-module-dirs (defvar doom-psuedo-module-dirs (list doom-private-dir)
(list doom-private-dir)
"Additional paths for modules that are outside of `doom-modules-dirs'. "Additional paths for modules that are outside of `doom-modules-dirs'.
`doom//reload-autoloads', `doom//byte-compile' and `doom-initialize-packages' `doom//reload-doom-autoloads', `doom//byte-compile' and
will include the directories in this list.") `doom-initialize-packages' will include the directories in this list.")
(defvar doom-packages () (defvar doom-packages ()
"A list of enabled packages. Each element is a sublist, whose CAR is the "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's name as a symbol, and whose CDR is the plist supplied to its
`package!' declaration. Set by `doom-initialize-packages'.") `package!' declaration. Set by `doom-initialize-packages'.")
(defvar doom-core-packages (defvar doom-core-packages '(persistent-soft use-package quelpa async)
'(persistent-soft use-package quelpa async)
"A list of packages that must be installed (and will be auto-installed if "A list of packages that must be installed (and will be auto-installed if
missing) and shouldn't be deleted.") missing) and shouldn't be deleted.")
(defvar doom-disabled-packages () (defvar doom-disabled-packages ()
"A list of packages that should be ignored by `def-package!'.") "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 (defvar doom-site-load-path load-path
"The starting load-path, before it is altered by `doom-initialize'.") "The starting load-path, before it is altered by `doom-initialize'.")
(defvar doom-autoload-file (concat doom-local-dir "autoloads.el") (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") (defvar doom-package-autoload-file (concat doom-local-dir "autoloads.pkg.el")
"Where to cache `load-path', `Info-directory-list', `doom-disabled-packages' "Where `doom//reload-package-autoloads' will generate its package.el autoloads
and `auto-mode-alist'.") file.")
(defvar doom-reload-hook nil (defvar doom-reload-hook nil
"A list of hooks to run when `doom//reload-load-path' is called.") "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) (file-relative-name load-file-name doom-emacs-dir)
(abbreviate-file-name load-file-name)))) (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) (defun doom|display-benchmark (&optional return-p)
"Display a benchmark, showing number of packages and modules, and how quickly "Display a benchmark, showing number of packages and modules, and how quickly
they were loaded at startup. they were loaded at startup.
@ -195,17 +173,59 @@ session, with a different init.el, like so:
'window-setup-hook)) '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 ;; 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) (defun doom-initialize (&optional force-p)
"Bootstrap Doom, if it hasn't already (or if FORCE-P is non-nil). "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 The bootstrap process involves making sure 1) the essential directories exist,
packages are installed, `doom-autoload-file' is loaded, `doom-packages-file' 2) the core packages are installed, 3) `doom-autoload-file' and
cache exists (and is loaded) and, finally, loads your private init.el (which `doom-package-autoload-file' exist and have been loaded, and 4) Doom's core
should contain your `doom!' block). files are loaded.
If the cache exists, much of this function isn't run, which substantially If the cache exists, much of this function isn't run, which substantially
reduces startup time. 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 for a list of all recognized module trees. Order defines precedence (from most
to least)." to least)."
(when (or force-p (not doom-init-p)) (when (or force-p (not doom-init-p))
(when (and (or force-p noninteractive) ;; Set this to prevent infinite recursive calls to `doom-initialize'
(file-exists-p doom-packages-file)) (setq doom-init-p t)
(message "Deleting packages.el cache") ;; `doom-autoload-file' tells Emacs where to load all its autoloaded
(delete-file doom-packages-file)) ;; functions from. This includes everything in core/autoload/*.el and all
(unless (load doom-packages-file 'noerror 'nomessage 'nosuffix) ;; the autoload files in your enabled modules.
;; Ensure core folders exist, otherwise we get errors (unless (doom-initialize-autoloads doom-autoload-file force-p)
(dolist (dir (list doom-local-dir doom-etc-dir doom-cache-dir doom-packages-dir)) (doom-ensure-core-directories)
(unless (file-directory-p dir) (doom-ensure-packages-initialized force-p)
(make-directory dir t))) (doom-ensure-core-packages)
;; Ensure plugins have been initialized ;; Regenerate `doom-autoload-file', which tells Doom where to find all its
(require 'package) ;; module autoloaded functions.
(setq package-activated-list nil (unless (or force-p noninteractive)
package--initialized nil) (doom//reload-doom-autoloads)))
(let (byte-compile-warnings) ;; Loads `doom-package-autoload-file', which caches `load-path',
(condition-case _ ;; `auto-mode-alist', `Info-directory-list', `doom-disabled-packages' and
(package-initialize) ;; `package-activated-list'. A big reduction in startup time.
('error (package-refresh-contents) (unless (doom-initialize-autoloads doom-package-autoload-file force-p)
(setq doom--refreshed-p t) (unless (or force-p noninteractive)
(package-initialize)))) (doom//reload-package-autoloads))))
;; 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))
;; Initialize Doom core ;; Initialize Doom core
(unless noninteractive (unless noninteractive
(require 'core-ui) (require 'core-ui)
(require 'core-editor) (require 'core-editor)
(require 'core-projects) (require 'core-projects)
(require 'core-keybinds)) (require 'core-keybinds)))
;; Bootstrap Doom
(unless doom-init-p (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 (unless noninteractive
(add-hook! 'doom-reload-hook
#'(doom|refresh-cache doom|display-benchmark))
(add-hook! 'emacs-startup-hook (add-hook! 'emacs-startup-hook
#'(doom|post-init doom|display-benchmark))) #'(doom|post-init doom|display-benchmark)))
(run-hooks 'doom-pre-init-hook) (run-hooks 'doom-pre-init-hook)
(when doom-private-dir (when doom-private-dir
(load (concat doom-private-dir "init") t t))) (let ((load-prefer-newer t))
(setq doom-init-p t)) (load (expand-file-name "init" doom-private-dir)
'noerror 'nomessage)))))
(defun doom-initialize-autoloads () (defun doom-initialize-autoloads (file &optional clear-p)
"Tries to load `doom-autoload-file', otherwise throws an error (unless in a "Tries to load FILE (an autoloads file). Otherwise tries to regenerate it. If
noninteractive session)." CLEAR-P is non-nil, regenerate it anyway."
(unless (unless clear-p
(condition-case-unless-debug e (load (file-name-sans-extension file) 'noerror 'nomessage)))
(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-packages (&optional force-p) (defun doom-initialize-packages (&optional force-p)
"Ensures that Doom's package management system, package.el and quelpa are "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 ensure all the necessary package metadata is initialized and available for
them." them."
(with-temp-buffer ; prevent buffer-local settings from propagating (with-temp-buffer ; prevent buffer-local settings from propagating
;; Prefer uncompiled files to reduce stale code issues (let ((load-prefer-newer t) ; reduce stale code issues
(let ((load-prefer-newer t)) (doom-modules (doom-module-table)))
;; package.el and quelpa handle themselves if their state changes during ;; 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, ;; 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 ;; 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)) collect (plist-get plist :path))
(cl-remove-if-not #'file-directory-p doom-psuedo-module-dirs))) (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 ;; Use-package modifications
@ -506,25 +540,40 @@ added, if the file exists."
(defmacro doom! (&rest modules) (defmacro doom! (&rest modules)
"Bootstraps DOOM Emacs and its modules. "Bootstraps DOOM Emacs and its modules.
MODULES is an malformed plist of modules to load." The bootstrap process involves making sure the essential directories exist, core
(let (init-forms config-forms file-name-handler-alist) packages are installed, `doom-autoload-file' is loaded, `doom-packages-file'
(let (module) cache exists (and is loaded) and, finally, loads your private init.el (which
(dolist (m modules) should contain your `doom!' block).
(cond ((keywordp m) (setq module m))
((not module) (error "No namespace specified in `doom!' for %s" m)) If the cache exists, much of this function isn't run, which substantially
((let ((submodule (if (listp m) (car m) m)) reduces startup time.
(flags (if (listp m) (cdr m))))
(let ((path (doom-module-locate-path module submodule))) The overall load order of Doom is as follows:
(if (not path)
(when doom-debug-mode ~/.emacs.d/init.el
(message "Couldn't find the %s %s module" module submodule)) ~/.emacs.d/core/core.el
(doom-module-set module submodule :flags flags :path path) `doom-pre-init-hook'
(push `(let ((doom--current-module ',(cons module submodule))) ~/.doom.d/init.el
(load! init ,path t)) Module init.el files
init-forms) `doom-init-hook'
(push `(let ((doom--current-module ',(cons module submodule))) Module config.el files
(load! config ,path t)) ~/.doom.d/config.el
config-forms)))))))) `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) `(let (file-name-handler-alist)
(setq doom-modules ',doom-modules) (setq doom-modules ',doom-modules)
,@(nreverse init-forms) ,@(nreverse init-forms)
@ -533,8 +582,9 @@ MODULES is an malformed plist of modules to load."
(let ((doom--stage 'config)) (let ((doom--stage 'config))
,@(nreverse config-forms) ,@(nreverse config-forms)
(when doom-private-dir (when doom-private-dir
(load ,(concat doom-private-dir "config") (let ((load-prefer-newer t))
t (not doom-debug-mode)))))))) (load ,(expand-file-name "config" doom-private-dir)
t (not doom-debug-mode)))))))))
(defmacro def-package! (name &rest plist) (defmacro def-package! (name &rest plist)
"A thin wrapper around `use-package'." "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 "Where your private customizations are placed. Must end in a slash. Respects
XDG directory conventions if ~/.config/doom exists.") 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 ;; 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/")) 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 ;; 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) (advice-add #'make-indirect-buffer :around #'doom*set-indirect-buffer-filename)
;; Truly silence startup message ;; 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-packages)
(require 'core-os) (require 'core-os)
(unless noninteractive (doom-initialize noninteractive)
(doom-initialize)) (if noninteractive
(require 'core-dispatcher)
(doom-initialize-modules))
(provide 'core) (provide 'core)
;;; core.el ends here ;;; core.el ends here

View file

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