cmd icon indicating copy to clipboard operation
cmd copied to clipboard

error-output cannot be retrieved from uiop's condition object

Open mfiano opened this issue 2 years ago • 5 comments

I'm not sure if this is something that can be worked around in cmd, but it is not possible to get at the process-info of a condition being signalled with the synchronous uiop:run-program. My idea was that I could pass an :error-output string/stream, and get at it when handling a condition, but by that point the process has already exited, and uiop has cleaned up its state.

I guess uiop does something similar to (run-program) (when error (cleanup) (signal))), so when the subprocess-error condition is signalled, its process slot is already nil.

Is there any way to work around this? I really would like to get the stderr output in a condition handler.

mfiano avatar May 21 '22 17:05 mfiano

Simple example:

(let ((s (make-string-output-stream)))
  (handler-case (uiop:run-program "exit 1" :error-output s)
    (uiop/run-program:subprocess-error (c) c)))
    
(inspect *)

mfiano avatar May 21 '22 17:05 mfiano

This works for me:

(let ((s (make-string-output-stream)))
  (handler-case (uiop:run-program "echo something >&2; exit 1" :error-output s)
    (uiop/run-program:subprocess-error (c)
      (print (get-output-stream-string s)))))

ruricolist avatar May 21 '22 20:05 ruricolist

Of course. s is in the lexical environment of that condition handler, but in the case that it's not, when the condition is being handled around a caller of the function calling uiop:run-program, this is where the issue is most problematic.

(in-package :supplier-package)

(defun foo (string)
  (let ((s (make-string-output-stream)))
    (cmd:$cmd "echo" string :error-output s)))

(in-package :client-package)

(defun some-user-function ()
  (handler-case (supplier-package:foo "hello")
    (uiop/run-program:subprocess-error (c)
      ;; print or resignal an error using stderr output here
    )))

mfiano avatar May 21 '22 20:05 mfiano

I've pushed up a feature to allow overriding the null device for output and error output. E.g.:

(defun foo (string)
  (cmd:$cmd "bash -c 'echo $0; echo busted >&2; exit 1'" string))

(defun some-user-function ()
  (let ((cmd:*null-error-output* (make-string-output-stream)))
    (handler-case (foo "hello")
      (uiop/run-program:subprocess-error ()
        (princ (get-output-stream-string cmd::*null-error-output*))
        ;; print or resignal an error using stderr output here
        ))))

I agree it would be better if when there was an error we got back an error object containing the stderr. I have to think more about how to do this in a way that (1) doesn't store unbounded amounts of error output, (2) works with launch-program's handling of error output (e.g. :if-error-output-does-not exist) and (3) works even if the user has explicitly redirected stderr.

ruricolist avatar May 30 '22 15:05 ruricolist

I have a branch up (https://github.com/ruricolist/cmd/tree/stderr-temporary) that partially handles this by storing error output in a temporary file if no stderr is specified. It doesn't "tee" the output yet though.

ruricolist avatar Jun 11 '22 14:06 ruricolist