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 logging | 11 import logging |
12 import os | 12 import os |
13 import subprocess | 13 import subprocess |
14 import sys | 14 import sys |
15 import tempfile | 15 import tempfile |
16 import time | 16 import time |
17 import threading | 17 import threading |
18 | 18 |
19 # Constants forwarded from subprocess. | 19 # Constants forwarded from subprocess. |
20 PIPE = subprocess.PIPE | 20 PIPE = subprocess.PIPE |
21 STDOUT = subprocess.STDOUT | 21 STDOUT = subprocess.STDOUT |
| 22 # Sends stdout or stderr to os.devnull. |
| 23 VOID = '/dev/null' |
| 24 |
22 | 25 |
23 # Globals. | 26 # Globals. |
24 # Set to True if you somehow need to disable this hack. | 27 # Set to True if you somehow need to disable this hack. |
25 SUBPROCESS_CLEANUP_HACKED = False | 28 SUBPROCESS_CLEANUP_HACKED = False |
26 | 29 |
27 | 30 |
28 class CalledProcessError(subprocess.CalledProcessError): | 31 class CalledProcessError(subprocess.CalledProcessError): |
29 """Augment the standard exception with more data.""" | 32 """Augment the standard exception with more data.""" |
30 def __init__(self, returncode, cmd, cwd, stdout, stderr): | 33 def __init__(self, returncode, cmd, cwd, stdout, stderr): |
31 super(CalledProcessError, self).__init__(returncode, cmd) | 34 super(CalledProcessError, self).__init__(returncode, cmd) |
(...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
116 if not is_english(name): | 119 if not is_english(name): |
117 env[name] = 'en_US.UTF-8' | 120 env[name] = 'en_US.UTF-8' |
118 fix_lang('LANG') | 121 fix_lang('LANG') |
119 fix_lang('LANGUAGE') | 122 fix_lang('LANGUAGE') |
120 return env | 123 return env |
121 | 124 |
122 | 125 |
123 def Popen(args, **kwargs): | 126 def Popen(args, **kwargs): |
124 """Wraps subprocess.Popen(). | 127 """Wraps subprocess.Popen(). |
125 | 128 |
126 Forces English output since it's easier to parse the stdout if it is always in | 129 Returns a subprocess.Popen object. |
127 English. | |
128 | 130 |
129 Sets shell=True on windows by default. You can override this by forcing shell | 131 - Forces English output since it's easier to parse the stdout if it is always |
130 parameter to a value. | 132 in English. |
| 133 - Sets shell=True on windows by default. You can override this by forcing |
| 134 shell parameter to a value. |
| 135 - Adds support for VOID to not buffer when not needed. |
131 | 136 |
132 Popen() can throw OSError when cwd or args[0] doesn't exist. | 137 Note: Popen() can throw OSError when cwd or args[0] doesn't exist. |
133 """ | 138 """ |
134 # Make sure we hack subprocess if necessary. | 139 # Make sure we hack subprocess if necessary. |
135 hack_subprocess() | 140 hack_subprocess() |
136 add_kill() | 141 add_kill() |
137 | 142 |
138 env = get_english_env(kwargs.get('env')) | 143 env = get_english_env(kwargs.get('env')) |
139 if env: | 144 if env: |
140 kwargs['env'] = env | 145 kwargs['env'] = env |
141 | 146 |
142 if not kwargs.get('shell') is None: | 147 if not kwargs.get('shell') is None: |
143 # *Sigh*: Windows needs shell=True, or else it won't search %PATH% for the | 148 # *Sigh*: Windows needs shell=True, or else it won't search %PATH% for the |
144 # executable, but shell=True makes subprocess on Linux fail when it's called | 149 # executable, but shell=True makes subprocess on Linux fail when it's called |
145 # with a list because it only tries to execute the first item in the list. | 150 # with a list because it only tries to execute the first item in the list. |
146 kwargs['shell'] = (sys.platform=='win32') | 151 kwargs['shell'] = (sys.platform=='win32') |
147 | 152 |
148 tmp_str = ' '.join(args) | 153 tmp_str = ' '.join(args) |
149 if kwargs.get('cwd', None): | 154 if kwargs.get('cwd', None): |
150 tmp_str += '; cwd=%s' % kwargs['cwd'] | 155 tmp_str += '; cwd=%s' % kwargs['cwd'] |
151 logging.debug(tmp_str) | 156 logging.debug(tmp_str) |
| 157 |
| 158 # Replaces VOID with handle to /dev/null. |
| 159 if kwargs.get('stdout') in (VOID, os.devnull): |
| 160 kwargs['stdout'] = open(os.devnull, 'w') |
| 161 if kwargs.get('stderr') in (VOID, os.devnull): |
| 162 kwargs['stderr'] = open(os.devnull, 'w') |
152 return subprocess.Popen(args, **kwargs) | 163 return subprocess.Popen(args, **kwargs) |
153 | 164 |
154 | 165 |
155 def call(args, timeout=None, **kwargs): | 166 def call(args, timeout=None, **kwargs): |
156 """Wraps subprocess.Popen().communicate(). | 167 """Wraps subprocess.Popen().communicate(). |
157 | 168 |
158 The process will be kill with error code -9 after |timeout| seconds if set. | 169 Returns ((stdout, stderr), returncode). |
159 | 170 |
160 Automatically passes stdin content as input so do not specify stdin=PIPE. | 171 - The process will be kill with error code -9 after |timeout| seconds if set. |
161 | 172 - Automatically passes stdin content as input so do not specify stdin=PIPE. |
162 Returns both communicate() tuple and return code wrapped in a tuple. | |
163 """ | 173 """ |
164 stdin = kwargs.pop('stdin', None) | 174 stdin = kwargs.pop('stdin', None) |
165 if stdin is not None: | 175 if stdin is not None: |
166 assert stdin != PIPE | 176 assert stdin != PIPE |
167 # When stdin is passed as an argument, use it as the actual input data and | 177 # When stdin is passed as an argument, use it as the actual input data and |
168 # set the Popen() parameter accordingly. | 178 # set the Popen() parameter accordingly. |
169 kwargs['stdin'] = PIPE | 179 kwargs['stdin'] = PIPE |
170 | 180 |
171 if not timeout: | 181 if not timeout: |
172 # Normal workflow. | 182 # Normal workflow. |
(...skipping 24 matching lines...) Expand all Loading... |
197 # or look at call()[1] == -9. | 207 # or look at call()[1] == -9. |
198 proc.returncode = -9 | 208 proc.returncode = -9 |
199 time.sleep(0.001) | 209 time.sleep(0.001) |
200 # Now that the process died, reset the cursor and read the file. | 210 # Now that the process died, reset the cursor and read the file. |
201 buff.seek(0) | 211 buff.seek(0) |
202 out = [buff.read(), None] | 212 out = [buff.read(), None] |
203 return out, proc.returncode | 213 return out, proc.returncode |
204 | 214 |
205 | 215 |
206 def check_call(args, **kwargs): | 216 def check_call(args, **kwargs): |
207 """Similar to subprocess.check_call() but use call() instead. | 217 """Improved version of subprocess.check_call(). |
208 | 218 |
209 This permits to include more details in CalledProcessError(). | 219 Returns (stdout, stderr), unlike subprocess.check_call(). |
210 | |
211 Runs a command and throws an exception if the command failed. | |
212 | |
213 Returns communicate() tuple. | |
214 """ | 220 """ |
215 out, returncode = call(args, **kwargs) | 221 out, returncode = call(args, **kwargs) |
216 if returncode: | 222 if returncode: |
217 raise CalledProcessError( | 223 raise CalledProcessError( |
218 returncode, args, kwargs.get('cwd'), out[0], out[1]) | 224 returncode, args, kwargs.get('cwd'), out[0], out[1]) |
219 return out | 225 return out |
220 | 226 |
221 | 227 |
222 def capture(args, **kwargs): | 228 def capture(args, **kwargs): |
223 """Captures stdout of a process call and returns it. | 229 """Captures stdout of a process call and returns it. |
224 | 230 |
225 Similar to check_output() excepts that it discards return code. | 231 Returns stdout. |
226 | 232 |
227 Discards communicate()[1]. By default sets stderr=STDOUT. | 233 - Discards returncode. |
| 234 - Discards stderr. By default sets stderr=STDOUT. |
228 """ | 235 """ |
229 if kwargs.get('stderr') is None: | 236 if kwargs.get('stderr') is None: |
230 kwargs['stderr'] = STDOUT | 237 kwargs['stderr'] = STDOUT |
231 return call(args, stdout=PIPE, **kwargs)[0][0] | 238 return call(args, stdout=PIPE, **kwargs)[0][0] |
232 | 239 |
233 | 240 |
234 def check_output(args, **kwargs): | 241 def check_output(args, **kwargs): |
235 """Captures stdout of a process call and returns it. | 242 """Captures stdout of a process call and returns it. |
236 | 243 |
237 Discards communicate()[1]. By default sets stderr=STDOUT. | 244 Returns stdout. |
238 | 245 |
239 Throws if return code is not 0. | 246 - Discards stderr. By default sets stderr=STDOUT. |
240 | 247 - Throws if return code is not 0. |
241 Works even prior to python 2.7. | 248 - Works even prior to python 2.7. |
242 """ | 249 """ |
243 if kwargs.get('stderr') is None: | 250 if kwargs.get('stderr') is None: |
244 kwargs['stderr'] = STDOUT | 251 kwargs['stderr'] = STDOUT |
245 return check_call(args, stdout=PIPE, **kwargs)[0] | 252 return check_call(args, stdout=PIPE, **kwargs)[0] |
OLD | NEW |