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 |