Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(139)

Side by Side Diff: subprocess2.py

Issue 6711080: Copy subprocess2 from commit-queue so it can be reused here. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: Created 9 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 # 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
7 In theory you shouldn't need anything else in subprocess, or this module failed.
8 """
9
10 import logging
11 import os
12 import subprocess
13 import sys
14 import tempfile
15 import time
16 import threading
17
18 # Constants forwarded from subprocess.
19 PIPE = subprocess.PIPE
20 STDOUT = subprocess.STDOUT
21
22 # Globals.
23 # Set to True if you somehow need to disable this hack.
24 SUBPROCESS_CLEANUP_HACKED = False
25
26
27 class CalledProcessError(subprocess.CalledProcessError):
28 """Augment the standard exception with more data."""
29 def __init__(self, returncode, cmd, cwd, stdout, stderr):
30 super(CalledProcessError, self).__init__(returncode, cmd)
31 self.stdout = stdout
32 self.stderr = stderr
33 self.cwd = cwd
34
35 def __str__(self):
36 out = 'Command %s returned non-zero exit status %s' % (
37 ' '.join(self.cmd), self.returncode)
38 if self.cwd:
39 out += ' in ' + self.cwd
40 return '\n'.join(filter(None, (out, self.stdout, self.stderr)))
41
42
43 def hack_subprocess():
44 """subprocess functions may throw exceptions when used in multiple threads.
45
46 See http://bugs.python.org/issue1731717 for more information.
47 """
48 global SUBPROCESS_CLEANUP_HACKED
49 if not SUBPROCESS_CLEANUP_HACKED and threading.activeCount() != 1:
50 # Only hack if there is ever multiple threads.
51 # There is no point to leak with only one thread.
52 subprocess._cleanup = lambda: None
53 SUBPROCESS_CLEANUP_HACKED = True
54
55
56 def get_english_env(env):
57 """Forces LANG and/or LANGUAGE to be English.
58
59 Forces encoding to utf-8 for subprocesses.
60
61 Returns None if it is unnecessary.
62 """
63 env = env or os.environ
64
65 # Test if it is necessary at all.
66 is_english = lambda name: env.get(name, 'en').startswith('en')
67
68 if is_english('LANG') and is_english('LANGUAGE'):
69 return None
70
71 # Requires modifications.
72 env = env.copy()
73 def fix_lang(name):
74 if not is_english(name):
75 env[name] = 'en_US.UTF-8'
76 fix_lang('LANG')
77 fix_lang('LANGUAGE')
78 return env
79
80
81 def Popen(args, **kwargs):
82 """Wraps subprocess.Popen().
83
84 Forces English output since it's easier to parse the stdout if it is always in
85 English.
86
87 Sets shell=True on windows by default. You can override this by forcing shell
88 parameter to a value.
89
90 Popen() can throw OSError when cwd or args[0] doesn't exist.
91 """
92 # Make sure we hack subprocess if necessary.
93 hack_subprocess()
94
95 env = get_english_env(kwargs.get('env'))
96 if env:
97 kwargs['env'] = env
98
99 if not kwargs.get('shell') is None:
100 # *Sigh*: Windows needs shell=True, or else it won't search %PATH% for the
101 # executable, but shell=True makes subprocess on Linux fail when it's called
102 # with a list because it only tries to execute the first item in the list.
103 kwargs['shell'] = (sys.platform=='win32')
104
105 tmp_str = ' '.join(args)
106 if kwargs.get('cwd', None):
107 tmp_str += '; cwd=%s' % kwargs['cwd']
108 logging.debug(tmp_str)
109 return subprocess.Popen(args, **kwargs)
110
111
112 def call(args, timeout=None, **kwargs):
113 """Wraps subprocess.Popen().communicate().
114
115 The process will be kill with error code -9 after |timeout| seconds if set.
116
117 Automatically passes stdin content as input so do not specify stdin=PIPE.
118
119 Returns both communicate() tuple and return code wrapped in a tuple.
120 """
121 stdin = kwargs.pop('stdin', None)
122 if stdin is not None:
123 assert stdin != PIPE
124 # When stdin is passed as an argument, use it as the actual input data and
125 # set the Popen() parameter accordingly.
126 kwargs['stdin'] = PIPE
127
128 if not timeout:
129 # Normal workflow.
130 proc = Popen(args, **kwargs)
131 if stdin is not None:
132 out = proc.communicate(stdin)
133 else:
134 out = proc.communicate()
135 else:
136 # Create a temporary file to workaround python's deadlock.
137 # http://docs.python.org/library/subprocess.html#subprocess.Popen.wait
138 # When the pipe fills up, it will deadlock this process. Using a real file
139 # works around that issue.
140 with tempfile.TemporaryFile() as buff:
141 start = time.time()
142 kwargs['stdout'] = buff
143 proc = Popen(args, **kwargs)
144 if stdin is not None:
145 proc.stdin.write(stdin)
146 while proc.returncode is None:
147 proc.poll()
148 if timeout and (time.time() - start) > timeout:
149 proc.kill()
150 proc.wait()
151 # It's -9 on linux and 1 on Windows. Standardize to -9.
152 # Do not throw an exception here, the user must use
153 # check_call(timeout=60) and check for e.returncode == -9 instead.
154 # or look at call()[1] == -9.
155 proc.returncode = -9
156 time.sleep(0.001)
157 # Now that the process died, reset the cursor and read the file.
158 buff.seek(0)
159 out = [buff.read(), None]
160 return out, proc.returncode
161
162
163 def check_call(args, **kwargs):
164 """Similar to subprocess.check_call() but use call() instead.
165
166 This permits to include more details in CalledProcessError().
167
168 Runs a command and throws an exception if the command failed.
169
170 Returns communicate() tuple.
171 """
172 out, returncode = call(args, **kwargs)
173 if returncode:
174 raise CalledProcessError(
175 returncode, args, kwargs.get('cwd'), out[0], out[1])
176 return out
177
178
179 def capture(args, **kwargs):
180 """Captures stdout of a process call and returns it.
181
182 Similar to check_output() excepts that it discards return code.
183
184 Discards communicate()[1]. By default sets stderr=STDOUT.
185 """
186 if kwargs.get('stderr') is None:
187 kwargs['stderr'] = STDOUT
188 return call(args, stdout=PIPE, **kwargs)[0][0]
189
190
191 def check_output(args, **kwargs):
192 """Captures stdout of a process call and returns it.
193
194 Discards communicate()[1]. By default sets stderr=STDOUT.
195
196 Throws if return code is not 0.
197
198 Works even prior to python 2.7.
199 """
200 if kwargs.get('stderr') is None:
201 kwargs['stderr'] = STDOUT
202 return check_call(args, stdout=PIPE, **kwargs)[0]
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698