emacs: Use make-process when available

make-process is a new function introduced in Emacs 25, which provides
greater control over process creation. Crucially, it allows
separately redirecting stderr directly to a buffer, which allows us to
avoid needing to use the shell to redirect to a temporary file in
order to correctly distinguish stdout and stderr.

* notmuch-lib.el: Use make-process when it is available; fall back to
  the previous method when not.
This commit is contained in:
Vladimir Panteleev 2017-08-17 17:57:12 +00:00 committed by David Bremner
parent 69946c47c9
commit fdf2b3007a

View file

@ -909,21 +909,42 @@ invoke `set-process-sentinel' directly on the returned process,
as that will interfere with the handling of stderr and the exit as that will interfere with the handling of stderr and the exit
status." status."
;; There is no way (as of Emacs 24.3) to capture stdout and stderr (let (err-file err-buffer proc
;; separately for asynchronous processes, or even to redirect stderr ;; Find notmuch using Emacs' `exec-path'
;; to a file, so we use a trivial shell wrapper to send stderr to a (command (or (executable-find notmuch-command)
;; temporary file and clean things up in the sentinel. (error "Command not found: %s" notmuch-command))))
(let* ((err-file (make-temp-file "nmerr")) (if (fboundp 'make-process)
;; Use a pipe (progn
(process-connection-type nil) (setq err-buffer (generate-new-buffer " *notmuch-stderr*"))
;; Find notmuch using Emacs' `exec-path' ;; Emacs 25 and newer has `make-process', which allows
(command (or (executable-find notmuch-command) ;; redirecting stderr independently from stdout to a
(error "command not found: %s" notmuch-command))) ;; separate buffer. As this allows us to avoid using a
(proc (apply #'start-process name buffer ;; temporary file and shell invocation, use it when
"/bin/sh" "-c" ;; available.
"exec 2>\"$1\"; shift; exec \"$0\" \"$@\"" (setq proc (make-process
command err-file args))) :name name
(process-put proc 'err-file err-file) :buffer buffer
:command (cons command args)
:connection-type 'pipe
:stderr err-buffer))
(process-put proc 'err-buffer err-buffer)
;; Silence "Process NAME stderr finished" in stderr by adding a
;; no-op sentinel to the fake stderr process object
(set-process-sentinel (get-buffer-process err-buffer) #'ignore))
;; On Emacs versions before 25, there is no way to capture
;; stdout and stderr separately for asynchronous processes, or
;; even to redirect stderr to a file, so we use a trivial shell
;; wrapper to send stderr to a temporary file and clean things
;; up in the sentinel.
(setq err-file (make-temp-file "nmerr"))
(let ((process-connection-type nil)) ;; Use a pipe
(setq proc (apply #'start-process name buffer
"/bin/sh" "-c"
"exec 2>\"$1\"; shift; exec \"$0\" \"$@\""
command err-file args)))
(process-put proc 'err-file err-file))
(process-put proc 'sub-sentinel sentinel) (process-put proc 'sub-sentinel sentinel)
(process-put proc 'real-command (cons notmuch-command args)) (process-put proc 'real-command (cons notmuch-command args))
(set-process-sentinel proc #'notmuch-start-notmuch-sentinel) (set-process-sentinel proc #'notmuch-start-notmuch-sentinel)
@ -932,10 +953,10 @@ status."
(defun notmuch-start-notmuch-sentinel (proc event) (defun notmuch-start-notmuch-sentinel (proc event)
"Process sentinel function used by `notmuch-start-notmuch'." "Process sentinel function used by `notmuch-start-notmuch'."
(let* ((err-file (process-get proc 'err-file)) (let* ((err-file (process-get proc 'err-file))
(err (with-temp-buffer (err-buffer (or (process-get proc 'err-buffer)
(insert-file-contents err-file) (find-file-noselect err-file)))
(unless (eobp) (err (when (not (zerop (buffer-size err-buffer)))
(buffer-string)))) (with-current-buffer err-buffer (buffer-string))))
(sub-sentinel (process-get proc 'sub-sentinel)) (sub-sentinel (process-get proc 'sub-sentinel))
(real-command (process-get proc 'real-command))) (real-command (process-get proc 'real-command)))
(condition-case err (condition-case err
@ -953,8 +974,8 @@ status."
;; If that didn't signal an error, then any error output was ;; If that didn't signal an error, then any error output was
;; really warning output. Show warnings, if any. ;; really warning output. Show warnings, if any.
(let ((warnings (let ((warnings
(with-temp-buffer (when err
(unless (= (second (insert-file-contents err-file)) 0) (with-current-buffer err-buffer
(goto-char (point-min)) (goto-char (point-min))
(end-of-line) (end-of-line)
;; Show first line; stuff remaining lines in the ;; Show first line; stuff remaining lines in the
@ -969,7 +990,8 @@ status."
;; Emacs behaves strangely if an error escapes from a sentinel, ;; Emacs behaves strangely if an error escapes from a sentinel,
;; so turn errors into messages. ;; so turn errors into messages.
(message "%s" (error-message-string err)))) (message "%s" (error-message-string err))))
(ignore-errors (delete-file err-file)))) (when err-buffer (kill-buffer err-buffer))
(when err-file (ignore-errors (delete-file err-file)))))
;; This variable is used only buffer local, but it needs to be ;; This variable is used only buffer local, but it needs to be
;; declared globally first to avoid compiler warnings. ;; declared globally first to avoid compiler warnings.