;;; 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