blob: c2f393a990004224f5329a03a74ca5bfd4f7ac86 [file] [log] [blame]
maruel@chromium.org4860f052011-03-25 20:34:38 +00001# coding=utf8
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
4# found in the LICENSE file.
5"""Collection of subprocess wrapper functions.
6
7In theory you shouldn't need anything else in subprocess, or this module failed.
8"""
9
maruel@chromium.org45d8db02011-03-31 20:43:56 +000010from __future__ import with_statement
maruel@chromium.org1d9f6292011-04-07 14:15:36 +000011import errno
maruel@chromium.org4860f052011-03-25 20:34:38 +000012import logging
13import os
14import subprocess
15import sys
maruel@chromium.org7eda8622011-11-10 02:23:43 +000016import tempfile
maruel@chromium.org4860f052011-03-25 20:34:38 +000017import time
18import threading
19
maruel@chromium.orga8e81632011-12-01 00:35:24 +000020
maruel@chromium.org4860f052011-03-25 20:34:38 +000021# Constants forwarded from subprocess.
22PIPE = subprocess.PIPE
23STDOUT = subprocess.STDOUT
maruel@chromium.org421982f2011-04-01 17:38:06 +000024# Sends stdout or stderr to os.devnull.
maruel@chromium.org0d5ef242011-04-18 13:52:58 +000025VOID = object()
maruel@chromium.org1d9f6292011-04-07 14:15:36 +000026# Error code when a process was killed because it timed out.
27TIMED_OUT = -2001
maruel@chromium.org4860f052011-03-25 20:34:38 +000028
29# Globals.
30# Set to True if you somehow need to disable this hack.
31SUBPROCESS_CLEANUP_HACKED = False
32
33
34class CalledProcessError(subprocess.CalledProcessError):
35 """Augment the standard exception with more data."""
36 def __init__(self, returncode, cmd, cwd, stdout, stderr):
37 super(CalledProcessError, self).__init__(returncode, cmd)
38 self.stdout = stdout
39 self.stderr = stderr
40 self.cwd = cwd
41
42 def __str__(self):
43 out = 'Command %s returned non-zero exit status %s' % (
44 ' '.join(self.cmd), self.returncode)
45 if self.cwd:
46 out += ' in ' + self.cwd
47 return '\n'.join(filter(None, (out, self.stdout, self.stderr)))
48
49
maruel@chromium.org1d9f6292011-04-07 14:15:36 +000050class CygwinRebaseError(CalledProcessError):
51 """Occurs when cygwin's fork() emulation fails due to rebased dll."""
52
53
maruel@chromium.orgfb3d3242011-04-01 14:03:08 +000054## Utility functions
55
56
57def kill_pid(pid):
58 """Kills a process by its process id."""
59 try:
60 # Unable to import 'module'
maruel@chromium.orgc98c0c52011-04-06 13:39:43 +000061 # pylint: disable=E1101,F0401
maruel@chromium.orgfb3d3242011-04-01 14:03:08 +000062 import signal
63 return os.kill(pid, signal.SIGKILL)
64 except ImportError:
65 pass
66
67
68def kill_win(process):
69 """Kills a process with its windows handle.
70
71 Has no effect on other platforms.
72 """
73 try:
74 # Unable to import 'module'
75 # pylint: disable=F0401
76 import win32process
77 # Access to a protected member _handle of a client class
78 # pylint: disable=W0212
79 return win32process.TerminateProcess(process._handle, -1)
80 except ImportError:
81 pass
82
83
84def add_kill():
85 """Adds kill() method to subprocess.Popen for python <2.6"""
86 if hasattr(subprocess.Popen, 'kill'):
87 return
88
89 if sys.platform == 'win32':
90 subprocess.Popen.kill = kill_win
91 else:
92 subprocess.Popen.kill = lambda process: kill_pid(process.pid)
93
94
maruel@chromium.org4860f052011-03-25 20:34:38 +000095def hack_subprocess():
96 """subprocess functions may throw exceptions when used in multiple threads.
97
98 See http://bugs.python.org/issue1731717 for more information.
99 """
100 global SUBPROCESS_CLEANUP_HACKED
101 if not SUBPROCESS_CLEANUP_HACKED and threading.activeCount() != 1:
102 # Only hack if there is ever multiple threads.
103 # There is no point to leak with only one thread.
104 subprocess._cleanup = lambda: None
105 SUBPROCESS_CLEANUP_HACKED = True
106
107
108def get_english_env(env):
109 """Forces LANG and/or LANGUAGE to be English.
110
111 Forces encoding to utf-8 for subprocesses.
112
113 Returns None if it is unnecessary.
114 """
maruel@chromium.orgc98c0c52011-04-06 13:39:43 +0000115 if sys.platform == 'win32':
116 return None
maruel@chromium.org4860f052011-03-25 20:34:38 +0000117 env = env or os.environ
118
119 # Test if it is necessary at all.
120 is_english = lambda name: env.get(name, 'en').startswith('en')
121
122 if is_english('LANG') and is_english('LANGUAGE'):
123 return None
124
125 # Requires modifications.
126 env = env.copy()
127 def fix_lang(name):
128 if not is_english(name):
129 env[name] = 'en_US.UTF-8'
130 fix_lang('LANG')
131 fix_lang('LANGUAGE')
132 return env
133
134
maruel@google.comef77f9e2011-11-24 15:24:02 +0000135class Popen(subprocess.Popen):
maruel@chromium.org57bf78d2011-09-08 18:57:33 +0000136 """Wraps subprocess.Popen() with various workarounds.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000137
maruel@chromium.org421982f2011-04-01 17:38:06 +0000138 - Forces English output since it's easier to parse the stdout if it is always
139 in English.
140 - Sets shell=True on windows by default. You can override this by forcing
141 shell parameter to a value.
142 - Adds support for VOID to not buffer when not needed.
maruel@chromium.orgdd9837f2011-11-30 01:55:22 +0000143 - Adds self.start property.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000144
maruel@chromium.org57bf78d2011-09-08 18:57:33 +0000145 Note: Popen() can throw OSError when cwd or args[0] doesn't exist. Translate
146 exceptions generated by cygwin when it fails trying to emulate fork().
maruel@chromium.org4860f052011-03-25 20:34:38 +0000147 """
maruel@google.comef77f9e2011-11-24 15:24:02 +0000148 def __init__(self, args, **kwargs):
149 # Make sure we hack subprocess if necessary.
150 hack_subprocess()
151 add_kill()
maruel@chromium.org4860f052011-03-25 20:34:38 +0000152
maruel@google.comef77f9e2011-11-24 15:24:02 +0000153 env = get_english_env(kwargs.get('env'))
154 if env:
155 kwargs['env'] = env
156 if kwargs.get('shell') is None:
157 # *Sigh*: Windows needs shell=True, or else it won't search %PATH% for
158 # the executable, but shell=True makes subprocess on Linux fail when it's
159 # called with a list because it only tries to execute the first item in
160 # the list.
161 kwargs['shell'] = bool(sys.platform=='win32')
maruel@chromium.org4860f052011-03-25 20:34:38 +0000162
maruel@google.comef77f9e2011-11-24 15:24:02 +0000163 if isinstance(args, basestring):
164 tmp_str = args
165 elif isinstance(args, (list, tuple)):
166 tmp_str = ' '.join(args)
167 else:
168 raise CalledProcessError(None, args, kwargs.get('cwd'), None, None)
169 if kwargs.get('cwd', None):
170 tmp_str += '; cwd=%s' % kwargs['cwd']
171 logging.debug(tmp_str)
maruel@chromium.org421982f2011-04-01 17:38:06 +0000172
maruel@google.comef77f9e2011-11-24 15:24:02 +0000173 def fix(stream):
174 if kwargs.get(stream) in (VOID, os.devnull):
175 # Replaces VOID with handle to /dev/null.
176 # Create a temporary file to workaround python's deadlock.
177 # http://docs.python.org/library/subprocess.html#subprocess.Popen.wait
178 # When the pipe fills up, it will deadlock this process. Using a real
179 # file works around that issue.
180 kwargs[stream] = open(os.devnull, 'w')
maruel@chromium.org1d9f6292011-04-07 14:15:36 +0000181
maruel@google.comef77f9e2011-11-24 15:24:02 +0000182 fix('stdout')
183 fix('stderr')
maruel@chromium.org1d9f6292011-04-07 14:15:36 +0000184
maruel@chromium.orgdd9837f2011-11-30 01:55:22 +0000185 self.start = time.time()
maruel@chromium.orga8e81632011-12-01 00:35:24 +0000186 self.shell = kwargs.get('shell', None)
maruel@chromium.org14e37ad2011-11-30 20:26:16 +0000187 # Silence pylint on MacOSX
188 self.returncode = None
maruel@chromium.orga8e81632011-12-01 00:35:24 +0000189
maruel@google.comef77f9e2011-11-24 15:24:02 +0000190 try:
191 super(Popen, self).__init__(args, **kwargs)
192 except OSError, e:
193 if e.errno == errno.EAGAIN and sys.platform == 'cygwin':
194 # Convert fork() emulation failure into a CygwinRebaseError().
195 raise CygwinRebaseError(
196 e.errno,
197 args,
198 kwargs.get('cwd'),
199 None,
200 'Visit '
201 'http://code.google.com/p/chromium/wiki/CygwinDllRemappingFailure '
202 'to learn how to fix this error; you need to rebase your cygwin '
203 'dlls')
204 # Popen() can throw OSError when cwd or args[0] doesn't exist. Let it go
205 # through
206 raise
maruel@chromium.org4860f052011-03-25 20:34:38 +0000207
208
maruel@chromium.org1f063db2011-04-18 19:04:52 +0000209def communicate(args, timeout=None, **kwargs):
maruel@chromium.orgdd9837f2011-11-30 01:55:22 +0000210 """Wraps subprocess.Popen().communicate() and add timeout support.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000211
maruel@chromium.org421982f2011-04-01 17:38:06 +0000212 Returns ((stdout, stderr), returncode).
maruel@chromium.org4860f052011-03-25 20:34:38 +0000213
maruel@chromium.org1d9f6292011-04-07 14:15:36 +0000214 - The process will be killed after |timeout| seconds and returncode set to
215 TIMED_OUT.
maruel@chromium.org421982f2011-04-01 17:38:06 +0000216 - Automatically passes stdin content as input so do not specify stdin=PIPE.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000217 """
218 stdin = kwargs.pop('stdin', None)
219 if stdin is not None:
maruel@chromium.org0d5ef242011-04-18 13:52:58 +0000220 if stdin is VOID:
221 kwargs['stdin'] = open(os.devnull, 'r')
222 stdin = None
223 else:
maruel@chromium.org39f645f2011-04-21 00:07:53 +0000224 assert isinstance(stdin, basestring)
maruel@chromium.org0d5ef242011-04-18 13:52:58 +0000225 # When stdin is passed as an argument, use it as the actual input data and
226 # set the Popen() parameter accordingly.
227 kwargs['stdin'] = PIPE
maruel@chromium.org4860f052011-03-25 20:34:38 +0000228
maruel@chromium.org7eda8622011-11-10 02:23:43 +0000229 if not timeout:
maruel@chromium.org4860f052011-03-25 20:34:38 +0000230 # Normal workflow.
maruel@chromium.org7eda8622011-11-10 02:23:43 +0000231 proc = Popen(args, **kwargs)
232 if stdin is not None:
maruel@chromium.org1d9f6292011-04-07 14:15:36 +0000233 return proc.communicate(stdin), proc.returncode
maruel@chromium.org4860f052011-03-25 20:34:38 +0000234 else:
maruel@chromium.org1d9f6292011-04-07 14:15:36 +0000235 return proc.communicate(), proc.returncode
236
maruel@chromium.org7eda8622011-11-10 02:23:43 +0000237 # Create a temporary file to workaround python's deadlock.
maruel@chromium.org1d9f6292011-04-07 14:15:36 +0000238 # http://docs.python.org/library/subprocess.html#subprocess.Popen.wait
maruel@chromium.org7eda8622011-11-10 02:23:43 +0000239 # When the pipe fills up, it will deadlock this process. Using a real file
240 # works around that issue.
241 with tempfile.TemporaryFile() as buff:
maruel@chromium.org7eda8622011-11-10 02:23:43 +0000242 kwargs['stdout'] = buff
243 proc = Popen(args, **kwargs)
maruel@chromium.orga8e81632011-12-01 00:35:24 +0000244 if proc.shell:
245 raise TypeError(
246 'Using timeout and shell simultaneously will cause a process leak '
247 'since the shell will be killed instead of the child process.')
maruel@chromium.org7eda8622011-11-10 02:23:43 +0000248 if stdin is not None:
249 proc.stdin.write(stdin)
250 while proc.returncode is None:
251 proc.poll()
maruel@chromium.orgdd9837f2011-11-30 01:55:22 +0000252 if timeout and (time.time() - proc.start) > timeout:
maruel@chromium.org7eda8622011-11-10 02:23:43 +0000253 proc.kill()
254 proc.wait()
255 # It's -9 on linux and 1 on Windows. Standardize to TIMED_OUT.
256 proc.returncode = TIMED_OUT
257 time.sleep(0.001)
258 # Now that the process died, reset the cursor and read the file.
259 buff.seek(0)
maruel@chromium.org4942e4a2011-11-15 15:50:50 +0000260 out = (buff.read(), None)
maruel@chromium.org7eda8622011-11-10 02:23:43 +0000261 return out, proc.returncode
maruel@chromium.org4860f052011-03-25 20:34:38 +0000262
263
maruel@chromium.org1f063db2011-04-18 19:04:52 +0000264def call(args, **kwargs):
265 """Emulates subprocess.call().
266
267 Automatically convert stdout=PIPE or stderr=PIPE to VOID.
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000268 In no case they can be returned since no code path raises
269 subprocess2.CalledProcessError.
maruel@chromium.org1f063db2011-04-18 19:04:52 +0000270 """
271 if kwargs.get('stdout') == PIPE:
272 kwargs['stdout'] = VOID
273 if kwargs.get('stderr') == PIPE:
274 kwargs['stderr'] = VOID
275 return communicate(args, **kwargs)[1]
276
277
maruel@chromium.org0bcd1d32011-04-26 15:55:49 +0000278def check_call_out(args, **kwargs):
maruel@chromium.org421982f2011-04-01 17:38:06 +0000279 """Improved version of subprocess.check_call().
maruel@chromium.org4860f052011-03-25 20:34:38 +0000280
maruel@chromium.org421982f2011-04-01 17:38:06 +0000281 Returns (stdout, stderr), unlike subprocess.check_call().
maruel@chromium.org4860f052011-03-25 20:34:38 +0000282 """
maruel@chromium.org1f063db2011-04-18 19:04:52 +0000283 out, returncode = communicate(args, **kwargs)
maruel@chromium.org4860f052011-03-25 20:34:38 +0000284 if returncode:
285 raise CalledProcessError(
286 returncode, args, kwargs.get('cwd'), out[0], out[1])
287 return out
288
289
maruel@chromium.org0bcd1d32011-04-26 15:55:49 +0000290def check_call(args, **kwargs):
291 """Emulate subprocess.check_call()."""
292 check_call_out(args, **kwargs)
293 return 0
294
295
maruel@chromium.org4860f052011-03-25 20:34:38 +0000296def capture(args, **kwargs):
297 """Captures stdout of a process call and returns it.
298
maruel@chromium.org421982f2011-04-01 17:38:06 +0000299 Returns stdout.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000300
maruel@chromium.org421982f2011-04-01 17:38:06 +0000301 - Discards returncode.
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000302 - Blocks stdin by default if not specified since no output will be visible.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000303 """
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000304 kwargs.setdefault('stdin', VOID)
305
306 # Like check_output, deny the caller from using stdout arg.
307 return communicate(args, stdout=PIPE, **kwargs)[0][0]
maruel@chromium.org4860f052011-03-25 20:34:38 +0000308
309
310def check_output(args, **kwargs):
maruel@chromium.org0bcd1d32011-04-26 15:55:49 +0000311 """Emulates subprocess.check_output().
maruel@chromium.org4860f052011-03-25 20:34:38 +0000312
maruel@chromium.org0bcd1d32011-04-26 15:55:49 +0000313 Captures stdout of a process call and returns stdout only.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000314
maruel@chromium.org421982f2011-04-01 17:38:06 +0000315 - Throws if return code is not 0.
316 - Works even prior to python 2.7.
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000317 - Blocks stdin by default if not specified since no output will be visible.
318 - As per doc, "The stdout argument is not allowed as it is used internally."
maruel@chromium.org4860f052011-03-25 20:34:38 +0000319 """
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000320 kwargs.setdefault('stdin', VOID)
maruel@chromium.orgdb59bfc2011-11-30 14:03:14 +0000321 if 'stdout' in kwargs:
322 raise ValueError('stdout argument not allowed, it will be overridden.')
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000323 return check_call_out(args, stdout=PIPE, **kwargs)[0]