Index: build/android/pylib/cmd_helper.py |
diff --git a/build/android/pylib/cmd_helper.py b/build/android/pylib/cmd_helper.py |
index e3abab74ffda3087eb239cddd850bbb874383161..f8815531a56922b9db6734bef01392f7c2bea90f 100644 |
--- a/build/android/pylib/cmd_helper.py |
+++ b/build/android/pylib/cmd_helper.py |
@@ -115,14 +115,20 @@ def GetCmdOutput(args, cwd=None, shell=False): |
return output |
-def _LogCommand(args, cwd=None): |
- if not isinstance(args, basestring): |
+def _ValidateAndLogCommand(args, cwd, shell): |
+ if isinstance(args, basestring): |
+ if not shell: |
+ raise Exception('string args must be run with shell=True') |
+ else: |
+ if shell: |
+ raise Exception('array args must be run with shell=False') |
args = ' '.join(SingleQuote(c) for c in args) |
if cwd is None: |
cwd = '' |
else: |
cwd = ':' + cwd |
logging.info('[host]%s> %s', cwd, args) |
+ return args |
def GetCmdStatusAndOutput(args, cwd=None, shell=False): |
@@ -133,18 +139,13 @@ def GetCmdStatusAndOutput(args, cwd=None, shell=False): |
the string or the first item in the args sequence. |
cwd: If not None, the subprocess's current directory will be changed to |
|cwd| before it's executed. |
- shell: Whether to execute args as a shell command. |
+ shell: Whether to execute args as a shell command. Must be True if args |
+ is a string and False if args is a sequence. |
Returns: |
The 2-tuple (exit code, output). |
""" |
- if isinstance(args, basestring): |
- if not shell: |
- raise Exception('string args must be run with shell=True') |
- elif shell: |
- raise Exception('array args must be run with shell=False') |
- |
- _LogCommand(args, cwd) |
+ _ValidateAndLogCommand(args, cwd, shell) |
pipe = Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, |
shell=shell, cwd=cwd) |
stdout, stderr = pipe.communicate() |
@@ -162,44 +163,16 @@ class TimeoutError(Exception): |
pass |
-def GetCmdStatusAndOutputWithTimeout(args, timeout, cwd=None, shell=False, |
- logfile=None): |
- """Executes a subprocess with a timeout. |
- |
- Args: |
- args: List of arguments to the program, the program to execute is the first |
- element. |
- timeout: the timeout in seconds or None to wait forever. |
- cwd: If not None, the subprocess's current directory will be changed to |
- |cwd| before it's executed. |
- shell: Whether to execute args as a shell command. |
- logfile: Optional file-like object that will receive output from the |
- command as it is running. |
- |
- Returns: |
- The 2-tuple (exit code, output). |
- """ |
+def _IterProcessStdout(process, timeout=None, buffer_size=4096, |
+ poll_interval=1): |
assert fcntl, 'fcntl module is required' |
- if isinstance(args, basestring): |
- if not shell: |
- raise Exception('string args must be run with shell=True') |
- elif shell: |
- raise Exception('array args must be run with shell=False') |
- |
- _LogCommand(args, cwd) |
- process = Popen(args, cwd=cwd, shell=shell, stdout=subprocess.PIPE, |
- stderr=subprocess.STDOUT) |
try: |
- end_time = (time.time() + timeout) if timeout else None |
- poll_interval = 1 |
- buffer_size = 4096 |
- child_fd = process.stdout.fileno() |
- output = StringIO.StringIO() |
- |
# Enable non-blocking reads from the child's stdout. |
+ child_fd = process.stdout.fileno() |
fl = fcntl.fcntl(child_fd, fcntl.F_GETFL) |
fcntl.fcntl(child_fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) |
+ end_time = (time.time() + timeout) if timeout else None |
while True: |
if end_time and time.time() > end_time: |
raise TimeoutError |
@@ -208,9 +181,7 @@ def GetCmdStatusAndOutputWithTimeout(args, timeout, cwd=None, shell=False, |
data = os.read(child_fd, buffer_size) |
if not data: |
break |
- if logfile: |
- logfile.write(data) |
- output.write(data) |
+ yield data |
if process.poll() is not None: |
break |
finally: |
@@ -221,4 +192,70 @@ def GetCmdStatusAndOutputWithTimeout(args, timeout, cwd=None, shell=False, |
except OSError: |
pass |
process.wait() |
+ |
+ |
+def GetCmdStatusAndOutputWithTimeout(args, timeout, cwd=None, shell=False, |
+ logfile=None): |
+ """Executes a subprocess with a timeout. |
+ |
+ Args: |
+ args: List of arguments to the program, the program to execute is the first |
+ element. |
+ timeout: the timeout in seconds or None to wait forever. |
+ cwd: If not None, the subprocess's current directory will be changed to |
+ |cwd| before it's executed. |
+ shell: Whether to execute args as a shell command. Must be True if args |
+ is a string and False if args is a sequence. |
+ logfile: Optional file-like object that will receive output from the |
+ command as it is running. |
+ |
+ Returns: |
+ The 2-tuple (exit code, output). |
+ """ |
+ _ValidateAndLogCommand(args, cwd, shell) |
+ output = StringIO.StringIO() |
+ process = Popen(args, cwd=cwd, shell=shell, stdout=subprocess.PIPE, |
+ stderr=subprocess.STDOUT) |
+ for data in _IterProcessStdout(process, timeout=timeout): |
+ if logfile: |
+ logfile.write(data) |
+ output.write(data) |
return process.returncode, output.getvalue() |
+ |
+ |
+def IterCmdOutputLines(args, timeout=None, cwd=None, shell=False, |
+ check_status=True): |
+ """Executes a subprocess and continuously yields lines from its output. |
+ |
+ Args: |
+ args: List of arguments to the program, the program to execute is the first |
+ element. |
+ cwd: If not None, the subprocess's current directory will be changed to |
+ |cwd| before it's executed. |
+ shell: Whether to execute args as a shell command. Must be True if args |
+ is a string and False if args is a sequence. |
+ check_status: A boolean indicating whether to check the exit status of the |
+ process after all output has been read. |
+ |
+ Yields: |
+ The output of the subprocess, line by line. |
+ |
+ Raises: |
+ CalledProcessError if check_status is True and the process exited with a |
+ non-zero exit status. |
+ """ |
+ cmd = _ValidateAndLogCommand(args, cwd, shell) |
+ process = Popen(args, cwd=cwd, shell=shell, stdout=subprocess.PIPE, |
+ stderr=subprocess.STDOUT) |
+ buffer_output = '' |
+ for data in _IterProcessStdout(process, timeout=timeout): |
+ buffer_output += data |
+ has_incomplete_line = buffer_output[-1] not in '\r\n' |
+ lines = buffer_output.splitlines() |
+ buffer_output = lines.pop() if has_incomplete_line else '' |
+ for line in lines: |
+ yield line |
+ if buffer_output: |
+ yield buffer_output |
+ if check_status and process.returncode: |
+ raise subprocess.CalledProcessError(process.returncode, cmd) |