| #!/usr/bin/python3 | 
 | # | 
 | # Copyright 2019, The Android Open Source Project | 
 | # | 
 | # Licensed under the Apache License, Version 2.0 (the "License"); | 
 | # you may not use this file except in compliance with the License. | 
 | # You may obtain a copy of the License at | 
 | # | 
 | #     http://www.apache.org/licenses/LICENSE-2.0 | 
 | # | 
 | # Unless required by applicable law or agreed to in writing, software | 
 | # distributed under the License is distributed on an "AS IS" BASIS, | 
 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 | # See the License for the specific language governing permissions and | 
 | # limitations under the License. | 
 |  | 
 | """ | 
 | Run a command using multiple cores in parallel. Stop when one exits zero and save the log from | 
 | that run. | 
 | """ | 
 |  | 
 | import argparse | 
 | import concurrent.futures | 
 | import contextlib | 
 | import itertools | 
 | import os | 
 | import os.path | 
 | import shutil | 
 | import subprocess | 
 | import tempfile | 
 |  | 
 |  | 
 | def run_one(cmd, tmpfile): | 
 |   """Run the command and log result to tmpfile. Return both the file name and returncode.""" | 
 |   with open(tmpfile, "x") as fd: | 
 |     return tmpfile, subprocess.run(cmd, stdout=fd).returncode | 
 |  | 
 | def main(): | 
 |   parser = argparse.ArgumentParser( | 
 |       description="""Run a command using multiple cores and save non-zero exit log | 
 |  | 
 |       The cmd should print all output to stdout. Stderr is not captured.""" | 
 |   ) | 
 |   parser.add_argument("--jobs", "-j", type=int, help="max number of jobs. default 60", default=60) | 
 |   parser.add_argument("cmd", help="command to run") | 
 |   parser.add_argument("--out", type=str, help="where to put result", default="out_log") | 
 |   args = parser.parse_args() | 
 |   cnt = 0 | 
 |   found_fail = False | 
 |   ids = itertools.count(0) | 
 |   with tempfile.TemporaryDirectory() as td: | 
 |     print("Temporary files in {}".format(td)) | 
 |     with concurrent.futures.ProcessPoolExecutor(args.jobs) as p: | 
 |       fs = set() | 
 |       while len(fs) != 0 or not found_fail: | 
 |         if not found_fail: | 
 |           for _, idx in zip(range(args.jobs - len(fs)), ids): | 
 |             fs.add(p.submit(run_one, args.cmd, os.path.join(td, "run_log." + str(idx)))) | 
 |         ws = concurrent.futures.wait(fs, return_when=concurrent.futures.FIRST_COMPLETED) | 
 |         fs = ws.not_done | 
 |         done = list(map(lambda a: a.result(), ws.done)) | 
 |         cnt += len(done) | 
 |         print("\r{} runs".format(cnt), end="") | 
 |         failed = [d for d,r in done if r != 0] | 
 |         succ = [d for d,r in done if r == 0] | 
 |         for f in succ: | 
 |           os.remove(f) | 
 |         if len(failed) != 0: | 
 |           if not found_fail: | 
 |             found_fail = True | 
 |             print("\rFailed at {} runs".format(cnt)) | 
 |             if len(failed) != 1: | 
 |               for f,i in zip(failed, range(len(failed))): | 
 |                 shutil.copyfile(f, args.out+"."+str(i)) | 
 |             else: | 
 |               shutil.copyfile(failed[0], args.out) | 
 |  | 
 | if __name__ == '__main__': | 
 |   main() |