Implement daisy-chaining for CLI sessions

elisp lacks an execv implementation (or mature subprocess library), so
we exploit some splenderiffic hackery to get Emacs to execute arbitrary
shell commands after a 'doom ...' command completes. This allows us to
daisy chain doom commands in distinct sessions (wonderful for reloading
doom after a 'doom upgrade', which we do). This minimizes errors when a
'doom upgrade' pulls in breaking changes to Doom's CLI.

We also bring 'doom run' into elisp, since this new functionality
enables us to.
This commit is contained in:
Henrik Lissner 2020-05-26 02:27:58 -04:00
parent e89b604f5f
commit a814239ec7
No known key found for this signature in database
GPG key ID: 5F6C0EA160557395
3 changed files with 45 additions and 38 deletions

View file

@ -1,13 +1,15 @@
#!/usr/bin/env sh
:; ( echo "$EMACS" | grep -q "term" ) && EMACS=emacs || EMACS=${EMACS:-emacs} # -*-emacs-lisp-*-
:; command -v $EMACS >/dev/null || { >&2 echo "Can't find emacs in your PATH"; exit 1; }
:; VERSION=$($EMACS --version | head -n1)
:; case "$VERSION" in *\ 2[0-5].[0-9]) echo "Detected Emacs $VERSION"; echo "Doom only supports Emacs 26.1 and newer"; echo; exit 2 ;; esac
:; DOOMBASE="$(dirname "$0")/.."
:; [ "$1" = -d ] || [ "$1" = --debug ] && { shift; export DEBUG=1; }
:; [ "$1" = run ] && { cd "$DOOMBASE"; shift; exec $EMACS -q --no-splash -l init.el -f doom-run-all-startup-hooks-h "$@"; exit 0; }
:; exec $EMACS --no-site-file --script "$0" -- "$@"
:; exit 0
:; _VERSION=$($EMACS --version | head -n1)
:; case "$_VERSION" in *\ 2[0-5].[0-9]) echo "Detected Emacs $_VERSION"; echo "Doom only supports Emacs 26.1 and newer"; echo; exit 2 ;; esac
:; _DOOMBASE="${EMACSDIR:-$(dirname "$0")/..}"
:; _DOOMPOST="$_DOOMBASE/.local/.doom.sh"
:; rm -f "$_DOOMPOST"
:; $EMACS --no-site-file --script "$0" -- "$@"
:; CODE=$?
:; [ -x "$_DOOMPOST" ] && PATH="$_DOOMBASE/bin:$PATH" "$_DOOMPOST" "$0" "$@"
:; exit $CODE
(let* ((loaddir (file-name-directory (file-truename load-file-name)))
(emacsdir (getenv "EMACSDIR"))
@ -46,7 +48,7 @@ with a different private module."
:bare t
(when emacsdir
(setq user-emacs-directory (file-name-as-directory emacsdir))
(print! (info "EMACSDIR=%s") localdir))
(print! (info "EMACSDIR=%s") emacsdir))
(when doomdir
(setenv "DOOMDIR" (file-name-as-directory doomdir))
(print! (info "DOOMDIR=%s") localdir))

View file

@ -15,16 +15,19 @@ following shell commands:
bin/doom update"
:bare t
(let ((doom-auto-discard force-p))
(if (delq
nil (list
(unless packages-only-p
(doom-cli-upgrade doom-auto-accept doom-auto-discard))
(doom-cli-execute "sync")
(when (doom-cli-packages-update)
(doom-autoloads-reload)
t)))
(print! (success "Done! Restart Emacs for changes to take effect."))
(print! "Nothing to do. Doom is up-to-date!"))))
(cond
(packages-only-p
(doom-cli-execute "sync" "-u")
(print! (success "Finished upgrading Doom Emacs")))
((not (doom-cli-upgrade doom-auto-accept doom-auto-discard))
(print! "Nothing to do. Doom is up-to-date!"))
(t
;; Reload Doom's CLI & libraries, in case there were any
;; upstream changes. Major changes will still break, however
(print! (info "Reloading Doom Emacs"))
(doom-cli-execute-after "doom" "upgrade" "-p" (if force-p "-f"))))))
;;
@ -113,24 +116,6 @@ following shell commands:
(equal (vc-git--rev-parse "HEAD") new-rev))
(error "Failed to check out %s" (substring new-rev 0 10)))
(print! (info "%s") (cdr result))
;; Reload Doom's CLI & libraries, in case there were any
;; upstream changes. Major changes will still break, however
(condition-case-unless-debug e
(progn
(mapc (lambda (f) (load (symbol-name f)))
'(core core-lib
core-cli
core-modules
core-packages))
(doom-initialize 'force))
(error
(signal 'doom-error
(list "Could not reload new version of Doom"
"Try running 'doom upgrade' again"
e))))
(print! (success "Finished upgrading Doom Emacs")))
t)))))
t))))))
(ignore-errors
(doom-call-process "git" "remote" "remove" doom-repo-remote))))))

View file

@ -166,6 +166,24 @@ COMMAND, and passes ARGS to it."
(doom--cli-process cli (remq nil args)))
(user-error "Couldn't find any %S command" command)))
(defun doom-cli-execute-after (&rest args)
"Execute shell command ARGS after this CLI session quits.
This is particularly useful when the capabilities of Emacs' batch terminal are
insufficient (like opening an instance of Emacs, or reloading Doom after a 'doom
upgrade')."
(let ((post-script (concat doom-local-dir ".doom.sh")))
(with-temp-file post-script
(insert "#!/usr/bin/env sh\n"
"rm -f " (prin1-to-string post-script) "\n"
"exec " (mapconcat #'shell-quote-argument (remq nil args) " ")
"\n"))
(let* ((current-mode (file-modes post-script))
(add-mode (logand ?\111 (default-file-modes))))
(or (/= (logand ?\111 current-mode) 0)
(zerop add-mode)
(set-file-modes post-script (logior current-mode add-mode))))))
(defmacro defcli! (name speclist &optional docstring &rest body)
"Defines a CLI command.
@ -423,7 +441,9 @@ All arguments are passed on to Emacs.
WARNING: this command exists for convenience and testing. Doom will suffer
additional overhead by being started this way. For the best performance, it is
best to run Doom out of ~/.emacs.d and ~/.doom.d."))
best to run Doom out of ~/.emacs.d and ~/.doom.d."
(apply #'doom-cli-execute-after invocation-name args)
nil))
(provide 'core-cli)
;;; core-cli.el ends here