aws_lambda_builders/workflows/custom_make/make.py (41 lines of code) (raw):
"""
Wrapper around calling make through a subprocess.
"""
import io
import logging
import shutil
import sys
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:
# Writing to stderr instead of using LOG.info
# since the logger library does not include ANSI
# formatting characters in the output
#
# stderr is used since stdout appears to be reserved
# for command responses
sys.stderr.buffer.write(line)
sys.stderr.flush()
# Gather total stdout
decoded_line = line.decode("utf-8").strip()
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