I move our hackiest and least offensive startup optimizations to core, so they're easy for me to keep track of (they'll likely change often, between major Emacs releases), to keep them from affecting non-Doom profiles, and make it easy for readers to use as a reference.
143 lines
7.5 KiB
EmacsLisp
143 lines
7.5 KiB
EmacsLisp
;;; early-init.el --- Doom's universal bootstrapper -*- lexical-binding: t -*-
|
|
;;; Commentary:
|
|
;;
|
|
;; early-init.el was introduced in Emacs 27.1 and is loaded before init.el, and
|
|
;; before Emacs initializes its UI or package.el, and before site files are
|
|
;; loaded. This is good place for startup optimizating, because only here can
|
|
;; you *prevent* things from loading, rather than turn them off after-the-fact.
|
|
;; As such, Doom does all its initializing here.
|
|
;;
|
|
;; This file is Doom's "universal bootstrapper" for both interactive and
|
|
;; non-interactive sessions. It's also the heart of its profile bootloader,
|
|
;; which allows you to switch between Emacs configs on demand using
|
|
;; `--init-directory DIR' (which was backported from Emacs 29) or `--profile
|
|
;; NAME` (more about profiles at `https://docs.doomemacs.org/-/developers' or
|
|
;; docs/developers.org).
|
|
;;
|
|
;; In summary, this file is responsible for:
|
|
;; - Setting up some universal startup optimizations.
|
|
;; - Determining where `user-emacs-directory' is from one of:
|
|
;; - `--init-directory DIR' (backported from 29)
|
|
;; - `--profile PROFILENAME'
|
|
;; - Do one of the following:
|
|
;; - Load `doom' and one of `doom-start' or `doom-cli'.
|
|
;; - Or (if the user is trying to load a non-Doom config) load
|
|
;; `user-emacs-directory'/early-init.el.
|
|
;;
|
|
;;; Code:
|
|
|
|
;; PERF: Garbage collection is a big contributor to startup times. This fends it
|
|
;; off, but will be reset later by `gcmh-mode'. Not resetting it later will
|
|
;; cause stuttering/freezes.
|
|
(setq gc-cons-threshold most-positive-fixnum)
|
|
|
|
;; PERF: Don't use precious startup time checking mtime on elisp bytecode.
|
|
;; Ensuring correctness is 'doom sync's job, not the interactive session's.
|
|
;; Still, stale byte-code will cause *heavy* losses in startup efficiency.
|
|
(setq load-prefer-newer noninteractive)
|
|
|
|
|
|
;;
|
|
;;; Bootstrap
|
|
|
|
(or
|
|
;; PERF: `file-name-handler-alist' is consulted often. Unsetting it offers a
|
|
;; notable saving in startup time.
|
|
(let (file-name-handler-alist)
|
|
;; FEAT: First, we process --init-directory and --profile to detect what
|
|
;; `user-emacs-directory' to load from. I avoid using
|
|
;; `command-switch-alist' to process --profile and --init-directory because
|
|
;; it is processed too late to change `user-emacs-directory' in time.
|
|
|
|
;; REVIEW: Backported from Emacs 29. Remove when 28 support is dropped.
|
|
(let ((initdir (or (cadr (member "--init-directory" command-line-args))
|
|
(getenv-internal "EMACSDIR"))))
|
|
(when initdir
|
|
;; FIX: Discard the switch to prevent "invalid option" errors later.
|
|
(push (cons "--init-directory" (lambda (_) (pop argv))) command-switch-alist)
|
|
(setq user-emacs-directory (expand-file-name initdir))))
|
|
;; Initialize a known profile, if requested.
|
|
(let ((profile (or (cadr (member "--profile" command-line-args))
|
|
(getenv-internal "DOOMPROFILE"))))
|
|
(when profile
|
|
;; FIX: Discard the switch to prevent "invalid option" errors later.
|
|
(push (cons "--profile" (lambda (_) (pop argv))) command-switch-alist)
|
|
;; While processing the requested profile, Doom loosely expects
|
|
;; `user-emacs-directory' to be changed. If it doesn't, then you're using
|
|
;; profiles.el as a glorified, runtime dir-locals.el (which is fine, if
|
|
;; intended).
|
|
(catch 'found
|
|
(let ((profiles-file (expand-file-name "profiles.el" user-emacs-directory)))
|
|
(when (file-exists-p profiles-file)
|
|
(with-temp-buffer
|
|
(let ((coding-system-for-read 'utf-8-auto))
|
|
(insert-file-contents profiles-file))
|
|
(condition-case-unless-debug e
|
|
(let ((profile-data (cdr (assq (intern profile) (read (current-buffer))))))
|
|
(dolist (var profile-data (if profile-data (throw 'found t)))
|
|
(if (eq (car var) 'env)
|
|
(dolist (env (cdr var)) (setenv (car env) (cdr env)))
|
|
(set (car var) (cdr var)))))
|
|
(error (error "Failed to parse profiles.el: %s" (error-message-string e))))))
|
|
;; If the requested profile isn't in profiles.el, then see if
|
|
;; $EMACSDIR/profiles/$DOOMPROFILE exists. These are implicit
|
|
;; profiles, where `emacs --profile foo` will be equivalent to `emacs
|
|
;; --init-directory $EMACSDIR/profile/foo', if that directory exists.
|
|
(let ((profile-dir
|
|
(expand-file-name
|
|
profile (or (getenv-internal "DOOMPROFILESDIR")
|
|
(expand-file-name "profiles/" user-emacs-directory)))))
|
|
(when (file-directory-p profile-dir)
|
|
(setq user-emacs-directory profile-dir)
|
|
(throw 'found t)))
|
|
(user-error "No %S profile found" profile)))
|
|
(when init-file-debug
|
|
(message "Selected profile: %s" profile))
|
|
;; Ensure the selected profile persists through the session
|
|
(setenv "DOOMPROFILE" profile)))
|
|
|
|
;; PERF: When `load'ing or `require'ing files, each permutation of
|
|
;; `load-suffixes' and `load-file-rep-suffixes' (then `load-suffixes' +
|
|
;; `load-file-rep-suffixes') is used to locate the file. Each permutation
|
|
;; is a file op, which is normally very fast, but they can add up over the
|
|
;; hundreds/thousands of files Emacs needs to load.
|
|
;;
|
|
;; To reduce that burden -- and since Doom doesn't load any dynamic modules
|
|
;; -- I remove `.so' from `load-suffixes' and pass the `must-suffix' arg to
|
|
;; `load'. See the docs of `load' for details.
|
|
(or (let ((load-suffixes '(".elc" ".el")))
|
|
;; Load the heart of Doom Emacs.
|
|
(if (load (expand-file-name "lisp/doom" user-emacs-directory)
|
|
'noerror 'nomessage nil 'must-suffix)
|
|
;; ...and prepare for the rest of the session.
|
|
(if noninteractive
|
|
(doom-require 'doom-cli)
|
|
;; HACK: This advice hijacks Emacs' initfile resolver to replace
|
|
;; $EMACSDIR/init.el (and ~/.emacs or ~/_emacs) with a
|
|
;; a Doom-provided init file. Later, this file will be
|
|
;; generated by 'doom sync' for the active Doom profile;
|
|
;; `doom-start' is its stand-in until that's implemented.
|
|
;;
|
|
;; This effort spares Emacs the overhead of searching for
|
|
;; initfiles we don't care about, enables savvier hackers to
|
|
;; use $EMACSDIR as their $DOOMDIR, and gives us an opportunity
|
|
;; to fall back to a "safe mode", so we can present a more
|
|
;; user-friendly failure state.
|
|
(define-advice startup--load-user-init-file (:filter-args (args) init-doom)
|
|
"Initialize Doom Emacs in an interactive session."
|
|
(list (lambda ()
|
|
(file-name-concat doom-core-dir "doom-start"))
|
|
(lambda ()
|
|
(file-name-concat doom-profiles-dir "safe-mode" "init.el"))
|
|
(caddr args))))))
|
|
;; Failing that, assume we're loading a non-Doom config and prepare.
|
|
(ignore
|
|
(setq early-init-file (expand-file-name "early-init" user-emacs-directory)
|
|
;; I make no assumptions about the config we're about to load, so
|
|
;; to limit side-effects, undo any leftover optimizations:
|
|
load-prefer-newer t))))
|
|
|
|
;; Then continue on to the config/profile we want to load.
|
|
(load early-init-file 'noerror 'nomessage nil 'must-suffix))
|
|
|
|
;;; early-init.el ends here
|