ayon-core/openpype/lib/execute.py

177 lines
4.9 KiB
Python

import os
import shlex
import subprocess
import platform
from .log import PypeLogger as Logger
# MSDN process creation flag (Windows only)
CREATE_NO_WINDOW = 0x08000000
def execute(args,
silent=False,
cwd=None,
env=None,
shell=None):
"""Execute command as process.
This will execute given command as process, monitor its output
and log it appropriately.
.. seealso::
:mod:`subprocess` module in Python.
Args:
args (list): list of arguments passed to process.
silent (bool): control output of executed process.
cwd (str): current working directory for process.
env (dict): environment variables for process.
shell (bool): use shell to execute, default is no.
Returns:
int: return code of process
"""
log_levels = ['DEBUG:', 'INFO:', 'ERROR:', 'WARNING:', 'CRITICAL:']
log = Logger().get_logger('execute')
log.info("Executing ({})".format(" ".join(args)))
popen = subprocess.Popen(
args,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
universal_newlines=True,
bufsize=1,
cwd=cwd,
env=env or os.environ,
shell=shell
)
# Blocks until finished
while True:
line = popen.stdout.readline()
if line == '':
break
if silent:
continue
line_test = False
for test_string in log_levels:
if line.startswith(test_string):
line_test = True
break
if not line_test:
print(line[:-1])
log.info("Execution is finishing up ...")
popen.wait()
return popen.returncode
def run_subprocess(*args, **kwargs):
"""Convenience method for getting output errors for subprocess.
Output logged when process finish.
Entered arguments and keyword arguments are passed to subprocess Popen.
Args:
*args: Variable length arument list passed to Popen.
**kwargs : Arbitary keyword arguments passed to Popen. Is possible to
pass `logging.Logger` object under "logger" if want to use
different than lib's logger.
Returns:
str: Full output of subprocess concatenated stdout and stderr.
Raises:
RuntimeError: Exception is raised if process finished with nonzero
return code.
"""
# Get environents from kwarg or use current process environments if were
# not passed.
env = kwargs.get("env") or os.environ
# Make sure environment contains only strings
filtered_env = {str(k): str(v) for k, v in env.items()}
# Use lib's logger if was not passed with kwargs.
logger = kwargs.pop("logger", None)
if logger is None:
logger = Logger.get_logger("run_subprocess")
# set overrides
kwargs['stdout'] = kwargs.get('stdout', subprocess.PIPE)
kwargs['stderr'] = kwargs.get('stderr', subprocess.PIPE)
kwargs['stdin'] = kwargs.get('stdin', subprocess.PIPE)
kwargs['env'] = filtered_env
proc = subprocess.Popen(*args, **kwargs)
full_output = ""
_stdout, _stderr = proc.communicate()
if _stdout:
_stdout = _stdout.decode("utf-8")
full_output += _stdout
logger.debug(_stdout)
if _stderr:
_stderr = _stderr.decode("utf-8")
# Add additional line break if output already containt stdout
if full_output:
full_output += "\n"
full_output += _stderr
logger.warning(_stderr)
if proc.returncode != 0:
exc_msg = "Executing arguments was not successful: \"{}\"".format(args)
if _stdout:
exc_msg += "\n\nOutput:\n{}".format(_stdout)
if _stderr:
exc_msg += "Error:\n{}".format(_stderr)
raise RuntimeError(exc_msg)
return full_output
def path_to_subprocess_arg(path):
"""Prepare path for subprocess arguments.
Returned path can be wrapped with quotes or kept as is.
"""
return subprocess.list2cmdline([path])
def get_pype_execute_args(*args):
"""Arguments to run pype command.
Arguments for subprocess when need to spawn new pype process. Which may be
needed when new python process for pype scripts must be executed in build
pype.
## Why is this needed?
Pype executed from code has different executable set to virtual env python
and must have path to script as first argument which is not needed for
build pype.
It is possible to pass any arguments that will be added after pype
executables.
"""
pype_executable = os.environ["OPENPYPE_EXECUTABLE"]
pype_args = [pype_executable]
executable_filename = os.path.basename(pype_executable)
if "python" in executable_filename.lower():
pype_args.append(
os.path.join(os.environ["OPENPYPE_ROOT"], "start.py")
)
if args:
pype_args.extend(args)
return pype_args