#!/usr/bin/env python

"""Run multiple command lines in parallel

Syntax: parallelize.py <command line> ...

Example: parallelize.py "runtests1.sh" "runtests2.sh"

This scripts runs multiple command lines in parallel, but presents the result as
if they were run in sequence. So if the first one fails, then we will exit with
the exit code of that first command line, and just terminate the following ones
in the background and pretend we never started them.
"""

import subprocess
import tempfile
import atexit
import shutil
import sys


if len(sys.argv) < 2:
    # One is the executable (this script) name, the rest are command lines
    print("ERROR: Need at least one, but suggest at least two command lines to run")
    print()
    print(__doc__)
    sys.exit(1)


class WrappedProcess:
    def __init__(self, commandline):
        self.commandline = commandline

        self.output = tempfile.NamedTemporaryFile()
        atexit.register(self.cleanup)

        # FIXME: Trick wrapped process that it's writing to a terminal. Possible
        # source of inspiration:
        # http://man7.org/tlpi/code/online/dist/pty/unbuffer.c.html

        # FIXME: Set stdin to pipe in from nowhere
        self.process = subprocess.Popen(
            commandline, bufsize=-1, stdout=self.output, stderr=self.output, shell=True)

    def cleanup(self):
        # This implicitly deletes the temp file
        self.output.close()


commands = sys.argv[1:]
processes = []

for command in commands:
    processes.append(WrappedProcess(command))

for process in processes:
    returncode = process.process.wait()

    with open(process.output.name, 'r') as output:
        shutil.copyfileobj(output, sys.stdout)

    if returncode != 0:
        # Terminate the rest of the processes now that this one failed
        for killme in processes:
            if killme.process.returncode is not None:
                # Already dead
                continue

            print("Terminating remaining process: {}".format(killme.commandline))
            killme.process.terminate()
            killme.process.wait()

        sys.exit(returncode)

@@ -511,22 +511,20 @@
 %0Aimport 
-shutil
+time
 %0Aimport 
@@ -1415,16 +1415,666 @@
 lose()%0A%0A
+    def tail_and_wait(self):%0A        %22%22%22Like process.wait(), but dumps process output to stdout while waiting%22%22%22%0A        with open(self.output.name, mode='rt') as follow_me:%0A            while self.process.poll() is None:%0A                line = follow_me.readline()%0A                if line:%0A                    sys.stdout.write(line)%0A                else:%0A                    time.sleep(0.5)%0A%0A            while True:%0A                # Print the rest of the file if needed%0A                line = follow_me.readline()%0A                if not line:%0A                    break%0A                sys.stdout.write(line)%0A%0A        return self.process.returncode%0A%0A
 %0Acommand
@@ -2233,120 +2233,22 @@
 ess.
-process.wait()%0A%0A    with open(process.output.name, 'r') as output:%0A        shutil.copyfileobj(output, sys.stdout
+tail_and_wait(
 )%0A%0A 

Print output as it arrives