#!/usr/bin/env python3 # Copyright (C) 2021 Inria # # This file is subject to the terms and conditions of the GNU Lesser # General Public License v2.1. See the file LICENSE in the top level # directory for more details. import sys import time import shlex import subprocess import argparse from contextlib import contextmanager SUCCESS = "\033[32;1m✓\033[0m" FAILED = "\033[31;1m×\033[0m" SPIN = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] class Programmer: @contextmanager def spawn_process(self): """Yield a subprocess running in background.""" kwargs = {} if self.verbose else { "stdout": subprocess.PIPE, "stderr": subprocess.STDOUT } yield subprocess.Popen(shlex.split(self.cmd), **kwargs) def spin(self, process): """Print a spinning icon while programmer process is running.""" print( "For full programmer output add PROGRAMMER_QUIET=0 or " "QUIET=0 to the make command line." ) while process.poll() is None: for index in range(len(SPIN)): sys.stdout.write( "\r \033[36;1m{}\033[0m {} in progress " "(programmer: '{}')" .format(SPIN[index], self.action, self.programmer) ) sys.stdout.flush() time.sleep(0.1) def print_status(self, process, elapsed): """Print status of background programmer process.""" print( "\r \u001b[2K{} {} {} (programmer: '{}' - duration: {:0.2f}s)" .format( FAILED if process.returncode != 0 else SUCCESS, self.action, "failed!" if process.returncode != 0 else "done!", self.programmer, elapsed ) ) # Print content of stdout (which also contain stderr) when the # subprocess failed if process.returncode != 0: print(process.stdout.read().decode()) def run(self): """Run the programmer in a background process.""" if not self.cmd.strip(): # Do nothing if programmer command is empty return 0 if self.verbose: print(self.cmd) start = time.time() with self.spawn_process() as proc: try: if self.verbose: proc.communicate() else: self.spin(proc) except KeyboardInterrupt: proc.terminate() proc.kill() elapsed = time.time() - start if not self.verbose: # When using the spinning icon, print the programmer status self.print_status(proc, elapsed) return proc.returncode def main(parser): """Main function.""" programmer = Programmer() parser.parse_args(namespace=programmer) # Return with same return code as subprocess sys.exit(programmer.run()) def parser(): """Return an argument parser.""" parser = argparse.ArgumentParser() parser.add_argument("--action", help="Programmer action") parser.add_argument("--cmd", help="Programmer command") parser.add_argument("--programmer", help="Programmer") parser.add_argument( "--verbose", action='store_true', default=False, help="Verbose output" ) return parser if __name__ == "__main__": main(parser())