diff --git a/bob/devtools/bootstrap.py b/bob/devtools/bootstrap.py index 585f013218dedb9debc790e82c7f2f315841896b..01c415c1790ac0e064d2c0a03802a254876a2ad7 100644 --- a/bob/devtools/bootstrap.py +++ b/bob/devtools/bootstrap.py @@ -36,9 +36,12 @@ _INTERVALS = ( import os import sys +import pty import glob import time +import errno import shutil +import select import platform import subprocess @@ -93,6 +96,9 @@ def human_time(seconds, granularity=2): def run_cmdline(cmd, env=None): '''Runs a command on a environment, logs output and reports status + Copied from: https://github.com/terminal-labs/cli-passthrough, which is in + turn based on https://stackoverflow.com/a/31953436. + Parameters: @@ -108,16 +114,43 @@ def run_cmdline(cmd, env=None): logger.info('(system) %s' % ' '.join(cmd)) start = time.time() - out = b'' - - p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, - env=env, bufsize=1, universal_newlines=True) - for line in iter(p.stdout.readline, ''): - sys.stdout.write(line) - sys.stdout.flush() - - if p.wait() != 0: + masters, slaves = zip(pty.openpty(), pty.openpty()) + + with subprocess.Popen(cmd, stdin=slaves[0], stdout=slaves[0], + stderr=slaves[1], env=env) as p: + + for fd in slaves: + os.close(fd) # no input + readable = { + masters[0]: sys.stdout.buffer, # store buffers seperately + masters[1]: sys.stderr.buffer, + } + + while readable: + + for fd in select.select(readable, [], [])[0]: + try: + data = os.read(fd, 1024) # read available + except OSError as e: + if e.errno != errno.EIO: + raise #XXX cleanup + del readable[fd] # EIO means EOF on some systems + else: + if not data: # EOF + del readable[fd] + else: + if fd == masters[0]: # We caught stdout + utils.echo(data.rstrip()) + else: # We caught stderr + utils.echo(data.rstrip(), err=True) + + readable[fd].flush() + + for fd in masters: + os.close(fd) + + if p.returncode != 0: raise RuntimeError("command `%s' exited with error state (%d)" % \ (' '.join(cmd), p.returncode)) @@ -126,7 +159,6 @@ def run_cmdline(cmd, env=None): logger.info('command took %s' % human_time(total)) - def touch(path): '''Python-implementation of the "touch" command-line application'''