Clean up ChunkedUpdateDrawingAreaProxy
[WebKit-https.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     def __init__(self,
57                  message=None,
58                  script_args=None,
59                  exit_code=None,
60                  output=None,
61                  cwd=None):
62         if not message:
63             message = 'Failed to run "%s"' % script_args
64             if exit_code:
65                 message += " exit_code: %d" % exit_code
66             if cwd:
67                 message += " cwd: %s" % cwd
68
69         Exception.__init__(self, message)
70         self.script_args = script_args # 'args' is already used by Exception
71         self.exit_code = exit_code
72         self.output = output
73         self.cwd = cwd
74
75     def message_with_output(self, output_limit=500):
76         if self.output:
77             if output_limit and len(self.output) > output_limit:
78                 return u"%s\nLast %s characters of output:\n%s" % \
79                     (self, output_limit, self.output[-output_limit:])
80             return u"%s\n%s" % (self, self.output)
81         return unicode(self)
82
83     def command_name(self):
84         command_path = self.script_args
85         if type(command_path) is list:
86             command_path = command_path[0]
87         return os.path.basename(command_path)
88
89
90 def run_command(*args, **kwargs):
91     # FIXME: This should not be a global static.
92     # New code should use Executive.run_command directly instead
93     return Executive().run_command(*args, **kwargs)
94
95
96 class Executive(object):
97
98     def _should_close_fds(self):
99         # We need to pass close_fds=True to work around Python bug #2320
100         # (otherwise we can hang when we kill DumpRenderTree when we are running
101         # multiple threads). See http://bugs.python.org/issue2320 .
102         # Note that close_fds isn't supported on Windows, but this bug only
103         # shows up on Mac and Linux.
104         return sys.platform not in ('win32', 'cygwin')
105
106     def _run_command_with_teed_output(self, args, teed_output):
107         args = map(unicode, args)  # Popen will throw an exception if args are non-strings (like int())
108         args = map(self._encode_argument_if_needed, args)
109
110         child_process = subprocess.Popen(args,
111                                          stdout=subprocess.PIPE,
112                                          stderr=subprocess.STDOUT,
113                                          close_fds=self._should_close_fds())
114
115         # Use our own custom wait loop because Popen ignores a tee'd
116         # stderr/stdout.
117         # FIXME: This could be improved not to flatten output to stdout.
118         while True:
119             output_line = child_process.stdout.readline()
120             if output_line == "" and child_process.poll() != None:
121                 # poll() is not threadsafe and can throw OSError due to:
122                 # http://bugs.python.org/issue1731717
123                 return child_process.poll()
124             # We assume that the child process wrote to us in utf-8,
125             # so no re-encoding is necessary before writing here.
126             teed_output.write(output_line)
127
128     # FIXME: Remove this deprecated method and move callers to run_command.
129     # FIXME: This method is a hack to allow running command which both
130     # capture their output and print out to stdin.  Useful for things
131     # like "build-webkit" where we want to display to the user that we're building
132     # but still have the output to stuff into a log file.
133     def run_and_throw_if_fail(self, args, quiet=False, decode_output=True):
134         # Cache the child's output locally so it can be used for error reports.
135         child_out_file = StringIO.StringIO()
136         tee_stdout = sys.stdout
137         if quiet:
138             dev_null = open(os.devnull, "w")  # FIXME: Does this need an encoding?
139             tee_stdout = dev_null
140         child_stdout = tee(child_out_file, tee_stdout)
141         exit_code = self._run_command_with_teed_output(args, child_stdout)
142         if quiet:
143             dev_null.close()
144
145         child_output = child_out_file.getvalue()
146         child_out_file.close()
147
148         if decode_output:
149             child_output = child_output.decode(self._child_process_encoding())
150
151         if exit_code:
152             raise ScriptError(script_args=args,
153                               exit_code=exit_code,
154                               output=child_output)
155         return child_output
156
157     def cpu_count(self):
158         if multiprocessing:
159             return multiprocessing.cpu_count()
160         # Darn.  We don't have the multiprocessing package.
161         system_name = platform.system()
162         if system_name == "Darwin":
163             return int(self.run_command(["sysctl", "-n", "hw.ncpu"]))
164         elif system_name == "Windows":
165             return int(os.environ.get('NUMBER_OF_PROCESSORS', 1))
166         elif system_name == "Linux":
167             num_cores = os.sysconf("SC_NPROCESSORS_ONLN")
168             if isinstance(num_cores, int) and num_cores > 0:
169                 return num_cores
170         # This quantity is a lie but probably a reasonable guess for modern
171         # machines.
172         return 2
173
174     def kill_process(self, pid):
175         """Attempts to kill the given pid.
176         Will fail silently if pid does not exist or insufficient permisssions."""
177         if sys.platform == "win32":
178             # We only use taskkill.exe on windows (not cygwin) because subprocess.pid
179             # is a CYGWIN pid and taskkill.exe expects a windows pid.
180             # Thankfully os.kill on CYGWIN handles either pid type.
181             command = ["taskkill.exe", "/f", "/pid", pid]
182             # taskkill will exit 128 if the process is not found.  We should log.
183             self.run_command(command, error_handler=self.ignore_error)
184             return
185
186         # According to http://docs.python.org/library/os.html
187         # os.kill isn't available on Windows. python 2.5.5 os.kill appears
188         # to work in cygwin, however it occasionally raises EAGAIN.
189         retries_left = 10 if sys.platform == "cygwin" else 1
190         while retries_left > 0:
191             try:
192                 retries_left -= 1
193                 os.kill(pid, signal.SIGKILL)
194             except OSError, e:
195                 if e.errno == errno.EAGAIN:
196                     if retries_left <= 0:
197                         _log.warn("Failed to kill pid %s.  Too many EAGAIN errors." % pid)
198                     continue
199                 if e.errno == errno.ESRCH:  # The process does not exist.
200                     _log.warn("Called kill_process with a non-existant pid %s" % pid)
201                     return
202                 raise
203
204     def _win32_check_running_pid(self, pid):
205
206         class PROCESSENTRY32(ctypes.Structure):
207             _fields_ = [("dwSize", ctypes.c_ulong),
208                         ("cntUsage", ctypes.c_ulong),
209                         ("th32ProcessID", ctypes.c_ulong),
210                         ("th32DefaultHeapID", ctypes.c_ulong),
211                         ("th32ModuleID", ctypes.c_ulong),
212                         ("cntThreads", ctypes.c_ulong),
213                         ("th32ParentProcessID", ctypes.c_ulong),
214                         ("pcPriClassBase", ctypes.c_ulong),
215                         ("dwFlags", ctypes.c_ulong),
216                         ("szExeFile", ctypes.c_char * 260)]
217
218         CreateToolhelp32Snapshot = ctypes.windll.kernel32.CreateToolhelp32Snapshot
219         Process32First = ctypes.windll.kernel32.Process32First
220         Process32Next = ctypes.windll.kernel32.Process32Next
221         CloseHandle = ctypes.windll.kernel32.CloseHandle
222         TH32CS_SNAPPROCESS = 0x00000002  # win32 magic number
223         hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
224         pe32 = PROCESSENTRY32()
225         pe32.dwSize = ctypes.sizeof(PROCESSENTRY32)
226         result = False
227         if not Process32First(hProcessSnap, ctypes.byref(pe32)):
228             _log.debug("Failed getting first process.")
229             CloseHandle(hProcessSnap)
230             return result
231         while True:
232             if pe32.th32ProcessID == pid:
233                 result = True
234                 break
235             if not Process32Next(hProcessSnap, ctypes.byref(pe32)):
236                 break
237         CloseHandle(hProcessSnap)
238         return result
239
240     def check_running_pid(self, pid):
241         """Return True if pid is alive, otherwise return False."""
242         if sys.platform in ('darwin', 'linux2', 'cygwin'):
243             try:
244                 os.kill(pid, 0)
245                 return True
246             except OSError:
247                 return False
248         elif sys.platform == 'win32':
249             return self._win32_check_running_pid(pid)
250
251         assert(False)
252
253     def _windows_image_name(self, process_name):
254         name, extension = os.path.splitext(process_name)
255         if not extension:
256             # taskkill expects processes to end in .exe
257             # If necessary we could add a flag to disable appending .exe.
258             process_name = "%s.exe" % name
259         return process_name
260
261     def kill_all(self, process_name):
262         """Attempts to kill processes matching process_name.
263         Will fail silently if no process are found."""
264         if sys.platform in ("win32", "cygwin"):
265             image_name = self._windows_image_name(process_name)
266             command = ["taskkill.exe", "/f", "/im", image_name]
267             # taskkill will exit 128 if the process is not found.  We should log.
268             self.run_command(command, error_handler=self.ignore_error)
269             return
270
271         # FIXME: This is inconsistent that kill_all uses TERM and kill_process
272         # uses KILL.  Windows is always using /f (which seems like -KILL).
273         # We should pick one mode, or add support for switching between them.
274         # Note: Mac OS X 10.6 requires -SIGNALNAME before -u USER
275         command = ["killall", "-TERM", "-u", os.getenv("USER"), process_name]
276         # killall returns 1 if no process can be found and 2 on command error.
277         # FIXME: We should pass a custom error_handler to allow only exit_code 1.
278         # We should log in exit_code == 1
279         self.run_command(command, error_handler=self.ignore_error)
280
281     # Error handlers do not need to be static methods once all callers are
282     # updated to use an Executive object.
283
284     @staticmethod
285     def default_error_handler(error):
286         raise error
287
288     @staticmethod
289     def ignore_error(error):
290         pass
291
292     def _compute_stdin(self, input):
293         """Returns (stdin, string_to_communicate)"""
294         # FIXME: We should be returning /dev/null for stdin
295         # or closing stdin after process creation to prevent
296         # child processes from getting input from the user.
297         if not input:
298             return (None, None)
299         if hasattr(input, "read"):  # Check if the input is a file.
300             return (input, None)  # Assume the file is in the right encoding.
301
302         # Popen in Python 2.5 and before does not automatically encode unicode objects.
303         # http://bugs.python.org/issue5290
304         # See https://bugs.webkit.org/show_bug.cgi?id=37528
305         # for an example of a regresion caused by passing a unicode string directly.
306         # FIXME: We may need to encode differently on different platforms.
307         if isinstance(input, unicode):
308             input = input.encode(self._child_process_encoding())
309         return (subprocess.PIPE, input)
310
311     def _command_for_printing(self, args):
312         """Returns a print-ready string representing command args.
313         The string should be copy/paste ready for execution in a shell."""
314         escaped_args = []
315         for arg in args:
316             if isinstance(arg, unicode):
317                 # Escape any non-ascii characters for easy copy/paste
318                 arg = arg.encode("unicode_escape")
319             # FIXME: Do we need to fix quotes here?
320             escaped_args.append(arg)
321         return " ".join(escaped_args)
322
323     # FIXME: run_and_throw_if_fail should be merged into this method.
324     def run_command(self,
325                     args,
326                     cwd=None,
327                     input=None,
328                     error_handler=None,
329                     return_exit_code=False,
330                     return_stderr=True,
331                     decode_output=True):
332         """Popen wrapper for convenience and to work around python bugs."""
333         assert(isinstance(args, list) or isinstance(args, tuple))
334         start_time = time.time()
335         args = map(unicode, args)  # Popen will throw an exception if args are non-strings (like int())
336         args = map(self._encode_argument_if_needed, args)
337
338         stdin, string_to_communicate = self._compute_stdin(input)
339         stderr = subprocess.STDOUT if return_stderr else None
340
341         process = subprocess.Popen(args,
342                                    stdin=stdin,
343                                    stdout=subprocess.PIPE,
344                                    stderr=stderr,
345                                    cwd=cwd,
346                                    close_fds=self._should_close_fds())
347         output = process.communicate(string_to_communicate)[0]
348
349         # run_command automatically decodes to unicode() unless explicitly told not to.
350         if decode_output:
351             output = output.decode(self._child_process_encoding())
352
353         # wait() is not threadsafe and can throw OSError due to:
354         # http://bugs.python.org/issue1731717
355         exit_code = process.wait()
356
357         _log.debug('"%s" took %.2fs' % (self._command_for_printing(args), time.time() - start_time))
358
359         if return_exit_code:
360             return exit_code
361
362         if exit_code:
363             script_error = ScriptError(script_args=args,
364                                        exit_code=exit_code,
365                                        output=output,
366                                        cwd=cwd)
367             (error_handler or self.default_error_handler)(script_error)
368         return output
369
370     def _child_process_encoding(self):
371         # Win32 Python 2.x uses CreateProcessA rather than CreateProcessW
372         # to launch subprocesses, so we have to encode arguments using the
373         # current code page.
374         if sys.platform == 'win32' and versioning.compare_version(sys, '3.0')[0] < 0:
375             return 'mbcs'
376         # All other platforms use UTF-8.
377         # FIXME: Using UTF-8 on Cygwin will confuse Windows-native commands
378         # which will expect arguments to be encoded using the current code
379         # page.
380         return 'utf-8'
381
382     def _should_encode_child_process_arguments(self):
383         # Cygwin's Python's os.execv doesn't support unicode command
384         # arguments, and neither does Cygwin's execv itself.
385         if sys.platform == 'cygwin':
386             return True
387
388         # Win32 Python 2.x uses CreateProcessA rather than CreateProcessW
389         # to launch subprocesses, so we have to encode arguments using the
390         # current code page.
391         if sys.platform == 'win32' and versioning.compare_version(sys, '3.0')[0] < 0:
392             return True
393
394         return False
395
396     def _encode_argument_if_needed(self, argument):
397         if not self._should_encode_child_process_arguments():
398             return argument
399         return argument.encode(self._child_process_encoding())