From a814239ec7ed22edaa684ed59e6512e42bfce192 Mon Sep 17 00:00:00 2001 From: Henrik Lissner Date: Tue, 26 May 2020 02:27:58 -0400 Subject: [PATCH] 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. --- bin/doom | 18 ++++++++++-------- core/cli/upgrade.el | 43 ++++++++++++++----------------------------- core/core-cli.el | 22 +++++++++++++++++++++- 3 files changed, 45 insertions(+), 38 deletions(-) diff --git a/bin/doom b/bin/doom index e41248ac7..c869160d7 100755 --- a/bin/doom +++ b/bin/doom @@ -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)) diff --git a/core/cli/upgrade.el b/core/cli/upgrade.el index f8067f1df..94f00b5ad 100644 --- a/core/cli/upgrade.el +++ b/core/cli/upgrade.el @@ -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)))))) diff --git a/core/core-cli.el b/core/core-cli.el index 0079433ee..ffb78c173 100644 --- a/core/core-cli.el +++ b/core/core-cli.el @@ -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