From 5af38fb08e7a6a1eb2c6efc0439b93c26ab61474 Mon Sep 17 00:00:00 2001 From: Henrik Lissner Date: Wed, 27 Jul 2022 22:25:08 +0200 Subject: [PATCH] feat: make bin/doom profile aware - Fixes Doom's former inability to (trivially) juggle multiple profiles based on the same EMACSDIR (see #6593). - Adds '--profile NAME' switch to bin/doom (also recognized $DOOMPROFILE). - Adds new doom-profile* variables. These will eventually replace doom-{local,etc,cache}-dir and doom-{autoloads,env}-file. This is intentionally messy to ensure backwards compatibility for a little while longer. This will be fixed over the next couple weeks. Ref: #6593 --- bin/doom | 10 ++-- core/cli/sync.el | 5 +- core/core-cli.el | 14 ++++++ core/core.el | 124 ++++++++++++++++++++++++++++++++++++----------- early-init.el | 68 +++++++++++++------------- 5 files changed, 154 insertions(+), 67 deletions(-) diff --git a/bin/doom b/bin/doom index 4fd0bd6be..939d19ac6 100755 --- a/bin/doom +++ b/bin/doom @@ -180,8 +180,7 @@ SEE ALSO: (doomdir ("--doomdir" dir) "Use Doom config living in `DIR' (e.g. ~/.doom.d)") (emacsdir ("--emacsdir" dir) "Use Doom install living in `DIR' (e.g. ~/.emacs.d)") (pager ("--pager" cmd) "Pager command to use for large output") - ;; TODO Implement after v3.0 - ;; (profile ("--profile" name) "Use profile named NAME") + (profile ("--profile" name) "Use profile named NAME") &flags (color? ("--color") "Whether or not to show ANSI color codes") &multiple @@ -205,13 +204,12 @@ SEE ALSO: (setq doom-print-backend (if (eq color? :yes) 'ansi))) ;; For these settings to take full effect, the script must be restarted: (when (and (equal (doom-cli-context-step context) 0) - (or ;; profile + (or profile debug? emacsdir doomdir)) - ;; TODO Implement after v3.0 - ;; (when profile - ;; (setenv "DOOMPROFILE" profile)) + (when profile + (setenv "DOOMPROFILE" profile)) (when debug? (setenv "DEBUG" "1") (print! (item "Debug mode enabled"))) diff --git a/core/cli/sync.el b/core/cli/sync.el index 56eaa7b06..d0ff2537f 100644 --- a/core/cli/sync.el +++ b/core/cli/sync.el @@ -49,7 +49,10 @@ OPTIONS: (add-hook 'kill-emacs-hook #'doom-sync--abort-warning-h) (when jobs (setq native-comp-async-jobs-number (truncate jobs))) - (print! (start "Synchronizing your config with Doom Emacs...")) + (print! (start "Synchronizing %S profile..." ) + (if doom-profile + (car (split-string doom-profile "@")) + "default")) (unwind-protect (print-group! (when (and (not noenvvar?) diff --git a/core/core-cli.el b/core/core-cli.el index 1ad144b5d..0d12ad4f2 100644 --- a/core/core-cli.el +++ b/core/core-cli.el @@ -39,6 +39,20 @@ ;; Ensure errors are sufficiently detailed from this point on. (setq debug-on-error t) +;;; Initialize profile +(let ((profile (getenv "DOOMPROFILE"))) + (when profile + (with-temp-buffer + (let ((coding-system-for-read 'utf-8-auto)) + (insert-file-contents (expand-file-name "profiles.el" user-emacs-directory))) + (condition-case e + (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 e))))))) + ;; HACK Load `cl' and site files manually to prevent polluting logs and stdout ;; with deprecation and/or file load messages. (let ((inhibit-message (not (or (getenv "DEBUG") init-file-debug)))) diff --git a/core/core.el b/core/core.el index 75a787e21..88857de38 100644 --- a/core/core.el +++ b/core/core.el @@ -128,30 +128,6 @@ (defconst doom-modules-dir (concat doom-emacs-dir "modules/") "The root directory for Doom's modules. Must end with a slash.") -(defconst doom-local-dir - (if-let (localdir (getenv-internal "DOOMLOCALDIR")) - (expand-file-name (file-name-as-directory localdir)) - (concat doom-emacs-dir ".local/")) - "Root directory for local storage. - -Use this as a storage location for this system's installation of Doom Emacs. - -These files should not be shared across systems. By default, it is used by -`doom-etc-dir' and `doom-cache-dir'. Must end with a slash.") - -;; DEPRECATED -(defconst doom-etc-dir (concat doom-local-dir "etc/") - "Directory for non-volatile local storage. - -Use this for files that don't change much, like server binaries, external -dependencies or long-term shared data. Must end with a slash.") - -;; DEPRECATED -(defconst doom-cache-dir (concat doom-local-dir "cache/") - "Directory for volatile local storage. - -Use this for files that change often, like cache files. Must end with a slash.") - (defconst doom-docs-dir (concat doom-emacs-dir "docs/") "Where Doom's documentation files are stored. Must end with a slash.") @@ -169,15 +145,109 @@ Use this for files that change often, like cache files. Must end with a slash.") Defaults to ~/.config/doom, ~/.doom.d or the value of the DOOMDIR envvar; whichever is found first. Must end in a slash.") -;; DEPRECATED +(defconst doom-profile + (if-let (profile (getenv-internal "DOOMPROFILE")) + ;; DEPRECATED Use `string-search' once 27 support is dropped + (if (string-match-p "@" profile) + profile + (concat profile "@latest")) + ;; TODO Restore this when profile system is complete + ;; "default@latest" + ) + "The name of the active profile.") + +;; TODO Use me +(defconst doom-profiles-file + (expand-file-name "profiles.el" user-emacs-directory) + "TODO") + +(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 + (if-let (localdir (getenv-internal "DOOMLOCALDIR")) + (expand-file-name (file-name-as-directory localdir)) + (if doom-profile + (expand-file-name doom-profile doom-profiles-dir) + (format "%s.local%s/" + doom-emacs-dir + (if doom-profile (concat "." doom-profile) "")))) + "Root directory for local storage. + +Use this as a storage location for this system's installation of Doom Emacs. + +These files should not be shared across systems. By default, it is used by +`doom-etc-dir' and `doom-cache-dir'. Must end with a slash.") + +(defconst doom-etc-dir + (if doom-profile + doom-profile-data-dir + (concat doom-local-dir "etc/")) + "Directory for non-volatile local storage. + +Use this for files that don't change much, like server binaries, external +dependencies or long-term shared data. Must end with a slash.") + +(defconst doom-cache-dir + (if doom-profile + doom-profile-cache-dir + (concat doom-local-dir "cache/")) + "Directory for volatile local storage. + +Use this for files that change often, like cache files. Must end with a slash.") + (defconst doom-autoloads-file - (concat doom-local-dir "autoloads." emacs-version ".el") + (if doom-profile + doom-profile-init-file + (concat doom-local-dir "autoloads." emacs-version ".el")) "Where `doom-reload-core-autoloads' stores its core autoloads. This file is responsible for informing Emacs where to find all of Doom's autoloaded core functions (in core/autoload/*.el).") -(defconst doom-env-file (concat doom-local-dir "env") +(defconst doom-env-file + (if doom-profile + (expand-file-name "env" doom-profile-dir) + (concat doom-local-dir "env")) "The location of your envvar file, generated by `doom env`. This file contains environment variables scraped from your shell environment, diff --git a/early-init.el b/early-init.el index d681a5f5d..141148320 100644 --- a/early-init.el +++ b/early-init.el @@ -121,44 +121,46 @@ (setq user-emacs-directory profile-dir) (throw 'found t))) - (user-error "No %S profile found" profile)))))) + (user-error "No %S profile found" profile))) + + ;; Ensure the selected profile persists through the session + (setenv "DOOMPROFILE" profile)))) ;; ;;; Bootstrap -;; 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 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)) +;; Load the heart of Doom Emacs +(unless (load (expand-file-name "core/core" user-emacs-directory) t t) + ;; ...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)) - ;; 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 - ;; 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 init-file - (expand-file-name "init.el" user-emacs-directory))) - nil ; TODO Replace with safe mode initfile - (caddr args)))) +;; 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 +;; 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 () + (if (boundp 'doom-core-dir) + (expand-file-name "core-start" doom-core-dir) + (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