OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 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 | 5 |
6 """ This module contains tools for running commands in a shell. """ | 6 """ This module contains tools for running commands in a shell. """ |
7 | 7 |
8 import datetime | 8 import datetime |
9 import os | 9 import os |
10 import Queue | 10 import Queue |
11 import select | 11 import select |
12 import subprocess | 12 import subprocess |
13 import sys | 13 import sys |
14 import threading | 14 import threading |
15 import time | 15 import time |
16 | 16 |
17 if 'nt' in os.name: | 17 if 'nt' in os.name: |
18 import ctypes | 18 import ctypes |
19 | 19 |
20 | 20 |
21 DEFAULT_SECS_BETWEEN_ATTEMPTS = 10 | 21 DEFAULT_SECS_BETWEEN_ATTEMPTS = 10 |
22 POLL_MILLIS = 250 | 22 POLL_MILLIS = 250 |
| 23 VERBOSE = True |
23 | 24 |
24 | 25 |
25 class CommandFailedException(Exception): | 26 class CommandFailedException(Exception): |
26 """Exception which gets raised when a command fails.""" | 27 """Exception which gets raised when a command fails.""" |
27 | 28 |
28 def __init__(self, output, *args): | 29 def __init__(self, output, *args): |
29 """Initialize the CommandFailedException. | 30 """Initialize the CommandFailedException. |
30 | 31 |
31 Args: | 32 Args: |
32 output: string; output from the command. | 33 output: string; output from the command. |
33 """ | 34 """ |
34 Exception.__init__(self, *args) | 35 Exception.__init__(self, *args) |
35 self._output = output | 36 self._output = output |
36 | 37 |
37 @property | 38 @property |
38 def output(self): | 39 def output(self): |
39 """Output from the command.""" | 40 """Output from the command.""" |
40 return self._output | 41 return self._output |
41 | 42 |
42 | 43 |
43 class TimeoutException(CommandFailedException): | 44 class TimeoutException(CommandFailedException): |
44 """CommandFailedException which gets raised when a subprocess exceeds its | 45 """CommandFailedException which gets raised when a subprocess exceeds its |
45 timeout. """ | 46 timeout. """ |
46 pass | 47 pass |
47 | 48 |
48 | 49 |
49 def run_async(cmd, echo=True, shell=False): | 50 def run_async(cmd, echo=None, shell=False): |
50 """ Run 'cmd' in a subprocess, returning a Popen class instance referring to | 51 """ Run 'cmd' in a subprocess, returning a Popen class instance referring to |
51 that process. (Non-blocking) """ | 52 that process. (Non-blocking) """ |
| 53 if echo is None: |
| 54 echo = VERBOSE |
52 if echo: | 55 if echo: |
53 print cmd | 56 print cmd |
54 if 'nt' in os.name: | 57 if 'nt' in os.name: |
55 # Windows has a bad habit of opening a dialog when a console program | 58 # Windows has a bad habit of opening a dialog when a console program |
56 # crashes, rather than just letting it crash. Therefore, when a program | 59 # crashes, rather than just letting it crash. Therefore, when a program |
57 # crashes on Windows, we don't find out until the build step times out. | 60 # crashes on Windows, we don't find out until the build step times out. |
58 # This code prevents the dialog from appearing, so that we find out | 61 # This code prevents the dialog from appearing, so that we find out |
59 # immediately and don't waste time waiting around. | 62 # immediately and don't waste time waiting around. |
60 SEM_NOGPFAULTERRORBOX = 0x0002 | 63 SEM_NOGPFAULTERRORBOX = 0x0002 |
61 ctypes.windll.kernel32.SetErrorMode(SEM_NOGPFAULTERRORBOX) | 64 ctypes.windll.kernel32.SetErrorMode(SEM_NOGPFAULTERRORBOX) |
(...skipping 30 matching lines...) Expand all Loading... |
92 # Python-style. | 95 # Python-style. |
93 for line in iter(self._file.readline, ''): | 96 for line in iter(self._file.readline, ''): |
94 self._queue.put(line) | 97 self._queue.put(line) |
95 if self._stopped: | 98 if self._stopped: |
96 break | 99 break |
97 | 100 |
98 def stop(self): | 101 def stop(self): |
99 self._stopped = True | 102 self._stopped = True |
100 | 103 |
101 | 104 |
102 def log_process_in_real_time(proc, echo=True, timeout=None, log_file=None, | 105 def log_process_in_real_time(proc, echo=None, timeout=None, log_file=None, |
103 halt_on_output=None, print_timestamps=True): | 106 halt_on_output=None, print_timestamps=True): |
104 """ Log the output of proc in real time until it completes. Return a tuple | 107 """ Log the output of proc in real time until it completes. Return a tuple |
105 containing the exit code of proc and the contents of stdout. | 108 containing the exit code of proc and the contents of stdout. |
106 | 109 |
107 proc: an instance of Popen referring to a running subprocess. | 110 proc: an instance of Popen referring to a running subprocess. |
108 echo: boolean indicating whether to print the output received from proc.stdout | 111 echo: boolean indicating whether to print the output received from proc.stdout |
109 timeout: number of seconds allotted for the process to run. Raises a | 112 timeout: number of seconds allotted for the process to run. Raises a |
110 TimeoutException if the run time exceeds the timeout. | 113 TimeoutException if the run time exceeds the timeout. |
111 log_file: an open file for writing output | 114 log_file: an open file for writing output |
112 halt_on_output: string; kill the process and return if this string is found | 115 halt_on_output: string; kill the process and return if this string is found |
113 in the output stream from the process. | 116 in the output stream from the process. |
114 print_timestamps: boolean indicating whether a formatted timestamp should be | 117 print_timestamps: boolean indicating whether a formatted timestamp should be |
115 prepended to each line of output. | 118 prepended to each line of output. |
116 """ | 119 """ |
| 120 if echo is None: |
| 121 echo = VERBOSE |
117 stdout_queue = Queue.Queue() | 122 stdout_queue = Queue.Queue() |
118 log_thread = EnqueueThread(proc.stdout, stdout_queue) | 123 log_thread = EnqueueThread(proc.stdout, stdout_queue) |
119 log_thread.start() | 124 log_thread.start() |
120 try: | 125 try: |
121 all_output = [] | 126 all_output = [] |
122 t_0 = time.time() | 127 t_0 = time.time() |
123 while True: | 128 while True: |
124 code = proc.poll() | 129 code = proc.poll() |
125 try: | 130 try: |
126 output = stdout_queue.get_nowait() | 131 output = stdout_queue.get_nowait() |
(...skipping 19 matching lines...) Expand all Loading... |
146 proc.terminate() | 151 proc.terminate() |
147 raise TimeoutException( | 152 raise TimeoutException( |
148 ''.join(all_output), | 153 ''.join(all_output), |
149 'Subprocess exceeded timeout of %ds' % timeout) | 154 'Subprocess exceeded timeout of %ds' % timeout) |
150 finally: | 155 finally: |
151 log_thread.stop() | 156 log_thread.stop() |
152 log_thread.join() | 157 log_thread.join() |
153 return (code, ''.join(all_output)) | 158 return (code, ''.join(all_output)) |
154 | 159 |
155 | 160 |
156 def log_process_after_completion(proc, echo=True, timeout=None, log_file=None): | 161 def log_process_after_completion(proc, echo=None, timeout=None, |
| 162 log_file=None): |
157 """ Wait for proc to complete and return a tuple containing the exit code of | 163 """ Wait for proc to complete and return a tuple containing the exit code of |
158 proc and the contents of stdout. Unlike log_process_in_real_time, does not | 164 proc and the contents of stdout. Unlike log_process_in_real_time, does not |
159 attempt to read stdout from proc in real time. | 165 attempt to read stdout from proc in real time. |
160 | 166 |
161 proc: an instance of Popen referring to a running subprocess. | 167 proc: an instance of Popen referring to a running subprocess. |
162 echo: boolean indicating whether to print the output received from proc.stdout | 168 echo: boolean indicating whether to print the output received from proc.stdout |
163 timeout: number of seconds allotted for the process to run. Raises a | 169 timeout: number of seconds allotted for the process to run. Raises a |
164 TimeoutException if the run time exceeds the timeout. | 170 TimeoutException if the run time exceeds the timeout. |
165 log_file: an open file for writing outout | 171 log_file: an open file for writing outout |
166 """ | 172 """ |
| 173 if echo is None: |
| 174 echo = VERBOSE |
167 t_0 = time.time() | 175 t_0 = time.time() |
168 code = None | 176 code = None |
169 while code is None: | 177 while code is None: |
170 if timeout and time.time() - t_0 > timeout: | 178 if timeout and time.time() - t_0 > timeout: |
171 raise TimeoutException( | 179 raise TimeoutException( |
172 proc.communicate()[0], | 180 proc.communicate()[0], |
173 'Subprocess exceeded timeout of %ds' % timeout) | 181 'Subprocess exceeded timeout of %ds' % timeout) |
174 time.sleep(0.5) | 182 time.sleep(0.5) |
175 code = proc.poll() | 183 code = proc.poll() |
176 output = proc.communicate()[0] | 184 output = proc.communicate()[0] |
177 if echo: | 185 if echo: |
178 print output | 186 print output |
179 if log_file: | 187 if log_file: |
180 log_file.write(output) | 188 log_file.write(output) |
181 log_file.flush() | 189 log_file.flush() |
182 return (code, output) | 190 return (code, output) |
183 | 191 |
184 | 192 |
185 def run(cmd, echo=True, shell=False, timeout=None, print_timestamps=True, | 193 def run(cmd, echo=None, shell=False, timeout=None, print_timestamps=True, |
186 log_in_real_time=True): | 194 log_in_real_time=True): |
187 """ Run 'cmd' in a shell and return the combined contents of stdout and | 195 """ Run 'cmd' in a shell and return the combined contents of stdout and |
188 stderr (Blocking). Throws an exception if the command exits non-zero. | 196 stderr (Blocking). Throws an exception if the command exits non-zero. |
189 | 197 |
190 cmd: list of strings (or single string, iff shell==True) indicating the | 198 cmd: list of strings (or single string, iff shell==True) indicating the |
191 command to run | 199 command to run |
192 echo: boolean indicating whether we should print the command and log output | 200 echo: boolean indicating whether we should print the command and log output |
193 shell: boolean indicating whether we are using advanced shell features. Use | 201 shell: boolean indicating whether we are using advanced shell features. Use |
194 only when absolutely necessary, since this allows a lot more freedom which | 202 only when absolutely necessary, since this allows a lot more freedom which |
195 could be exploited by malicious code. See the warning here: | 203 could be exploited by malicious code. See the warning here: |
196 http://docs.python.org/library/subprocess.html#popen-constructor | 204 http://docs.python.org/library/subprocess.html#popen-constructor |
197 timeout: optional, integer indicating the maximum elapsed time in seconds | 205 timeout: optional, integer indicating the maximum elapsed time in seconds |
198 print_timestamps: boolean indicating whether a formatted timestamp should be | 206 print_timestamps: boolean indicating whether a formatted timestamp should be |
199 prepended to each line of output. Unused if echo or log_in_real_time is | 207 prepended to each line of output. Unused if echo or log_in_real_time is |
200 False. | 208 False. |
201 log_in_real_time: boolean indicating whether to read stdout from the | 209 log_in_real_time: boolean indicating whether to read stdout from the |
202 subprocess in real time instead of when the process finishes. If echo is | 210 subprocess in real time instead of when the process finishes. If echo is |
203 False, we never log in real time, even if log_in_real_time is True. | 211 False, we never log in real time, even if log_in_real_time is True. |
204 """ | 212 """ |
| 213 if echo is None: |
| 214 echo = VERBOSE |
205 proc = run_async(cmd, echo=echo, shell=shell) | 215 proc = run_async(cmd, echo=echo, shell=shell) |
206 # If we're not printing the output, we don't care if the output shows up in | 216 # If we're not printing the output, we don't care if the output shows up in |
207 # real time, so don't bother. | 217 # real time, so don't bother. |
208 if log_in_real_time and echo: | 218 if log_in_real_time and echo: |
209 (returncode, output) = log_process_in_real_time(proc, echo=echo, | 219 (returncode, output) = log_process_in_real_time(proc, echo=echo, |
210 timeout=timeout, print_timestamps=print_timestamps) | 220 timeout=timeout, print_timestamps=print_timestamps) |
211 else: | 221 else: |
212 (returncode, output) = log_process_after_completion(proc, echo=echo, | 222 (returncode, output) = log_process_after_completion(proc, echo=echo, |
213 timeout=timeout) | 223 timeout=timeout) |
214 if returncode != 0: | 224 if returncode != 0: |
215 raise CommandFailedException( | 225 raise CommandFailedException( |
216 output, | 226 output, |
217 'Command failed with code %d: %s' % (returncode, cmd)) | 227 'Command failed with code %d: %s' % (returncode, cmd)) |
218 return output | 228 return output |
219 | 229 |
220 | 230 |
221 def run_retry(cmd, echo=True, shell=False, attempts=1, | 231 def run_retry(cmd, echo=None, shell=False, attempts=1, |
222 secs_between_attempts=DEFAULT_SECS_BETWEEN_ATTEMPTS, | 232 secs_between_attempts=DEFAULT_SECS_BETWEEN_ATTEMPTS, |
223 timeout=None, print_timestamps=True): | 233 timeout=None, print_timestamps=True): |
224 """ Wrapper for run() which makes multiple attempts until either the command | 234 """ Wrapper for run() which makes multiple attempts until either the command |
225 succeeds or the maximum number of attempts is reached. """ | 235 succeeds or the maximum number of attempts is reached. """ |
| 236 if echo is None: |
| 237 echo = VERBOSE |
226 attempt = 1 | 238 attempt = 1 |
227 while True: | 239 while True: |
228 try: | 240 try: |
229 return run(cmd, echo=echo, shell=shell, timeout=timeout, | 241 return run(cmd, echo=echo, shell=shell, timeout=timeout, |
230 print_timestamps=print_timestamps) | 242 print_timestamps=print_timestamps) |
231 except CommandFailedException: | 243 except CommandFailedException: |
232 if attempt >= attempts: | 244 if attempt >= attempts: |
233 raise | 245 raise |
234 print 'Command failed. Retrying in %d seconds...' % secs_between_attempts | 246 print 'Command failed. Retrying in %d seconds...' % secs_between_attempts |
235 time.sleep(secs_between_attempts) | 247 time.sleep(secs_between_attempts) |
236 attempt += 1 | 248 attempt += 1 |
OLD | NEW |