diff options
Diffstat (limited to 'WebKitTools/Scripts/webkitpy/common/system/executive.py')
-rw-r--r-- | WebKitTools/Scripts/webkitpy/common/system/executive.py | 399 |
1 files changed, 0 insertions, 399 deletions
diff --git a/WebKitTools/Scripts/webkitpy/common/system/executive.py b/WebKitTools/Scripts/webkitpy/common/system/executive.py deleted file mode 100644 index 85a683a..0000000 --- a/WebKitTools/Scripts/webkitpy/common/system/executive.py +++ /dev/null @@ -1,399 +0,0 @@ -# Copyright (c) 2009, Google Inc. All rights reserved. -# Copyright (c) 2009 Apple Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -try: - # This API exists only in Python 2.6 and higher. :( - import multiprocessing -except ImportError: - multiprocessing = None - -import ctypes -import errno -import logging -import os -import platform -import StringIO -import signal -import subprocess -import sys -import time - -from webkitpy.common.system.deprecated_logging import tee -from webkitpy.python24 import versioning - - -_log = logging.getLogger("webkitpy.common.system") - - -class ScriptError(Exception): - - def __init__(self, - message=None, - script_args=None, - exit_code=None, - output=None, - cwd=None): - if not message: - message = 'Failed to run "%s"' % script_args - if exit_code: - message += " exit_code: %d" % exit_code - if cwd: - message += " cwd: %s" % cwd - - Exception.__init__(self, message) - self.script_args = script_args # 'args' is already used by Exception - self.exit_code = exit_code - self.output = output - self.cwd = cwd - - def message_with_output(self, output_limit=500): - if self.output: - if output_limit and len(self.output) > output_limit: - return u"%s\nLast %s characters of output:\n%s" % \ - (self, output_limit, self.output[-output_limit:]) - return u"%s\n%s" % (self, self.output) - return unicode(self) - - def command_name(self): - command_path = self.script_args - if type(command_path) is list: - command_path = command_path[0] - return os.path.basename(command_path) - - -def run_command(*args, **kwargs): - # FIXME: This should not be a global static. - # New code should use Executive.run_command directly instead - return Executive().run_command(*args, **kwargs) - - -class Executive(object): - - def _should_close_fds(self): - # We need to pass close_fds=True to work around Python bug #2320 - # (otherwise we can hang when we kill DumpRenderTree when we are running - # multiple threads). See http://bugs.python.org/issue2320 . - # Note that close_fds isn't supported on Windows, but this bug only - # shows up on Mac and Linux. - return sys.platform not in ('win32', 'cygwin') - - def _run_command_with_teed_output(self, args, teed_output): - args = map(unicode, args) # Popen will throw an exception if args are non-strings (like int()) - args = map(self._encode_argument_if_needed, args) - - child_process = subprocess.Popen(args, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - close_fds=self._should_close_fds()) - - # Use our own custom wait loop because Popen ignores a tee'd - # stderr/stdout. - # FIXME: This could be improved not to flatten output to stdout. - while True: - output_line = child_process.stdout.readline() - if output_line == "" and child_process.poll() != None: - # poll() is not threadsafe and can throw OSError due to: - # http://bugs.python.org/issue1731717 - return child_process.poll() - # We assume that the child process wrote to us in utf-8, - # so no re-encoding is necessary before writing here. - teed_output.write(output_line) - - # FIXME: Remove this deprecated method and move callers to run_command. - # FIXME: This method is a hack to allow running command which both - # capture their output and print out to stdin. Useful for things - # like "build-webkit" where we want to display to the user that we're building - # but still have the output to stuff into a log file. - def run_and_throw_if_fail(self, args, quiet=False, decode_output=True): - # Cache the child's output locally so it can be used for error reports. - child_out_file = StringIO.StringIO() - tee_stdout = sys.stdout - if quiet: - dev_null = open(os.devnull, "w") # FIXME: Does this need an encoding? - tee_stdout = dev_null - child_stdout = tee(child_out_file, tee_stdout) - exit_code = self._run_command_with_teed_output(args, child_stdout) - if quiet: - dev_null.close() - - child_output = child_out_file.getvalue() - child_out_file.close() - - if decode_output: - child_output = child_output.decode(self._child_process_encoding()) - - if exit_code: - raise ScriptError(script_args=args, - exit_code=exit_code, - output=child_output) - return child_output - - def cpu_count(self): - if multiprocessing: - return multiprocessing.cpu_count() - # Darn. We don't have the multiprocessing package. - system_name = platform.system() - if system_name == "Darwin": - return int(self.run_command(["sysctl", "-n", "hw.ncpu"])) - elif system_name == "Windows": - return int(os.environ.get('NUMBER_OF_PROCESSORS', 1)) - elif system_name == "Linux": - num_cores = os.sysconf("SC_NPROCESSORS_ONLN") - if isinstance(num_cores, int) and num_cores > 0: - return num_cores - # This quantity is a lie but probably a reasonable guess for modern - # machines. - return 2 - - def kill_process(self, pid): - """Attempts to kill the given pid. - Will fail silently if pid does not exist or insufficient permisssions.""" - if sys.platform == "win32": - # We only use taskkill.exe on windows (not cygwin) because subprocess.pid - # is a CYGWIN pid and taskkill.exe expects a windows pid. - # Thankfully os.kill on CYGWIN handles either pid type. - command = ["taskkill.exe", "/f", "/pid", pid] - # taskkill will exit 128 if the process is not found. We should log. - self.run_command(command, error_handler=self.ignore_error) - return - - # According to http://docs.python.org/library/os.html - # os.kill isn't available on Windows. python 2.5.5 os.kill appears - # to work in cygwin, however it occasionally raises EAGAIN. - retries_left = 10 if sys.platform == "cygwin" else 1 - while retries_left > 0: - try: - retries_left -= 1 - os.kill(pid, signal.SIGKILL) - except OSError, e: - if e.errno == errno.EAGAIN: - if retries_left <= 0: - _log.warn("Failed to kill pid %s. Too many EAGAIN errors." % pid) - continue - if e.errno == errno.ESRCH: # The process does not exist. - _log.warn("Called kill_process with a non-existant pid %s" % pid) - return - raise - - def _win32_check_running_pid(self, pid): - - class PROCESSENTRY32(ctypes.Structure): - _fields_ = [("dwSize", ctypes.c_ulong), - ("cntUsage", ctypes.c_ulong), - ("th32ProcessID", ctypes.c_ulong), - ("th32DefaultHeapID", ctypes.c_ulong), - ("th32ModuleID", ctypes.c_ulong), - ("cntThreads", ctypes.c_ulong), - ("th32ParentProcessID", ctypes.c_ulong), - ("pcPriClassBase", ctypes.c_ulong), - ("dwFlags", ctypes.c_ulong), - ("szExeFile", ctypes.c_char * 260)] - - CreateToolhelp32Snapshot = ctypes.windll.kernel32.CreateToolhelp32Snapshot - Process32First = ctypes.windll.kernel32.Process32First - Process32Next = ctypes.windll.kernel32.Process32Next - CloseHandle = ctypes.windll.kernel32.CloseHandle - TH32CS_SNAPPROCESS = 0x00000002 # win32 magic number - hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) - pe32 = PROCESSENTRY32() - pe32.dwSize = ctypes.sizeof(PROCESSENTRY32) - result = False - if not Process32First(hProcessSnap, ctypes.byref(pe32)): - _log.debug("Failed getting first process.") - CloseHandle(hProcessSnap) - return result - while True: - if pe32.th32ProcessID == pid: - result = True - break - if not Process32Next(hProcessSnap, ctypes.byref(pe32)): - break - CloseHandle(hProcessSnap) - return result - - def check_running_pid(self, pid): - """Return True if pid is alive, otherwise return False.""" - if sys.platform in ('darwin', 'linux2', 'cygwin'): - try: - os.kill(pid, 0) - return True - except OSError: - return False - elif sys.platform == 'win32': - return self._win32_check_running_pid(pid) - - assert(False) - - def _windows_image_name(self, process_name): - name, extension = os.path.splitext(process_name) - if not extension: - # taskkill expects processes to end in .exe - # If necessary we could add a flag to disable appending .exe. - process_name = "%s.exe" % name - return process_name - - def kill_all(self, process_name): - """Attempts to kill processes matching process_name. - Will fail silently if no process are found.""" - if sys.platform in ("win32", "cygwin"): - image_name = self._windows_image_name(process_name) - command = ["taskkill.exe", "/f", "/im", image_name] - # taskkill will exit 128 if the process is not found. We should log. - self.run_command(command, error_handler=self.ignore_error) - return - - # FIXME: This is inconsistent that kill_all uses TERM and kill_process - # uses KILL. Windows is always using /f (which seems like -KILL). - # We should pick one mode, or add support for switching between them. - # Note: Mac OS X 10.6 requires -SIGNALNAME before -u USER - command = ["killall", "-TERM", "-u", os.getenv("USER"), process_name] - # killall returns 1 if no process can be found and 2 on command error. - # FIXME: We should pass a custom error_handler to allow only exit_code 1. - # We should log in exit_code == 1 - self.run_command(command, error_handler=self.ignore_error) - - # Error handlers do not need to be static methods once all callers are - # updated to use an Executive object. - - @staticmethod - def default_error_handler(error): - raise error - - @staticmethod - def ignore_error(error): - pass - - def _compute_stdin(self, input): - """Returns (stdin, string_to_communicate)""" - # FIXME: We should be returning /dev/null for stdin - # or closing stdin after process creation to prevent - # child processes from getting input from the user. - if not input: - return (None, None) - if hasattr(input, "read"): # Check if the input is a file. - return (input, None) # Assume the file is in the right encoding. - - # Popen in Python 2.5 and before does not automatically encode unicode objects. - # http://bugs.python.org/issue5290 - # See https://bugs.webkit.org/show_bug.cgi?id=37528 - # for an example of a regresion caused by passing a unicode string directly. - # FIXME: We may need to encode differently on different platforms. - if isinstance(input, unicode): - input = input.encode(self._child_process_encoding()) - return (subprocess.PIPE, input) - - def _command_for_printing(self, args): - """Returns a print-ready string representing command args. - The string should be copy/paste ready for execution in a shell.""" - escaped_args = [] - for arg in args: - if isinstance(arg, unicode): - # Escape any non-ascii characters for easy copy/paste - arg = arg.encode("unicode_escape") - # FIXME: Do we need to fix quotes here? - escaped_args.append(arg) - return " ".join(escaped_args) - - # FIXME: run_and_throw_if_fail should be merged into this method. - def run_command(self, - args, - cwd=None, - input=None, - error_handler=None, - return_exit_code=False, - return_stderr=True, - decode_output=True): - """Popen wrapper for convenience and to work around python bugs.""" - assert(isinstance(args, list) or isinstance(args, tuple)) - start_time = time.time() - args = map(unicode, args) # Popen will throw an exception if args are non-strings (like int()) - args = map(self._encode_argument_if_needed, args) - - stdin, string_to_communicate = self._compute_stdin(input) - stderr = subprocess.STDOUT if return_stderr else None - - process = subprocess.Popen(args, - stdin=stdin, - stdout=subprocess.PIPE, - stderr=stderr, - cwd=cwd, - close_fds=self._should_close_fds()) - output = process.communicate(string_to_communicate)[0] - - # run_command automatically decodes to unicode() unless explicitly told not to. - if decode_output: - output = output.decode(self._child_process_encoding()) - - # wait() is not threadsafe and can throw OSError due to: - # http://bugs.python.org/issue1731717 - exit_code = process.wait() - - _log.debug('"%s" took %.2fs' % (self._command_for_printing(args), time.time() - start_time)) - - if return_exit_code: - return exit_code - - if exit_code: - script_error = ScriptError(script_args=args, - exit_code=exit_code, - output=output, - cwd=cwd) - (error_handler or self.default_error_handler)(script_error) - return output - - def _child_process_encoding(self): - # Win32 Python 2.x uses CreateProcessA rather than CreateProcessW - # to launch subprocesses, so we have to encode arguments using the - # current code page. - if sys.platform == 'win32' and versioning.compare_version(sys, '3.0')[0] < 0: - return 'mbcs' - # All other platforms use UTF-8. - # FIXME: Using UTF-8 on Cygwin will confuse Windows-native commands - # which will expect arguments to be encoded using the current code - # page. - return 'utf-8' - - def _should_encode_child_process_arguments(self): - # Cygwin's Python's os.execv doesn't support unicode command - # arguments, and neither does Cygwin's execv itself. - if sys.platform == 'cygwin': - return True - - # Win32 Python 2.x uses CreateProcessA rather than CreateProcessW - # to launch subprocesses, so we have to encode arguments using the - # current code page. - if sys.platform == 'win32' and versioning.compare_version(sys, '3.0')[0] < 0: - return True - - return False - - def _encode_argument_if_needed(self, argument): - if not self._should_encode_child_process_arguments(): - return argument - return argument.encode(self._child_process_encoding()) |