2011-02-05 Sheriff Bot <webkit.review.bot@gmail.com>
[WebKit.git] / Tools / Scripts / webkitpy / common / system / executive.py
1 # Copyright (c) 2009, Google Inc. All rights reserved.
2 # Copyright (c) 2009 Apple Inc. All rights reserved.
3 #
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are
6 # met:
7 #
8 #     * Redistributions of source code must retain the above copyright
9 # notice, this list of conditions and the following disclaimer.
10 #     * Redistributions in binary form must reproduce the above
11 # copyright notice, this list of conditions and the following disclaimer
12 # in the documentation and/or other materials provided with the
13 # distribution.
14 #     * Neither the name of Google Inc. nor the names of its
15 # contributors may be used to endorse or promote products derived from
16 # this software without specific prior written permission.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30 try:
31     # This API exists only in Python 2.6 and higher.  :(
32     import multiprocessing
33 except ImportError:
34     multiprocessing = None
35
36 import ctypes
37 import errno
38 import logging
39 import os
40 import platform
41 import StringIO
42 import signal
43 import subprocess
44 import sys
45 import time
46
47 from webkitpy.common.system.deprecated_logging import tee
48 from webkitpy.python24 import versioning
49
50
51 _log = logging.getLogger("webkitpy.common.system")
52
53
54 class ScriptError(Exception):
55
56     # This is a custom List.__str__ implementation to allow size limiting.
57     def _string_from_args(self, args, limit=100):
58         args_string = unicode(args)
59         # We could make this much fancier, but for now this is OK.
60         if len(args_string) > limit:
61             return args_string[:limit - 3] + "..."
62         return args_string
63
64     def __init__(self,
65                  message=None,
66                  script_args=None,
67                  exit_code=None,
68                  output=None,
69                  cwd=None):
70         if not message:
71             message = 'Failed to run "%s"' % self._string_from_args(script_args)
72             if exit_code:
73                 message += " exit_code: %d" % exit_code
74             if cwd:
75                 message += " cwd: %s" % cwd
76
77         Exception.__init__(self, message)
78         self.script_args = script_args # 'args' is already used by Exception
79         self.exit_code = exit_code
80         self.output = output
81         self.cwd = cwd
82
83     def message_with_output(self, output_limit=500):
84         if self.output:
85             if output_limit and len(self.output) > output_limit:
86                 return u"%s\n\nLast %s characters of output:\n%s" % \
87                     (self, output_limit, self.output[-output_limit:])
88             return u"%s\n\n%s" % (self, self.output)
89         return unicode(self)
90
91     def command_name(self):
92         command_path = self.script_args
93         if type(command_path) is list:
94             command_path = command_path[0]
95         return os.path.basename(command_path)
96
97
98 def run_command(*args, **kwargs):
99     # FIXME: This should not be a global static.
100     # New code should use Executive.run_command directly instead
101     return Executive().run_command(*args, **kwargs)
102
103
104 class Executive(object):
105
106     def _should_close_fds(self):
107         # We need to pass close_fds=True to work around Python bug #2320
108         # (otherwise we can hang when we kill DumpRenderTree when we are running
109         # multiple threads). See http://bugs.python.org/issue2320 .
110         # Note that close_fds isn't supported on Windows, but this bug only
111         # shows up on Mac and Linux.
112         return sys.platform not in ('win32', 'cygwin')
113
114     def _run_command_with_teed_output(self, args, teed_output):
115         args = map(unicode, args)  # Popen will throw an exception if args are non-strings (like int())
116         args = map(self._encode_argument_if_needed, args)
117
118         child_process = subprocess.Popen(args,
119                                          stdout=subprocess.PIPE,
120                                          stderr=subprocess.STDOUT,
121                                          close_fds=self._should_close_fds())
122
123         # Use our own custom wait loop because Popen ignores a tee'd
124         # stderr/stdout.
125         # FIXME: This could be improved not to flatten output to stdout.
126         while True:
127             output_line = child_process.stdout.readline()
128             if output_line == "" and child_process.poll() != None:
129                 # poll() is not threadsafe and can throw OSError due to:
130                 # http://bugs.python.org/issue1731717
131                 return child_process.poll()
132             # We assume that the child process wrote to us in utf-8,
133             # so no re-encoding is necessary before writing here.
134             teed_output.write(output_line)
135
136     # FIXME: Remove this deprecated method and move callers to run_command.
137     # FIXME: This method is a hack to allow running command which both
138     # capture their output and print out to stdin.  Useful for things
139     # like "build-webkit" where we want to display to the user that we're building
140     # but still have the output to stuff into a log file.
141     def run_and_throw_if_fail(self, args, quiet=False, decode_output=True):
142         # Cache the child's output locally so it can be used for error reports.
143         child_out_file = StringIO.StringIO()
144         tee_stdout = sys.stdout
145         if quiet:
146             dev_null = open(os.devnull, "w")  # FIXME: Does this need an encoding?
147             tee_stdout = dev_null
148         child_stdout = tee(child_out_file, tee_stdout)
149         exit_code = self._run_command_with_teed_output(args, child_stdout)
150         if quiet:
151             dev_null.close()
152
153         child_output = child_out_file.getvalue()
154         child_out_file.close()
155
156         if decode_output:
157             child_output = child_output.decode(self._child_process_encoding())
158
159         if exit_code:
160             raise ScriptError(script_args=args,
161                               exit_code=exit_code,
162                               output=child_output)
163         return child_output
164
165     def cpu_count(self):
166         if multiprocessing:
167             return multiprocessing.cpu_count()
168         # Darn.  We don't have the multiprocessing package.
169         system_name = platform.system()
170         if system_name == "Darwin":
171             return int(self.run_command(["sysctl", "-n", "hw.ncpu"]))
172         elif system_name == "Windows":
173             return int(os.environ.get('NUMBER_OF_PROCESSORS', 1))
174         elif system_name == "Linux":
175             num_cores = os.sysconf("SC_NPROCESSORS_ONLN")
176             if isinstance(num_cores, int) and num_cores > 0:
177                 return num_cores
178         # This quantity is a lie but probably a reasonable guess for modern
179         # machines.
180         return 2
181
182     def kill_process(self, pid):
183         """Attempts to kill the given pid.
184         Will fail silently if pid does not exist or insufficient permisssions."""
185         if sys.platform == "win32":
186             # We only use taskkill.exe on windows (not cygwin) because subprocess.pid
187             # is a CYGWIN pid and taskkill.exe expects a windows pid.
188             # Thankfully os.kill on CYGWIN handles either pid type.
189             command = ["taskkill.exe", "/f", "/pid", pid]
190             # taskkill will exit 128 if the process is not found.  We should log.
191             self.run_command(command, error_handler=self.ignore_error)
192             return
193
194         # According to http://docs.python.org/library/os.html
195         # os.kill isn't available on Windows. python 2.5.5 os.kill appears
196         # to work in cygwin, however it occasionally raises EAGAIN.
197         retries_left = 10 if sys.platform == "cygwin" else 1
198         while retries_left > 0:
199             try:
200                 retries_left -= 1
201                 os.kill(pid, signal.SIGKILL)
202             except OSError, e:
203                 if e.errno == errno.EAGAIN:
204                     if retries_left <= 0:
205                         _log.warn("Failed to kill pid %s.  Too many EAGAIN errors." % pid)
206                     continue
207                 if e.errno == errno.ESRCH:  # The process does not exist.
208                     _log.warn("Called kill_process with a non-existant pid %s" % pid)
209                     return
210                 raise
211
212     def _win32_check_running_pid(self, pid):
213
214         class PROCESSENTRY32(ctypes.Structure):
215             _fields_ = [("dwSize", ctypes.c_ulong),
216                         ("cntUsage", ctypes.c_ulong),
217                         ("th32ProcessID", ctypes.c_ulong),
218                         ("th32DefaultHeapID", ctypes.c_ulong),
219                         ("th32ModuleID", ctypes.c_ulong),
220                         ("cntThreads", ctypes.c_ulong),
221                         ("th32ParentProcessID", ctypes.c_ulong),
222                         ("pcPriClassBase", ctypes.c_ulong),
223                         ("dwFlags", ctypes.c_ulong),
224                         ("szExeFile", ctypes.c_char * 260)]
225
226         CreateToolhelp32Snapshot = ctypes.windll.kernel32.CreateToolhelp32Snapshot
227         Process32First = ctypes.windll.kernel32.Process32First
228         Process32Next = ctypes.windll.kernel32.Process32Next
229         CloseHandle = ctypes.windll.kernel32.CloseHandle
230         TH32CS_SNAPPROCESS = 0x00000002  # win32 magic number
231         hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
232         pe32 = PROCESSENTRY32()
233         pe32.dwSize = ctypes.sizeof(PROCESSENTRY32)
234         result = False
235         if not Process32First(hProcessSnap, ctypes.byref(pe32)):
236             _log.debug("Failed getting first process.")
237             CloseHandle(hProcessSnap)
238             return result
239         while True:
240             if pe32.th32ProcessID == pid:
241                 result = True
242                 break
243             if not Process32Next(hProcessSnap, ctypes.byref(pe32)):
244                 break
245         CloseHandle(hProcessSnap)
246         return result
247
248     def check_running_pid(self, pid):
249         """Return True if pid is alive, otherwise return False."""
250         if sys.platform in ('darwin', 'linux2', 'cygwin'):
251             try:
252                 os.kill(pid, 0)
253                 return True
254             except OSError:
255                 return False
256         elif sys.platform == 'win32':
257             return self._win32_check_running_pid(pid)
258
259         assert(False)
260
261     def _windows_image_name(self, process_name):
262         name, extension = os.path.splitext(process_name)
263         if not extension:
264             # taskkill expects processes to end in .exe
265             # If necessary we could add a flag to disable appending .exe.
266             process_name = "%s.exe" % name
267         return process_name
268
269     def kill_all(self, process_name):
270         """Attempts to kill processes matching process_name.
271         Will fail silently if no process are found."""
272         if sys.platform in ("win32", "cygwin"):
273             image_name = self._windows_image_name(process_name)
274             command = ["taskkill.exe", "/f", "/im", image_name]
275             # taskkill will exit 128 if the process is not found.  We should log.
276             self.run_command(command, error_handler=self.ignore_error)
277             return
278
279         # FIXME: This is inconsistent that kill_all uses TERM and kill_process
280         # uses KILL.  Windows is always using /f (which seems like -KILL).
281         # We should pick one mode, or add support for switching between them.
282         # Note: Mac OS X 10.6 requires -SIGNALNAME before -u USER
283         command = ["killall", "-TERM", "-u", os.getenv("USER"), process_name]
284         # killall returns 1 if no process can be found and 2 on command error.
285         # FIXME: We should pass a custom error_handler to allow only exit_code 1.
286         # We should log in exit_code == 1
287         self.run_command(command, error_handler=self.ignore_error)
288
289     # Error handlers do not need to be static methods once all callers are
290     # updated to use an Executive object.
291
292     @staticmethod
293     def default_error_handler(error):
294         raise error
295
296     @staticmethod
297     def ignore_error(error):
298         pass
299
300     def _compute_stdin(self, input):
301         """Returns (stdin, string_to_communicate)"""
302         # FIXME: We should be returning /dev/null for stdin
303         # or closing stdin after process creation to prevent
304         # child processes from getting input from the user.
305         if not input:
306             return (None, None)
307         if hasattr(input, "read"):  # Check if the input is a file.
308             return (input, None)  # Assume the file is in the right encoding.
309
310         # Popen in Python 2.5 and before does not automatically encode unicode objects.
311         # http://bugs.python.org/issue5290
312         # See https://bugs.webkit.org/show_bug.cgi?id=37528
313         # for an example of a regresion caused by passing a unicode string directly.
314         # FIXME: We may need to encode differently on different platforms.
315         if isinstance(input, unicode):
316             input = input.encode(self._child_process_encoding())
317         return (subprocess.PIPE, input)
318
319     def _command_for_printing(self, args):
320         """Returns a print-ready string representing command args.
321         The string should be copy/paste ready for execution in a shell."""
322         escaped_args = []
323         for arg in args:
324             if isinstance(arg, unicode):
325                 # Escape any non-ascii characters for easy copy/paste
326                 arg = arg.encode("unicode_escape")
327             # FIXME: Do we need to fix quotes here?
328             escaped_args.append(arg)
329         return " ".join(escaped_args)
330
331     # FIXME: run_and_throw_if_fail should be merged into this method.
332     def run_command(self,
333                     args,
334                     cwd=None,
335                     input=None,
336                     error_handler=None,
337                     return_exit_code=False,
338                     return_stderr=True,
339                     decode_output=True):
340         """Popen wrapper for convenience and to work around python bugs."""
341         assert(isinstance(args, list) or isinstance(args, tuple))
342         start_time = time.time()
343         args = map(unicode, args)  # Popen will throw an exception if args are non-strings (like int())
344         args = map(self._encode_argument_if_needed, args)
345
346         stdin, string_to_communicate = self._compute_stdin(input)
347         stderr = subprocess.STDOUT if return_stderr else None
348
349         process = subprocess.Popen(args,
350                                    stdin=stdin,
351                                    stdout=subprocess.PIPE,
352                                    stderr=stderr,
353                                    cwd=cwd,
354                                    close_fds=self._should_close_fds())
355         output = process.communicate(string_to_communicate)[0]
356
357         # run_command automatically decodes to unicode() unless explicitly told not to.
358         if decode_output:
359             output = output.decode(self._child_process_encoding())
360
361         # wait() is not threadsafe and can throw OSError due to:
362         # http://bugs.python.org/issue1731717
363         exit_code = process.wait()
364
365         _log.debug('"%s" took %.2fs' % (self._command_for_printing(args), time.time() - start_time))
366
367         if return_exit_code:
368             return exit_code
369
370         if exit_code:
371             script_error = ScriptError(script_args=args,
372                                        exit_code=exit_code,
373                                        output=output,
374                                        cwd=cwd)
375             (error_handler or self.default_error_handler)(script_error)
376         return output
377
378     def _child_process_encoding(self):
379         # Win32 Python 2.x uses CreateProcessA rather than CreateProcessW
380         # to launch subprocesses, so we have to encode arguments using the
381         # current code page.
382         if sys.platform == 'win32' and versioning.compare_version(sys, '3.0')[0] < 0:
383             return 'mbcs'
384         # All other platforms use UTF-8.
385         # FIXME: Using UTF-8 on Cygwin will confuse Windows-native commands
386         # which will expect arguments to be encoded using the current code
387         # page.
388         return 'utf-8'
389
390     def _should_encode_child_process_arguments(self):
391         # Cygwin's Python's os.execv doesn't support unicode command
392         # arguments, and neither does Cygwin's execv itself.
393         if sys.platform == 'cygwin':
394             return True
395
396         # Win32 Python 2.x uses CreateProcessA rather than CreateProcessW
397         # to launch subprocesses, so we have to encode arguments using the
398         # current code page.
399         if sys.platform == 'win32' and versioning.compare_version(sys, '3.0')[0] < 0:
400             return True
401
402         return False
403
404     def _encode_argument_if_needed(self, argument):
405         if not self._should_encode_child_process_arguments():
406             return argument
407         return argument.encode(self._child_process_encoding())