refactor!: complete profile gen and init systems
BREAKING CHANGE: This commit makes three breaking changes: - Doom now fully and dynamically generates (and byte-compiles) your profile and its init files, which includes your autoloads, loading your init files and modules, and then some. This replaces doom-initialize-modules, doom-initialize-core-modules, and doom-module-loader, which have been removed. This has also improved startup time by a bit, but if you use these functions in your CLIs, for instance, this will be a breaking change. - `doom sync` is now required for Doom to see your profiles (and must be run whenever you change them, or when you up/downgrade Emacs across major versions). - $DOOMDIR/init.el is now read much earlier than it used to be. Before any of doom-{ui,keybinds,editor,projects}, before any autoloads are loaded, and before your load-path has been populated with your packages. It now runs in the context of early-init.el, giving users freer range over what they can affect, but a more minimalistic environment to do it in. If you must have some logic run when all that is set up, add it to one of the module hooks added in e08f68b or 283308a. This also poses a significant change to Doom's load order (see the commentary change in lib/doom.el), along with the following (non breaking) changes: 1. Adds a new `doom profiles sync` command. This will forcibly resync your profiles, while `doom sync` will only do so if your profiles have changed. 2. Doom now fully and dynamically generates (and byte-compiles) your user-init-file, which includes loading all your init files, modules, and custom-file. This replaces the job of doom-initialize-modules, doom-initialize-core-modules, and doom-module-loader, which have been removed. This has also improved startup time by a bit. 3. Defines new doom-state-dir variable, though not used yet (saving that and the other breaking changes for the 3.0 release). 4. Redesigns profile directory variables (doom-profile-*-dir) to prepare for future XDG-compliance. 5. Removed unused/unimportant profile variables in doom.el. 6. Added lisp/doom-profiles.el. It's hardly feature complete, but it's enough to power the system as it is now. 7. Updates the "load order" commentary in doom.el to reflect these changes.
This commit is contained in:
parent
3d6e0311b9
commit
b914830403
15 changed files with 981 additions and 402 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1,7 +1,5 @@
|
|||
# machine generated doom profiles or metadata
|
||||
/profiles/*
|
||||
!/profiles/*.org
|
||||
!/profiles/*@static/
|
||||
/profiles/init.*el
|
||||
/.local*/
|
||||
|
||||
# possible user config files
|
||||
|
|
1
bin/doom
1
bin/doom
|
@ -259,6 +259,7 @@ SEE ALSO:
|
|||
(defcli-group! "Config Management"
|
||||
:docs "Commands for maintaining your Doom Emacs configuration."
|
||||
(defcli-autoload! ((sync s)))
|
||||
(defcli-autoload! ((profiles profile)))
|
||||
(defcli-autoload! ((upgrade up)))
|
||||
(defcli-autoload! (env))
|
||||
(defcli-autoload! ((build b purge p rollback)) "packages")
|
||||
|
|
|
@ -714,11 +714,92 @@ These are side-by-side comparisons, showing how to bind keys with and without
|
|||
**** TODO with-file-contents!
|
||||
|
||||
** TODO Configuration files
|
||||
*** TODO doomprofiles.el
|
||||
*** =profiles.el=
|
||||
:PROPERTIES:
|
||||
:ID: f9bce7da-d155-4727-9b6f-b566b5b8d824
|
||||
:END:
|
||||
This file can live in any of:
|
||||
|
||||
- =$DOOMDIR/profiles.el=
|
||||
- =$EMACSDIR/profiles.el=
|
||||
- =~/.config/doom-profiles.el=
|
||||
- =~/.doom-profiles.el=
|
||||
|
||||
Here is an exhaustive example of all its syntax and capabilities:
|
||||
#+begin_src emacs-lisp
|
||||
;; -*- mode: emacs-lisp; -*-
|
||||
((profile1
|
||||
;; The permitted formats of each entry:
|
||||
(var . value)
|
||||
("envvar" . value)
|
||||
(var :directive values...)
|
||||
|
||||
;; `user-emacs-directory' is often the first variable you want to set, so
|
||||
;; Emacs knows where this profile lives. If you don't, it'll use the config
|
||||
;; living in the default locations (~/.config/emacs or ~/.emacs.d).
|
||||
(user-emacs-directory . "~/another/emacs/config/")
|
||||
;; If this is a Doom config, you'll also want to set `doom-user-dir', which
|
||||
;; defaults to ~/.config/doom or ~/.doom.d:
|
||||
(doom-user-dir . "~/another/doom/config/")
|
||||
;; If a CAR is a string, it is assumed you want to set an environment
|
||||
;; variable. (Side-note: setting DOOMDIR will be unnecessary if you're setting
|
||||
;; `doom-user-dir' above).
|
||||
("DOOMDIR" . "~/another/doom/config/")
|
||||
|
||||
;; Doom profiles support a number of special directives. They are:
|
||||
;;
|
||||
;; (VAR :path SEGMENTS...) -- set VAR to an exapnded path built from SEGMENTS,
|
||||
;; relative to `user-emacs-directory', unless an absolute path is in SEGMENTS.
|
||||
(doom-cache-dir :path doom-user-dir ".local/cache")
|
||||
(doom-data-dir :path doom-user-dir ".local/data")
|
||||
(doom-state-dir :path doom-user-dir ".local/state")
|
||||
;; (VAR :plist VALUE) -- use VALUE as a literal plist; ignoring any profile
|
||||
;; directives that may be in it.
|
||||
(some-plist :plist (:foo bar :baz womp))
|
||||
;; (VAR :eval FORMS...) -- use to evaluate arbitrary elisp forms. Note that
|
||||
;; his runs early in early-init.el. It's wise to assume no APIs are available
|
||||
;; or loaded, only the previous bindings in this profile.
|
||||
(doom-theme :eval (if (equal (system-name) "foo") 'doom-one 'doom-dracula))
|
||||
;; Though discouraged, you may evaluate forms without a binding by using `_'.
|
||||
;; You really should be doing this in the profile though...
|
||||
(_ :eval (message "Hello world!"))
|
||||
(_ :eval (with-eval-after-load 'company (setq-default company-idle-delay 2.0)))
|
||||
;; (VAR :prepend FORMS...) or (VAR :append FORMS...) -- prepend or append the
|
||||
;; evaluated result of each form in FORMS to VAR (a list). If VAR is undefined
|
||||
;; at startup, it will be deferred until the variable is available.
|
||||
(load-path :prepend (expand-file-name "packages/" doom-user-dir))
|
||||
(load-path :prepend (expand-file-name "lisp/" doom-user-dir))
|
||||
(load-path :append (expand-file-name "fallback/" doom-user-dir))
|
||||
(exec-path :prepend (expand-file-name "bin/" doom-user-dir))
|
||||
(auto-mode-alist :prepend '("\\.el\\'" . lisp-mode)))
|
||||
|
||||
(profile2
|
||||
...)
|
||||
|
||||
(profile3
|
||||
...))
|
||||
#+end_src
|
||||
|
||||
*** =.doomprofile=
|
||||
:PROPERTIES:
|
||||
:ID: ac37ac6f-6082-4c34-b98c-962bc1e528c9
|
||||
:END:
|
||||
This file takes after the second level of =profiles.el='s format (see a more
|
||||
complete example in [[id:f9bce7da-d155-4727-9b6f-b566b5b8d824][the previous section]]). For example:
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
;;; -*- mode: emacs-lisp -*-
|
||||
;; A .doomprofile can be placed under an implicit profile. Same rules as
|
||||
;; .doom-profiles.el, but one level deeper.
|
||||
|
||||
((var . value)
|
||||
("envvar" . value)
|
||||
(var :directive values...))
|
||||
#+end_src
|
||||
|
||||
*** TODO =.doomrc=
|
||||
*** TODO =.doomproject=
|
||||
*** TODO =.doommodule=
|
||||
*** TODO =.doomprofile=
|
||||
** TODO Templates
|
||||
*** TODO User configuration
|
||||
*** TODO Module
|
||||
|
|
|
@ -62,39 +62,16 @@
|
|||
(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)))
|
||||
;; Running 'doom sync' will (re)generate a lightweight profile
|
||||
;; bootstrapper in $EMACSDIR/profiles/init.el, after reading
|
||||
;; $EMACSDIR/profiles.el, $DOOMDIR/profiles,
|
||||
;; $XDG_CONFIG_HOME/doom-profiles.el, and ~/.doom-profiles.el. All it
|
||||
;; needs is for `$DOOMPROFILE' to be set.
|
||||
(setenv "DOOMPROFILE" profile)
|
||||
(or (load (expand-file-name (format "profiles/init.%d" emacs-major-version)
|
||||
user-emacs-directory)
|
||||
'noerror 'nomessage nil t)
|
||||
(user-error "Profiles not initialized yet; run 'doom sync' first"))))
|
||||
|
||||
;; PERF: When `load'ing or `require'ing files, each permutation of
|
||||
;; `load-suffixes' and `load-file-rep-suffixes' (then `load-suffixes' +
|
||||
|
@ -110,29 +87,10 @@
|
|||
(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))))))
|
||||
(doom-require (if noninteractive 'doom-cli 'doom-start))))
|
||||
;; 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)
|
||||
(setq user-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))))
|
||||
|
|
|
@ -26,57 +26,6 @@ hoist buggy forms into autoloads.")
|
|||
;;
|
||||
;;; Library
|
||||
|
||||
(defun doom-autoloads-reload (&optional file)
|
||||
"Regenerates Doom's autoloads and writes them to FILE."
|
||||
(unless file
|
||||
;; TODO Uncomment when profile system is implemented
|
||||
;; (make-directory doom-profile-dir t)
|
||||
;; (setq file (expand-file-name "init.el" doom-profile-dir))
|
||||
(setq file doom-autoloads-file))
|
||||
(print! (start "(Re)generating autoloads file..."))
|
||||
(print-group!
|
||||
(cl-check-type file string)
|
||||
(doom-initialize-packages)
|
||||
(and (print! (start "Generating autoloads file..."))
|
||||
(doom-autoloads--write
|
||||
file
|
||||
`((unless (equal doom-version ,doom-version)
|
||||
(signal 'doom-error
|
||||
(list "The installed version of Doom has changed since last 'doom sync' ran"
|
||||
"Run 'doom sync' to bring Doom up to speed"))))
|
||||
(cl-loop for var in doom-autoloads-cached-vars
|
||||
when (boundp var)
|
||||
collect `(set ',var ',(symbol-value var)))
|
||||
;; Cache module state and flags in symbol plists for quick lookup by
|
||||
;; `modulep!' later.
|
||||
(cl-loop for (category . modules) in (seq-group-by #'car (doom-module-list))
|
||||
collect `(setplist ',category
|
||||
(quote ,(cl-loop for (_ . module) in modules
|
||||
nconc `(,module ,(get category module))))))
|
||||
(doom-autoloads--scan
|
||||
(append (doom-glob doom-core-dir "lib/*.el")
|
||||
(cl-loop for dir
|
||||
in (append (doom-module-load-path doom-modules-dirs)
|
||||
(list doom-user-dir))
|
||||
if (doom-glob dir "autoload.el") collect (car it)
|
||||
if (doom-glob dir "autoload/*.el") append it)
|
||||
(mapcan #'doom-glob doom-autoloads-files))
|
||||
nil)
|
||||
(doom-autoloads--scan
|
||||
(mapcar #'straight--autoloads-file
|
||||
(seq-difference (hash-table-keys straight--build-cache)
|
||||
doom-autoloads-excluded-packages))
|
||||
doom-autoloads-excluded-files
|
||||
'literal)
|
||||
;; TODO Uncomment when profile system is implemented
|
||||
;; `((unless noninteractive (require 'doom-start)))
|
||||
)
|
||||
(print! (start "Byte-compiling autoloads file..."))
|
||||
(doom-autoloads--compile-file file)
|
||||
(print! (success "Generated %s")
|
||||
(relpath (byte-compile-dest-file file)
|
||||
doom-emacs-dir)))))
|
||||
|
||||
(defun doom-autoloads--write (file &rest forms)
|
||||
(make-directory (file-name-directory file) 'parents)
|
||||
(condition-case-unless-debug e
|
||||
|
|
|
@ -159,14 +159,7 @@ in."
|
|||
(print! (start "Checking Doom Emacs..."))
|
||||
(condition-case-unless-debug ex
|
||||
(print-group!
|
||||
(let ((noninteractive nil)
|
||||
kill-emacs-query-functions
|
||||
kill-emacs-hook)
|
||||
(defvar doom-reloading-p nil)
|
||||
(unless (file-exists-p doom-autoloads-file)
|
||||
(user-error "Autoloads file not generated. Did you remember to run 'doom sync'?"))
|
||||
(require 'doom-start)
|
||||
(doom-initialize-packages))
|
||||
|
||||
(print! (success "Initialized Doom Emacs %s") doom-version)
|
||||
(print!
|
||||
|
@ -254,7 +247,7 @@ in."
|
|||
(doctor-file (doom-module-expand-path (car key) (cdr key) "doctor.el"))
|
||||
(packages-file (doom-module-expand-path (car key) (cdr key) "packages.el")))
|
||||
(cl-loop with doom-output-indent = 6
|
||||
for name in (let (doom-packages
|
||||
for name in (let* (doom-packages
|
||||
doom-disabled-packages)
|
||||
(load packages-file 'noerror 'nomessage)
|
||||
(mapcar #'car doom-packages))
|
||||
|
|
|
@ -45,6 +45,7 @@ OPTIONS:
|
|||
Defaults to the maximum number of threads (or 1, if your CPU's threadcount
|
||||
can't be determined)."
|
||||
:benchmark t
|
||||
(call! '(profiles sync))
|
||||
(run-hooks 'doom-before-sync-hook)
|
||||
(add-hook 'kill-emacs-hook #'doom-sync--abort-warning-h)
|
||||
(when jobs
|
||||
|
@ -63,9 +64,9 @@ OPTIONS:
|
|||
(when update?
|
||||
(doom-packages-update))
|
||||
(doom-packages-purge purge? 'builds-p purge? purge? purge?)
|
||||
(run-hooks 'doom-after-sync-hook)
|
||||
(when (doom-autoloads-reload)
|
||||
(print! (item "Restart Emacs or use 'M-x doom/reload' for changes to take effect")))
|
||||
(when (doom-profile-generate)
|
||||
(print! (item "Restart Emacs or use 'M-x doom/reload' for changes to take effect"))
|
||||
(run-hooks 'doom-after-sync-hook))
|
||||
t)
|
||||
(remove-hook 'kill-emacs-hook #'doom-sync--abort-warning-h)))
|
||||
|
||||
|
|
|
@ -72,9 +72,9 @@
|
|||
;; (doom-require 'doom-lib 'autoloads)
|
||||
|
||||
;; Ensure straight and core packages are ready to go for CLI commands.
|
||||
;; (require 'doom-profiles)
|
||||
(require 'doom-modules)
|
||||
(require 'doom-packages)
|
||||
(require 'doom-profiles)
|
||||
;; For any last-minute initialization.
|
||||
(run-hooks 'doom-before-init-hook))
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
;;; Custom error types
|
||||
(define-error 'doom-error "An unexpected Doom error")
|
||||
(define-error 'doom-nosync-error "Doom hasn't been initialized yet; did you remember to run 'doom sync' in the shell?" 'doom-error)
|
||||
(define-error 'doom-core-error "Unexpected error in Doom's core" 'doom-error)
|
||||
(define-error 'doom-hook-error "Error in a Doom startup hook" 'doom-error)
|
||||
(define-error 'doom-autoload-error "Error in Doom's autoloads file" 'doom-error)
|
||||
|
|
|
@ -99,49 +99,6 @@ your `doom!' block, a warning is emitted before replacing it with :emacs vc and
|
|||
(defvar doom--current-flags nil)
|
||||
|
||||
|
||||
;;
|
||||
;;; Bootstrap API
|
||||
|
||||
(defun doom-initialize-core-modules ()
|
||||
"Load Doom's core files for an interactive session."
|
||||
(require 'doom-keybinds)
|
||||
(require 'doom-ui)
|
||||
(require 'doom-projects)
|
||||
(require 'doom-editor))
|
||||
|
||||
(defun doom-module-loader (file)
|
||||
"Return a closure that loads FILE from a module.
|
||||
|
||||
This closure takes two arguments: a cons cell containing (CATEGORY . MODULE)
|
||||
symbols, and that module's plist."
|
||||
(lambda (module plist)
|
||||
(let ((doom--current-module module)
|
||||
(doom--current-flags (cdr (get (car module) (cdr module))))
|
||||
(inhibit-redisplay t))
|
||||
(load! file (plist-get plist :path) t))))
|
||||
|
||||
(defun doom-initialize-modules (&optional force-p no-config-p)
|
||||
"Loads the init.el in `doom-user-dir' and sets up hooks for a healthy session
|
||||
of Dooming. Will noop if used more than once, unless FORCE-P is non-nil."
|
||||
(when (or force-p (not doom-init-modules-p))
|
||||
(setq doom-init-modules-p t)
|
||||
(unless no-config-p
|
||||
(doom-log "Initializing core modules")
|
||||
(doom-initialize-core-modules))
|
||||
(when-let (init-p (load! doom-module-init-file doom-user-dir t))
|
||||
(doom-log "Initializing user config")
|
||||
(doom-run-hooks 'doom-before-module-init-hook)
|
||||
(maphash (doom-module-loader doom-module-init-file) doom-modules)
|
||||
(doom-run-hooks 'doom-after-module-init-hook)
|
||||
(unless no-config-p
|
||||
(doom-run-hooks 'doom-before-module-config-hook)
|
||||
(maphash (doom-module-loader doom-module-config-file) doom-modules)
|
||||
(doom-run-hooks 'doom-after-module-config-hook)
|
||||
(load! "config" doom-user-dir t)
|
||||
(when custom-file
|
||||
(load custom-file 'noerror (not doom-debug-mode)))))))
|
||||
|
||||
|
||||
;;
|
||||
;;; Module API
|
||||
|
||||
|
|
422
lisp/doom-profiles.el
Normal file
422
lisp/doom-profiles.el
Normal file
|
@ -0,0 +1,422 @@
|
|||
;;; lisp/doom-profiles.el -*- lexical-binding: t; -*-
|
||||
;;; Commentary:
|
||||
;;; Code:
|
||||
(eval-when-compile (require 'doom)) ; be silent, o'byte-compiler
|
||||
|
||||
|
||||
;;
|
||||
;;; Variables
|
||||
|
||||
;;; File/directory variables
|
||||
(defvar doom-profiles-dir doom-data-dir
|
||||
"Where generated profiles are kept.
|
||||
|
||||
Profile directories are in the format {data-profiles-dir}/$NAME/@/$VERSION, for
|
||||
example: '~/.local/share/doom/_/@/0/'")
|
||||
|
||||
(defvar doom-profile-dirs
|
||||
(list (file-name-concat doom-user-dir "profiles")
|
||||
(file-name-concat doom-emacs-dir "profiles"))
|
||||
"A list of directories to search for implicit Doom profiles in.")
|
||||
|
||||
(defvar doom-profile-config-files
|
||||
(list (file-name-concat doom-user-dir "profiles.el")
|
||||
(file-name-concat doom-emacs-dir "profiles.el")
|
||||
(expand-file-name "doom-profiles.el" (or (getenv "XDG_CONFIG_HOME") "~/.config"))
|
||||
(expand-file-name "~/.doom-profiles.el"))
|
||||
"A list of potential locations for a profiles.el file.
|
||||
|
||||
`doom-profiles-initialize' will load and merge all profiles defined in the above
|
||||
files, and will write a summary profiles.el to the first entry in this
|
||||
variable.")
|
||||
|
||||
(defvar doom-profiles-bootstrap-file
|
||||
(file-name-concat doom-emacs-dir (format "profiles/init.%d.el" emacs-major-version))
|
||||
"Where Doom writes its profile bootstrap script.")
|
||||
|
||||
(defvar doom-profile-init-file-name (format "init.%d.el" emacs-major-version)
|
||||
"TODO")
|
||||
|
||||
(defvar doom-profile-init-dir-name (format "init.%d.d" emacs-major-version)
|
||||
"The subdirectory of `doom-profile-dir'")
|
||||
|
||||
(defvar doom-profiles-config-file-name ".doomprofile"
|
||||
"TODO")
|
||||
|
||||
;;; Profile storage variables
|
||||
(defvar doom-profile-generators
|
||||
'(("05-init-vars.auto.el" . doom-profile--generate-init-vars)
|
||||
("80-loaddefs.auto.el" . doom-profile--generate-doom-autoloads)
|
||||
("90-loaddefs-packages.auto.el" . doom-profile--generate-package-autoloads)
|
||||
("95-load-modules.auto.el" . doom-profile--generate-load-modules))
|
||||
"An alist mapping file names to generator functions.
|
||||
|
||||
The file will be generated in `doom-profile-dir'/`doom-profile-init-dir-name',
|
||||
and later combined into `doom-profile-dir'/`doom-profile-init-file-name' in
|
||||
lexicographical order. These partials are left behind in case the use wants to
|
||||
load them directly (for whatever use), or for commands to use (e.g.
|
||||
`doom/reload-autoloads' loads any file with a NN-loaddefs[-.] prefix to
|
||||
accomplish its namesake).
|
||||
|
||||
Files with an .auto.el suffix will be automatically deleted whenever the profile
|
||||
is regenerated. Users (or Doom CLIs, like `doom env') may add their own
|
||||
generators to this list, or to `doom-profile-dir'/`doom-profile-init-dir-name',
|
||||
and they will be included in the profile init file next time `doom sync' is
|
||||
run.")
|
||||
|
||||
(defvar doom--profiles ())
|
||||
|
||||
;; TODO Restore this in 3.0
|
||||
(defconst doom-profile-default nil)
|
||||
;; (defconst doom-profile-default (cons "_" "0"))
|
||||
|
||||
|
||||
;;
|
||||
;;; Helpers
|
||||
|
||||
(defun doom-profiles-read (&rest paths)
|
||||
"TODO"
|
||||
(let (profiles)
|
||||
(dolist (path (flatten-list paths))
|
||||
(cond
|
||||
((file-directory-p path)
|
||||
(dolist (subdir (doom-files-in (file-truename path) :depth 0 :match "/[^.][^/]+$" :type 'dirs :map #'file-name-base))
|
||||
(unless (string-prefix-p "_" subdir)
|
||||
(cl-pushnew
|
||||
(cons (intern subdir)
|
||||
(if-let (profile-file (file-exists-p! doom-profiles-config-file-name path))
|
||||
(car (doom-file-read profile-file :by 'read*))
|
||||
(let ((subdir (file-name-as-directory (abbreviate-file-name subdir))))
|
||||
`((user-emacs-directory . ,subdir)
|
||||
,@(when (file-exists-p! "lisp/doom.el" subdir)
|
||||
'(doom-user-dir . ,subdir))))))
|
||||
profiles
|
||||
:test #'eq
|
||||
:key #'car))))
|
||||
((file-exists-p path)
|
||||
(dolist (profile (car (doom-file-read path :by 'read*)))
|
||||
(unless (string-prefix-p "_" (symbol-name (car profile)))
|
||||
(cl-pushnew profile profiles
|
||||
:test #'eq
|
||||
:key #'car))))))
|
||||
(when (assq '_ profiles)
|
||||
(signal 'doom-profile-error (list "Profile cannot be named _, as this is reserved for the implicit global profile")))
|
||||
(nreverse profiles)))
|
||||
|
||||
(defun doom-profiles-autodetect ()
|
||||
"Return all known profiles as a nested alist.
|
||||
|
||||
This reads all profiles in `doom-profile-config-files', then reads implicit profiles
|
||||
living in `doom-profile-dirs', then caches them in `doom--profiles'. If RELOAD?
|
||||
is non-nil, refresh the cache."
|
||||
(doom-profiles-read doom-profile-config-files
|
||||
doom-profile-dirs))
|
||||
|
||||
(defun doom-profiles-outdated-p ()
|
||||
"Return non-nil if files in `doom-profiles-bootstrap-file' are outdated."
|
||||
(cl-find-if (doom-rpartial #'file-newer-than-file-p doom-profiles-bootstrap-file)
|
||||
doom-profile-config-files))
|
||||
|
||||
(defun doom-profile<-id (id)
|
||||
"Return a (NAME . VERSION) profile cons cell from an id string NAME@VERSION."
|
||||
(save-match-data
|
||||
(if (string-match "^\\([^@]+\\)@\\(.+\\)$" id)
|
||||
(cons (match-string 1 id)
|
||||
(match-string 2 id))
|
||||
(cons id (cdr doom-profile-default)))))
|
||||
|
||||
(defun doom-profile->id (profile)
|
||||
"Return a NAME@VERSION id string from profile cons cell (NAME . VERSION)."
|
||||
(cl-check-type profile cons)
|
||||
(format "%s@%s" (car profile) (cdr profile)))
|
||||
|
||||
;; TODO (defun doom-profile--read (profile)
|
||||
;; (doom-profile-create ))
|
||||
|
||||
;; TODO (defun doom-profile-initialize (profile-name &optional ref)
|
||||
;; )
|
||||
|
||||
(defun doom-profiles-save (profiles file)
|
||||
"Generate a profile bootstrapper for Doom to load at startup."
|
||||
(doom-file-write
|
||||
file `(";; -*- lexical-binding: t; tab-width: 8; -*-\n"
|
||||
";; Updated: " ,(format-time-string "%Y-%m-%d %H:%M:%S") "\n"
|
||||
";; Generated by 'doom profiles sync' or 'doom sync'.\n"
|
||||
";; DO NOT EDIT THIS BY HAND!\n"
|
||||
,(format "%S" doom-version)
|
||||
(funcall
|
||||
(alist-get
|
||||
(intern (getenv-internal "DOOMPROFILE"))
|
||||
(list
|
||||
,@(cl-loop
|
||||
with deferred?
|
||||
= (seq-find (fn! (memq (car-safe %) '(:prepend :prepend? :append :append?)))
|
||||
(mapcar #'cdr profiles))
|
||||
with deferred-varsym = (make-symbol "deferred-vars")
|
||||
for (name . bindings) in profiles
|
||||
collect
|
||||
`(cons ',name
|
||||
(lambda ()
|
||||
(let ,(if deferred? '(--deferred-vars--))
|
||||
,@(cl-loop
|
||||
for (var . val) in bindings
|
||||
collect
|
||||
(pcase (car-safe val)
|
||||
(:path
|
||||
`(,(if (stringp var) 'setenv 'set)
|
||||
',var ,(if (cddr val)
|
||||
(macroexpand-all
|
||||
`(cl-loop with path = ',(cadr val)
|
||||
for dir in ',(cddr val)
|
||||
do (setq path (expand-file-name dir path))
|
||||
finally return path))
|
||||
`(file-truename ,(cadr val)))))
|
||||
(:eval
|
||||
(if (eq var '_)
|
||||
(macroexp-progn (cdr val))
|
||||
`(,(if (stringp var) 'setenv 'set)
|
||||
',var ,(macroexp-progn (cdr val)))))
|
||||
(:plist
|
||||
`(,(if (stringp var) 'setenv 'set)
|
||||
',var ',(if (stringp var)
|
||||
(prin1-to-string (cadr val))
|
||||
(cadr val))))
|
||||
((or :prepend :prepend?)
|
||||
(if (stringp var)
|
||||
`(setenv ',var (concat ,val (getenv ,var)))
|
||||
(setq deferred? t)
|
||||
`(push (cons ',var
|
||||
(lambda ()
|
||||
(dolist (item (list ,@(cdr val)))
|
||||
,(if (eq (car val) :append?)
|
||||
`(add-to-list ',var item)
|
||||
`(push item ',var)))))
|
||||
--deferred-vars--)))
|
||||
((or :append :append?)
|
||||
(if (stringp var)
|
||||
`(setenv ,var (concat (getenv ,var) ,val))
|
||||
(setq deferred? t)
|
||||
`(push (cons ',var
|
||||
(lambda ()
|
||||
(dolist (item (list ,@(cdr val)))
|
||||
,(if (eq (car val) :append?)
|
||||
`(add-to-list ',var item 'append)
|
||||
`(setq ',var (append ',var (list item)))))))
|
||||
--deferred-vars--)))
|
||||
(_ `(,(if (stringp var) 'setenv 'set)
|
||||
',var ,(if (and (symbolp var)
|
||||
(or (eq var 'user-emacs-directory)
|
||||
(string-match-p "^doom-.+-dir$" (symbol-name var))))
|
||||
`(file-truename ,var)
|
||||
''var)))))
|
||||
,@(when deferred?
|
||||
`((defun --defer-vars-- (_)
|
||||
(dolist (var --deferred-vars--)
|
||||
(when (boundp (car var))
|
||||
(funcall (cdr var))
|
||||
(setq --deferred-vars-- (delete var --deferred-vars--))))
|
||||
(unless --deferred-vars--
|
||||
(remove-hook 'after-load-functions #'--defer-vars--)
|
||||
(unintern '--defer-vars-- obarray)
|
||||
(unintern '--deferred-vars-- obarray)))
|
||||
(add-hook 'after-load-functions #'--defer-vars--)
|
||||
(--defer-vars--))))))))
|
||||
(lambda ()
|
||||
(if (or noninteractive
|
||||
(file-equal-p user-emacs-directory "~/.config/emacs")
|
||||
(file-equal-p user-emacs-directory "~/.emacs.d"))
|
||||
(user-error "Failed to find profile: %s" id)
|
||||
(user-error "To be a bootloader, Doom must be installed in ~/.config/emacs or ~/.emacs.d"))))))
|
||||
:mode #o600
|
||||
:printfn #'pp)
|
||||
(byte-compile-file file))
|
||||
|
||||
(defun doom-profile-p (profile-name)
|
||||
"Return t if PROFILE-NAME is a valid and existing profile."
|
||||
(when (stringp profile-name)
|
||||
(setq profile-name (intern profile-name)))
|
||||
(and (assq profile-name (doom-profiles))
|
||||
t))
|
||||
|
||||
(defun doom-profile-get (profile-name &optional property null-value)
|
||||
"Return PROFILE-NAME's PROFILE, otherwise its PROPERTY, otherwise NULL-VALUE."
|
||||
(when (stringp profile-name)
|
||||
(setq profile-name (intern profile-name)))
|
||||
(if-let (profile (assq profile-name (doom-profiles)))
|
||||
(if property
|
||||
(if-let (propval (assq property (cdr profile)))
|
||||
(cdr propval)
|
||||
null-value)
|
||||
profile)
|
||||
null-value))
|
||||
|
||||
(defun doom-profile-emacs-dir (profile-name)
|
||||
"Return the `user-emacs-directory' for PROFILE-NAME.
|
||||
|
||||
If the profile doesn't specify one, fall back to `doom-emacs-dir'."
|
||||
(doom-profile-get profile-name 'user-emacs-directory doom-emacs-dir))
|
||||
|
||||
(defun doom-profile-init-file (&optional profile-id version)
|
||||
"Return the init file for PROFILE-ID at VERSION.
|
||||
|
||||
Defaults to the profile at `doom-profile-default'."
|
||||
(cl-destructuring-bind (profile . version)
|
||||
(if (and (stringp profile-id) (null version))
|
||||
(doom-profile<-id profile-id)
|
||||
(cl-check-type profile-id (or null string))
|
||||
(cl-check-type version (or null string))
|
||||
(cons (or profile-id (car doom-profile-default))
|
||||
(or version (cdr doom-profile-default))))
|
||||
(file-name-concat doom-data-dir
|
||||
profile "@" version
|
||||
(format doom-profile-init-file-name emacs-major-version))))
|
||||
|
||||
|
||||
;;
|
||||
;;; Data structures
|
||||
|
||||
;; TODO
|
||||
|
||||
|
||||
;;
|
||||
;;; API
|
||||
|
||||
;; TODO (defun doom-profile-create (name))
|
||||
|
||||
;; TODO (defun doom-profile-hash (profile))
|
||||
|
||||
;; TODO (defmacro with-profile! (profile &rest body))
|
||||
|
||||
|
||||
;;
|
||||
;;; Generators
|
||||
|
||||
(defun doom-profile-generate (&optional _profile regenerate-only?)
|
||||
"Generate profile init files."
|
||||
(doom-initialize-packages)
|
||||
(let* ((default-directory doom-profile-dir)
|
||||
(init-dir doom-profile-init-dir-name)
|
||||
(init-file doom-profile-init-file-name))
|
||||
(print! (start "(Re)building profile in %s/...") (dirname doom-profile-dir))
|
||||
(condition-case-unless-debug e
|
||||
(with-file-modes #o750
|
||||
(print-group!
|
||||
(make-directory init-dir t)
|
||||
(print! (start "Deleting old init files..."))
|
||||
(print-group! :level 'info
|
||||
(cl-loop for file in (cons init-file (doom-glob "*.elc"))
|
||||
if (file-exists-p file)
|
||||
do (print! (item "Deleting %s...") file)
|
||||
and do (delete-file file)))
|
||||
(let ((auto-files (doom-glob init-dir "*.auto.el")))
|
||||
(print! (start "Generating %d init files...") (length doom-profile-generators))
|
||||
(print-group! :level 'info
|
||||
(dolist (file auto-files)
|
||||
(print! (item "Deleting %s...") file)
|
||||
(delete-file file))
|
||||
(pcase-dolist (`(,file . ,fn) doom-profile-generators)
|
||||
(let ((file (doom-path init-dir file)))
|
||||
(doom-log "Building %s..." file)
|
||||
(doom-file-write file (funcall fn))))))
|
||||
(with-file! init-file
|
||||
(insert ";; -*- coding: utf-8; lexical-binding: t; -*-\n"
|
||||
";; This file was autogenerated; do not edit it by hand!\n")
|
||||
;; Doom needs to be synced/rebuilt if either Doom or Emacs has been
|
||||
;; up/downgraded. This is because byte-code isn't backwards
|
||||
;; compatible, and many packages (including Doom), make in absolute
|
||||
;; paths into their caches that need to be refreshed.
|
||||
(prin1 `(unless (equal doom-version ,doom-version)
|
||||
(error ,(concat
|
||||
"The installed version of Doom (%s) has changed (to %s) since last "
|
||||
"'doom sync'. Run 'doom sync' to bring Doom up to speed")
|
||||
,doom-version doom-version))
|
||||
(current-buffer))
|
||||
(dolist (file (doom-glob init-dir "*.el"))
|
||||
(print-group! :level 'info
|
||||
(print! (start "Reading %s...") file))
|
||||
(doom-file-read file :by 'insert)))
|
||||
(print! (start "Byte-compiling %s...") (relpath init-file))
|
||||
(print-group!
|
||||
(let ((byte-compile-warnings (if init-file-debug '(suspicious make-local callargs))))
|
||||
(byte-compile-file init-file)))
|
||||
(print! (success "Built %s") (byte-compile-dest-file init-file))))
|
||||
(error (delete-file init-file)
|
||||
(delete-file (byte-compile-dest-file init-file))
|
||||
(signal 'doom-autoload-error (list init-file e))))))
|
||||
|
||||
(defun doom-profile--generate-init-vars ()
|
||||
(setq doom-autoloads-cached-vars '(load-path
|
||||
Info-directory-list
|
||||
auto-mode-alist
|
||||
interpreter-mode-alist))
|
||||
(let ((v (version-to-list doom-version))
|
||||
(ref (doom-call-process "git" "-C" (doom-path doom-emacs-dir) "rev-parse" "HEAD"))
|
||||
(branch (doom-call-process "git" "-C" (doom-path doom-emacs-dir) "branch" "--show-current")))
|
||||
`(,@(cl-loop for var in doom-autoloads-cached-vars
|
||||
if (boundp var)
|
||||
collect `(set-default ',var ',(symbol-value var)))
|
||||
(setplist 'doom-version
|
||||
'(major ,(nth 0 v)
|
||||
minor ,(nth 1 v)
|
||||
build ,(nth 2 v)
|
||||
tag ,(cadr (split-string doom-version "-" t))
|
||||
ref ,(if (zerop (car ref)) (cdr ref))
|
||||
branch ,(if (zerop (car branch)) (cdr branch)))))))
|
||||
|
||||
(defun doom-profile--generate-load-modules ()
|
||||
(let ((module-list (cddr (doom-module-list))))
|
||||
`((set 'doom-disabled-packages ',doom-disabled-packages)
|
||||
(set 'doom-modules ',doom-modules)
|
||||
;; Cache module state and flags in symbol plists for quick lookup by
|
||||
;; `modulep!' later.
|
||||
,@(cl-loop for (category . modules) in (seq-group-by #'car (doom-module-list))
|
||||
collect `(setplist ',category
|
||||
(quote ,(cl-loop for (_ . module) in modules
|
||||
nconc `(,module ,(get category module))))))
|
||||
(doom-run-hooks 'doom-before-modules-init-hook)
|
||||
;; TODO: Until these files are byte-compiler-ready, I must use `load'
|
||||
;; instead of `require', as to not invite the byte-compiler to load them
|
||||
;; while this init file is compiled.
|
||||
(doom-load ,(doom-path doom-core-dir "doom-keybinds"))
|
||||
(doom-load ,(doom-path doom-core-dir "doom-ui"))
|
||||
(doom-load ,(doom-path doom-core-dir "doom-projects"))
|
||||
(doom-load ,(doom-path doom-core-dir "doom-editor"))
|
||||
,@(cl-loop for (cat . mod) in module-list
|
||||
if (doom-module-locate-path cat mod (concat doom-module-init-file ".el"))
|
||||
collect `(let ((doom--current-module '(,cat . ,mod))
|
||||
(doom--current-flags ',(doom-module-get cat mod :flags)))
|
||||
(doom-load ,it)))
|
||||
(doom-run-hooks 'doom-after-modules-init-hook)
|
||||
(doom-run-hooks 'doom-before-modules-config-hook)
|
||||
,@(cl-loop for (cat . mod) in module-list
|
||||
if (doom-module-locate-path cat mod (concat doom-module-config-file ".el"))
|
||||
collect `(let ((doom--current-module '(,cat . ,mod))
|
||||
(doom--current-flags ',(doom-module-get cat mod :flags)))
|
||||
(doom-load ,it)))
|
||||
(doom-run-hooks 'doom-after-modules-config-hook)
|
||||
(let ((old-custom-file custom-file))
|
||||
(doom-load ,(doom-path doom-user-dir doom-module-config-file) 'noerror)
|
||||
(when (eq custom-file old-custom-file)
|
||||
(doom-load custom-file 'noerror))))))
|
||||
|
||||
(defun doom-profile--generate-doom-autoloads ()
|
||||
(doom-autoloads--scan
|
||||
(append (doom-glob doom-core-dir "lib/*.el")
|
||||
(cl-loop for dir
|
||||
in (append (doom-module-load-path doom-modules-dirs)
|
||||
(list doom-user-dir))
|
||||
if (doom-glob dir "autoload.el") collect (car it)
|
||||
if (doom-glob dir "autoload/*.el") append it)
|
||||
(mapcan #'doom-glob doom-autoloads-files))
|
||||
nil))
|
||||
|
||||
(defun doom-profile--generate-package-autoloads ()
|
||||
(doom-autoloads--scan
|
||||
(mapcar #'straight--autoloads-file
|
||||
(seq-difference (hash-table-keys straight--build-cache)
|
||||
doom-autoloads-excluded-packages))
|
||||
doom-autoloads-excluded-files
|
||||
'literal))
|
||||
|
||||
(provide 'doom-profiles)
|
||||
;;; doom-profiles.el ends here
|
|
@ -246,28 +246,6 @@ If RETURN-P, return the message as a string instead of displaying it."
|
|||
;;
|
||||
;;; Let 'er rip!
|
||||
|
||||
;;; Load loaddefs
|
||||
;; Doom caches a lot of information in `doom-autoloads-file'. Module and package
|
||||
;; autoloads, autodefs like `set-company-backend!', and variables like
|
||||
;; `doom-modules', `doom-disabled-packages', `load-path', `auto-mode-alist', and
|
||||
;; `Info-directory-list'. etc. Compiling them into one place is a big reduction
|
||||
;; in startup time.
|
||||
(condition-case-unless-debug e
|
||||
;; Avoid `file-name-sans-extension' for premature optimization reasons.
|
||||
;; `string-remove-suffix' is cheaper because it performs no file sanity
|
||||
;; checks; just plain ol' string manipulation.
|
||||
(load (string-remove-suffix ".el" doom-autoloads-file) nil 'nomessage)
|
||||
(file-missing
|
||||
;; If the autoloads file fails to load then the user forgot to sync, or
|
||||
;; aborted a doom command midway!
|
||||
(if (locate-file doom-autoloads-file load-path)
|
||||
;; Something inside the autoloads file is triggering this error;
|
||||
;; forward it to the caller!
|
||||
(signal 'doom-autoload-error e)
|
||||
(signal 'doom-error
|
||||
(list "Doom is in an incomplete state"
|
||||
"run 'doom sync' on the command line to repair it")))))
|
||||
|
||||
;;; Load envvar file
|
||||
;; 'doom env' generates an envvar file. This is a snapshot of your shell
|
||||
;; environment, which Doom loads here. This is helpful in scenarios where Emacs
|
||||
|
@ -278,7 +256,23 @@ If RETURN-P, return the message as a string instead of displaying it."
|
|||
(setq-default process-environment (get 'process-environment 'initial-value))
|
||||
(doom-load-envvars-file doom-env-file 'noerror))
|
||||
|
||||
;; Bootstrap the interactive session
|
||||
;;; Load core modules and set up their autoloads
|
||||
(require 'doom-modules)
|
||||
(autoload 'doom-initialize-packages "doom-packages")
|
||||
;; TODO (autoload 'doom-profiles-initialize "doom-profiles")
|
||||
;; TODO (autoload 'doom-packages-initialize "doom-packages")
|
||||
|
||||
;; UX: There's a chance the user will later use package.el or straight in this
|
||||
;; interactive session. If they do, make sure they're properly initialized
|
||||
;; when they do.
|
||||
(with-eval-after-load 'package (require 'doom-packages))
|
||||
(with-eval-after-load 'straight (doom-initialize-packages))
|
||||
|
||||
;; A last ditch opportunity to undo dodgy optimizations or do extra
|
||||
;; configuration before the session is complicated by user config and packages.
|
||||
(doom-run-hooks 'doom-before-init-hook)
|
||||
|
||||
;;; Last minute setup
|
||||
(add-hook 'after-change-major-mode-hook #'doom-run-local-var-hooks-h 100)
|
||||
(add-hook 'hack-local-variables-hook #'doom-run-local-var-hooks-h)
|
||||
(add-hook 'doom-after-init-hook #'doom-load-packages-incrementally-h)
|
||||
|
@ -287,21 +281,96 @@ If RETURN-P, return the message as a string instead of displaying it."
|
|||
(doom-run-hook-on 'doom-first-file-hook '(find-file-hook dired-initial-position-hook))
|
||||
(doom-run-hook-on 'doom-first-input-hook '(pre-command-hook))
|
||||
|
||||
;;; Setup autoloads for major core libraries
|
||||
;; UX: There's a chance the user will later use package.el or straight in this
|
||||
;; interactive session. If they do, make sure they're properly initialized
|
||||
;; when they do.
|
||||
(autoload 'doom-initialize-packages "doom-packages")
|
||||
(eval-after-load 'package '(require 'doom-packages))
|
||||
(eval-after-load 'straight '(doom-initialize-packages))
|
||||
(require 'doom-modules)
|
||||
;;; Load $DOOMDIR/init.el early
|
||||
;; TODO: Catch errors
|
||||
(doom-load (file-name-concat doom-user-dir doom-module-init-file) t)
|
||||
|
||||
;; A last ditch opportunity to undo dodgy optimizations or do extra
|
||||
;; configuration before the session is complicated by user config and packages.
|
||||
(doom-run-hooks 'doom-before-init-hook)
|
||||
;;; Load the rest of $DOOMDIR + modules if noninteractive
|
||||
;; If the user is loading this file from a batch script, let's assume they want
|
||||
;; to load their userland config as well.
|
||||
(when noninteractive
|
||||
(doom-require 'doom-profiles)
|
||||
(let ((init-file (doom-profile-init-file)))
|
||||
(unless (file-exists-p init-file)
|
||||
(user-error "Profile init file hasn't been generated. Did you forgot to run 'doom sync'?"))
|
||||
(let (kill-emacs-query-functions
|
||||
kill-emacs-hook)
|
||||
;; Loads modules, then $DOOMDIR/config.el
|
||||
(doom-load init-file 'noerror)
|
||||
(doom-initialize-packages))))
|
||||
|
||||
;; Load user config + modules
|
||||
(doom-initialize-modules)
|
||||
;;; Entry point
|
||||
;; HACK: This advice hijacks Emacs' initfile loader to accomplish the following:
|
||||
;;
|
||||
;; 1. Load the profile init file directory (generated on `doom sync`)
|
||||
;; 2. Ignore initfiles we don't care about (like $EMACSDIR/init.el, ~/.emacs,
|
||||
;; and ~/_emacs) -- and spare us the IO of searching for them, and allows
|
||||
;; savvy hackers to use $EMACSDIR as their $DOOMDIR, if they wanted.
|
||||
;; 3. Cut down on unnecessary logic in Emacs' bootstrapper.
|
||||
;; 4. Offer a more user-friendly error state/screen, especially for errors
|
||||
;; emitted from Doom's core or the user's config.
|
||||
(define-advice startup--load-user-init-file (:override (file-fn _ _) init-doom 100)
|
||||
(let ((debug-on-error-from-init-file nil)
|
||||
(debug-on-error-should-be-set nil)
|
||||
(debug-on-error-initial (if (eq init-file-debug t) 'startup init-file-debug)))
|
||||
(let ((debug-on-error debug-on-error-initial))
|
||||
(condition-case-unless-debug error
|
||||
(when init-file-user
|
||||
(let ((init-file-name
|
||||
;; This dynamically generated init file stores a lot of
|
||||
;; precomputed information, such as module and package
|
||||
;; autoloads, and values for expensive variables like
|
||||
;; `doom-modules', `doom-disabled-packages', `load-path',
|
||||
;; `auto-mode-alist', and `Info-directory-list'. etc.
|
||||
;; Compiling them in one place is a big reduction in startup
|
||||
;; time, and by keeping a history of them, you get a snapshot
|
||||
;; of your config in time.
|
||||
(file-name-concat doom-profile-dir (format "init.%d.elc" emacs-major-version))))
|
||||
;; If `user-init-file' is t, then `load' will store the name of
|
||||
;; the file that it loads into `user-init-file'.
|
||||
(setq user-init-file t)
|
||||
(when init-file-name
|
||||
(load init-file-name 'noerror 'nomessage nil 'must-suffix))
|
||||
;; If we did not find the user's init file, set user-init-file
|
||||
;; conclusively. Don't let it be set from default.el.
|
||||
(when (eq user-init-file t)
|
||||
(signal 'doom-nosync-error (list init-file-name))))
|
||||
;; If we loaded a compiled file, set `user-init-file' to the source
|
||||
;; version if that exists.
|
||||
(setq user-init-file
|
||||
(concat (string-remove-suffix (format ".%d.elc" emacs-major-version)
|
||||
user-init-file)
|
||||
".el")))
|
||||
;; TODO: Add safe-mode profile.
|
||||
;; (error
|
||||
;; ;; HACK: This is not really this variable's intended purpose, but it
|
||||
;; ;; doesn't mind what value its set to, only that its non-nil, so I'm
|
||||
;; ;; exploiting its dynamic scope to pass the error to the profile.
|
||||
;; (setq init-file-had-error error)
|
||||
;; (load (file-name-concat doom-emacs-dir "profiles" "safe-mode" "init.el")
|
||||
;; nil 'nomessage 'nosuffix))
|
||||
(error
|
||||
(display-warning
|
||||
'initialization
|
||||
(format-message "\
|
||||
An error occurred while loading `%s':\n\n%s%s%s\n\n\
|
||||
To ensure normal operation, you should investigate and remove the
|
||||
cause of the error in your initialization file. Start Emacs with
|
||||
the `--debug-init' option to view a complete error backtrace."
|
||||
user-init-file
|
||||
(get (car error) 'error-message)
|
||||
(if (cdr error) ": " "")
|
||||
(mapconcat (lambda (s) (prin1-to-string s t))
|
||||
(cdr error) ", "))
|
||||
:warning)
|
||||
(setq init-file-had-error t)))
|
||||
;; If we can tell that the init file altered debug-on-error, arrange to
|
||||
;; preserve the value that it set up.
|
||||
(or (eq debug-on-error debug-on-error-initial)
|
||||
(setq debug-on-error-should-be-set t
|
||||
debug-on-error-from-init-file debug-on-error)))
|
||||
(when debug-on-error-should-be-set
|
||||
(setq debug-on-error debug-on-error-from-init-file))))
|
||||
|
||||
(provide 'doom-start)
|
||||
;;; doom-start.el ends here
|
||||
|
|
184
lisp/doom.el
184
lisp/doom.el
|
@ -32,23 +32,36 @@
|
|||
;;
|
||||
;; The overall load order of Doom is as follows:
|
||||
;;
|
||||
;; $EMACSDIR/early-init.el
|
||||
;; $EMACSDIR/lisp/doom.el
|
||||
;; $EMACSDIR/lisp/doom-start.el
|
||||
;; `doom-before-init-hook'
|
||||
;; $DOOMDIR/init.el
|
||||
;; `doom-before-modules-init-hook'
|
||||
;; {$DOOMDIR,~/.emacs.d}/modules/*/*/init.el
|
||||
;; `doom-after-modules-init-hook'
|
||||
;; `doom-before-modules-config-hook'
|
||||
;; {$DOOMDIR,~/.emacs.d}/modules/*/*/config.el
|
||||
;; `doom-after-modules-config-hook'
|
||||
;; $DOOMDIR/config.el
|
||||
;; `after-init-hook'
|
||||
;; `emacs-startup-hook'
|
||||
;; `doom-init-ui-hook'
|
||||
;; `window-setup-hook'
|
||||
;; `doom-after-init-hook'
|
||||
;; > $EMACSDIR/early-init.el
|
||||
;; > $EMACSDIR/lisp/doom.el
|
||||
;; - $EMACSDIR/lisp/doom-lib.el
|
||||
;; > $EMACSDIR/lisp/doom-start.el
|
||||
;; - $EMACSDIR/doom-{keybinds,ui,projects,editor}.el
|
||||
;; - hook: `doom-before-init-hook'
|
||||
;; - $DOOMDIR/init.el
|
||||
;; > $XDG_DATA_HOME/doom/$PROFILE/@/curr/init.el (replaces $EMACSDIR/init.el)
|
||||
;; - hook: `doom-before-modules-init-hook'
|
||||
;; - {$DOOMDIR,$EMACSDIR}/modules/*/*/init.el
|
||||
;; - hook: `doom-after-modules-init-hook'
|
||||
;; - hook: `doom-before-modules-config-hook'
|
||||
;; - {$DOOMDIR,$EMACSDIR}/modules/*/*/config.el
|
||||
;; - hook: `doom-after-modules-config-hook'
|
||||
;; - $DOOMDIR/config.el
|
||||
;; - `custom-file' or $DOOMDIR/custom.el
|
||||
;; > The rest of `command-line' (Emacs startup)
|
||||
;; - hook: `after-init-hook'
|
||||
;; - hook: `emacs-startup-hook'
|
||||
;; - hook: `window-setup-hook'
|
||||
;; - hook: `doom-init-ui-hook'
|
||||
;; - hook: `doom-after-init-hook'
|
||||
;; > After startup is complete:
|
||||
;; - On first input: `doom-first-input-hook'
|
||||
;; - On first switched-to buffer: `doom-first-buffer-hook'
|
||||
;; - On first opened file: `doom-first-file-hook'
|
||||
;;
|
||||
;; This is Doom's heart, where I define all its major constants and variables,
|
||||
;; set only its sanest global defaults, employ its hackiest (and least
|
||||
;; offensive) optimizations, and load the minimum for all Doom sessions.
|
||||
;;
|
||||
;;; Code:
|
||||
|
||||
|
@ -187,54 +200,11 @@
|
|||
Defaults to ~/.config/doom, ~/.doom.d or the value of the DOOMDIR envvar;
|
||||
whichever is found first. Must end in a slash.")
|
||||
|
||||
(defconst doom-profiles-dir
|
||||
(if-let (profilesdir (getenv-internal "DOOMPROFILESDIR"))
|
||||
(expand-file-name "./" profilesdir)
|
||||
(expand-file-name "profiles/" doom-emacs-dir))
|
||||
"Where Doom stores its profiles.
|
||||
|
||||
Profiles are essentially snapshots of Doom Emacs environments. Every time you
|
||||
update or sync, you create a new generation of a profile (which can be easily
|
||||
rolled back or switched between with the DOOMPROFILE envvar). Must end in a
|
||||
slash.")
|
||||
|
||||
(defconst doom-profile-dir
|
||||
(expand-file-name (concat (or doom-profile "default@latest") "/")
|
||||
doom-profiles-dir)
|
||||
"The path to the current, active profile.
|
||||
|
||||
Must end in a slash.")
|
||||
|
||||
(defconst doom-profile-data-dir
|
||||
(expand-file-name "data/" doom-profile-dir)
|
||||
"Where file storage/servers for the current, active profile is kept.
|
||||
|
||||
Use this for long-living files that contain shared data that the user would
|
||||
reasonably want to keep, and/or are required for Emacs to function correctly.
|
||||
Must end in a slash.")
|
||||
|
||||
(defconst doom-profile-cache-dir
|
||||
(expand-file-name "cache/" doom-profile-dir)
|
||||
"Where file caches for the current, active profile is kept.
|
||||
|
||||
Use this for non-essential data files that, when deleted, won't cause breakage
|
||||
or misbehavior, and can be restored. This includes server binaries or programs
|
||||
downloaded/installed by packages. Must end in a slash.")
|
||||
|
||||
(defconst doom-profile-init-file
|
||||
(expand-file-name "init.el" doom-profile-dir)
|
||||
"TODO")
|
||||
|
||||
|
||||
;;
|
||||
;;; DEPRECATED file/directory vars
|
||||
|
||||
(defconst doom-local-dir
|
||||
;; DEPRECATED: .local will be removed entirely in 3.0
|
||||
(defvar doom-local-dir
|
||||
(if-let (localdir (getenv-internal "DOOMLOCALDIR"))
|
||||
(expand-file-name (file-name-as-directory localdir))
|
||||
(if doom-profile
|
||||
doom-profile-dir
|
||||
(expand-file-name ".local/" doom-emacs-dir)))
|
||||
(expand-file-name ".local/" doom-emacs-dir))
|
||||
"Root directory for local storage.
|
||||
|
||||
Use this as a storage location for this system's installation of Doom Emacs.
|
||||
|
@ -243,32 +213,82 @@ These files should not be shared across systems. By default, it is used by
|
|||
`doom-data-dir' and `doom-cache-dir'. Must end with a slash.")
|
||||
|
||||
(define-obsolete-variable-alias 'doom-etc-dir 'doom-data-dir "3.0.0")
|
||||
(defconst doom-data-dir
|
||||
(defvar doom-data-dir
|
||||
(if doom-profile
|
||||
doom-profile-data-dir
|
||||
(if IS-WINDOWS
|
||||
(expand-file-name "doomemacs/data/" (getenv-internal "APPDATA"))
|
||||
(expand-file-name "doom/" (or (getenv-internal "XDG_DATA_HOME") "~/.local/share")))
|
||||
;; DEPRECATED: .local will be removed entirely in 3.0
|
||||
(concat doom-local-dir "etc/"))
|
||||
"Directory for non-volatile local storage.
|
||||
"Where Doom stores its global data files.
|
||||
|
||||
Use this for files that don't change much, like server binaries, external
|
||||
dependencies or long-term shared data. Must end with a slash.")
|
||||
Data files contain shared and long-lived data that Doom, Emacs, and their
|
||||
packages require to function correctly or at all. Deleting them by hand will
|
||||
cause breakage, and require user intervention (e.g. a 'doom sync' or 'doom env')
|
||||
to restore.
|
||||
|
||||
(defconst doom-cache-dir
|
||||
Use this for: server binaries, package source, pulled module libraries,
|
||||
generated files for profiles, profiles themselves, autoloads/loaddefs, etc.
|
||||
|
||||
For profile-local data files, use `doom-profile-data-dir' instead.")
|
||||
|
||||
(defvar doom-cache-dir
|
||||
(if doom-profile
|
||||
doom-profile-cache-dir
|
||||
(if IS-WINDOWS
|
||||
(expand-file-name "doomemacs/cache/" (getenv-internal "APPDATA"))
|
||||
(expand-file-name "doom/" (or (getenv-internal "XDG_CACHE_HOME") "~/.cache")))
|
||||
;; DEPRECATED: .local will be removed entirely in 3.0
|
||||
(concat doom-local-dir "cache/"))
|
||||
"Directory for volatile local storage.
|
||||
"Where Doom stores its global cache files.
|
||||
|
||||
Use this for files that change often, like cache files. Must end with a slash.")
|
||||
Cache files represent non-essential data that shouldn't be problematic when
|
||||
deleted (besides, perhaps, a one-time performance hit), lack portability (and so
|
||||
shouldn't be copied to other systems/configs), and are regenerated when needed,
|
||||
without user input (e.g. a 'doom sync').
|
||||
|
||||
(defconst doom-autoloads-file
|
||||
Some examples: images/data caches, elisp bytecode, natively compiled elisp,
|
||||
session files, ELPA archives, authinfo files, org-persist, etc.
|
||||
|
||||
For profile-local cache files, use `doom-profile-cache-dir' instead.")
|
||||
|
||||
(defvar doom-state-dir
|
||||
(if doom-profile
|
||||
doom-profile-init-file
|
||||
(concat doom-local-dir "autoloads." emacs-version ".el"))
|
||||
"Where `doom-reload-core-autoloads' stores its core autoloads.
|
||||
(if IS-WINDOWS
|
||||
(expand-file-name "doomemacs/state/" (getenv-internal "APPDATA"))
|
||||
(expand-file-name "doom/" (or (getenv-internal "XDG_STATE_HOME") "~/.local/state")))
|
||||
;; DEPRECATED: .local will be removed entirely in 3.0
|
||||
(concat doom-local-dir "state/"))
|
||||
"Where Doom stores its global state files.
|
||||
|
||||
This file is responsible for informing Emacs where to find all of Doom's
|
||||
autoloaded core functions (in lisp/lib/*.el).")
|
||||
State files contain non-essential, unportable, but persistent data which, if
|
||||
lost won't cause breakage, but may be inconvenient as they cannot be
|
||||
automatically regenerated or restored. For example, a recently-opened file list
|
||||
is not essential, but losing it means losing this record, and restoring it
|
||||
requires revisiting all those files.
|
||||
|
||||
Use this for: history, logs, user-saved data, autosaves/backup files, known
|
||||
projects, recent files, bookmarks.
|
||||
|
||||
For profile-local state files, use `doom-profile-state-dir' instead.")
|
||||
|
||||
;;; Profile file/directory variables
|
||||
(defvar doom-profile-cache-dir
|
||||
(file-name-concat doom-cache-dir (car doom-profile))
|
||||
"For profile-local cache files under `doom-cache-dir'.")
|
||||
|
||||
(defvar doom-profile-data-dir
|
||||
(file-name-concat doom-data-dir (car doom-profile))
|
||||
"For profile-local data files under `doom-data-dir'.")
|
||||
|
||||
(defvar doom-profile-state-dir
|
||||
(file-name-concat doom-state-dir (car doom-profile))
|
||||
"For profile-local state files under `doom-state-dir'.")
|
||||
|
||||
(defconst doom-profile-dir
|
||||
(file-name-concat doom-profile-data-dir "@" (cdr doom-profile))
|
||||
"Where generated files for the active profile are kept.")
|
||||
|
||||
;; DEPRECATED: Will be moved to cli/env
|
||||
(defconst doom-env-file
|
||||
(file-name-concat (if doom-profile
|
||||
doom-profile-dir
|
||||
|
@ -329,14 +349,6 @@ users).")
|
|||
;; depending on font size.
|
||||
(setq frame-inhibit-implied-resize t)
|
||||
|
||||
;; PERF: Emacs supports a "default init file", which is a library named
|
||||
;; "default.el" living anywhere in your `load-path' (or `$EMACSLOADPATH').
|
||||
;; It's loaded after $EMACSDIR/init.el, but there really is no reason to
|
||||
;; do so. Doom doesn't define one, users shouldn't use one, and it seems
|
||||
;; too magical when an explicit `-l FILE' would do. I do away with it for
|
||||
;; the *miniscule* savings in file IO spent trying to load it.
|
||||
(setq inhibit-default-init t)
|
||||
|
||||
;; PERF,UX: Reduce *Message* noise at startup. An empty scratch buffer (or
|
||||
;; the dashboard) is more than enough, and faster to display.
|
||||
(setq inhibit-startup-screen t
|
||||
|
|
|
@ -1,17 +1,154 @@
|
|||
#+title: Doom's profile directory
|
||||
|
||||
This directory houses Doom's profiles (both generated or static), which in turn
|
||||
will contain all "local" data for that profile, including packages, caches,
|
||||
server files, and so on. It's also where generated files (like autoloads) are
|
||||
written to.
|
||||
* Introduction
|
||||
In order to power Doom's soon-to-be generational package manager, I wrote a
|
||||
profile system. This system can effectively replace [[https://github.com/plexus/chemacs2][Chemacs]]; permitting you to
|
||||
switch between multiple Emacs configs on-demand (and those configs don't have to
|
||||
be Doom configs).
|
||||
|
||||
This directory may serve as an alternative to =$EMACSDIR/profiles.el= for
|
||||
[[https://github.com/doomemacs/doomemacs/commit/5b6b204bcbcf69d541c49ca55a2d5c3604f04dad][declaring profiles]]: each directory here is an implicit profile, so assuming
|
||||
=$EMACSDIR/profiles/foo/init.el= exists, then ~emacs --profile foo~ will be
|
||||
equivalent to ~emacs --init-directory $EMACSDIR/profiles/foo~.
|
||||
While I work on the formal documentation for this system, I've created this
|
||||
brief guide to walk users through their use. *However, for this to work, Doom
|
||||
must live in =~/.emacs.d= or =~/.config/emacs=.* I'll refer to this as
|
||||
=$EMACSDIR= (and your private Doom config, in =~/.doom.d= or =~/.config/doom=,
|
||||
as =$DOOMDIR=).
|
||||
|
||||
#+begin_quote
|
||||
*Warning:* Generated (or static) profiles will follow the =X@Y= naming
|
||||
convention. To avoid conflicts, avoid naming any profile you put in here the
|
||||
same way. For example: =default@latest=, =test@942=, =safe-mode@static=,.
|
||||
#+end_quote
|
||||
* How use profiles
|
||||
1. Declare all your profiles in either:
|
||||
- One or multiple profile files at:
|
||||
- =$DOOMDIR/profiles.el=
|
||||
- =$EMACSDIR/profiles.el=
|
||||
- =~/.config/doom-profiles.el=
|
||||
- =~/.doom-profiles.el=
|
||||
|
||||
[[id:f9bce7da-d155-4727-9b6f-b566b5b8d824][Example profiles.el file]].
|
||||
|
||||
- Or an implicit profile, which are inferred from the sub-directories of:
|
||||
- =$DOOMDIR/profiles/=
|
||||
- =$EMACSDIR/profiles/=
|
||||
|
||||
Implicit profiles may have a =.doomprofile= file to apply additional
|
||||
settings. [[id:ac37ac6f-6082-4c34-b98c-962bc1e528c9][Example .doomprofile]].
|
||||
|
||||
2. To run ~$ doom sync~ whenever you change the above, to regenerate Doom's
|
||||
cached profile loader (generated at =$EMACSDIR/profiles/init.X.elc=, where X
|
||||
is your major Emacs version).
|
||||
|
||||
3. To launch them:
|
||||
- Launch the profile you want: ~$ emacs --profile FOO~
|
||||
- Use ~bin/doom~ on the profile you want: ~$ doom sync --profile FOO~
|
||||
|
||||
* Auto-generated profiles
|
||||
Doom v3's sandbox and transactional package manager are capable of generating
|
||||
profiles on-the-fly. The former for rapid, isolated testing, and the latter for
|
||||
rollback/snapshot traversal for disaster recovery purposes.
|
||||
|
||||
These auto-generated profiles will be stored and versioned in:
|
||||
=$XDG_DATA_HOME/doom/$PROFILE_NAME/@/$PROFILE_VERSION/=
|
||||
|
||||
* Fallback profile
|
||||
Unlike Chemacs, Doom's profiles has no notion of a "default"/fallback profile.
|
||||
The fallback profile is the Doom installation doing the bootloading. This
|
||||
"global" profile is unique in that it won't respect a =.doomprofile= -- in other
|
||||
words, it's not treated as a normal profile.
|
||||
|
||||
It is this way so that the profiles system imposes no overhead on users that
|
||||
aren't interested in the profile system (or prefer to use Chemacs).
|
||||
|
||||
However, you can emulate this behavior by registering the "global" profile as a
|
||||
profile, and setting ~$DOOMPROFILE~ or aliasing ~emacs~, like so:
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
;; in a profiles.el file
|
||||
((default)
|
||||
|
||||
...)
|
||||
#+end_src
|
||||
|
||||
#+begin_src bash
|
||||
# in .zshrc or .bash_profile
|
||||
export DOOMPROFILE=default
|
||||
|
||||
# Or
|
||||
alias emacs='emacs --profile default'
|
||||
#+end_src
|
||||
|
||||
* Gotchas
|
||||
There are two caveats with this profile system:
|
||||
|
||||
- It requires that Doom live in =~/.config/emacs= or =~/.emacs.d=. A
|
||||
non-standard install location won't work, unless you use Emacs 29's new
|
||||
=--init-directory DIR= switch and launch Emacs with ~emacs --init-directory
|
||||
~/nonstandard/emacs.d --profile NAME~. =bin/doom= is fine with it, though.
|
||||
|
||||
- The profile system can be storage-inefficient. A barebones Doom config
|
||||
averages at ~1mb without installed packages and ~3.75mb /with/ (straight alone
|
||||
is 2.6m). A fully-fledged Doom config can average 500mb-1.4gb; the majority of
|
||||
which are packages, but include server binaries, elisp+native bytecode, and
|
||||
caches add up too.
|
||||
|
||||
To mitigate this, Doom dedups packages across snapshots of a single profile
|
||||
(e.g. =profile@23= -> =profile@24=), but it cannot (yet) do this across
|
||||
profiles (e.g. if =profile1= and =profile2= both install =org=). Even then,
|
||||
packages whose recipes change (either locally or upstream) may dodge this
|
||||
deduplication and get cloned anew (to ensure historical integrity) -- though
|
||||
this shouldn't happen often, but can build up over time.
|
||||
|
||||
So v3 will introduce a ~doom gc~ command, which offers a couple nix.gc-esque
|
||||
switches to control it. E.g.
|
||||
|
||||
- Acts on the "global" profile:
|
||||
- ~doom gc --older-than 21d~
|
||||
- ~doom gc --keep 10~
|
||||
- Act on a specific profile:
|
||||
- ~doom gc --profile foo ...~
|
||||
- Act on all known profiles
|
||||
- ~doom gc --profiles '*' ...~
|
||||
|
||||
Users can change defaults from their =init.el= or =cli.el=, or configure ~doom
|
||||
sync~ to auto-GC by whatever rules they like. And the good doctor will warn
|
||||
you if you haven't GCed in a while, or you're in excess of some threshold
|
||||
(which I haven't decided yet).
|
||||
|
||||
* How to switch from Chemacs
|
||||
1. Delete [[https://github.com/plexus/chemacs2][Chemacs]] from =$EMACSDIR=.
|
||||
|
||||
2. Install Doom there: ~$ git clone https://github.com/doomemacs/doomemacs
|
||||
~/.config/emacs~
|
||||
|
||||
3. Move =~/.emacs-profiles.el= to =~/.config/doom/profiles.el= and transform the
|
||||
string keys to symbols and adapt =env= entries like so:
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
;; ~/.emacs-profiles.el
|
||||
(("default" (user-emacs-directory . "~/.emacs.default")
|
||||
(env ("DOOMDIR" . "~/.doom.private")))
|
||||
("spacemacs" (user-emacs-directory . "~/spacemacs"))
|
||||
("prelude" (user-emacs-directory . "~/prelude")))
|
||||
|
||||
;; ~/.config/emacs/profiles.el
|
||||
((default (user-emacs-directory . "~/.emacs.default")
|
||||
("DOOMDIR" . "~/.doom.private"))
|
||||
(spacemacs (user-emacs-directory . "~/spacemacs"))
|
||||
(prelude (user-emacs-directory . "~/prelude")))
|
||||
#+end_src
|
||||
|
||||
A comprehensive example of Doom's profiles.el file can be found
|
||||
[[id:f9bce7da-d155-4727-9b6f-b566b5b8d824][in docs/examples.org]].
|
||||
|
||||
*Differences with Chemacs profiles:*
|
||||
- Keys are symbols, not strings.
|
||||
- Doom's profiles.el has a syntax for evaluating code, expanding paths, and
|
||||
appending/prepending to variables (with deferral). See the examples.org
|
||||
link above.
|
||||
- Doom's profile system won't install [[https://github.com/raxod502/straight.el][Straight.el]] for you.
|
||||
- Doom does not have a special "default" profile. If you don't specify a
|
||||
--profile, it will simply start up the Doom config living in
|
||||
=~/.config/emacs=. See the "Fallback profile" section below for a
|
||||
workaround.
|
||||
|
||||
4. Then launch a profile. E.g. ~$ emacs --profile prelude~.
|
||||
|
||||
* But Doom is kinda heavy to be a bootloader...
|
||||
I agree! To remedy that, I'll soon split Doom up into three projects: its core
|
||||
(where its bootloader lives), its official modules, and its community
|
||||
contributed modules. At that point, Doom will be much lighter!
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue