-
Notifications
You must be signed in to change notification settings - Fork 1k
/
Copy pathrunner.py
123 lines (102 loc) · 5.55 KB
/
runner.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import io
import subprocess
import sys
from subprocess import PIPE, Popen, STDOUT
import six
from conans.client.tools import environment_append
from conans.errors import ConanException
from conans.util.files import decode_text
from conans.util.runners import pyinstaller_bundle_env_cleaned
class _UnbufferedWrite(object):
def __init__(self, stream):
self._stream = stream._stream if hasattr(stream, "_stream") else stream
def write(self, *args, **kwargs):
self._stream.write(*args, **kwargs)
self._stream.flush()
class ConanRunner(object):
def __init__(self, print_commands_to_output=False, generate_run_log_file=False,
log_run_to_output=True, output=None):
self._print_commands_to_output = print_commands_to_output
self._generate_run_log_file = generate_run_log_file
self._log_run_to_output = log_run_to_output
self._output = output
def __call__(self, command, output=True, log_filepath=None, cwd=None, subprocess=False):
"""
@param command: Command to execute
@param output: Instead of print to sys.stdout print to that stream. Could be None
@param log_filepath: If specified, also log to a file
@param cwd: Move to directory to execute
"""
if output and isinstance(output, io.StringIO) and six.PY2:
# in py2 writing to a StringIO requires unicode, otherwise it fails
print("*** WARN: Invalid output parameter of type io.StringIO(), "
"use six.StringIO() instead ***")
user_output = output if output and hasattr(output, "write") else None
stream_output = user_output or self._output or sys.stdout
if hasattr(stream_output, "flush"):
# We do not want output from different streams to get mixed (sys.stdout, os.system)
stream_output = _UnbufferedWrite(stream_output)
if not self._generate_run_log_file:
log_filepath = None
# Log the command call in output and logger
call_message = "\n----Running------\n> %s\n-----------------\n" % (command,)
if self._print_commands_to_output and stream_output and self._log_run_to_output:
stream_output.write(call_message)
with pyinstaller_bundle_env_cleaned():
# Remove credentials before running external application
with environment_append({'CONAN_LOGIN_ENCRYPTION_KEY': None}):
# No output has to be redirected to logs or buffer or omitted
if (output is True and not self._output and not log_filepath and self._log_run_to_output
and not subprocess):
return self._simple_os_call(command, cwd)
elif log_filepath:
if stream_output:
stream_output.write("Logging command output to file '%s'\n" % (log_filepath,))
with open(log_filepath, "a+") as log_handler:
if self._print_commands_to_output:
log_handler.write(call_message)
return self._pipe_os_call(command, stream_output, log_handler, cwd, user_output)
else:
return self._pipe_os_call(command, stream_output, None, cwd, user_output)
def _pipe_os_call(self, command, stream_output, log_handler, cwd, user_output):
try:
# piping both stdout, stderr and then later only reading one will hang the process
# if the other fills the pip. So piping stdout, and redirecting stderr to stdout,
# so both are merged and use just a single get_stream_lines() call
capture_output = log_handler or not self._log_run_to_output or user_output \
or (stream_output and isinstance(stream_output._stream, six.StringIO))
if capture_output:
proc = Popen(command, shell=isinstance(command, six.string_types), stdout=PIPE,
stderr=STDOUT, cwd=cwd)
else:
proc = Popen(command, shell=isinstance(command, six.string_types), cwd=cwd)
except Exception as e:
raise ConanException("Error while executing '%s'\n\t%s" % (command, str(e)))
def get_stream_lines(the_stream):
while True:
line = the_stream.readline()
if not line:
break
decoded_line = decode_text(line)
if stream_output and self._log_run_to_output:
try:
stream_output.write(decoded_line)
except UnicodeEncodeError: # be aggressive on text encoding
decoded_line = decoded_line.encode("latin-1", "ignore").decode("latin-1",
"ignore")
stream_output.write(decoded_line)
if log_handler:
# Write decoded in PY2 causes some ASCII encoding problems
# tried to open the log_handler binary but same result.
log_handler.write(line if six.PY2 else decoded_line)
if capture_output:
get_stream_lines(proc.stdout)
proc.communicate()
ret = proc.returncode
return ret
@staticmethod
def _simple_os_call(command, cwd):
try:
return subprocess.call(command, cwd=cwd, shell=isinstance(command, six.string_types))
except Exception as e:
raise ConanException("Error while executing '%s'\n\t%s" % (command, str(e)))