| OLD | NEW |
| 1 # coding=utf8 | 1 # coding=utf8 |
| 2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 """Collection of subprocess wrapper functions. | 5 """Collection of subprocess wrapper functions. |
| 6 | 6 |
| 7 In theory you shouldn't need anything else in subprocess, or this module failed. | 7 In theory you shouldn't need anything else in subprocess, or this module failed. |
| 8 """ | 8 """ |
| 9 | 9 |
| 10 from __future__ import with_statement | 10 from __future__ import with_statement |
| 11 import errno |
| 11 import logging | 12 import logging |
| 12 import os | 13 import os |
| 13 import subprocess | 14 import subprocess |
| 14 import sys | 15 import sys |
| 15 import tempfile | 16 import tempfile |
| 16 import time | 17 import time |
| 17 import threading | 18 import threading |
| 18 | 19 |
| 19 # Constants forwarded from subprocess. | 20 # Constants forwarded from subprocess. |
| 20 PIPE = subprocess.PIPE | 21 PIPE = subprocess.PIPE |
| 21 STDOUT = subprocess.STDOUT | 22 STDOUT = subprocess.STDOUT |
| 22 # Sends stdout or stderr to os.devnull. | 23 # Sends stdout or stderr to os.devnull. |
| 23 VOID = '/dev/null' | 24 VOID = '/dev/null' |
| 24 | 25 # Error code when a process was killed because it timed out. |
| 26 TIMED_OUT = -2001 |
| 25 | 27 |
| 26 # Globals. | 28 # Globals. |
| 27 # Set to True if you somehow need to disable this hack. | 29 # Set to True if you somehow need to disable this hack. |
| 28 SUBPROCESS_CLEANUP_HACKED = False | 30 SUBPROCESS_CLEANUP_HACKED = False |
| 29 | 31 |
| 30 | 32 |
| 31 class CalledProcessError(subprocess.CalledProcessError): | 33 class CalledProcessError(subprocess.CalledProcessError): |
| 32 """Augment the standard exception with more data.""" | 34 """Augment the standard exception with more data.""" |
| 33 def __init__(self, returncode, cmd, cwd, stdout, stderr): | 35 def __init__(self, returncode, cmd, cwd, stdout, stderr): |
| 34 super(CalledProcessError, self).__init__(returncode, cmd) | 36 super(CalledProcessError, self).__init__(returncode, cmd) |
| 35 self.stdout = stdout | 37 self.stdout = stdout |
| 36 self.stderr = stderr | 38 self.stderr = stderr |
| 37 self.cwd = cwd | 39 self.cwd = cwd |
| 38 | 40 |
| 39 def __str__(self): | 41 def __str__(self): |
| 40 out = 'Command %s returned non-zero exit status %s' % ( | 42 out = 'Command %s returned non-zero exit status %s' % ( |
| 41 ' '.join(self.cmd), self.returncode) | 43 ' '.join(self.cmd), self.returncode) |
| 42 if self.cwd: | 44 if self.cwd: |
| 43 out += ' in ' + self.cwd | 45 out += ' in ' + self.cwd |
| 44 return '\n'.join(filter(None, (out, self.stdout, self.stderr))) | 46 return '\n'.join(filter(None, (out, self.stdout, self.stderr))) |
| 45 | 47 |
| 46 | 48 |
| 49 class CygwinRebaseError(CalledProcessError): |
| 50 """Occurs when cygwin's fork() emulation fails due to rebased dll.""" |
| 51 |
| 52 |
| 47 ## Utility functions | 53 ## Utility functions |
| 48 | 54 |
| 49 | 55 |
| 50 def kill_pid(pid): | 56 def kill_pid(pid): |
| 51 """Kills a process by its process id.""" | 57 """Kills a process by its process id.""" |
| 52 try: | 58 try: |
| 53 # Unable to import 'module' | 59 # Unable to import 'module' |
| 54 # pylint: disable=E1101,F0401 | 60 # pylint: disable=E1101,F0401 |
| 55 import signal | 61 import signal |
| 56 return os.kill(pid, signal.SIGKILL) | 62 return os.kill(pid, signal.SIGKILL) |
| (...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 149 # *Sigh*: Windows needs shell=True, or else it won't search %PATH% for the | 155 # *Sigh*: Windows needs shell=True, or else it won't search %PATH% for the |
| 150 # executable, but shell=True makes subprocess on Linux fail when it's called | 156 # executable, but shell=True makes subprocess on Linux fail when it's called |
| 151 # with a list because it only tries to execute the first item in the list. | 157 # with a list because it only tries to execute the first item in the list. |
| 152 kwargs['shell'] = bool(sys.platform=='win32') | 158 kwargs['shell'] = bool(sys.platform=='win32') |
| 153 | 159 |
| 154 tmp_str = ' '.join(args) | 160 tmp_str = ' '.join(args) |
| 155 if kwargs.get('cwd', None): | 161 if kwargs.get('cwd', None): |
| 156 tmp_str += '; cwd=%s' % kwargs['cwd'] | 162 tmp_str += '; cwd=%s' % kwargs['cwd'] |
| 157 logging.debug(tmp_str) | 163 logging.debug(tmp_str) |
| 158 | 164 |
| 159 # Replaces VOID with handle to /dev/null. | 165 def fix(stream): |
| 160 if kwargs.get('stdout') in (VOID, os.devnull): | 166 if kwargs.get(stream) in (VOID, os.devnull): |
| 161 kwargs['stdout'] = open(os.devnull, 'w') | 167 # Replaces VOID with handle to /dev/null. |
| 162 if kwargs.get('stderr') in (VOID, os.devnull): | 168 # Create a temporary file to workaround python's deadlock. |
| 163 kwargs['stderr'] = open(os.devnull, 'w') | 169 # http://docs.python.org/library/subprocess.html#subprocess.Popen.wait |
| 164 return subprocess.Popen(args, **kwargs) | 170 # When the pipe fills up, it will deadlock this process. Using a real file |
| 171 # works around that issue. |
| 172 kwargs[stream] = open(os.devnull, 'w') |
| 173 |
| 174 fix('stdout') |
| 175 fix('stderr') |
| 176 |
| 177 try: |
| 178 return subprocess.Popen(args, **kwargs) |
| 179 except OSError, e: |
| 180 if e.errno == errno.EAGAIN and sys.platform == 'cygwin': |
| 181 # Convert fork() emulation failure into a CygwinRebaseError(). |
| 182 raise CygwinRebaseError( |
| 183 e.errno, |
| 184 args, |
| 185 kwargs.get('cwd'), |
| 186 None, |
| 187 'Visit ' |
| 188 'http://code.google.com/p/chromium/wiki/CygwinDllRemappingFailure to ' |
| 189 'learn how to fix this error; you need to rebase your cygwin dlls') |
| 190 # Popen() can throw OSError when cwd or args[0] doesn't exist. Let it go |
| 191 # through |
| 192 raise |
| 165 | 193 |
| 166 | 194 |
| 167 def call(args, timeout=None, **kwargs): | 195 def call(args, timeout=None, **kwargs): |
| 168 """Wraps subprocess.Popen().communicate(). | 196 """Wraps subprocess.Popen().communicate(). |
| 169 | 197 |
| 170 Returns ((stdout, stderr), returncode). | 198 Returns ((stdout, stderr), returncode). |
| 171 | 199 |
| 172 - The process will be kill with error code -9 after |timeout| seconds if set. | 200 - The process will be killed after |timeout| seconds and returncode set to |
| 201 TIMED_OUT. |
| 173 - Automatically passes stdin content as input so do not specify stdin=PIPE. | 202 - Automatically passes stdin content as input so do not specify stdin=PIPE. |
| 174 """ | 203 """ |
| 175 stdin = kwargs.pop('stdin', None) | 204 stdin = kwargs.pop('stdin', None) |
| 176 if stdin is not None: | 205 if stdin is not None: |
| 177 assert stdin != PIPE | 206 assert stdin != PIPE |
| 178 # When stdin is passed as an argument, use it as the actual input data and | 207 # When stdin is passed as an argument, use it as the actual input data and |
| 179 # set the Popen() parameter accordingly. | 208 # set the Popen() parameter accordingly. |
| 180 kwargs['stdin'] = PIPE | 209 kwargs['stdin'] = PIPE |
| 181 | 210 |
| 182 if not timeout: | 211 if not timeout: |
| 183 # Normal workflow. | 212 # Normal workflow. |
| 184 proc = Popen(args, **kwargs) | 213 proc = Popen(args, **kwargs) |
| 185 if stdin is not None: | 214 if stdin is not None: |
| 186 out = proc.communicate(stdin) | 215 return proc.communicate(stdin), proc.returncode |
| 187 else: | 216 else: |
| 188 out = proc.communicate() | 217 return proc.communicate(), proc.returncode |
| 189 else: | 218 |
| 190 # Create a temporary file to workaround python's deadlock. | 219 # Create a temporary file to workaround python's deadlock. |
| 191 # http://docs.python.org/library/subprocess.html#subprocess.Popen.wait | 220 # http://docs.python.org/library/subprocess.html#subprocess.Popen.wait |
| 192 # When the pipe fills up, it will deadlock this process. Using a real file | 221 # When the pipe fills up, it will deadlock this process. Using a real file |
| 193 # works around that issue. | 222 # works around that issue. |
| 194 with tempfile.TemporaryFile() as buff: | 223 with tempfile.TemporaryFile() as buff: |
| 195 start = time.time() | 224 start = time.time() |
| 196 kwargs['stdout'] = buff | 225 kwargs['stdout'] = buff |
| 197 proc = Popen(args, **kwargs) | 226 proc = Popen(args, **kwargs) |
| 198 if stdin is not None: | 227 if stdin is not None: |
| 199 proc.stdin.write(stdin) | 228 proc.stdin.write(stdin) |
| 200 while proc.returncode is None: | 229 while proc.returncode is None: |
| 201 proc.poll() | 230 proc.poll() |
| 202 if timeout and (time.time() - start) > timeout: | 231 if timeout and (time.time() - start) > timeout: |
| 203 proc.kill() | 232 proc.kill() |
| 204 proc.wait() | 233 proc.wait() |
| 205 # It's -9 on linux and 1 on Windows. Standardize to -9. | 234 # It's -9 on linux and 1 on Windows. Standardize to TIMED_OUT. |
| 206 # Do not throw an exception here, the user must use | 235 proc.returncode = TIMED_OUT |
| 207 # check_call(timeout=60) and check for e.returncode == -9 instead. | 236 time.sleep(0.001) |
| 208 # or look at call()[1] == -9. | 237 # Now that the process died, reset the cursor and read the file. |
| 209 proc.returncode = -9 | 238 buff.seek(0) |
| 210 time.sleep(0.001) | 239 out = [buff.read(), None] |
| 211 # Now that the process died, reset the cursor and read the file. | |
| 212 buff.seek(0) | |
| 213 out = [buff.read(), None] | |
| 214 return out, proc.returncode | 240 return out, proc.returncode |
| 215 | 241 |
| 216 | 242 |
| 217 def check_call(args, **kwargs): | 243 def check_call(args, **kwargs): |
| 218 """Improved version of subprocess.check_call(). | 244 """Improved version of subprocess.check_call(). |
| 219 | 245 |
| 220 Returns (stdout, stderr), unlike subprocess.check_call(). | 246 Returns (stdout, stderr), unlike subprocess.check_call(). |
| 221 """ | 247 """ |
| 222 out, returncode = call(args, **kwargs) | 248 out, returncode = call(args, **kwargs) |
| 223 if returncode: | 249 if returncode: |
| (...skipping 24 matching lines...) Expand all Loading... |
| 248 | 274 |
| 249 - Discards stderr. By default sets stderr=STDOUT. | 275 - Discards stderr. By default sets stderr=STDOUT. |
| 250 - Throws if return code is not 0. | 276 - Throws if return code is not 0. |
| 251 - Works even prior to python 2.7. | 277 - Works even prior to python 2.7. |
| 252 """ | 278 """ |
| 253 if kwargs.get('stdout') is None: | 279 if kwargs.get('stdout') is None: |
| 254 kwargs['stdout'] = PIPE | 280 kwargs['stdout'] = PIPE |
| 255 if kwargs.get('stderr') is None: | 281 if kwargs.get('stderr') is None: |
| 256 kwargs['stderr'] = STDOUT | 282 kwargs['stderr'] = STDOUT |
| 257 return check_call(args, **kwargs)[0] | 283 return check_call(args, **kwargs)[0] |
| OLD | NEW |