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.
319 lines
13 KiB
Bash
Executable file
319 lines
13 KiB
Bash
Executable file
#!/usr/bin/env sh
|
|
:; # -*- mode: emacs-lisp; lexical-binding: t -*-
|
|
:; case "$EMACS" in *term*) EMACS=emacs ;; *) EMACS="${EMACS:-emacs}" ;; esac
|
|
:; emacs="$EMACS -q --no-site-file --batch"
|
|
:; tmpdir=`$emacs --eval '(princ (temporary-file-directory))' 2>/dev/null`
|
|
:; [ -z "$tmpdir" ] && { >&2 echo "Error: failed to run Emacs with command '$EMACS'"; >&2 echo; >&2 echo "Are you sure Emacs is installed and in your \$PATH?"; exit 1; }
|
|
:; export __DOOMPID="${__DOOMPID:-$$}"
|
|
:; export __DOOMSTEP="$((__DOOMSTEP+1))"
|
|
:; export __DOOMGEOM="${__DOOMGEOM:-`tput cols 2>/dev/null`x`tput lines 2>/dev/null`}"
|
|
:; export __DOOMGPIPE=${__DOOMGPIPE:-$__DOOMPIPE}
|
|
:; export __DOOMPIPE=; [ -t 0 ] || __DOOMPIPE="${__DOOMPIPE}0"; [ -t 1 ] || __DOOMPIPE="${__DOOMPIPE}1"
|
|
:; $emacs --load "$0" -- "$@" || exit=$?
|
|
:; [ "${exit:-0}" -eq 254 ] && { sh "${tmpdir}/doom.${__DOOMPID}.${__DOOMSTEP}.sh" "$0" "$@" && true; exit="$?"; }
|
|
:; exit $exit
|
|
|
|
;; This magical mess of a shebang is necessary for any script that relies on
|
|
;; Doom's CLI framework, because Emacs' tty libraries and capabilities are too
|
|
;; immature (borderline non-existent) at the time of writing (28.1). This
|
|
;; shebang sets out to accomplish these three goals:
|
|
;;
|
|
;; 1. To produce a more helpful error if Emacs isn't installed or broken. It
|
|
;; must do so without assuming whether $EMACS is a shell command (e.g. 'snap
|
|
;; run emacs') or an absolute path (to an emacs executable). I've avoided
|
|
;; 'command -v $EMACS' for this reason.
|
|
;;
|
|
;; 2. To allow this Emacs session to "exit into" a child process (since Elisp
|
|
;; lacks an analogue for exec system calls) by calling an auto-generated and
|
|
;; self-destructing "exit script" if the parent Emacs process exits with code
|
|
;; 254. It takes care to prevent nested child instances from clobbering the
|
|
;; exit script.
|
|
;;
|
|
;; 3. To expose some information about the terminal and session:
|
|
;; - $__DOOMGEOM holds the dimensions of the terminal (W . H).
|
|
;; - $__DOOMPIPE indicates whether the script has been piped (in and/or out).
|
|
;; - $__DOOMGPIPE indicates whether one of this process' parent has been
|
|
;; piped to/from.
|
|
;; - $__DOOMPID is a unique identifier for the parent script, so
|
|
;; child processes can identify which persistent data files (like logs) it
|
|
;; has access to.
|
|
;; - $__DOOMSTEP counts how many levels deep we are in the dream (appending
|
|
;; this to the exit script's filename avoids child processes clobbering the
|
|
;; same exit script and causing read errors).
|
|
;; - $TMPDIR (or $TEMP and $TMP on Windows) aren't guaranteed to have values,
|
|
;; and mktemp isn't available on all systems, but you know what is? Emacs!
|
|
;; So I use it to print `temporary-file-directory'. And it seconds as a
|
|
;; quick sanity check for Emacs' existence (for goal #1).
|
|
;;
|
|
;; Other weird facts about this shebang line:
|
|
;;
|
|
;; - The :; hack exploits properties of : and ; in shell scripting and elisp to
|
|
;; allow shell script and elisp to coexist in the same file without either's
|
|
;; interpreter throwing foreign syntax errors:
|
|
;;
|
|
;; - In elisp, ":" is a valid keyword symbol literal; it evaluates to itself
|
|
;; and has no side-effect.
|
|
;; - In the shell, ":" is a valid command that does nothing and ignores its
|
|
;; arguments.
|
|
;; - In elisp, ";" begins a comment. I.e. the interpreter ignores everything
|
|
;; after it.
|
|
;; - In the shell, ";" is a command separator.
|
|
;;
|
|
;; Put together, plus a strategically placed exit call, the shell will read
|
|
;; one part of this file and ignore the rest, while the elisp interpreter will
|
|
;; do the opposite.
|
|
;; - I intentionally avoid loading site files, so lisp/doom-cli.el can load them
|
|
;; by hand later. There, I can suppress and deal with unhelpful warnings (e.g.
|
|
;; "package cl is deprecated"), "Loading X...DONE" spam, and any other
|
|
;; disasterous side-effects.
|
|
;;
|
|
;; But be careful not to use -Q! It implies --no-site-lisp, which omits the
|
|
;; site-lisp directory from `load-path'.
|
|
;; - POSIX-compliancy is paramount: there's no guarantee what /bin/sh will be
|
|
;; symlinked to in the esoteric OSes/distros Emacs users use.
|
|
;; - The user may have a noexec flag set on /tmp, so pass the exit script to
|
|
;; /bin/sh rather than executing them directly.
|
|
|
|
;; In CLI sessions, prefer correctness over performance.
|
|
(setq load-prefer-newer t)
|
|
|
|
;; Doom's core sets up everything we need; including `doom-*-dir' variables,
|
|
;; universal defaults, and autoloads for doom-*-initialize functions.
|
|
(condition-case e
|
|
(let* ((bin-dir (file-name-directory (file-truename load-file-name)))
|
|
(init-file (expand-file-name "../early-init.el" bin-dir)))
|
|
(or (and (load init-file nil 'nomessage)
|
|
(featurep 'doom))
|
|
(user-error "Failed to load Doom from %s" init-file)))
|
|
;; Prevent ugly backtraces for trivial errors
|
|
(user-error (message "Error: %s" (cadr e))
|
|
(kill-emacs 2)))
|
|
|
|
;; UX: Abort if the user is using 'doom' as root, unless ~/.emacs.d is owned by
|
|
;; root, in which case we assume the user genuinely wants root to be their
|
|
;; primary user account for Emacs.
|
|
(when (equal 0 (user-real-uid))
|
|
(unless (equal 0 (file-attribute-user-id (file-attributes doom-emacs-dir)))
|
|
(message
|
|
(concat
|
|
"Error: this script was executed as root, which is likely not what you want.\n"
|
|
"It will cause file permissions errors later, when you run Doom as another\n"
|
|
"user.\n\n"
|
|
"If this really *is* what you want, then change the owner of your Emacs\n"
|
|
"config to root:\n\n"
|
|
;; TODO Add cmd.exe/powershell commands
|
|
" chown root:root -R " (abbreviate-file-name doom-emacs-dir) "\n\n"
|
|
"Aborting..."))
|
|
(kill-emacs 2)))
|
|
|
|
|
|
;;
|
|
;;; Entry point
|
|
|
|
(defcli! doom (&args _command)
|
|
"A command line interface to Doom Emacs.
|
|
|
|
Includes package management, diagnostics, unit tests, and byte-compilation.
|
|
|
|
This tool also makes it trivial to launch Emacs out of a different folder or
|
|
with a different private module.
|
|
|
|
ENVIRONMENT VARIABLES:
|
|
`$EMACS'
|
|
The Emacs executable or command to use for any Emacs operations in this or
|
|
other Doom CLI shell scripts (default: first emacs found in `$PATH').
|
|
|
|
`$EMACSDIR'
|
|
The location of your Doom Emacs installation (defaults to ~/.config/emacs or
|
|
~/.emacs.d; whichever is found first). This is *not* your private Doom
|
|
configuration. The `--emacsdir' option also sets this variable.
|
|
|
|
`$DOOMDIR'
|
|
The location of your private configuration for Doom Emacs (defaults to
|
|
~/.config/doom or ~/.doom.d; whichever it finds first). This is *not* the
|
|
place you've cloned doomemacs/doomemacs to. The `--doomdir' option also sets
|
|
this variable.
|
|
|
|
`$DOOMPAGER'
|
|
The pager to invoke for large output (default: \"less +g\"). The `--pager'
|
|
option also sets this variable.
|
|
|
|
`$DOOMPROFILE'
|
|
(Not implemented yet) Which Doom profile to activate (default: \"current\").
|
|
|
|
`$DOOMPROFILESDIR'
|
|
(Not implemented yet) Where to find or write generated Doom profiles
|
|
(default: `$EMACSDIR'/profiles).
|
|
|
|
EXIT CODES:
|
|
0 Successful run
|
|
1 General internal error
|
|
2 Error with Emacs/Doom install or execution context
|
|
3 Unrecognized user input error
|
|
4 Command not found, or is incorrect/deprecated
|
|
5 Invalid, missing, or extra options/arguments
|
|
6-49 Reserved for Doom
|
|
50-200 Reserved for custom user codes
|
|
254 Successful run (but then execute `doom-cli-restart-script')
|
|
255 Uncaught internal errors
|
|
|
|
SEE ALSO:
|
|
https://doomemacs.org Homepage
|
|
https://docs.doomemacs.org Official documentation
|
|
https://discourse.doomemacs.org Discourse (discussion & support forum)
|
|
https://doomemacs.org/discord Discord chat server
|
|
https://doomemacs.org/roadmap Development roadmap
|
|
https://git.doomemacs.org Shortcut to Github org
|
|
https://git.doomemacs.org/todo Global issue tracker"
|
|
:partial t)
|
|
|
|
(defcli! :before
|
|
((force? ("-!" "--force") "Suppress prompts by auto-accepting their consequences")
|
|
(debug? ("-D" "--debug") "Enable debug output")
|
|
(verbose? ("-v" "--verbose") "Enable verbose output")
|
|
(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")
|
|
(profile ("--profile" name) "Use profile named NAME")
|
|
(bench? ("--benchmark") "Always display the benchmark")
|
|
&flags
|
|
(color? ("--color") "Whether or not to show ANSI color codes")
|
|
&multiple
|
|
(loads ("-L" "--load" "--strict-load" file) "Load elisp `FILE' before executing `COMMAND'")
|
|
(evals ("-E" "--eval" form) "Evaluate `FORM' before executing commands")
|
|
&input input
|
|
&context context
|
|
&args _)
|
|
"OPTIONS:
|
|
-E, -eval
|
|
Can be used multiple times.
|
|
|
|
-L, --load, --strict-load
|
|
Can be used multiple times to load multiple files. Both -L and --load will
|
|
silently fail on missing files, but --strict-load won't.
|
|
|
|
Warning: files loaded this way load too late to define new commands. To
|
|
define commands, do so from `$DOOMDIR'/cli.el, `$DOOMDIR'/init.el, or a
|
|
.doomrc file in the current project tree."
|
|
(when bench?
|
|
(setq doom-cli-benchmark-threshold 'always))
|
|
(unless init-file-debug ; debug-mode implies verbose
|
|
(when verbose?
|
|
(setq doom-print-minimum-level 'info)))
|
|
(when color?
|
|
(setq doom-print-backend (if (eq color? :yes) 'ansi)))
|
|
(when pager
|
|
(setq doom-cli-pager pager))
|
|
(when force?
|
|
(setf (doom-cli-context-suppress-prompts-p context) t))
|
|
;; For these settings to take full effect, the script must be restarted:
|
|
(when (or debug?
|
|
profile
|
|
emacsdir
|
|
doomdir)
|
|
(let (omit)
|
|
(when debug?
|
|
(setenv "DEBUG" "1")
|
|
(setq init-file-debug t)
|
|
(push "--debug" omit))
|
|
(when profile
|
|
(setenv "DOOMPROFILE" profile)
|
|
(push "--profile=" omit))
|
|
(when emacsdir
|
|
(setenv "EMACSDIR" emacsdir)
|
|
(push "--emacsdir=" omit))
|
|
(when doomdir
|
|
(setenv "DOOMDIR" doomdir)
|
|
(push "--doomdir=" omit))
|
|
(exit! :restart :omit omit)))
|
|
;; Load extra files and forms, as per given options.
|
|
(dolist (file loads)
|
|
(load (doom-path (cdr file))
|
|
(not (equal (car file) "--strict-load"))
|
|
(not init-file-debug) t))
|
|
(dolist (form evals)
|
|
(eval (read (cdr form)) t)))
|
|
|
|
|
|
;;
|
|
;;; Load user config + commands
|
|
|
|
;; Load $DOOMDIR/init.el, to read the user's `doom!' block and give users an
|
|
;; opportunity to customize the CLI environment, if they like. Otherwise, they
|
|
;; can do so in .doomrc or .doomproject.
|
|
(load! doom-module-init-file doom-user-dir t)
|
|
|
|
;; There are a lot of CLIs, and some have expensive initialization, so best we
|
|
;; load them lazily.
|
|
(let ((dir (doom-path doom-core-dir "cli")))
|
|
;; Library for generating autoloads files for Doom modules & packages.
|
|
(load! "autoloads" dir)
|
|
|
|
(defcli-group!
|
|
:prefix 'doom
|
|
;; Import this for implicit 'X help' commands for your script:
|
|
(defcli-alias! ((help h)) (:root :help))
|
|
;; And suggest its use when errors occur.
|
|
(add-to-list 'doom-help-commands "%p h[elp] %c")
|
|
|
|
(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")
|
|
(defcli-autoload! ((install i)))
|
|
(defcli-autoload! ((compile c)))
|
|
(defcli-autoload! (clean) "compile")
|
|
|
|
;; TODO Post-3.0 commands
|
|
;; (load! "gc" dir)
|
|
;; (load! "module" dir)
|
|
;; (load! "nuke" dir)
|
|
;; (load! "package" dir)
|
|
;; (load! "profile" dir)
|
|
;; (defcli-obsolete! ((compile c)) (sync "--compile") "v3.0.0")
|
|
;; (defcli-obsolete! ((build b)) (sync "--rebuild") "v3.0.0")
|
|
)
|
|
|
|
(defcli-group! "Diagnostics"
|
|
:docs "Commands for troubleshooting and debugging Doom."
|
|
(defcli-autoload! ((doctor doc)))
|
|
(defcli-autoload! (info))
|
|
(defcli-alias! ((version v)) (:root :version)))
|
|
|
|
(defcli-group! "Development"
|
|
:docs "Commands for developing or launching Doom."
|
|
(defcli-autoload! (ci))
|
|
(defcli-autoload! (make))
|
|
(defcli-autoload! (run))
|
|
|
|
;; FIXME Test framework
|
|
;; (load! "test" dir)
|
|
)
|
|
|
|
(let ((cli-file "cli"))
|
|
(defcli-group! "Module commands"
|
|
(dolist (key (doom-module-list))
|
|
(when-let (path (doom-module-expand-path (car key) (cdr key) cli-file))
|
|
(defcli-group! :prefix (format "+%s" (cdr key))
|
|
(doom-load path t)))))
|
|
|
|
(load! cli-file doom-user-dir t))
|
|
|
|
;; Allow per-project Doom settings in .doom files.
|
|
(let (doomrc)
|
|
(cond
|
|
((setq doomrc (getenv "DOOMRC"))
|
|
(load! doomrc))
|
|
((setq doomrc (locate-dominating-file default-directory ".doomrc"))
|
|
(load! ".doomrc" doomrc))))))
|
|
|
|
|
|
;;
|
|
;;; Let 'er rip
|
|
|
|
(run! "doom" (cdr (member "--" argv)))
|
|
|
|
;;; doom ends here, unless...
|