- 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
307 lines
12 KiB
Bash
Executable file
307 lines
12 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 --no-x-resources --no-splash --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 core/core-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.
|
|
|
|
;; Ensure Doom runs out of this file's parent directory (or $EMACSDIR), where
|
|
;; Doom is presumably installed.
|
|
(setq user-emacs-directory
|
|
(if (getenv-internal "EMACSDIR")
|
|
(file-name-as-directory
|
|
(file-truename (getenv-internal "EMACSDIR")))
|
|
(expand-file-name
|
|
"../" (file-name-directory (file-truename load-file-name)))))
|
|
|
|
|
|
;;
|
|
;;; Sanity checks
|
|
|
|
(when (equal (user-real-uid) 0)
|
|
;; If ~/.emacs.d is owned by root, assume the user genuinely wants root to be
|
|
;; their primary user, otherwise complain.
|
|
(unless (= 0 (file-attribute-user-id (file-attributes user-emacs-directory)))
|
|
(message
|
|
(concat
|
|
"Error: this script is being run 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 user-emacs-directory) "\n\n"
|
|
"Aborting..."))
|
|
(kill-emacs 2)))
|
|
|
|
|
|
;;
|
|
;;; Load Doom's CLI framework
|
|
|
|
(require 'core-cli (expand-file-name "core/core-cli" user-emacs-directory))
|
|
|
|
;; Load $DOOMDIR/init.el, to read the user's `doom!' block, and so users can
|
|
;; customize things early, if they like.
|
|
(load! doom-module-init-file doom-private-dir t)
|
|
|
|
|
|
;;
|
|
;;; 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/issues Global issue tracker"
|
|
:partial t)
|
|
|
|
(defcli! :before
|
|
((force? ("-!" "--force") "Suppress prompts by auto-accepting their consequences")
|
|
(debug? ("-D" "--debug") "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")
|
|
&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 or `$DOOMDIR'/init.el
|
|
instead."
|
|
(when color?
|
|
(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
|
|
debug?
|
|
emacsdir
|
|
doomdir))
|
|
(when profile
|
|
(setenv "DOOMPROFILE" profile))
|
|
(when debug?
|
|
(setenv "DEBUG" "1")
|
|
(print! (item "Debug mode enabled")))
|
|
(when emacsdir
|
|
(setenv "EMACSDIR" emacsdir))
|
|
(when doomdir
|
|
(setenv "DOOMDIR" doomdir))
|
|
(exit! :restart))
|
|
;; But these don't need a restart:
|
|
(when pager
|
|
(setq doom-cli-pager pager))
|
|
(when force?
|
|
(setf (doom-cli-context-suppress-prompts-p context) t)
|
|
(doom-log "User requested all prompts be suppressed"))
|
|
;; 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)))
|
|
|
|
|
|
;;
|
|
;;; Commands
|
|
|
|
(let ((dir (doom-path doom-core-dir "cli")))
|
|
;; It'd be simple to just load these files directly, but because there could
|
|
;; be a lot of them (and some of them have expensive dependencies), I use
|
|
;; `defautoload!' to load them lazily.
|
|
(add-to-list 'doom-cli-load-path dir)
|
|
|
|
;; Library for generating autoloads files for Doom modules & packages.
|
|
(load! "autoloads" dir)
|
|
|
|
(defgroup!
|
|
:prefix 'doom
|
|
;; Import this for implicit 'X help' commands for your script:
|
|
(defalias! ((help h)) (:root :help))
|
|
;; And suggest its use when errors occur.
|
|
(add-to-list 'doom-help-commands "%p h[elp] %c")
|
|
|
|
(defgroup! "Config Management"
|
|
:docs "Commands for maintaining your Doom Emacs configuration."
|
|
(defautoload! ((sync s refresh re)))
|
|
(defautoload! ((upgrade up)))
|
|
(defautoload! (env))
|
|
(defautoload! ((build b purge p rollback)) "packages")
|
|
(defautoload! ((install i)))
|
|
(defautoload! ((compile c)))
|
|
(defautoload! (clean) "compile")
|
|
|
|
;; TODO Post-3.0 commands
|
|
;; (load! "gc" dir)
|
|
;; (load! "module" dir)
|
|
;; (load! "nuke" dir)
|
|
;; (load! "package" dir)
|
|
;; (load! "profile" dir)
|
|
;; (defobsolete! ((compile c)) (sync "--compile") "v3.0.0")
|
|
;; (defobsolete! ((build b)) (sync "--rebuild") "v3.0.0")
|
|
)
|
|
|
|
(defgroup! "Diagnostics"
|
|
:docs "Commands for troubleshooting and debugging Doom."
|
|
(defautoload! ((doctor doc)))
|
|
(defautoload! (info))
|
|
(defalias! ((version v)) (:root :version)))
|
|
|
|
(defgroup! "Development"
|
|
:docs "Commands for developing or launching Doom."
|
|
(defautoload! (ci))
|
|
(defautoload! (make))
|
|
(defautoload! (run))
|
|
|
|
;; FIXME Test framework
|
|
;; (load! "test" dir)
|
|
)
|
|
|
|
(let ((cli-file "cli"))
|
|
(defgroup! "Module commands"
|
|
(dolist (key (hash-table-keys doom-modules))
|
|
(when-let (path (plist-get (gethash key doom-modules) :path))
|
|
(defgroup! :prefix (format "+%s" (cdr key))
|
|
(load! cli-file path t)))))
|
|
|
|
(doom-log "Loading $DOOMDIR/cli.el")
|
|
(load! cli-file doom-private-dir t))))
|
|
|
|
|
|
;;
|
|
;;; Let 'er rip
|
|
|
|
(run! "doom" (cdr (member "--" argv)))
|
|
|
|
;;; doom ends here, unless...
|