doomemacs/early-init.el

189 lines
9.3 KiB
EmacsLisp
Raw Normal View History

;;; 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).
2022-07-27 12:06:07 +02:00
;;
;; 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:
2018-03-28 00:41:57 -04:00
;; 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)
(eval-and-compile
;; 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))
;; UX: If debug mode is on, be more verbose about loaded files.
(setq force-load-messages init-file-debug)
;; PERF: Employ various startup optimizations. This benefits all sessions,
;; including noninteractive ones...
(unless (or (daemonp) ; ...but be more liberal in daemon sessions
init-file-debug ; ...and don't interfere with the debugger
(boundp 'doom-version)) ; ...or if doom is already loaded
;; PERF: `file-name-handler-alist' is consulted on each `require', `load' and
;; various path/io functions (like `expand-file-name' or `file-remote-p').
;; You get a noteable, boost to startup times by unsetting this.
2021-04-20 22:34:59 -04:00
(let ((old-file-name-handler-alist file-name-handler-alist))
(setq file-name-handler-alist
;; HACK: If the bundled elisp for this Emacs install isn't
;; byte-compiled (but is compressed), then leave the gzip file
;; handler there so Emacs won't forget how to read read them.
;;
;; calc-loaddefs.el is our heuristic for this because it is built-in
;; to all supported versions of Emacs, and calc.el explicitly loads
;; it uncompiled. This ensures that the only other, possible
;; fallback would be calc-loaddefs.el.gz.
(if (eval-when-compile
(locate-file-internal "calc-loaddefs.el" load-path nil))
nil
(list (rassq 'jka-compr-handler file-name-handler-alist))))
2021-04-20 22:34:59 -04:00
;; ...but restore `file-name-handler-alist' later, because it is needed for
;; handling encrypted or compressed files, among other things.
(defun doom-reset-file-handler-alist-h ()
(setq file-name-handler-alist
;; Merge instead of overwrite because there may have been changes to
2021-04-20 22:34:59 -04:00
;; `file-name-handler-alist' since startup we want to preserve.
(delete-dups (append file-name-handler-alist
old-file-name-handler-alist))))
(add-hook 'emacs-startup-hook #'doom-reset-file-handler-alist-h 101))
;; PERF: Site files tend to use `load-file', which emits "Loading X..."
;; messages in the echo area. Writing to the echo-area triggers a redisplay,
;; which can be expensive during startup. This can also cause an ugly flash
;; of white when first creating the frame. This attempts try to avoid both.
(define-advice load-file (:override (file) silence)
2022-07-27 12:06:07 +02:00
(load file nil :nomessage))
;; FIX: ...Then undo our `load-file' advice later, as to limit the scope of
;; any edge cases it may possibly introduce.
(define-advice startup--load-user-init-file (:before (&rest _) init-doom)
(advice-remove #'load-file #'load-file@silence)))
;;
feat: allow Doom be used as a config bootloader This allows users to use Doom core to switch between Emacs configs (they don't have to be Doom configs either). Taking after Chemacs, these configs (called "profiles") can be declared in $EMACSDIR/profiles.el or implicitly as directories under $EMACSDIR/profiles/ (symlinks work too). Launch a profile with `emacs --profile foo` or by setting $DOOMPROFILE: `DOOMPROFILE=foo emacs`. An example profiles.el looks like this: ((doomemacs (user-emacs-directory . "~/.config/emacs") (env ("DOOMDIR" . "~/.config/doom"))) (spacemacs (user-emacs-directory . "~/.config/spacemacs")) (prelude (user-emacs-directory . "~/.config/prelude")) (altdoom (user-emacs-directory . "~/.config/doomemacs") (env ("DOOMDIR" . "~/.config/doomprivate1"))) (altdoom2 (user-emacs-directory . "~/.config/doomemacs") (env ("DOOMDIR" . "~/.config/doomprivate2")))) Chemacs users will find the format of this file familiar; the biggest differences are: - Keys are symbols, not strings - There is no, special "default" profile. The fallback profile is the Doom Emacs config doing the bootloading, living in ~/.config/emacs or ~/.emacs.d. If you don't like that, set $DOOMPROFILE in your dotfiles to the name of another profile. WARNING: bin/doom does not understand --profile or $DOOMPROFILE yet. To sync a particular profile, you'll have to run its bin/doom directly, e.g. To sync the "global" doom: ~/.config/emacs/bin/doom sync To sync your "altdoom" (and "altdoom2") profiles: ~/.config/doomemacs/bin/doom sync
2022-07-27 12:05:56 +02:00
;;; Detect `user-emacs-directory'
;; Prevent recursive profile processing, in case you're loading a Doom profile.
(unless (boundp 'doom-version)
;; Not using `command-switch-alist' to process --profile and --init-directory
;; was intentional. `command-switch-alist' is processed too late at startup to
;; change `user-emacs-directory' in time.
;; DEPRECATED: Backported from Emacs 29.
feat: allow Doom be used as a config bootloader This allows users to use Doom core to switch between Emacs configs (they don't have to be Doom configs either). Taking after Chemacs, these configs (called "profiles") can be declared in $EMACSDIR/profiles.el or implicitly as directories under $EMACSDIR/profiles/ (symlinks work too). Launch a profile with `emacs --profile foo` or by setting $DOOMPROFILE: `DOOMPROFILE=foo emacs`. An example profiles.el looks like this: ((doomemacs (user-emacs-directory . "~/.config/emacs") (env ("DOOMDIR" . "~/.config/doom"))) (spacemacs (user-emacs-directory . "~/.config/spacemacs")) (prelude (user-emacs-directory . "~/.config/prelude")) (altdoom (user-emacs-directory . "~/.config/doomemacs") (env ("DOOMDIR" . "~/.config/doomprivate1"))) (altdoom2 (user-emacs-directory . "~/.config/doomemacs") (env ("DOOMDIR" . "~/.config/doomprivate2")))) Chemacs users will find the format of this file familiar; the biggest differences are: - Keys are symbols, not strings - There is no, special "default" profile. The fallback profile is the Doom Emacs config doing the bootloading, living in ~/.config/emacs or ~/.emacs.d. If you don't like that, set $DOOMPROFILE in your dotfiles to the name of another profile. WARNING: bin/doom does not understand --profile or $DOOMPROFILE yet. To sync a particular profile, you'll have to run its bin/doom directly, e.g. To sync the "global" doom: ~/.config/emacs/bin/doom sync To sync your "altdoom" (and "altdoom2") profiles: ~/.config/doomemacs/bin/doom sync
2022-07-27 12:05:56 +02:00
(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))))
feat: allow Doom be used as a config bootloader This allows users to use Doom core to switch between Emacs configs (they don't have to be Doom configs either). Taking after Chemacs, these configs (called "profiles") can be declared in $EMACSDIR/profiles.el or implicitly as directories under $EMACSDIR/profiles/ (symlinks work too). Launch a profile with `emacs --profile foo` or by setting $DOOMPROFILE: `DOOMPROFILE=foo emacs`. An example profiles.el looks like this: ((doomemacs (user-emacs-directory . "~/.config/emacs") (env ("DOOMDIR" . "~/.config/doom"))) (spacemacs (user-emacs-directory . "~/.config/spacemacs")) (prelude (user-emacs-directory . "~/.config/prelude")) (altdoom (user-emacs-directory . "~/.config/doomemacs") (env ("DOOMDIR" . "~/.config/doomprivate1"))) (altdoom2 (user-emacs-directory . "~/.config/doomemacs") (env ("DOOMDIR" . "~/.config/doomprivate2")))) Chemacs users will find the format of this file familiar; the biggest differences are: - Keys are symbols, not strings - There is no, special "default" profile. The fallback profile is the Doom Emacs config doing the bootloading, living in ~/.config/emacs or ~/.emacs.d. If you don't like that, set $DOOMPROFILE in your dotfiles to the name of another profile. WARNING: bin/doom does not understand --profile or $DOOMPROFILE yet. To sync a particular profile, you'll have to run its bin/doom directly, e.g. To sync the "global" doom: ~/.config/emacs/bin/doom sync To sync your "altdoom" (and "altdoom2") profiles: ~/.config/doomemacs/bin/doom sync
2022-07-27 12:05:56 +02:00
(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)
feat: allow Doom be used as a config bootloader This allows users to use Doom core to switch between Emacs configs (they don't have to be Doom configs either). Taking after Chemacs, these configs (called "profiles") can be declared in $EMACSDIR/profiles.el or implicitly as directories under $EMACSDIR/profiles/ (symlinks work too). Launch a profile with `emacs --profile foo` or by setting $DOOMPROFILE: `DOOMPROFILE=foo emacs`. An example profiles.el looks like this: ((doomemacs (user-emacs-directory . "~/.config/emacs") (env ("DOOMDIR" . "~/.config/doom"))) (spacemacs (user-emacs-directory . "~/.config/spacemacs")) (prelude (user-emacs-directory . "~/.config/prelude")) (altdoom (user-emacs-directory . "~/.config/doomemacs") (env ("DOOMDIR" . "~/.config/doomprivate1"))) (altdoom2 (user-emacs-directory . "~/.config/doomemacs") (env ("DOOMDIR" . "~/.config/doomprivate2")))) Chemacs users will find the format of this file familiar; the biggest differences are: - Keys are symbols, not strings - There is no, special "default" profile. The fallback profile is the Doom Emacs config doing the bootloading, living in ~/.config/emacs or ~/.emacs.d. If you don't like that, set $DOOMPROFILE in your dotfiles to the name of another profile. WARNING: bin/doom does not understand --profile or $DOOMPROFILE yet. To sync a particular profile, you'll have to run its bin/doom directly, e.g. To sync the "global" doom: ~/.config/emacs/bin/doom sync To sync your "altdoom" (and "altdoom2") profiles: ~/.config/doomemacs/bin/doom sync
2022-07-27 12:05:56 +02:00
(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))))
feat: allow Doom be used as a config bootloader This allows users to use Doom core to switch between Emacs configs (they don't have to be Doom configs either). Taking after Chemacs, these configs (called "profiles") can be declared in $EMACSDIR/profiles.el or implicitly as directories under $EMACSDIR/profiles/ (symlinks work too). Launch a profile with `emacs --profile foo` or by setting $DOOMPROFILE: `DOOMPROFILE=foo emacs`. An example profiles.el looks like this: ((doomemacs (user-emacs-directory . "~/.config/emacs") (env ("DOOMDIR" . "~/.config/doom"))) (spacemacs (user-emacs-directory . "~/.config/spacemacs")) (prelude (user-emacs-directory . "~/.config/prelude")) (altdoom (user-emacs-directory . "~/.config/doomemacs") (env ("DOOMDIR" . "~/.config/doomprivate1"))) (altdoom2 (user-emacs-directory . "~/.config/doomemacs") (env ("DOOMDIR" . "~/.config/doomprivate2")))) Chemacs users will find the format of this file familiar; the biggest differences are: - Keys are symbols, not strings - There is no, special "default" profile. The fallback profile is the Doom Emacs config doing the bootloading, living in ~/.config/emacs or ~/.emacs.d. If you don't like that, set $DOOMPROFILE in your dotfiles to the name of another profile. WARNING: bin/doom does not understand --profile or $DOOMPROFILE yet. To sync a particular profile, you'll have to run its bin/doom directly, e.g. To sync the "global" doom: ~/.config/emacs/bin/doom sync To sync your "altdoom" (and "altdoom2") profiles: ~/.config/doomemacs/bin/doom sync
2022-07-27 12:05:56 +02:00
;;
;;; Bootstrap
(let (init-file)
;; Load the heart of Doom Emacs
(if (load (expand-file-name "lisp/doom" user-emacs-directory) 'noerror 'nomessage)
;; ...and prepare for an interactive session.
(if noninteractive
(require 'doom-cli)
(setq init-file (expand-file-name "doom-start" doom-core-dir)))
;; ...but if that fails, then this is likely not a Doom config.
(setq early-init-file (expand-file-name "early-init" user-emacs-directory))
(load early-init-file 'noerror 'nomessage))
;; We hijack Emacs' initfile resolver to inject our own entry point. Why do
;; this? Because:
;;
;; - It spares Emacs the effort of looking for/loading useless initfiles, like
;; ~/.emacs and ~/_emacs. And skips ~/.emacs.d/init.el, which won't exist if
;; you're using Doom (fyi: doom hackers or chemacs users could then use
;; $EMACSDIR as their $DOOMDIR, if they wanted).
;; - Later, 'doom sync' will dynamically generate its bootstrap file, which
;; will be important for Doom's profile system later. Until then, we'll use
;; lisp/doom-start.el.
;; - A "fallback" initfile can be trivially specified, in case the
;; bootstrapper is missing (if the user hasn't run 'doom sync' or is a
;; first-timer). This is an opportunity to display a "safe mode" environment
;; that's less intimidating and more helpful than the broken state errors
;; would've left Emacs in, otherwise.
;; - A generated config allows for a file IO optimized startup.
(define-advice startup--load-user-init-file (:filter-args (args) init-doom)
"Initialize Doom Emacs in an interactive session."
(list (lambda ()
(or init-file
(expand-file-name "init.el" user-emacs-directory)))
(when (boundp 'doom-profiles-dir)
(lambda ()
(expand-file-name "safe-mode@static/init.el" doom-profiles-dir)))
(caddr args))))
;;; early-init.el ends here