77 lines
2.0 KiB
Python
77 lines
2.0 KiB
Python
import errno
|
|
import os
|
|
import pty
|
|
import selectors
|
|
import subprocess
|
|
|
|
MAX_ERROR_OUTPUT_BYTES = 8192
|
|
|
|
|
|
def _read_chunk(fd, treat_eio_as_eof=False):
|
|
try:
|
|
return os.read(fd, 4096)
|
|
except OSError as exc:
|
|
if treat_eio_as_eof and exc.errno == errno.EIO:
|
|
return b""
|
|
raise
|
|
|
|
|
|
def _stream_output(fd, process, reporter, treat_eio_as_eof=False):
|
|
selector = selectors.DefaultSelector()
|
|
recent_output = bytearray()
|
|
|
|
try:
|
|
selector.register(fd, selectors.EVENT_READ)
|
|
|
|
while selector.get_map():
|
|
for key, _ in selector.select():
|
|
data = _read_chunk(key.fileobj, treat_eio_as_eof=treat_eio_as_eof)
|
|
if not data:
|
|
selector.unregister(key.fileobj)
|
|
os.close(key.fileobj)
|
|
continue
|
|
|
|
reporter._clear()
|
|
os.write(1, data)
|
|
reporter._render()
|
|
recent_output.extend(data)
|
|
if len(recent_output) > MAX_ERROR_OUTPUT_BYTES:
|
|
del recent_output[:-MAX_ERROR_OUTPUT_BYTES]
|
|
finally:
|
|
selector.close()
|
|
|
|
return_code = process.wait()
|
|
if return_code != 0:
|
|
raise subprocess.CalledProcessError(return_code, process.args, output=bytes(recent_output))
|
|
|
|
|
|
def run_command_with_reporter(cmd, cwd=None, reporter=None):
|
|
if reporter is None:
|
|
subprocess.run(cmd, cwd=cwd, check=True)
|
|
return
|
|
|
|
try:
|
|
master_fd, slave_fd = pty.openpty()
|
|
except OSError:
|
|
process = subprocess.Popen(
|
|
cmd,
|
|
cwd=cwd,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
)
|
|
assert process.stdout is not None
|
|
_stream_output(process.stdout.fileno(), process, reporter)
|
|
return
|
|
|
|
try:
|
|
process = subprocess.Popen(
|
|
cmd,
|
|
cwd=cwd,
|
|
stdout=slave_fd,
|
|
stderr=slave_fd,
|
|
)
|
|
finally:
|
|
os.close(slave_fd)
|
|
|
|
_stream_output(master_fd, process, reporter, treat_eio_as_eof=True)
|