blob: 07a8aeba7a31ac9df49f2ed42b6acc9b94b090fd [file] [log] [blame]
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001# Copyright 2009 Google Inc. All Rights Reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +000015"""Generic utils."""
16
maruel@chromium.org167b9e62009-09-17 17:41:02 +000017import errno
maruel@chromium.orgd9141bf2009-12-23 16:13:32 +000018import logging
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000019import os
msb@chromium.orgac915bb2009-11-13 17:03:01 +000020import re
bradnelson@google.com8f9c69f2009-09-17 00:48:28 +000021import stat
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000022import subprocess
23import sys
maruel@chromium.org9e5317a2010-08-13 20:35:11 +000024import threading
maruel@chromium.org167b9e62009-09-17 17:41:02 +000025import time
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +000026import threading
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000027import xml.dom.minidom
maruel@chromium.org167b9e62009-09-17 17:41:02 +000028import xml.parsers.expat
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000029
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000030
maruel@chromium.org9a2f37e2009-12-19 16:03:28 +000031class CheckCallError(OSError):
32 """CheckCall() returned non-0."""
maruel@chromium.org7be5ef22010-01-30 22:31:50 +000033 def __init__(self, command, cwd, retcode, stdout, stderr=None):
34 OSError.__init__(self, command, cwd, retcode, stdout, stderr)
maruel@chromium.org9a2f37e2009-12-19 16:03:28 +000035 self.command = command
36 self.cwd = cwd
37 self.retcode = retcode
38 self.stdout = stdout
maruel@chromium.org7be5ef22010-01-30 22:31:50 +000039 self.stderr = stderr
maruel@chromium.org9a2f37e2009-12-19 16:03:28 +000040
41
maruel@chromium.orgea8c1a92009-12-20 17:21:59 +000042def CheckCall(command, cwd=None, print_error=True):
maruel@chromium.org9a2f37e2009-12-19 16:03:28 +000043 """Like subprocess.check_call() but returns stdout.
44
45 Works on python 2.4
46 """
maruel@chromium.org116704f2010-06-11 17:34:38 +000047 logging.debug('%s, cwd=%s' % (str(command), str(cwd)))
maruel@chromium.org18111352009-12-20 17:21:28 +000048 try:
maruel@chromium.orgea8c1a92009-12-20 17:21:59 +000049 stderr = None
50 if not print_error:
51 stderr = subprocess.PIPE
maruel@chromium.org18111352009-12-20 17:21:28 +000052 process = subprocess.Popen(command, cwd=cwd,
53 shell=sys.platform.startswith('win'),
maruel@chromium.orgea8c1a92009-12-20 17:21:59 +000054 stdout=subprocess.PIPE,
55 stderr=stderr)
maruel@chromium.org7be5ef22010-01-30 22:31:50 +000056 std_out, std_err = process.communicate()
maruel@chromium.org18111352009-12-20 17:21:28 +000057 except OSError, e:
maruel@chromium.org01d8c1d2010-01-07 01:56:59 +000058 raise CheckCallError(command, cwd, e.errno, None)
maruel@chromium.org18111352009-12-20 17:21:28 +000059 if process.returncode:
maruel@chromium.org7be5ef22010-01-30 22:31:50 +000060 raise CheckCallError(command, cwd, process.returncode, std_out, std_err)
61 return std_out, std_err
maruel@chromium.org9a2f37e2009-12-19 16:03:28 +000062
63
msb@chromium.orgac915bb2009-11-13 17:03:01 +000064def SplitUrlRevision(url):
65 """Splits url and returns a two-tuple: url, rev"""
66 if url.startswith('ssh:'):
67 # Make sure ssh://test@example.com/test.git@stable works
maruel@chromium.org116704f2010-06-11 17:34:38 +000068 regex = r'(ssh://(?:[\w]+@)?[-\w:\.]+/[-\w\./]+)(?:@(.+))?'
msb@chromium.orgac915bb2009-11-13 17:03:01 +000069 components = re.search(regex, url).groups()
70 else:
maruel@chromium.org116704f2010-06-11 17:34:38 +000071 components = url.split('@', 1)
msb@chromium.orgac915bb2009-11-13 17:03:01 +000072 if len(components) == 1:
73 components += [None]
74 return tuple(components)
75
76
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000077def ParseXML(output):
78 try:
79 return xml.dom.minidom.parseString(output)
80 except xml.parsers.expat.ExpatError:
81 return None
82
83
84def GetNamedNodeText(node, node_name):
85 child_nodes = node.getElementsByTagName(node_name)
86 if not child_nodes:
87 return None
88 assert len(child_nodes) == 1 and child_nodes[0].childNodes.length == 1
89 return child_nodes[0].firstChild.nodeValue
90
91
92def GetNodeNamedAttributeText(node, node_name, attribute_name):
93 child_nodes = node.getElementsByTagName(node_name)
94 if not child_nodes:
95 return None
96 assert len(child_nodes) == 1
97 return child_nodes[0].getAttribute(attribute_name)
98
99
100class Error(Exception):
101 """gclient exception class."""
102 pass
103
104
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000105def SyntaxErrorToError(filename, e):
106 """Raises a gclient_utils.Error exception with the human readable message"""
107 try:
108 # Try to construct a human readable error message
109 if filename:
110 error_message = 'There is a syntax error in %s\n' % filename
111 else:
112 error_message = 'There is a syntax error\n'
113 error_message += 'Line #%s, character %s: "%s"' % (
114 e.lineno, e.offset, re.sub(r'[\r\n]*$', '', e.text))
115 except:
116 # Something went wrong, re-raise the original exception
117 raise e
118 else:
119 raise Error(error_message)
120
121
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000122class PrintableObject(object):
123 def __str__(self):
124 output = ''
125 for i in dir(self):
126 if i.startswith('__'):
127 continue
128 output += '%s = %s\n' % (i, str(getattr(self, i, '')))
129 return output
130
131
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000132def FileRead(filename, mode='rU'):
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000133 content = None
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000134 f = open(filename, mode)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000135 try:
136 content = f.read()
137 finally:
138 f.close()
139 return content
140
141
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000142def FileWrite(filename, content, mode='w'):
143 f = open(filename, mode)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000144 try:
145 f.write(content)
146 finally:
147 f.close()
148
149
150def RemoveDirectory(*path):
151 """Recursively removes a directory, even if it's marked read-only.
152
153 Remove the directory located at *path, if it exists.
154
155 shutil.rmtree() doesn't work on Windows if any of the files or directories
156 are read-only, which svn repositories and some .svn files are. We need to
157 be able to force the files to be writable (i.e., deletable) as we traverse
158 the tree.
159
160 Even with all this, Windows still sometimes fails to delete a file, citing
161 a permission error (maybe something to do with antivirus scans or disk
162 indexing). The best suggestion any of the user forums had was to wait a
163 bit and try again, so we do that too. It's hand-waving, but sometimes it
164 works. :/
165
166 On POSIX systems, things are a little bit simpler. The modes of the files
167 to be deleted doesn't matter, only the modes of the directories containing
168 them are significant. As the directory tree is traversed, each directory
169 has its mode set appropriately before descending into it. This should
170 result in the entire tree being removed, with the possible exception of
171 *path itself, because nothing attempts to change the mode of its parent.
172 Doing so would be hazardous, as it's not a directory slated for removal.
173 In the ordinary case, this is not a problem: for our purposes, the user
174 will never lack write permission on *path's parent.
175 """
maruel@chromium.orgd9141bf2009-12-23 16:13:32 +0000176 logging.debug(path)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000177 file_path = os.path.join(*path)
178 if not os.path.exists(file_path):
179 return
180
181 if os.path.islink(file_path) or not os.path.isdir(file_path):
maruel@chromium.org116704f2010-06-11 17:34:38 +0000182 raise Error('RemoveDirectory asked to remove non-directory %s' % file_path)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000183
184 has_win32api = False
185 if sys.platform == 'win32':
186 has_win32api = True
187 # Some people don't have the APIs installed. In that case we'll do without.
188 try:
189 win32api = __import__('win32api')
190 win32con = __import__('win32con')
191 except ImportError:
192 has_win32api = False
193 else:
194 # On POSIX systems, we need the x-bit set on the directory to access it,
195 # the r-bit to see its contents, and the w-bit to remove files from it.
196 # The actual modes of the files within the directory is irrelevant.
197 os.chmod(file_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
198 for fn in os.listdir(file_path):
199 fullpath = os.path.join(file_path, fn)
200
201 # If fullpath is a symbolic link that points to a directory, isdir will
202 # be True, but we don't want to descend into that as a directory, we just
203 # want to remove the link. Check islink and treat links as ordinary files
204 # would be treated regardless of what they reference.
205 if os.path.islink(fullpath) or not os.path.isdir(fullpath):
206 if sys.platform == 'win32':
207 os.chmod(fullpath, stat.S_IWRITE)
208 if has_win32api:
209 win32api.SetFileAttributes(fullpath, win32con.FILE_ATTRIBUTE_NORMAL)
210 try:
211 os.remove(fullpath)
212 except OSError, e:
213 if e.errno != errno.EACCES or sys.platform != 'win32':
214 raise
215 print 'Failed to delete %s: trying again' % fullpath
216 time.sleep(0.1)
217 os.remove(fullpath)
218 else:
219 RemoveDirectory(fullpath)
220
221 if sys.platform == 'win32':
222 os.chmod(file_path, stat.S_IWRITE)
223 if has_win32api:
224 win32api.SetFileAttributes(file_path, win32con.FILE_ATTRIBUTE_NORMAL)
225 try:
226 os.rmdir(file_path)
227 except OSError, e:
228 if e.errno != errno.EACCES or sys.platform != 'win32':
229 raise
230 print 'Failed to remove %s: trying again' % file_path
231 time.sleep(0.1)
232 os.rmdir(file_path)
233
234
235def SubprocessCall(command, in_directory, fail_status=None):
236 """Runs command, a list, in directory in_directory.
237
238 This function wraps SubprocessCallAndFilter, but does not perform the
239 filtering functions. See that function for a more complete usage
240 description.
241 """
242 # Call subprocess and capture nothing:
243 SubprocessCallAndFilter(command, in_directory, True, True, fail_status)
244
245
246def SubprocessCallAndFilter(command,
247 in_directory,
248 print_messages,
249 print_stdout,
maruel@chromium.org6e29d572010-06-04 17:32:20 +0000250 fail_status=None, filter_fn=None):
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000251 """Runs command, a list, in directory in_directory.
252
253 If print_messages is true, a message indicating what is being done
dpranke@google.com22e29d42009-10-28 00:48:26 +0000254 is printed to stdout. If print_messages is false, the message is printed
255 only if we actually need to print something else as well, so you can
256 get the context of the output. If print_messages is false and print_stdout
257 is false, no output at all is generated.
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000258
259 Also, if print_stdout is true, the command's stdout is also forwarded
260 to stdout.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000261
maruel@chromium.org6e29d572010-06-04 17:32:20 +0000262 If a filter_fn function is specified, it is expected to take a single
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000263 string argument, and it will be called with each line of the
264 subprocess's output. Each line has had the trailing newline character
265 trimmed.
266
267 If the command fails, as indicated by a nonzero exit status, gclient will
268 exit with an exit status of fail_status. If fail_status is None (the
269 default), gclient will raise an Error exception.
270 """
maruel@chromium.orgd9141bf2009-12-23 16:13:32 +0000271 logging.debug(command)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000272 if print_messages:
maruel@chromium.org116704f2010-06-11 17:34:38 +0000273 print('\n________ running \'%s\' in \'%s\''
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000274 % (' '.join(command), in_directory))
275
276 # *Sigh*: Windows needs shell=True, or else it won't search %PATH% for the
277 # executable, but shell=True makes subprocess on Linux fail when it's called
278 # with a list because it only tries to execute the first item in the list.
279 kid = subprocess.Popen(command, bufsize=0, cwd=in_directory,
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000280 shell=(sys.platform == 'win32'), stdout=subprocess.PIPE,
dpranke@google.com5cc6c572009-11-06 20:04:56 +0000281 stderr=subprocess.STDOUT)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000282
283 # Also, we need to forward stdout to prevent weird re-ordering of output.
284 # This has to be done on a per byte basis to make sure it is not buffered:
285 # normally buffering is done for each line, but if svn requests input, no
286 # end-of-line character is output after the prompt and it would not show up.
287 in_byte = kid.stdout.read(1)
maruel@chromium.org116704f2010-06-11 17:34:38 +0000288 in_line = ''
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000289 while in_byte:
maruel@chromium.org116704f2010-06-11 17:34:38 +0000290 if in_byte != '\r':
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000291 if print_stdout:
dpranke@google.com22e29d42009-10-28 00:48:26 +0000292 if not print_messages:
maruel@chromium.org116704f2010-06-11 17:34:38 +0000293 print('\n________ running \'%s\' in \'%s\''
dpranke@google.com22e29d42009-10-28 00:48:26 +0000294 % (' '.join(command), in_directory))
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000295 print_messages = True
dpranke@google.com9e890f92009-10-28 01:32:29 +0000296 sys.stdout.write(in_byte)
maruel@chromium.org116704f2010-06-11 17:34:38 +0000297 if in_byte != '\n':
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000298 in_line += in_byte
maruel@chromium.org116704f2010-06-11 17:34:38 +0000299 if in_byte == '\n' and filter_fn:
maruel@chromium.org6e29d572010-06-04 17:32:20 +0000300 filter_fn(in_line)
maruel@chromium.org116704f2010-06-11 17:34:38 +0000301 in_line = ''
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000302 in_byte = kid.stdout.read(1)
303 rv = kid.wait()
304
305 if rv:
maruel@chromium.org116704f2010-06-11 17:34:38 +0000306 msg = 'failed to run command: %s' % ' '.join(command)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000307
308 if fail_status != None:
309 print >>sys.stderr, msg
310 sys.exit(fail_status)
311
312 raise Error(msg)
313
314
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000315def FindGclientRoot(from_dir, filename='.gclient'):
maruel@chromium.orga9371762009-12-22 18:27:38 +0000316 """Tries to find the gclient root."""
317 path = os.path.realpath(from_dir)
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000318 while not os.path.exists(os.path.join(path, filename)):
maruel@chromium.orga9371762009-12-22 18:27:38 +0000319 next = os.path.split(path)
320 if not next[1]:
321 return None
322 path = next[0]
maruel@chromium.orgd9141bf2009-12-23 16:13:32 +0000323 logging.info('Found gclient root at ' + path)
maruel@chromium.orga9371762009-12-22 18:27:38 +0000324 return path
maruel@chromium.org3ccbf7e2009-12-22 20:46:42 +0000325
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000326
maruel@chromium.org3ccbf7e2009-12-22 20:46:42 +0000327def PathDifference(root, subpath):
328 """Returns the difference subpath minus root."""
329 root = os.path.realpath(root)
330 subpath = os.path.realpath(subpath)
331 if not subpath.startswith(root):
332 return None
333 # If the root does not have a trailing \ or /, we add it so the returned
334 # path starts immediately after the seperator regardless of whether it is
335 # provided.
336 root = os.path.join(root, '')
337 return subpath[len(root):]
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000338
339
340def FindFileUpwards(filename, path=None):
341 """Search upwards from the a directory (default: current) to find a file."""
342 if not path:
343 path = os.getcwd()
344 path = os.path.realpath(path)
345 while True:
346 file_path = os.path.join(path, filename)
347 if os.path.isfile(file_path):
348 return file_path
349 (new_path, _) = os.path.split(path)
350 if new_path == path:
351 return None
352 path = new_path
353
354
355def GetGClientRootAndEntries(path=None):
356 """Returns the gclient root and the dict of entries."""
357 config_file = '.gclient_entries'
358 config_path = FindFileUpwards(config_file, path)
359
360 if not config_path:
maruel@chromium.org116704f2010-06-11 17:34:38 +0000361 print "Can't find %s" % config_file
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000362 return None
363
364 env = {}
365 execfile(config_path, env)
366 config_dir = os.path.dirname(config_path)
367 return config_dir, env['entries']
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000368
369
370class WorkItem(object):
371 """One work item."""
372 # A list of string, each being a WorkItem name.
373 requirements = []
374 # A unique string representing this work item.
375 name = None
376
377 def run(self):
378 pass
379
380
381class ExecutionQueue(object):
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000382 """Runs a set of WorkItem that have interdependencies and were WorkItem are
383 added as they are processed.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000384
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000385 In gclient's case, Dependencies sometime needs to be run out of order due to
386 From() keyword. This class manages that all the required dependencies are run
387 before running each one.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000388
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000389 Methods of this class are thread safe.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000390 """
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000391 def __init__(self, jobs, progress):
392 """jobs specifies the number of concurrent tasks to allow. progress is a
393 Progress instance."""
394 # Set when a thread is done or a new item is enqueued.
395 self.ready_cond = threading.Condition()
396 # Maximum number of concurrent tasks.
397 self.jobs = jobs
398 # List of WorkItem, for gclient, these are Dependency instances.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000399 self.queued = []
400 # List of strings representing each Dependency.name that was run.
401 self.ran = []
402 # List of items currently running.
403 self.running = []
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000404 # Exceptions thrown if any.
405 self.exceptions = []
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000406 self.progress = progress
407 if self.progress:
408 self.progress.update()
409
410 def enqueue(self, d):
411 """Enqueue one Dependency to be executed later once its requirements are
412 satisfied.
413 """
414 assert isinstance(d, WorkItem)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000415 self.ready_cond.acquire()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000416 try:
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000417 self.queued.append(d)
418 total = len(self.queued) + len(self.ran) + len(self.running)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000419 logging.debug('enqueued(%s)' % d.name)
420 if self.progress:
421 self.progress._total = total + 1
422 self.progress.update(0)
423 self.ready_cond.notifyAll()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000424 finally:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000425 self.ready_cond.release()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000426
427 def flush(self, *args, **kwargs):
428 """Runs all enqueued items until all are executed."""
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000429 self.ready_cond.acquire()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000430 try:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000431 while True:
432 # Check for task to run first, then wait.
433 while True:
434 if self.exceptions:
435 # Systematically flush the queue when there is an exception logged
436 # in.
437 self.queued = []
438 # Flush threads that have terminated.
439 self.running = [t for t in self.running if t.isAlive()]
440 if not self.queued and not self.running:
441 break
442 if self.jobs == len(self.running):
443 break
444 for i in xrange(len(self.queued)):
445 # Verify its requirements.
446 for r in self.queued[i].requirements:
447 if not r in self.ran:
448 # Requirement not met.
449 break
450 else:
451 # Start one work item: all its requirements are satisfied.
452 d = self.queued.pop(i)
453 new_thread = self._Worker(self, d, args=args, kwargs=kwargs)
454 if self.jobs > 1:
455 # Start the thread.
456 self.running.append(new_thread)
457 new_thread.start()
458 else:
459 # Run the 'thread' inside the main thread.
460 new_thread.run()
461 break
462 else:
463 # Couldn't find an item that could run. Break out the outher loop.
464 break
465 if not self.queued and not self.running:
466 break
467 # We need to poll here otherwise Ctrl-C isn't processed.
468 self.ready_cond.wait(10)
469 # Something happened: self.enqueue() or a thread terminated. Loop again.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000470 finally:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000471 self.ready_cond.release()
472 assert not self.running, 'Now guaranteed to be single-threaded'
473 if self.exceptions:
maruel@chromium.orgc8d064b2010-08-16 16:46:14 +0000474 # To get back the stack location correctly, the raise a, b, c form must be
475 # used, passing a tuple as the first argument doesn't work.
476 e = self.exceptions.pop(0)
477 raise e[0], e[1], e[2]
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000478 if self.progress:
479 self.progress.end()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000480
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000481 class _Worker(threading.Thread):
482 """One thread to execute one WorkItem."""
483 def __init__(self, parent, item, args=(), kwargs=None):
484 threading.Thread.__init__(self, name=item.name or 'Worker')
485 self.args = args
486 self.kwargs = kwargs or {}
487 self.item = item
488 self.parent = parent
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000489
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000490 def run(self):
491 """Runs in its own thread."""
492 logging.debug('running(%s)' % self.item.name)
493 exception = None
494 try:
495 self.item.run(*self.args, **self.kwargs)
maruel@chromium.orgc8d064b2010-08-16 16:46:14 +0000496 except Exception:
497 # Catch exception location.
498 exception = sys.exc_info()
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000499
500 # This assumes the following code won't throw an exception. Bad.
501 self.parent.ready_cond.acquire()
502 try:
503 if exception:
504 self.parent.exceptions.append(exception)
505 if self.parent.progress:
506 self.parent.progress.update(1)
507 assert not self.item.name in self.parent.ran
508 if not self.item.name in self.parent.ran:
509 self.parent.ran.append(self.item.name)
510 finally:
511 self.parent.ready_cond.notifyAll()
512 self.parent.ready_cond.release()