blob: 1f4fdf22c5a5899023dfcad48a200b1204bade2f [file] [log] [blame]
[email protected]06617272010-11-04 13:50:501# Copyright (c) 2010 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
[email protected]5f3eee32009-09-17 00:34:304
[email protected]5aeb7dd2009-11-17 18:09:015"""Generic utils."""
6
[email protected]167b9e62009-09-17 17:41:027import errno
[email protected]d9141bf2009-12-23 16:13:328import logging
[email protected]5f3eee32009-09-17 00:34:309import os
[email protected]3742c842010-09-09 19:27:1410import Queue
[email protected]ac915bb2009-11-13 17:03:0111import re
[email protected]8f9c69f2009-09-17 00:48:2812import stat
[email protected]5f3eee32009-09-17 00:34:3013import subprocess
14import sys
[email protected]9e5317a2010-08-13 20:35:1115import threading
[email protected]167b9e62009-09-17 17:41:0216import time
[email protected]5f3eee32009-09-17 00:34:3017
[email protected]5f3eee32009-09-17 00:34:3018
[email protected]58ef2972011-04-01 21:00:1119def hack_subprocess():
20 """subprocess functions may throw exceptions when used in multiple threads.
21
22 See http://bugs.python.org/issue1731717 for more information.
23 """
24 subprocess._cleanup = lambda: None
[email protected]06617272010-11-04 13:50:5025
26
[email protected]66c83e62010-09-07 14:18:4527class Error(Exception):
28 """gclient exception class."""
29 pass
30
31
32class CheckCallError(OSError, Error):
[email protected]9a2f37e2009-12-19 16:03:2833 """CheckCall() returned non-0."""
[email protected]66c83e62010-09-07 14:18:4534 def __init__(self, command, cwd, returncode, stdout, stderr=None):
[email protected]ff3e4a82011-04-23 01:23:4235 OSError.__init__(self, command, cwd, returncode)
[email protected]ad80e3b2010-09-09 14:18:2836 Error.__init__(self, command)
[email protected]9a2f37e2009-12-19 16:03:2837 self.command = command
38 self.cwd = cwd
[email protected]66c83e62010-09-07 14:18:4539 self.returncode = returncode
[email protected]9a2f37e2009-12-19 16:03:2840 self.stdout = stdout
[email protected]7be5ef22010-01-30 22:31:5041 self.stderr = stderr
[email protected]9a2f37e2009-12-19 16:03:2842
[email protected]7b194c12010-09-07 20:57:0943 def __str__(self):
44 out = ' '.join(self.command)
45 if self.cwd:
46 out += ' in ' + self.cwd
47 if self.returncode is not None:
48 out += ' returned %d' % self.returncode
49 if self.stdout is not None:
50 out += '\nstdout: %s\n' % self.stdout
51 if self.stderr is not None:
52 out += '\nstderr: %s\n' % self.stderr
53 return out
54
[email protected]9a2f37e2009-12-19 16:03:2855
[email protected]58ef2972011-04-01 21:00:1156def Popen(args, **kwargs):
57 """Calls subprocess.Popen() with hacks to work around certain behaviors.
58
59 Ensure English outpout for svn and make it work reliably on Windows.
60 """
61 logging.debug(u'%s, cwd=%s' % (u' '.join(args), kwargs.get('cwd', '')))
62 if not 'env' in kwargs:
63 # It's easier to parse the stdout if it is always in English.
64 kwargs['env'] = os.environ.copy()
65 kwargs['env']['LANGUAGE'] = 'en'
66 if not 'shell' in kwargs:
67 # *Sigh*: Windows needs shell=True, or else it won't search %PATH% for the
68 # executable, but shell=True makes subprocess on Linux fail when it's called
69 # with a list because it only tries to execute the first item in the list.
70 kwargs['shell'] = (sys.platform=='win32')
71 try:
72 return subprocess.Popen(args, **kwargs)
73 except OSError, e:
74 if e.errno == errno.EAGAIN and sys.platform == 'cygwin':
75 raise Error(
76 'Visit '
77 'http://code.google.com/p/chromium/wiki/CygwinDllRemappingFailure to '
78 'learn how to fix this error; you need to rebase your cygwin dlls')
79 raise
80
81
[email protected]ac610232010-10-13 14:01:3182def CheckCall(command, print_error=True, **kwargs):
[email protected]3a292682010-08-23 18:54:5583 """Similar subprocess.check_call() but redirects stdout and
84 returns (stdout, stderr).
[email protected]9a2f37e2009-12-19 16:03:2885
86 Works on python 2.4
87 """
[email protected]18111352009-12-20 17:21:2888 try:
[email protected]ea8c1a92009-12-20 17:21:5989 stderr = None
90 if not print_error:
91 stderr = subprocess.PIPE
[email protected]ac610232010-10-13 14:01:3192 process = Popen(command, stdout=subprocess.PIPE, stderr=stderr, **kwargs)
[email protected]7be5ef22010-01-30 22:31:5093 std_out, std_err = process.communicate()
[email protected]18111352009-12-20 17:21:2894 except OSError, e:
[email protected]ac610232010-10-13 14:01:3195 raise CheckCallError(command, kwargs.get('cwd', None), e.errno, None)
[email protected]18111352009-12-20 17:21:2896 if process.returncode:
[email protected]ac610232010-10-13 14:01:3197 raise CheckCallError(command, kwargs.get('cwd', None), process.returncode,
98 std_out, std_err)
[email protected]7be5ef22010-01-30 22:31:5099 return std_out, std_err
[email protected]9a2f37e2009-12-19 16:03:28100
101
[email protected]ac915bb2009-11-13 17:03:01102def SplitUrlRevision(url):
103 """Splits url and returns a two-tuple: url, rev"""
104 if url.startswith('ssh:'):
[email protected]78b8cd12010-10-26 12:47:07105 # Make sure ssh://[email protected]/~/test.git@stable works
106 regex = r'(ssh://(?:[-\w]+@)?[-\w:\.]+/[-~\w\./]+)(?:@(.+))?'
[email protected]ac915bb2009-11-13 17:03:01107 components = re.search(regex, url).groups()
108 else:
[email protected]116704f2010-06-11 17:34:38109 components = url.split('@', 1)
[email protected]ac915bb2009-11-13 17:03:01110 if len(components) == 1:
111 components += [None]
112 return tuple(components)
113
114
[email protected]eaab7842011-04-28 09:07:58115def IsDateRevision(revision):
116 """Returns true if the given revision is of the form "{ ... }"."""
117 return bool(revision and re.match(r'^\{.+\}$', str(revision)))
118
119
120def MakeDateRevision(date):
121 """Returns a revision representing the latest revision before the given
122 date."""
123 return "{" + date + "}"
124
125
[email protected]5990f9d2010-07-07 18:02:58126def SyntaxErrorToError(filename, e):
127 """Raises a gclient_utils.Error exception with the human readable message"""
128 try:
129 # Try to construct a human readable error message
130 if filename:
131 error_message = 'There is a syntax error in %s\n' % filename
132 else:
133 error_message = 'There is a syntax error\n'
134 error_message += 'Line #%s, character %s: "%s"' % (
135 e.lineno, e.offset, re.sub(r'[\r\n]*$', '', e.text))
136 except:
137 # Something went wrong, re-raise the original exception
138 raise e
139 else:
140 raise Error(error_message)
141
142
[email protected]5f3eee32009-09-17 00:34:30143class PrintableObject(object):
144 def __str__(self):
145 output = ''
146 for i in dir(self):
147 if i.startswith('__'):
148 continue
149 output += '%s = %s\n' % (i, str(getattr(self, i, '')))
150 return output
151
152
[email protected]5aeb7dd2009-11-17 18:09:01153def FileRead(filename, mode='rU'):
[email protected]5f3eee32009-09-17 00:34:30154 content = None
[email protected]5aeb7dd2009-11-17 18:09:01155 f = open(filename, mode)
[email protected]5f3eee32009-09-17 00:34:30156 try:
157 content = f.read()
158 finally:
159 f.close()
160 return content
161
162
[email protected]5aeb7dd2009-11-17 18:09:01163def FileWrite(filename, content, mode='w'):
164 f = open(filename, mode)
[email protected]5f3eee32009-09-17 00:34:30165 try:
166 f.write(content)
167 finally:
168 f.close()
169
170
[email protected]f9040722011-03-09 14:47:51171def rmtree(path):
172 """shutil.rmtree() on steroids.
[email protected]5f3eee32009-09-17 00:34:30173
[email protected]f9040722011-03-09 14:47:51174 Recursively removes a directory, even if it's marked read-only.
[email protected]5f3eee32009-09-17 00:34:30175
176 shutil.rmtree() doesn't work on Windows if any of the files or directories
177 are read-only, which svn repositories and some .svn files are. We need to
178 be able to force the files to be writable (i.e., deletable) as we traverse
179 the tree.
180
181 Even with all this, Windows still sometimes fails to delete a file, citing
182 a permission error (maybe something to do with antivirus scans or disk
183 indexing). The best suggestion any of the user forums had was to wait a
184 bit and try again, so we do that too. It's hand-waving, but sometimes it
185 works. :/
186
187 On POSIX systems, things are a little bit simpler. The modes of the files
188 to be deleted doesn't matter, only the modes of the directories containing
189 them are significant. As the directory tree is traversed, each directory
190 has its mode set appropriately before descending into it. This should
191 result in the entire tree being removed, with the possible exception of
192 *path itself, because nothing attempts to change the mode of its parent.
193 Doing so would be hazardous, as it's not a directory slated for removal.
194 In the ordinary case, this is not a problem: for our purposes, the user
195 will never lack write permission on *path's parent.
196 """
[email protected]f9040722011-03-09 14:47:51197 if not os.path.exists(path):
[email protected]5f3eee32009-09-17 00:34:30198 return
199
[email protected]f9040722011-03-09 14:47:51200 if os.path.islink(path) or not os.path.isdir(path):
201 raise Error('Called rmtree(%s) in non-directory' % path)
[email protected]5f3eee32009-09-17 00:34:30202
[email protected]5f3eee32009-09-17 00:34:30203 if sys.platform == 'win32':
[email protected]5f3eee32009-09-17 00:34:30204 # Some people don't have the APIs installed. In that case we'll do without.
[email protected]1edee692011-03-12 19:39:13205 win32api = None
206 win32con = None
[email protected]5f3eee32009-09-17 00:34:30207 try:
[email protected]1edee692011-03-12 19:39:13208 # Unable to import 'XX'
209 # pylint: disable=F0401
210 import win32api, win32con
[email protected]5f3eee32009-09-17 00:34:30211 except ImportError:
[email protected]f9040722011-03-09 14:47:51212 pass
[email protected]5f3eee32009-09-17 00:34:30213 else:
214 # On POSIX systems, we need the x-bit set on the directory to access it,
215 # the r-bit to see its contents, and the w-bit to remove files from it.
216 # The actual modes of the files within the directory is irrelevant.
[email protected]f9040722011-03-09 14:47:51217 os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
[email protected]5f3eee32009-09-17 00:34:30218
[email protected]f9040722011-03-09 14:47:51219 def remove(func, subpath):
220 if sys.platform == 'win32':
221 os.chmod(subpath, stat.S_IWRITE)
222 if win32api and win32con:
223 win32api.SetFileAttributes(subpath, win32con.FILE_ATTRIBUTE_NORMAL)
224 try:
225 func(subpath)
226 except OSError, e:
227 if e.errno != errno.EACCES or sys.platform != 'win32':
228 raise
229 # Failed to delete, try again after a 100ms sleep.
230 time.sleep(0.1)
231 func(subpath)
232
233 for fn in os.listdir(path):
[email protected]5f3eee32009-09-17 00:34:30234 # If fullpath is a symbolic link that points to a directory, isdir will
235 # be True, but we don't want to descend into that as a directory, we just
236 # want to remove the link. Check islink and treat links as ordinary files
237 # would be treated regardless of what they reference.
[email protected]f9040722011-03-09 14:47:51238 fullpath = os.path.join(path, fn)
[email protected]5f3eee32009-09-17 00:34:30239 if os.path.islink(fullpath) or not os.path.isdir(fullpath):
[email protected]f9040722011-03-09 14:47:51240 remove(os.remove, fullpath)
[email protected]5f3eee32009-09-17 00:34:30241 else:
[email protected]f9040722011-03-09 14:47:51242 # Recurse.
243 rmtree(fullpath)
[email protected]5f3eee32009-09-17 00:34:30244
[email protected]f9040722011-03-09 14:47:51245 remove(os.rmdir, path)
246
247# TODO(maruel): Rename the references.
248RemoveDirectory = rmtree
[email protected]5f3eee32009-09-17 00:34:30249
250
[email protected]17d01792010-09-01 18:07:10251def CheckCallAndFilterAndHeader(args, always=False, **kwargs):
252 """Adds 'header' support to CheckCallAndFilter.
[email protected]5f3eee32009-09-17 00:34:30253
[email protected]17d01792010-09-01 18:07:10254 If |always| is True, a message indicating what is being done
255 is printed to stdout all the time even if not output is generated. Otherwise
256 the message header is printed only if the call generated any ouput.
[email protected]5f3eee32009-09-17 00:34:30257 """
[email protected]17d01792010-09-01 18:07:10258 stdout = kwargs.get('stdout', None) or sys.stdout
259 if always:
[email protected]559c3f82010-08-23 19:26:08260 stdout.write('\n________ running \'%s\' in \'%s\'\n'
[email protected]17d01792010-09-01 18:07:10261 % (' '.join(args), kwargs.get('cwd', '.')))
262 else:
263 filter_fn = kwargs.get('filter_fn', None)
264 def filter_msg(line):
265 if line is None:
266 stdout.write('\n________ running \'%s\' in \'%s\'\n'
267 % (' '.join(args), kwargs.get('cwd', '.')))
268 elif filter_fn:
269 filter_fn(line)
270 kwargs['filter_fn'] = filter_msg
271 kwargs['call_filter_on_first_line'] = True
272 # Obviously.
273 kwargs['print_stdout'] = True
274 return CheckCallAndFilter(args, **kwargs)
[email protected]5f3eee32009-09-17 00:34:30275
[email protected]17d01792010-09-01 18:07:10276
[email protected]e0de9cb2010-09-17 15:07:14277def SoftClone(obj):
278 """Clones an object. copy.copy() doesn't work on 'file' objects."""
[email protected]4ed34182010-09-17 15:57:47279 if obj.__class__.__name__ == 'SoftCloned':
280 return obj
[email protected]cb2985f2010-11-03 14:08:31281 class SoftCloned(object):
282 pass
[email protected]4ed34182010-09-17 15:57:47283 new_obj = SoftCloned()
[email protected]e0de9cb2010-09-17 15:07:14284 for member in dir(obj):
285 if member.startswith('_'):
286 continue
287 setattr(new_obj, member, getattr(obj, member))
288 return new_obj
[email protected]db111f72010-09-08 13:36:53289
[email protected]e0de9cb2010-09-17 15:07:14290
291def MakeFileAutoFlush(fileobj, delay=10):
292 """Creates a file object clone to automatically flush after N seconds."""
293 if hasattr(fileobj, 'last_flushed_at'):
294 # Already patched. Just update delay.
295 fileobj.delay = delay
296 return fileobj
297
[email protected]b17b55b2010-11-03 14:42:37298 # Attribute 'XXX' defined outside __init__
299 # pylint: disable=W0201
[email protected]e0de9cb2010-09-17 15:07:14300 new_fileobj = SoftClone(fileobj)
[email protected]4ed34182010-09-17 15:57:47301 if not hasattr(new_fileobj, 'lock'):
302 new_fileobj.lock = threading.Lock()
[email protected]e0de9cb2010-09-17 15:07:14303 new_fileobj.last_flushed_at = time.time()
304 new_fileobj.delay = delay
[email protected]4ed34182010-09-17 15:57:47305 new_fileobj.old_auto_flush_write = new_fileobj.write
[email protected]e0de9cb2010-09-17 15:07:14306 # Silence pylint.
307 new_fileobj.flush = fileobj.flush
308
309 def auto_flush_write(out):
310 new_fileobj.old_auto_flush_write(out)
[email protected]db111f72010-09-08 13:36:53311 should_flush = False
[email protected]e0de9cb2010-09-17 15:07:14312 new_fileobj.lock.acquire()
[email protected]9c531262010-09-08 13:41:13313 try:
[email protected]e0de9cb2010-09-17 15:07:14314 if (new_fileobj.delay and
315 (time.time() - new_fileobj.last_flushed_at) > new_fileobj.delay):
[email protected]db111f72010-09-08 13:36:53316 should_flush = True
[email protected]e0de9cb2010-09-17 15:07:14317 new_fileobj.last_flushed_at = time.time()
[email protected]9c531262010-09-08 13:41:13318 finally:
[email protected]e0de9cb2010-09-17 15:07:14319 new_fileobj.lock.release()
[email protected]db111f72010-09-08 13:36:53320 if should_flush:
[email protected]e0de9cb2010-09-17 15:07:14321 new_fileobj.flush()
[email protected]db111f72010-09-08 13:36:53322
[email protected]e0de9cb2010-09-17 15:07:14323 new_fileobj.write = auto_flush_write
324 return new_fileobj
[email protected]db111f72010-09-08 13:36:53325
326
[email protected]4ed34182010-09-17 15:57:47327def MakeFileAnnotated(fileobj):
328 """Creates a file object clone to automatically prepends every line in worker
329 threads with a NN> prefix."""
330 if hasattr(fileobj, 'output_buffers'):
331 # Already patched.
332 return fileobj
[email protected]cb1e97a2010-09-09 20:09:20333
[email protected]b17b55b2010-11-03 14:42:37334 # Attribute 'XXX' defined outside __init__
335 # pylint: disable=W0201
[email protected]4ed34182010-09-17 15:57:47336 new_fileobj = SoftClone(fileobj)
337 if not hasattr(new_fileobj, 'lock'):
338 new_fileobj.lock = threading.Lock()
339 new_fileobj.output_buffers = {}
340 new_fileobj.old_annotated_write = new_fileobj.write
[email protected]cb1e97a2010-09-09 20:09:20341
[email protected]4ed34182010-09-17 15:57:47342 def annotated_write(out):
343 index = getattr(threading.currentThread(), 'index', None)
344 if index is None:
345 # Undexed threads aren't buffered.
346 new_fileobj.old_annotated_write(out)
347 return
[email protected]cb1e97a2010-09-09 20:09:20348
[email protected]4ed34182010-09-17 15:57:47349 new_fileobj.lock.acquire()
350 try:
351 # Use a dummy array to hold the string so the code can be lockless.
352 # Strings are immutable, requiring to keep a lock for the whole dictionary
353 # otherwise. Using an array is faster than using a dummy object.
354 if not index in new_fileobj.output_buffers:
355 obj = new_fileobj.output_buffers[index] = ['']
356 else:
357 obj = new_fileobj.output_buffers[index]
358 finally:
359 new_fileobj.lock.release()
360
361 # Continue lockless.
362 obj[0] += out
363 while '\n' in obj[0]:
364 line, remaining = obj[0].split('\n', 1)
[email protected]e939bb52011-06-01 22:59:15365 if line:
366 new_fileobj.old_annotated_write('%d>%s\n' % (index, line))
[email protected]4ed34182010-09-17 15:57:47367 obj[0] = remaining
368
369 def full_flush():
370 """Flush buffered output."""
371 orphans = []
372 new_fileobj.lock.acquire()
373 try:
374 # Detect threads no longer existing.
375 indexes = (getattr(t, 'index', None) for t in threading.enumerate())
[email protected]cb2985f2010-11-03 14:08:31376 indexes = filter(None, indexes)
[email protected]4ed34182010-09-17 15:57:47377 for index in new_fileobj.output_buffers:
378 if not index in indexes:
379 orphans.append((index, new_fileobj.output_buffers[index][0]))
380 for orphan in orphans:
381 del new_fileobj.output_buffers[orphan[0]]
382 finally:
383 new_fileobj.lock.release()
384
385 # Don't keep the lock while writting. Will append \n when it shouldn't.
386 for orphan in orphans:
[email protected]e939bb52011-06-01 22:59:15387 if orphan[1]:
388 new_fileobj.old_annotated_write('%d>%s\n' % (orphan[0], orphan[1]))
[email protected]4ed34182010-09-17 15:57:47389
390 new_fileobj.write = annotated_write
391 new_fileobj.full_flush = full_flush
392 return new_fileobj
[email protected]cb1e97a2010-09-09 20:09:20393
394
[email protected]17d01792010-09-01 18:07:10395def CheckCallAndFilter(args, stdout=None, filter_fn=None,
396 print_stdout=None, call_filter_on_first_line=False,
397 **kwargs):
398 """Runs a command and calls back a filter function if needed.
399
400 Accepts all subprocess.Popen() parameters plus:
401 print_stdout: If True, the command's stdout is forwarded to stdout.
402 filter_fn: A function taking a single string argument called with each line
403 of the subprocess's output. Each line has the trailing newline
404 character trimmed.
405 stdout: Can be any bufferable output.
406
407 stderr is always redirected to stdout.
408 """
409 assert print_stdout or filter_fn
410 stdout = stdout or sys.stdout
411 filter_fn = filter_fn or (lambda x: None)
412 assert not 'stderr' in kwargs
[email protected]2b9aa8e2010-08-25 20:01:42413 kid = Popen(args, bufsize=0,
414 stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
415 **kwargs)
[email protected]5f3eee32009-09-17 00:34:30416
[email protected]17d01792010-09-01 18:07:10417 # Do a flush of stdout before we begin reading from the subprocess's stdout
[email protected]559c3f82010-08-23 19:26:08418 stdout.flush()
[email protected]8ad1cee2010-08-16 19:12:27419
[email protected]5f3eee32009-09-17 00:34:30420 # Also, we need to forward stdout to prevent weird re-ordering of output.
421 # This has to be done on a per byte basis to make sure it is not buffered:
422 # normally buffering is done for each line, but if svn requests input, no
423 # end-of-line character is output after the prompt and it would not show up.
424 in_byte = kid.stdout.read(1)
[email protected]17d01792010-09-01 18:07:10425 if in_byte:
426 if call_filter_on_first_line:
427 filter_fn(None)
428 in_line = ''
429 while in_byte:
430 if in_byte != '\r':
431 if print_stdout:
432 stdout.write(in_byte)
433 if in_byte != '\n':
434 in_line += in_byte
435 else:
436 filter_fn(in_line)
437 in_line = ''
[email protected]17d01792010-09-01 18:07:10438 in_byte = kid.stdout.read(1)
439 # Flush the rest of buffered output. This is only an issue with
440 # stdout/stderr not ending with a \n.
441 if len(in_line):
442 filter_fn(in_line)
[email protected]5f3eee32009-09-17 00:34:30443 rv = kid.wait()
[email protected]5f3eee32009-09-17 00:34:30444 if rv:
[email protected]7b194c12010-09-07 20:57:09445 raise CheckCallError(args, kwargs.get('cwd', None), rv, None)
[email protected]17d01792010-09-01 18:07:10446 return 0
[email protected]5f3eee32009-09-17 00:34:30447
448
[email protected]9eda4112010-06-11 18:56:10449def FindGclientRoot(from_dir, filename='.gclient'):
[email protected]a9371762009-12-22 18:27:38450 """Tries to find the gclient root."""
[email protected]20760a52010-09-08 08:47:28451 real_from_dir = os.path.realpath(from_dir)
452 path = real_from_dir
[email protected]9eda4112010-06-11 18:56:10453 while not os.path.exists(os.path.join(path, filename)):
[email protected]3a292682010-08-23 18:54:55454 split_path = os.path.split(path)
455 if not split_path[1]:
[email protected]a9371762009-12-22 18:27:38456 return None
[email protected]3a292682010-08-23 18:54:55457 path = split_path[0]
[email protected]20760a52010-09-08 08:47:28458
459 # If we did not find the file in the current directory, make sure we are in a
460 # sub directory that is controlled by this configuration.
461 if path != real_from_dir:
462 entries_filename = os.path.join(path, filename + '_entries')
463 if not os.path.exists(entries_filename):
464 # If .gclient_entries does not exist, a previous call to gclient sync
465 # might have failed. In that case, we cannot verify that the .gclient
466 # is the one we want to use. In order to not to cause too much trouble,
467 # just issue a warning and return the path anyway.
[email protected]cb2985f2010-11-03 14:08:31468 print >> sys.stderr, ("%s file in parent directory %s might not be the "
[email protected]20760a52010-09-08 08:47:28469 "file you want to use" % (filename, path))
470 return path
471 scope = {}
472 try:
473 exec(FileRead(entries_filename), scope)
474 except SyntaxError, e:
475 SyntaxErrorToError(filename, e)
476 all_directories = scope['entries'].keys()
477 path_to_check = real_from_dir[len(path)+1:]
478 while path_to_check:
479 if path_to_check in all_directories:
480 return path
481 path_to_check = os.path.dirname(path_to_check)
482 return None
[email protected]3742c842010-09-09 19:27:14483
[email protected]d9141bf2009-12-23 16:13:32484 logging.info('Found gclient root at ' + path)
[email protected]a9371762009-12-22 18:27:38485 return path
[email protected]3ccbf7e2009-12-22 20:46:42486
[email protected]9eda4112010-06-11 18:56:10487
[email protected]3ccbf7e2009-12-22 20:46:42488def PathDifference(root, subpath):
489 """Returns the difference subpath minus root."""
490 root = os.path.realpath(root)
491 subpath = os.path.realpath(subpath)
492 if not subpath.startswith(root):
493 return None
494 # If the root does not have a trailing \ or /, we add it so the returned
495 # path starts immediately after the seperator regardless of whether it is
496 # provided.
497 root = os.path.join(root, '')
498 return subpath[len(root):]
[email protected]f43d0192010-04-15 02:36:04499
500
501def FindFileUpwards(filename, path=None):
502 """Search upwards from the a directory (default: current) to find a file."""
503 if not path:
504 path = os.getcwd()
505 path = os.path.realpath(path)
506 while True:
507 file_path = os.path.join(path, filename)
508 if os.path.isfile(file_path):
509 return file_path
510 (new_path, _) = os.path.split(path)
511 if new_path == path:
512 return None
513 path = new_path
514
515
516def GetGClientRootAndEntries(path=None):
517 """Returns the gclient root and the dict of entries."""
518 config_file = '.gclient_entries'
519 config_path = FindFileUpwards(config_file, path)
520
521 if not config_path:
[email protected]116704f2010-06-11 17:34:38522 print "Can't find %s" % config_file
[email protected]f43d0192010-04-15 02:36:04523 return None
524
525 env = {}
526 execfile(config_path, env)
527 config_dir = os.path.dirname(config_path)
528 return config_dir, env['entries']
[email protected]80cbe8b2010-08-13 13:53:07529
530
531class WorkItem(object):
532 """One work item."""
533 # A list of string, each being a WorkItem name.
534 requirements = []
535 # A unique string representing this work item.
536 name = None
537
[email protected]77e4eca2010-09-21 13:23:07538 def run(self, work_queue):
539 """work_queue is passed as keyword argument so it should be
[email protected]3742c842010-09-09 19:27:14540 the last parameters of the function when you override it."""
[email protected]80cbe8b2010-08-13 13:53:07541 pass
542
543
544class ExecutionQueue(object):
[email protected]9e5317a2010-08-13 20:35:11545 """Runs a set of WorkItem that have interdependencies and were WorkItem are
546 added as they are processed.
[email protected]80cbe8b2010-08-13 13:53:07547
[email protected]9e5317a2010-08-13 20:35:11548 In gclient's case, Dependencies sometime needs to be run out of order due to
549 From() keyword. This class manages that all the required dependencies are run
550 before running each one.
[email protected]80cbe8b2010-08-13 13:53:07551
[email protected]9e5317a2010-08-13 20:35:11552 Methods of this class are thread safe.
[email protected]80cbe8b2010-08-13 13:53:07553 """
[email protected]9e5317a2010-08-13 20:35:11554 def __init__(self, jobs, progress):
555 """jobs specifies the number of concurrent tasks to allow. progress is a
556 Progress instance."""
[email protected]58ef2972011-04-01 21:00:11557 hack_subprocess()
[email protected]9e5317a2010-08-13 20:35:11558 # Set when a thread is done or a new item is enqueued.
559 self.ready_cond = threading.Condition()
560 # Maximum number of concurrent tasks.
561 self.jobs = jobs
562 # List of WorkItem, for gclient, these are Dependency instances.
[email protected]80cbe8b2010-08-13 13:53:07563 self.queued = []
564 # List of strings representing each Dependency.name that was run.
565 self.ran = []
566 # List of items currently running.
567 self.running = []
[email protected]9e5317a2010-08-13 20:35:11568 # Exceptions thrown if any.
[email protected]3742c842010-09-09 19:27:14569 self.exceptions = Queue.Queue()
570 # Progress status
[email protected]80cbe8b2010-08-13 13:53:07571 self.progress = progress
572 if self.progress:
[email protected]3742c842010-09-09 19:27:14573 self.progress.update(0)
[email protected]80cbe8b2010-08-13 13:53:07574
575 def enqueue(self, d):
576 """Enqueue one Dependency to be executed later once its requirements are
577 satisfied.
578 """
579 assert isinstance(d, WorkItem)
[email protected]9e5317a2010-08-13 20:35:11580 self.ready_cond.acquire()
[email protected]80cbe8b2010-08-13 13:53:07581 try:
[email protected]80cbe8b2010-08-13 13:53:07582 self.queued.append(d)
583 total = len(self.queued) + len(self.ran) + len(self.running)
[email protected]9e5317a2010-08-13 20:35:11584 logging.debug('enqueued(%s)' % d.name)
585 if self.progress:
586 self.progress._total = total + 1
587 self.progress.update(0)
588 self.ready_cond.notifyAll()
[email protected]80cbe8b2010-08-13 13:53:07589 finally:
[email protected]9e5317a2010-08-13 20:35:11590 self.ready_cond.release()
[email protected]80cbe8b2010-08-13 13:53:07591
592 def flush(self, *args, **kwargs):
593 """Runs all enqueued items until all are executed."""
[email protected]3742c842010-09-09 19:27:14594 kwargs['work_queue'] = self
[email protected]9e5317a2010-08-13 20:35:11595 self.ready_cond.acquire()
[email protected]80cbe8b2010-08-13 13:53:07596 try:
[email protected]9e5317a2010-08-13 20:35:11597 while True:
598 # Check for task to run first, then wait.
599 while True:
[email protected]3742c842010-09-09 19:27:14600 if not self.exceptions.empty():
601 # Systematically flush the queue when an exception logged.
[email protected]9e5317a2010-08-13 20:35:11602 self.queued = []
[email protected]3742c842010-09-09 19:27:14603 self._flush_terminated_threads()
604 if (not self.queued and not self.running or
605 self.jobs == len(self.running)):
606 # No more worker threads or can't queue anything.
[email protected]9e5317a2010-08-13 20:35:11607 break
[email protected]3742c842010-09-09 19:27:14608
609 # Check for new tasks to start.
[email protected]9e5317a2010-08-13 20:35:11610 for i in xrange(len(self.queued)):
611 # Verify its requirements.
612 for r in self.queued[i].requirements:
613 if not r in self.ran:
614 # Requirement not met.
615 break
616 else:
617 # Start one work item: all its requirements are satisfied.
[email protected]3742c842010-09-09 19:27:14618 self._run_one_task(self.queued.pop(i), args, kwargs)
[email protected]9e5317a2010-08-13 20:35:11619 break
620 else:
621 # Couldn't find an item that could run. Break out the outher loop.
622 break
[email protected]3742c842010-09-09 19:27:14623
[email protected]9e5317a2010-08-13 20:35:11624 if not self.queued and not self.running:
[email protected]3742c842010-09-09 19:27:14625 # We're done.
[email protected]9e5317a2010-08-13 20:35:11626 break
627 # We need to poll here otherwise Ctrl-C isn't processed.
628 self.ready_cond.wait(10)
629 # Something happened: self.enqueue() or a thread terminated. Loop again.
[email protected]80cbe8b2010-08-13 13:53:07630 finally:
[email protected]9e5317a2010-08-13 20:35:11631 self.ready_cond.release()
[email protected]3742c842010-09-09 19:27:14632
[email protected]9e5317a2010-08-13 20:35:11633 assert not self.running, 'Now guaranteed to be single-threaded'
[email protected]3742c842010-09-09 19:27:14634 if not self.exceptions.empty():
[email protected]c8d064b2010-08-16 16:46:14635 # To get back the stack location correctly, the raise a, b, c form must be
636 # used, passing a tuple as the first argument doesn't work.
[email protected]3742c842010-09-09 19:27:14637 e = self.exceptions.get()
[email protected]c8d064b2010-08-16 16:46:14638 raise e[0], e[1], e[2]
[email protected]80cbe8b2010-08-13 13:53:07639 if self.progress:
640 self.progress.end()
[email protected]80cbe8b2010-08-13 13:53:07641
[email protected]3742c842010-09-09 19:27:14642 def _flush_terminated_threads(self):
643 """Flush threads that have terminated."""
644 running = self.running
645 self.running = []
646 for t in running:
647 if t.isAlive():
648 self.running.append(t)
649 else:
650 t.join()
[email protected]97ae58e2011-03-18 00:29:20651 sys.stdout.full_flush() # pylint: disable=E1101
[email protected]3742c842010-09-09 19:27:14652 if self.progress:
[email protected]55a2eb82010-10-06 23:35:18653 self.progress.update(1, t.item.name)
[email protected]acc45672010-09-09 21:21:21654 assert not t.item.name in self.ran
655 if not t.item.name in self.ran:
656 self.ran.append(t.item.name)
[email protected]3742c842010-09-09 19:27:14657
658 def _run_one_task(self, task_item, args, kwargs):
659 if self.jobs > 1:
660 # Start the thread.
661 index = len(self.ran) + len(self.running) + 1
[email protected]77e4eca2010-09-21 13:23:07662 new_thread = self._Worker(task_item, index, args, kwargs)
[email protected]3742c842010-09-09 19:27:14663 self.running.append(new_thread)
664 new_thread.start()
665 else:
666 # Run the 'thread' inside the main thread. Don't try to catch any
667 # exception.
668 task_item.run(*args, **kwargs)
669 self.ran.append(task_item.name)
670 if self.progress:
[email protected]55a2eb82010-10-06 23:35:18671 self.progress.update(1, ', '.join(t.item.name for t in self.running))
[email protected]3742c842010-09-09 19:27:14672
[email protected]9e5317a2010-08-13 20:35:11673 class _Worker(threading.Thread):
674 """One thread to execute one WorkItem."""
[email protected]4ed34182010-09-17 15:57:47675 def __init__(self, item, index, args, kwargs):
[email protected]9e5317a2010-08-13 20:35:11676 threading.Thread.__init__(self, name=item.name or 'Worker')
[email protected]3742c842010-09-09 19:27:14677 logging.info(item.name)
[email protected]9e5317a2010-08-13 20:35:11678 self.item = item
[email protected]4ed34182010-09-17 15:57:47679 self.index = index
[email protected]3742c842010-09-09 19:27:14680 self.args = args
681 self.kwargs = kwargs
[email protected]80cbe8b2010-08-13 13:53:07682
[email protected]9e5317a2010-08-13 20:35:11683 def run(self):
684 """Runs in its own thread."""
685 logging.debug('running(%s)' % self.item.name)
[email protected]3742c842010-09-09 19:27:14686 work_queue = self.kwargs['work_queue']
[email protected]9e5317a2010-08-13 20:35:11687 try:
688 self.item.run(*self.args, **self.kwargs)
[email protected]c8d064b2010-08-16 16:46:14689 except Exception:
690 # Catch exception location.
[email protected]3742c842010-09-09 19:27:14691 logging.info('Caught exception in thread %s' % self.item.name)
692 logging.info(str(sys.exc_info()))
693 work_queue.exceptions.put(sys.exc_info())
694 logging.info('Task %s done' % self.item.name)
[email protected]9e5317a2010-08-13 20:35:11695
[email protected]3742c842010-09-09 19:27:14696 work_queue.ready_cond.acquire()
[email protected]9e5317a2010-08-13 20:35:11697 try:
[email protected]3742c842010-09-09 19:27:14698 work_queue.ready_cond.notifyAll()
[email protected]9e5317a2010-08-13 20:35:11699 finally:
[email protected]3742c842010-09-09 19:27:14700 work_queue.ready_cond.release()