""" Wrapper around calling make through a subprocess. """ import io import logging import shutil import threading LOG = logging.getLogger(__name__) class MakeExecutionError(Exception): """ Exception raised in case Make execution fails. It will pass on the standard error output from the Make console. """ MESSAGE = "Make Failed: {message}" def __init__(self, **kwargs): Exception.__init__(self, self.MESSAGE.format(**kwargs)) class SubProcessMake(object): """ Wrapper around the Make command line utility, making it easy to consume execution results. """ def __init__(self, osutils, make_exe=None): """ :type osutils: aws_lambda_builders.workflows.custom_make.utils.OSUtils :param osutils: An instance of OS Utilities for file manipulation :type make_exe: str :param make_exe: Path to the Make binary. If not set, the default executable path make will be used """ self.osutils = osutils if make_exe is None: if osutils.is_windows(): make_exe = "make.exe" else: make_exe = "make" self.make_exe = make_exe def run(self, args, env=None, cwd=None): """ Runs the action. :type args: list :param args: Command line arguments to pass to Make :type env: dict :param env : environment variables dictionary to be passed into subprocess :type cwd: str :param cwd: Directory where to execute the command (defaults to current dir) :rtype: str :return: text of the standard output from the command :raises aws_lambda_builders.workflows.custom_make.make.MakeExecutionError: when the command executes with a non-zero return code. The exception will contain the text of the standard error output from the command. :raises ValueError: if arguments are not provided, or not a list """ if not isinstance(args, list): raise ValueError("args must be a list") if not args: raise ValueError("requires at least one arg") invoke_make = [self.make_exe] + args LOG.debug("executing Make: %s", invoke_make) p = self.osutils.popen(invoke_make, stdout=self.osutils.pipe, stderr=self.osutils.pipe, cwd=cwd, env=env) # Create a stdout variable that will contain the final stitched stdout result stdout = "" # Create a buffer and use a thread to gather the stderr stream into the buffer stderr_buf = io.BytesIO() stderr_thread = threading.Thread(target=shutil.copyfileobj, args=(p.stderr, stderr_buf), daemon=True) stderr_thread.start() # Log every stdout line by iterating for line in p.stdout: decoded_line = line.decode("utf-8").strip() LOG.info(decoded_line) # Gather total stdout stdout += decoded_line # Wait for the process to exit and stderr thread to end. return_code = p.wait() stderr_thread.join() if return_code != 0: # Raise an Error with the appropriate value from the stderr buffer. raise MakeExecutionError(message=stderr_buf.getvalue().decode("utf8").strip()) return stdout