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
This commit is contained in:
Henrik Lissner 2022-07-27 12:05:56 +02:00
parent 5108ffc44d
commit 5b6b204bcb
No known key found for this signature in database
GPG key ID: B60957CA074D39A3

View file

@ -66,29 +66,65 @@
(advice-remove #'load-file #'load-file@silence)))
;;
;;; 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 29. Remove this when 27/28 support is removed.
(let ((initdir (or (cadr (member "--init-directory" command-line-args))
(getenv-internal "EMACSDIR"))))
(when initdir
;; Discard the switch to prevent "invalid option" errors later.
(add-to-list 'command-switch-alist (cons "--init-directory" (lambda (_) (pop argv))))
(setq user-emacs-directory initdir)))
(let ((profile (or (cadr (member "--profile" command-line-args))
(getenv-internal "DOOMPROFILE"))))
(when profile
;; Discard the switch to prevent "invalid option" errors later.
(add-to-list 'command-switch-alist (cons "--profile" (lambda (_) (pop argv))))
;; Then process the requested profile, which should set
;; `user-emacs-directory'. If it doesn't, then you're essentially using
;; profiles.el as a glorified, runtime dir-locals.el -- which is fine.
(let ((profiles-dir (or (getenv-internal "DOOMPROFILESDIR")
(expand-file-name "profiles/" user-emacs-directory)))
(profiles-file (expand-file-name "profiles.el" user-emacs-directory)))
(if (file-exists-p profiles-file)
(with-temp-buffer
(let ((coding-system-for-read 'utf-8-auto))
(insert-file-contents profiles-file))
(condition-case err
(dolist (var (or (cdr (assq (intern profile) (read (current-buffer))))
(user-error "No %S profile found" profile)))
(if (eq var 'env)
(dolist (env var) (setenv (car env) (cdr env)))
(set (car var) (cdr var))))
(error (error "Failed to parse profiles.el: %s" (error-message-string err)))))
;; If the profile doesn't exist, then use anything in
;; $EMACSDIR/profiles/* as implicit profiles. I.e. If a foo profile
;; doesn't exist, `emacs --profile foo' is equivalent to `emacs
;; --init-directory $EMACSDIR/profile/foo', if the directory exists.
(let ((profile-dir (expand-file-name profile profiles-dir)))
(if (file-directory-p profile-dir)
(setq user-emacs-directory profile-dir)
(user-error "No profiles.el to look up %S in" profile))))))))
;;
;;; Bootstrap
(let (;; DEPRECATED Backported from 29. Remove when 27/28 support is removed.
(initdir (cadr (member "--init-directory" command-line-args)))
initfile)
;; But discard the switches later to prevent "invalid option" errors.
(when initdir
(add-to-list 'command-switch-alist (cons "--init-directory" (lambda (_) (pop argv)))))
;; Detect emacs directory
(setq user-emacs-directory
(cond (initdir (expand-file-name initdir))
((getenv-internal "EMACSDIR"))
(user-emacs-directory)))
;; Let er rip
(let (init-file)
;; Load the heart of Doom Emacs
(if (load (expand-file-name "core/core" user-emacs-directory) t t)
;; ...and prepare it for an interactive session.
(setq initfile (expand-file-name "core-start" doom-core-dir))
;; ...but if that fails, then assume this isn't a Doom config.
(setq init-file (expand-file-name "core-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 t t))
@ -99,19 +135,19 @@
;; ~/.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 is
;; important for Doom's soon-to-be profile system (which can replace Chemacs).
;; Until then, we'll use core/core-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.
;; - Later, 'doom sync' will dynamically generate its bootstrap file, which
;; will be important for Doom's profile system later. Until then, we'll use
;; core/core-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 initfile
(or init-file
(expand-file-name "init.el" user-emacs-directory)))
nil ; TODO Replace with safe mode initfile
(caddr args))))