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
|
@ -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)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue