Index: build/android/pylib/cmd_helper.py |
diff --git a/build/android/pylib/cmd_helper.py b/build/android/pylib/cmd_helper.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..f8815531a56922b9db6734bef01392f7c2bea90f |
--- /dev/null |
+++ b/build/android/pylib/cmd_helper.py |
@@ -0,0 +1,261 @@ |
+# Copyright (c) 2012 The Chromium Authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+ |
+"""A wrapper for subprocess to make calling shell commands easier.""" |
+ |
+import logging |
+import os |
+import pipes |
+import select |
+import signal |
+import string |
+import StringIO |
+import subprocess |
+import time |
+ |
+# fcntl is not available on Windows. |
+try: |
+ import fcntl |
+except ImportError: |
+ fcntl = None |
+ |
+_SafeShellChars = frozenset(string.ascii_letters + string.digits + '@%_-+=:,./') |
+ |
+def SingleQuote(s): |
+ """Return an shell-escaped version of the string using single quotes. |
+ |
+ Reliably quote a string which may contain unsafe characters (e.g. space, |
+ quote, or other special characters such as '$'). |
+ |
+ The returned value can be used in a shell command line as one token that gets |
+ to be interpreted literally. |
+ |
+ Args: |
+ s: The string to quote. |
+ |
+ Return: |
+ The string quoted using single quotes. |
+ """ |
+ return pipes.quote(s) |
+ |
+def DoubleQuote(s): |
+ """Return an shell-escaped version of the string using double quotes. |
+ |
+ Reliably quote a string which may contain unsafe characters (e.g. space |
+ or quote characters), while retaining some shell features such as variable |
+ interpolation. |
+ |
+ The returned value can be used in a shell command line as one token that gets |
+ to be further interpreted by the shell. |
+ |
+ The set of characters that retain their special meaning may depend on the |
+ shell implementation. This set usually includes: '$', '`', '\', '!', '*', |
+ and '@'. |
+ |
+ Args: |
+ s: The string to quote. |
+ |
+ Return: |
+ The string quoted using double quotes. |
+ """ |
+ if not s: |
+ return '""' |
+ elif all(c in _SafeShellChars for c in s): |
+ return s |
+ else: |
+ return '"' + s.replace('"', '\\"') + '"' |
+ |
+ |
+def Popen(args, stdout=None, stderr=None, shell=None, cwd=None, env=None): |
+ return subprocess.Popen( |
+ args=args, cwd=cwd, stdout=stdout, stderr=stderr, |
+ shell=shell, close_fds=True, env=env, |
+ preexec_fn=lambda: signal.signal(signal.SIGPIPE, signal.SIG_DFL)) |
+ |
+ |
+def Call(args, stdout=None, stderr=None, shell=None, cwd=None, env=None): |
+ pipe = Popen(args, stdout=stdout, stderr=stderr, shell=shell, cwd=cwd, |
+ env=env) |
+ pipe.communicate() |
+ return pipe.wait() |
+ |
+ |
+def RunCmd(args, cwd=None): |
+ """Opens a subprocess to execute a program and returns its return value. |
+ |
+ Args: |
+ args: A string or a sequence of program arguments. The program to execute is |
+ 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. |
+ |
+ Returns: |
+ Return code from the command execution. |
+ """ |
+ logging.info(str(args) + ' ' + (cwd or '')) |
+ return Call(args, cwd=cwd) |
+ |
+ |
+def GetCmdOutput(args, cwd=None, shell=False): |
+ """Open a subprocess to execute a program and returns its output. |
+ |
+ Args: |
+ args: A string or a sequence of program arguments. The program to execute is |
+ 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. |
+ |
+ Returns: |
+ Captures and returns the command's stdout. |
+ Prints the command's stderr to logger (which defaults to stdout). |
+ """ |
+ (_, output) = GetCmdStatusAndOutput(args, cwd, shell) |
+ return output |
+ |
+ |
+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): |
+ """Executes a subprocess and returns its exit code and output. |
+ |
+ Args: |
+ args: A string or a sequence of program arguments. The program to execute is |
+ 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. Must be True if args |
+ is a string and False if args is a sequence. |
+ |
+ Returns: |
+ The 2-tuple (exit code, output). |
+ """ |
+ _ValidateAndLogCommand(args, cwd, shell) |
+ pipe = Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, |
+ shell=shell, cwd=cwd) |
+ stdout, stderr = pipe.communicate() |
+ |
+ if stderr: |
+ logging.critical(stderr) |
+ if len(stdout) > 4096: |
+ logging.debug('Truncated output:') |
+ logging.debug(stdout[:4096]) |
+ return (pipe.returncode, stdout) |
+ |
+ |
+class TimeoutError(Exception): |
+ """Module-specific timeout exception.""" |
+ pass |
+ |
+ |
+def _IterProcessStdout(process, timeout=None, buffer_size=4096, |
+ poll_interval=1): |
+ assert fcntl, 'fcntl module is required' |
+ try: |
+ # 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 |
+ read_fds, _, _ = select.select([child_fd], [], [], poll_interval) |
+ if child_fd in read_fds: |
+ data = os.read(child_fd, buffer_size) |
+ if not data: |
+ break |
+ yield data |
+ if process.poll() is not None: |
+ break |
+ finally: |
+ try: |
+ # Make sure the process doesn't stick around if we fail with an |
+ # exception. |
+ process.kill() |
+ 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) |