blob: 069d5d759e669317815ea069353e1b05358f2554 [file] [log] [blame]
[email protected]99ac1c52012-01-16 14:52:121# Copyright (c) 2012 The Chromium Authors. All rights reserved.
[email protected]06617272010-11-04 13:50:502# 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]dae209f2012-07-03 16:08:157import codecs
[email protected]267f33e2014-02-28 22:02:328import cStringIO
[email protected]fe0d1902014-04-08 20:50:449import datetime
[email protected]d9141bf2009-12-23 16:13:3210import logging
[email protected]5f3eee32009-09-17 00:34:3011import os
[email protected]c28d3772013-07-12 19:42:3712import pipes
[email protected]fc616382014-03-18 20:32:0413import platform
[email protected]3742c842010-09-09 19:27:1414import Queue
[email protected]ac915bb2009-11-13 17:03:0115import re
[email protected]8f9c69f2009-09-17 00:48:2816import stat
[email protected]6b4a2ab2013-04-18 15:50:2717import subprocess
[email protected]5f3eee32009-09-17 00:34:3018import sys
[email protected]0e0436a2011-10-25 13:32:4119import tempfile
[email protected]9e5317a2010-08-13 20:35:1120import threading
[email protected]167b9e62009-09-17 17:41:0221import time
[email protected]eb5edbc2012-01-16 17:03:2822import urlparse
[email protected]5f3eee32009-09-17 00:34:3023
[email protected]ca0f8392011-09-08 17:15:1524import subprocess2
25
[email protected]5f3eee32009-09-17 00:34:3026
[email protected]f2d7d6b2013-10-17 20:41:4327RETRY_MAX = 3
28RETRY_INITIAL_SLEEP = 0.5
[email protected]fe0d1902014-04-08 20:50:4429START = datetime.datetime.now()
[email protected]f2d7d6b2013-10-17 20:41:4330
31
[email protected]6a9b1682014-03-24 18:35:2332_WARNINGS = []
33
34
[email protected]ff113292014-03-25 06:02:0835# These repos are known to cause OOM errors on 32-bit platforms, due the the
36# very large objects they contain. It is not safe to use threaded index-pack
37# when cloning/fetching them.
38THREADED_INDEX_PACK_BLACKLIST = [
39 'https://chromium.googlesource.com/chromium/reference_builds/chrome_win.git'
40]
41
42
[email protected]66c83e62010-09-07 14:18:4543class Error(Exception):
44 """gclient exception class."""
[email protected]4a3c17e2013-05-24 23:59:2945 def __init__(self, msg, *args, **kwargs):
46 index = getattr(threading.currentThread(), 'index', 0)
47 if index:
48 msg = '\n'.join('%d> %s' % (index, l) for l in msg.splitlines())
49 super(Error, self).__init__(msg, *args, **kwargs)
[email protected]66c83e62010-09-07 14:18:4550
[email protected]3ac1c4e2014-01-16 02:44:4251
[email protected]fe0d1902014-04-08 20:50:4452def Elapsed(until=None):
53 if until is None:
54 until = datetime.datetime.now()
55 return str(until - START).partition('.')[0]
56
57
[email protected]6a9b1682014-03-24 18:35:2358def PrintWarnings():
59 """Prints any accumulated warnings."""
60 if _WARNINGS:
61 print >> sys.stderr, '\n\nWarnings:'
62 for warning in _WARNINGS:
63 print >> sys.stderr, warning
64
65
66def AddWarning(msg):
67 """Adds the given warning message to the list of accumulated warnings."""
68 _WARNINGS.append(msg)
69
70
[email protected]ac915bb2009-11-13 17:03:0171def SplitUrlRevision(url):
72 """Splits url and returns a two-tuple: url, rev"""
73 if url.startswith('ssh:'):
[email protected]78b8cd12010-10-26 12:47:0774 # Make sure ssh://[email protected]/~/test.git@stable works
[email protected]71b13572013-10-16 17:28:1175 regex = r'(ssh://(?:[-.\w]+@)?[-\w:\.]+/[-~\w\./]+)(?:@(.+))?'
[email protected]ac915bb2009-11-13 17:03:0176 components = re.search(regex, url).groups()
77 else:
[email protected]f1eccaf2014-04-11 15:51:3378 components = url.rsplit('@', 1)
79 if re.match(r'^\w+\@', url) and '@' not in components[0]:
80 components = [url]
81
[email protected]ac915bb2009-11-13 17:03:0182 if len(components) == 1:
83 components += [None]
84 return tuple(components)
85
86
[email protected]5439ea52014-08-06 17:18:1887def IsGitSha(revision):
88 """Returns true if the given string is a valid hex-encoded sha"""
89 return re.match('^[a-fA-F0-9]{6,40}$', revision) is not None
90
91
[email protected]eaab7842011-04-28 09:07:5892def IsDateRevision(revision):
93 """Returns true if the given revision is of the form "{ ... }"."""
94 return bool(revision and re.match(r'^\{.+\}$', str(revision)))
95
96
97def MakeDateRevision(date):
98 """Returns a revision representing the latest revision before the given
99 date."""
100 return "{" + date + "}"
101
102
[email protected]5990f9d2010-07-07 18:02:58103def SyntaxErrorToError(filename, e):
104 """Raises a gclient_utils.Error exception with the human readable message"""
105 try:
106 # Try to construct a human readable error message
107 if filename:
108 error_message = 'There is a syntax error in %s\n' % filename
109 else:
110 error_message = 'There is a syntax error\n'
111 error_message += 'Line #%s, character %s: "%s"' % (
112 e.lineno, e.offset, re.sub(r'[\r\n]*$', '', e.text))
113 except:
114 # Something went wrong, re-raise the original exception
115 raise e
116 else:
117 raise Error(error_message)
118
119
[email protected]5f3eee32009-09-17 00:34:30120class PrintableObject(object):
121 def __str__(self):
122 output = ''
123 for i in dir(self):
124 if i.startswith('__'):
125 continue
126 output += '%s = %s\n' % (i, str(getattr(self, i, '')))
127 return output
128
129
[email protected]5aeb7dd2009-11-17 18:09:01130def FileRead(filename, mode='rU'):
[email protected]51e84fb2012-07-03 23:06:21131 with open(filename, mode=mode) as f:
[email protected]c3cd5372012-07-11 17:39:24132 # codecs.open() has different behavior than open() on python 2.6 so use
133 # open() and decode manually.
[email protected]2b99d432012-07-12 18:10:28134 s = f.read()
135 try:
136 return s.decode('utf-8')
137 except UnicodeDecodeError:
138 return s
[email protected]5f3eee32009-09-17 00:34:30139
140
[email protected]5aeb7dd2009-11-17 18:09:01141def FileWrite(filename, content, mode='w'):
[email protected]dae209f2012-07-03 16:08:15142 with codecs.open(filename, mode=mode, encoding='utf-8') as f:
[email protected]5f3eee32009-09-17 00:34:30143 f.write(content)
[email protected]5f3eee32009-09-17 00:34:30144
145
[email protected]ef509e42013-09-20 13:19:08146def safe_rename(old, new):
147 """Renames a file reliably.
148
[email protected]3ac1c4e2014-01-16 02:44:42149 Sometimes os.rename does not work because a dying git process keeps a handle
150 on it for a few seconds. An exception is then thrown, which make the program
[email protected]ef509e42013-09-20 13:19:08151 give up what it was doing and remove what was deleted.
[email protected]3ac1c4e2014-01-16 02:44:42152 The only solution is to catch the exception and try again until it works.
[email protected]ef509e42013-09-20 13:19:08153 """
154 # roughly 10s
155 retries = 100
156 for i in range(retries):
157 try:
158 os.rename(old, new)
159 break
160 except OSError:
161 if i == (retries - 1):
162 # Give up.
163 raise
164 # retry
165 logging.debug("Renaming failed from %s to %s. Retrying ..." % (old, new))
166 time.sleep(0.1)
167
168
[email protected]f9040722011-03-09 14:47:51169def rmtree(path):
170 """shutil.rmtree() on steroids.
[email protected]5f3eee32009-09-17 00:34:30171
[email protected]f9040722011-03-09 14:47:51172 Recursively removes a directory, even if it's marked read-only.
[email protected]5f3eee32009-09-17 00:34:30173
174 shutil.rmtree() doesn't work on Windows if any of the files or directories
175 are read-only, which svn repositories and some .svn files are. We need to
176 be able to force the files to be writable (i.e., deletable) as we traverse
177 the tree.
178
179 Even with all this, Windows still sometimes fails to delete a file, citing
180 a permission error (maybe something to do with antivirus scans or disk
181 indexing). The best suggestion any of the user forums had was to wait a
182 bit and try again, so we do that too. It's hand-waving, but sometimes it
183 works. :/
184
185 On POSIX systems, things are a little bit simpler. The modes of the files
186 to be deleted doesn't matter, only the modes of the directories containing
187 them are significant. As the directory tree is traversed, each directory
188 has its mode set appropriately before descending into it. This should
189 result in the entire tree being removed, with the possible exception of
190 *path itself, because nothing attempts to change the mode of its parent.
191 Doing so would be hazardous, as it's not a directory slated for removal.
192 In the ordinary case, this is not a problem: for our purposes, the user
193 will never lack write permission on *path's parent.
194 """
[email protected]f9040722011-03-09 14:47:51195 if not os.path.exists(path):
[email protected]5f3eee32009-09-17 00:34:30196 return
197
[email protected]f9040722011-03-09 14:47:51198 if os.path.islink(path) or not os.path.isdir(path):
199 raise Error('Called rmtree(%s) in non-directory' % path)
[email protected]5f3eee32009-09-17 00:34:30200
[email protected]5f3eee32009-09-17 00:34:30201 if sys.platform == 'win32':
[email protected]6b4a2ab2013-04-18 15:50:27202 # Give up and use cmd.exe's rd command.
203 path = os.path.normcase(path)
204 for _ in xrange(3):
205 exitcode = subprocess.call(['cmd.exe', '/c', 'rd', '/q', '/s', path])
206 if exitcode == 0:
207 return
208 else:
209 print >> sys.stderr, 'rd exited with code %d' % exitcode
210 time.sleep(3)
211 raise Exception('Failed to remove path %s' % path)
212
213 # On POSIX systems, we need the x-bit set on the directory to access it,
214 # the r-bit to see its contents, and the w-bit to remove files from it.
215 # The actual modes of the files within the directory is irrelevant.
216 os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
[email protected]5f3eee32009-09-17 00:34:30217
[email protected]f9040722011-03-09 14:47:51218 def remove(func, subpath):
[email protected]6b4a2ab2013-04-18 15:50:27219 func(subpath)
[email protected]f9040722011-03-09 14:47:51220
221 for fn in os.listdir(path):
[email protected]5f3eee32009-09-17 00:34:30222 # If fullpath is a symbolic link that points to a directory, isdir will
223 # be True, but we don't want to descend into that as a directory, we just
224 # want to remove the link. Check islink and treat links as ordinary files
225 # would be treated regardless of what they reference.
[email protected]f9040722011-03-09 14:47:51226 fullpath = os.path.join(path, fn)
[email protected]5f3eee32009-09-17 00:34:30227 if os.path.islink(fullpath) or not os.path.isdir(fullpath):
[email protected]f9040722011-03-09 14:47:51228 remove(os.remove, fullpath)
[email protected]5f3eee32009-09-17 00:34:30229 else:
[email protected]f9040722011-03-09 14:47:51230 # Recurse.
231 rmtree(fullpath)
[email protected]5f3eee32009-09-17 00:34:30232
[email protected]f9040722011-03-09 14:47:51233 remove(os.rmdir, path)
234
[email protected]5f3eee32009-09-17 00:34:30235
[email protected]6c48a302011-10-20 23:44:20236def safe_makedirs(tree):
237 """Creates the directory in a safe manner.
238
239 Because multiple threads can create these directories concurently, trap the
240 exception and pass on.
241 """
242 count = 0
243 while not os.path.exists(tree):
244 count += 1
245 try:
246 os.makedirs(tree)
247 except OSError, e:
248 # 17 POSIX, 183 Windows
249 if e.errno not in (17, 183):
250 raise
251 if count > 40:
252 # Give up.
253 raise
254
255
[email protected]c28d3772013-07-12 19:42:37256def CommandToStr(args):
257 """Converts an arg list into a shell escaped string."""
258 return ' '.join(pipes.quote(arg) for arg in args)
259
260
[email protected]f2ed3fb2012-11-09 23:39:49261def CheckCallAndFilterAndHeader(args, always=False, header=None, **kwargs):
[email protected]17d01792010-09-01 18:07:10262 """Adds 'header' support to CheckCallAndFilter.
[email protected]5f3eee32009-09-17 00:34:30263
[email protected]17d01792010-09-01 18:07:10264 If |always| is True, a message indicating what is being done
265 is printed to stdout all the time even if not output is generated. Otherwise
266 the message header is printed only if the call generated any ouput.
[email protected]5f3eee32009-09-17 00:34:30267 """
[email protected]f2ed3fb2012-11-09 23:39:49268 stdout = kwargs.setdefault('stdout', sys.stdout)
269 if header is None:
270 header = "\n________ running '%s' in '%s'\n" % (
[email protected]4aad1852013-07-12 21:32:51271 ' '.join(args), kwargs.get('cwd', '.'))
[email protected]f2ed3fb2012-11-09 23:39:49272
[email protected]17d01792010-09-01 18:07:10273 if always:
[email protected]f2ed3fb2012-11-09 23:39:49274 stdout.write(header)
[email protected]17d01792010-09-01 18:07:10275 else:
[email protected]f2ed3fb2012-11-09 23:39:49276 filter_fn = kwargs.get('filter_fn')
[email protected]17d01792010-09-01 18:07:10277 def filter_msg(line):
278 if line is None:
[email protected]f2ed3fb2012-11-09 23:39:49279 stdout.write(header)
[email protected]17d01792010-09-01 18:07:10280 elif filter_fn:
281 filter_fn(line)
282 kwargs['filter_fn'] = filter_msg
283 kwargs['call_filter_on_first_line'] = True
284 # Obviously.
[email protected]f2ed3fb2012-11-09 23:39:49285 kwargs.setdefault('print_stdout', True)
[email protected]17d01792010-09-01 18:07:10286 return CheckCallAndFilter(args, **kwargs)
[email protected]5f3eee32009-09-17 00:34:30287
[email protected]17d01792010-09-01 18:07:10288
[email protected]042f0e72011-10-23 00:04:35289class Wrapper(object):
290 """Wraps an object, acting as a transparent proxy for all properties by
291 default.
292 """
293 def __init__(self, wrapped):
294 self._wrapped = wrapped
295
296 def __getattr__(self, name):
297 return getattr(self._wrapped, name)
[email protected]db111f72010-09-08 13:36:53298
[email protected]e0de9cb2010-09-17 15:07:14299
[email protected]042f0e72011-10-23 00:04:35300class AutoFlush(Wrapper):
[email protected]e0de9cb2010-09-17 15:07:14301 """Creates a file object clone to automatically flush after N seconds."""
[email protected]042f0e72011-10-23 00:04:35302 def __init__(self, wrapped, delay):
303 super(AutoFlush, self).__init__(wrapped)
304 if not hasattr(self, 'lock'):
305 self.lock = threading.Lock()
306 self.__last_flushed_at = time.time()
307 self.delay = delay
[email protected]e0de9cb2010-09-17 15:07:14308
[email protected]042f0e72011-10-23 00:04:35309 @property
310 def autoflush(self):
311 return self
[email protected]e0de9cb2010-09-17 15:07:14312
[email protected]042f0e72011-10-23 00:04:35313 def write(self, out, *args, **kwargs):
314 self._wrapped.write(out, *args, **kwargs)
[email protected]db111f72010-09-08 13:36:53315 should_flush = False
[email protected]042f0e72011-10-23 00:04:35316 self.lock.acquire()
[email protected]9c531262010-09-08 13:41:13317 try:
[email protected]042f0e72011-10-23 00:04:35318 if self.delay and (time.time() - self.__last_flushed_at) > self.delay:
[email protected]db111f72010-09-08 13:36:53319 should_flush = True
[email protected]042f0e72011-10-23 00:04:35320 self.__last_flushed_at = time.time()
[email protected]9c531262010-09-08 13:41:13321 finally:
[email protected]042f0e72011-10-23 00:04:35322 self.lock.release()
[email protected]db111f72010-09-08 13:36:53323 if should_flush:
[email protected]042f0e72011-10-23 00:04:35324 self.flush()
[email protected]db111f72010-09-08 13:36:53325
326
[email protected]042f0e72011-10-23 00:04:35327class Annotated(Wrapper):
[email protected]4ed34182010-09-17 15:57:47328 """Creates a file object clone to automatically prepends every line in worker
[email protected]042f0e72011-10-23 00:04:35329 threads with a NN> prefix.
330 """
331 def __init__(self, wrapped, include_zero=False):
332 super(Annotated, self).__init__(wrapped)
333 if not hasattr(self, 'lock'):
334 self.lock = threading.Lock()
335 self.__output_buffers = {}
336 self.__include_zero = include_zero
[email protected]cb1e97a2010-09-09 20:09:20337
[email protected]042f0e72011-10-23 00:04:35338 @property
339 def annotated(self):
340 return self
[email protected]cb1e97a2010-09-09 20:09:20341
[email protected]042f0e72011-10-23 00:04:35342 def write(self, out):
343 index = getattr(threading.currentThread(), 'index', 0)
344 if not index and not self.__include_zero:
345 # Unindexed threads aren't buffered.
346 return self._wrapped.write(out)
[email protected]cb1e97a2010-09-09 20:09:20347
[email protected]042f0e72011-10-23 00:04:35348 self.lock.acquire()
[email protected]4ed34182010-09-17 15:57:47349 try:
350 # Use a dummy array to hold the string so the code can be lockless.
351 # Strings are immutable, requiring to keep a lock for the whole dictionary
352 # otherwise. Using an array is faster than using a dummy object.
[email protected]042f0e72011-10-23 00:04:35353 if not index in self.__output_buffers:
354 obj = self.__output_buffers[index] = ['']
[email protected]4ed34182010-09-17 15:57:47355 else:
[email protected]042f0e72011-10-23 00:04:35356 obj = self.__output_buffers[index]
[email protected]4ed34182010-09-17 15:57:47357 finally:
[email protected]042f0e72011-10-23 00:04:35358 self.lock.release()
[email protected]4ed34182010-09-17 15:57:47359
360 # Continue lockless.
361 obj[0] += out
362 while '\n' in obj[0]:
363 line, remaining = obj[0].split('\n', 1)
[email protected]e939bb52011-06-01 22:59:15364 if line:
[email protected]042f0e72011-10-23 00:04:35365 self._wrapped.write('%d>%s\n' % (index, line))
[email protected]4ed34182010-09-17 15:57:47366 obj[0] = remaining
367
[email protected]042f0e72011-10-23 00:04:35368 def flush(self):
[email protected]4ed34182010-09-17 15:57:47369 """Flush buffered output."""
370 orphans = []
[email protected]042f0e72011-10-23 00:04:35371 self.lock.acquire()
[email protected]4ed34182010-09-17 15:57:47372 try:
373 # Detect threads no longer existing.
374 indexes = (getattr(t, 'index', None) for t in threading.enumerate())
[email protected]cb2985f2010-11-03 14:08:31375 indexes = filter(None, indexes)
[email protected]042f0e72011-10-23 00:04:35376 for index in self.__output_buffers:
[email protected]4ed34182010-09-17 15:57:47377 if not index in indexes:
[email protected]042f0e72011-10-23 00:04:35378 orphans.append((index, self.__output_buffers[index][0]))
[email protected]4ed34182010-09-17 15:57:47379 for orphan in orphans:
[email protected]042f0e72011-10-23 00:04:35380 del self.__output_buffers[orphan[0]]
[email protected]4ed34182010-09-17 15:57:47381 finally:
[email protected]042f0e72011-10-23 00:04:35382 self.lock.release()
[email protected]4ed34182010-09-17 15:57:47383
384 # Don't keep the lock while writting. Will append \n when it shouldn't.
385 for orphan in orphans:
[email protected]e939bb52011-06-01 22:59:15386 if orphan[1]:
[email protected]042f0e72011-10-23 00:04:35387 self._wrapped.write('%d>%s\n' % (orphan[0], orphan[1]))
388 return self._wrapped.flush()
[email protected]4ed34182010-09-17 15:57:47389
[email protected]042f0e72011-10-23 00:04:35390
391def MakeFileAutoFlush(fileobj, delay=10):
392 autoflush = getattr(fileobj, 'autoflush', None)
393 if autoflush:
394 autoflush.delay = delay
395 return fileobj
396 return AutoFlush(fileobj, delay)
397
398
399def MakeFileAnnotated(fileobj, include_zero=False):
400 if getattr(fileobj, 'annotated', None):
401 return fileobj
402 return Annotated(fileobj)
[email protected]cb1e97a2010-09-09 20:09:20403
404
[email protected]2fd6c3f2013-05-03 21:57:55405GCLIENT_CHILDREN = []
406GCLIENT_CHILDREN_LOCK = threading.Lock()
407
408
409class GClientChildren(object):
410 @staticmethod
411 def add(popen_obj):
412 with GCLIENT_CHILDREN_LOCK:
413 GCLIENT_CHILDREN.append(popen_obj)
414
415 @staticmethod
416 def remove(popen_obj):
417 with GCLIENT_CHILDREN_LOCK:
418 GCLIENT_CHILDREN.remove(popen_obj)
419
420 @staticmethod
421 def _attemptToKillChildren():
422 global GCLIENT_CHILDREN
423 with GCLIENT_CHILDREN_LOCK:
424 zombies = [c for c in GCLIENT_CHILDREN if c.poll() is None]
425
426 for zombie in zombies:
427 try:
428 zombie.kill()
429 except OSError:
430 pass
431
432 with GCLIENT_CHILDREN_LOCK:
433 GCLIENT_CHILDREN = [k for k in GCLIENT_CHILDREN if k.poll() is not None]
434
435 @staticmethod
436 def _areZombies():
437 with GCLIENT_CHILDREN_LOCK:
438 return bool(GCLIENT_CHILDREN)
439
440 @staticmethod
441 def KillAllRemainingChildren():
442 GClientChildren._attemptToKillChildren()
443
444 if GClientChildren._areZombies():
445 time.sleep(0.5)
446 GClientChildren._attemptToKillChildren()
447
448 with GCLIENT_CHILDREN_LOCK:
449 if GCLIENT_CHILDREN:
450 print >> sys.stderr, 'Could not kill the following subprocesses:'
451 for zombie in GCLIENT_CHILDREN:
452 print >> sys.stderr, ' ', zombie.pid
453
454
[email protected]17d01792010-09-01 18:07:10455def CheckCallAndFilter(args, stdout=None, filter_fn=None,
456 print_stdout=None, call_filter_on_first_line=False,
[email protected]f2d7d6b2013-10-17 20:41:43457 retry=False, **kwargs):
[email protected]17d01792010-09-01 18:07:10458 """Runs a command and calls back a filter function if needed.
459
[email protected]57bf78d2011-09-08 18:57:33460 Accepts all subprocess2.Popen() parameters plus:
[email protected]17d01792010-09-01 18:07:10461 print_stdout: If True, the command's stdout is forwarded to stdout.
462 filter_fn: A function taking a single string argument called with each line
[email protected]57bf78d2011-09-08 18:57:33463 of the subprocess2's output. Each line has the trailing newline
[email protected]17d01792010-09-01 18:07:10464 character trimmed.
465 stdout: Can be any bufferable output.
[email protected]f2d7d6b2013-10-17 20:41:43466 retry: If the process exits non-zero, sleep for a brief interval and try
467 again, up to RETRY_MAX times.
[email protected]17d01792010-09-01 18:07:10468
469 stderr is always redirected to stdout.
470 """
471 assert print_stdout or filter_fn
472 stdout = stdout or sys.stdout
[email protected]267f33e2014-02-28 22:02:32473 output = cStringIO.StringIO()
[email protected]17d01792010-09-01 18:07:10474 filter_fn = filter_fn or (lambda x: None)
[email protected]5f3eee32009-09-17 00:34:30475
[email protected]f2d7d6b2013-10-17 20:41:43476 sleep_interval = RETRY_INITIAL_SLEEP
477 run_cwd = kwargs.get('cwd', os.getcwd())
478 for _ in xrange(RETRY_MAX + 1):
479 kid = subprocess2.Popen(
480 args, bufsize=0, stdout=subprocess2.PIPE, stderr=subprocess2.STDOUT,
481 **kwargs)
[email protected]2fd6c3f2013-05-03 21:57:55482
[email protected]f2d7d6b2013-10-17 20:41:43483 GClientChildren.add(kid)
[email protected]8ad1cee2010-08-16 19:12:27484
[email protected]f2d7d6b2013-10-17 20:41:43485 # Do a flush of stdout before we begin reading from the subprocess2's stdout
486 stdout.flush()
487
488 # Also, we need to forward stdout to prevent weird re-ordering of output.
489 # This has to be done on a per byte basis to make sure it is not buffered:
490 # normally buffering is done for each line, but if svn requests input, no
491 # end-of-line character is output after the prompt and it would not show up.
492 try:
493 in_byte = kid.stdout.read(1)
494 if in_byte:
495 if call_filter_on_first_line:
496 filter_fn(None)
497 in_line = ''
498 while in_byte:
[email protected]267f33e2014-02-28 22:02:32499 output.write(in_byte)
500 if print_stdout:
501 stdout.write(in_byte)
[email protected]fe0d1902014-04-08 20:50:44502 if in_byte not in ['\r', '\n']:
503 in_line += in_byte
[email protected]109cb9d2011-09-14 20:03:11504 else:
505 filter_fn(in_line)
506 in_line = ''
[email protected]f2d7d6b2013-10-17 20:41:43507 in_byte = kid.stdout.read(1)
508 # Flush the rest of buffered output. This is only an issue with
509 # stdout/stderr not ending with a \n.
510 if len(in_line):
[email protected]85d3e3a2011-10-07 17:12:00511 filter_fn(in_line)
[email protected]f2d7d6b2013-10-17 20:41:43512 rv = kid.wait()
[email protected]2fd6c3f2013-05-03 21:57:55513
[email protected]f2d7d6b2013-10-17 20:41:43514 # Don't put this in a 'finally,' since the child may still run if we get
515 # an exception.
516 GClientChildren.remove(kid)
[email protected]2fd6c3f2013-05-03 21:57:55517
[email protected]f2d7d6b2013-10-17 20:41:43518 except KeyboardInterrupt:
519 print >> sys.stderr, 'Failed while running "%s"' % ' '.join(args)
520 raise
[email protected]109cb9d2011-09-14 20:03:11521
[email protected]f2d7d6b2013-10-17 20:41:43522 if rv == 0:
[email protected]267f33e2014-02-28 22:02:32523 return output.getvalue()
[email protected]f2d7d6b2013-10-17 20:41:43524 if not retry:
525 break
526 print ("WARNING: subprocess '%s' in %s failed; will retry after a short "
527 'nap...' % (' '.join('"%s"' % x for x in args), run_cwd))
[email protected]91507f72013-10-22 12:18:25528 time.sleep(sleep_interval)
[email protected]f2d7d6b2013-10-17 20:41:43529 sleep_interval *= 2
530 raise subprocess2.CalledProcessError(
531 rv, args, kwargs.get('cwd', None), None, None)
[email protected]5f3eee32009-09-17 00:34:30532
533
[email protected]5a306a22014-02-24 22:13:59534class GitFilter(object):
535 """A filter_fn implementation for quieting down git output messages.
536
537 Allows a custom function to skip certain lines (predicate), and will throttle
538 the output of percentage completed lines to only output every X seconds.
539 """
[email protected]fe0d1902014-04-08 20:50:44540 PERCENT_RE = re.compile('(.*) ([0-9]{1,3})% .*')
[email protected]5a306a22014-02-24 22:13:59541
[email protected]fe0d1902014-04-08 20:50:44542 def __init__(self, time_throttle=0, predicate=None, out_fh=None):
[email protected]5a306a22014-02-24 22:13:59543 """
544 Args:
545 time_throttle (int): GitFilter will throttle 'noisy' output (such as the
546 XX% complete messages) to only be printed at least |time_throttle|
547 seconds apart.
548 predicate (f(line)): An optional function which is invoked for every line.
549 The line will be skipped if predicate(line) returns False.
[email protected]fe0d1902014-04-08 20:50:44550 out_fh: File handle to write output to.
[email protected]5a306a22014-02-24 22:13:59551 """
552 self.last_time = 0
553 self.time_throttle = time_throttle
554 self.predicate = predicate
[email protected]fe0d1902014-04-08 20:50:44555 self.out_fh = out_fh or sys.stdout
556 self.progress_prefix = None
[email protected]5a306a22014-02-24 22:13:59557
558 def __call__(self, line):
559 # git uses an escape sequence to clear the line; elide it.
560 esc = line.find(unichr(033))
561 if esc > -1:
562 line = line[:esc]
563 if self.predicate and not self.predicate(line):
564 return
565 now = time.time()
566 match = self.PERCENT_RE.match(line)
[email protected]fe0d1902014-04-08 20:50:44567 if match:
568 if match.group(1) != self.progress_prefix:
569 self.progress_prefix = match.group(1)
570 elif now - self.last_time < self.time_throttle:
571 return
572 self.last_time = now
573 self.out_fh.write('[%s] ' % Elapsed())
574 print >> self.out_fh, line
[email protected]5a306a22014-02-24 22:13:59575
576
[email protected]9eda4112010-06-11 18:56:10577def FindGclientRoot(from_dir, filename='.gclient'):
[email protected]a9371762009-12-22 18:27:38578 """Tries to find the gclient root."""
[email protected]20760a52010-09-08 08:47:28579 real_from_dir = os.path.realpath(from_dir)
580 path = real_from_dir
[email protected]9eda4112010-06-11 18:56:10581 while not os.path.exists(os.path.join(path, filename)):
[email protected]3a292682010-08-23 18:54:55582 split_path = os.path.split(path)
583 if not split_path[1]:
[email protected]a9371762009-12-22 18:27:38584 return None
[email protected]3a292682010-08-23 18:54:55585 path = split_path[0]
[email protected]20760a52010-09-08 08:47:28586
587 # If we did not find the file in the current directory, make sure we are in a
588 # sub directory that is controlled by this configuration.
589 if path != real_from_dir:
590 entries_filename = os.path.join(path, filename + '_entries')
591 if not os.path.exists(entries_filename):
592 # If .gclient_entries does not exist, a previous call to gclient sync
593 # might have failed. In that case, we cannot verify that the .gclient
594 # is the one we want to use. In order to not to cause too much trouble,
595 # just issue a warning and return the path anyway.
[email protected]cb2985f2010-11-03 14:08:31596 print >> sys.stderr, ("%s file in parent directory %s might not be the "
[email protected]20760a52010-09-08 08:47:28597 "file you want to use" % (filename, path))
598 return path
599 scope = {}
600 try:
601 exec(FileRead(entries_filename), scope)
602 except SyntaxError, e:
603 SyntaxErrorToError(filename, e)
604 all_directories = scope['entries'].keys()
605 path_to_check = real_from_dir[len(path)+1:]
606 while path_to_check:
607 if path_to_check in all_directories:
608 return path
609 path_to_check = os.path.dirname(path_to_check)
610 return None
[email protected]3742c842010-09-09 19:27:14611
[email protected]d9141bf2009-12-23 16:13:32612 logging.info('Found gclient root at ' + path)
[email protected]a9371762009-12-22 18:27:38613 return path
[email protected]3ccbf7e2009-12-22 20:46:42614
[email protected]9eda4112010-06-11 18:56:10615
[email protected]3ccbf7e2009-12-22 20:46:42616def PathDifference(root, subpath):
617 """Returns the difference subpath minus root."""
618 root = os.path.realpath(root)
619 subpath = os.path.realpath(subpath)
620 if not subpath.startswith(root):
621 return None
622 # If the root does not have a trailing \ or /, we add it so the returned
623 # path starts immediately after the seperator regardless of whether it is
624 # provided.
625 root = os.path.join(root, '')
626 return subpath[len(root):]
[email protected]f43d0192010-04-15 02:36:04627
628
629def FindFileUpwards(filename, path=None):
[email protected]13595ff2011-10-13 01:25:07630 """Search upwards from the a directory (default: current) to find a file.
[email protected]f2ed3fb2012-11-09 23:39:49631
[email protected]13595ff2011-10-13 01:25:07632 Returns nearest upper-level directory with the passed in file.
633 """
[email protected]f43d0192010-04-15 02:36:04634 if not path:
635 path = os.getcwd()
636 path = os.path.realpath(path)
637 while True:
638 file_path = os.path.join(path, filename)
[email protected]13595ff2011-10-13 01:25:07639 if os.path.exists(file_path):
640 return path
[email protected]f43d0192010-04-15 02:36:04641 (new_path, _) = os.path.split(path)
642 if new_path == path:
643 return None
644 path = new_path
645
646
[email protected]3ac1c4e2014-01-16 02:44:42647def GetMacWinOrLinux():
648 """Returns 'mac', 'win', or 'linux', matching the current platform."""
649 if sys.platform.startswith(('cygwin', 'win')):
650 return 'win'
651 elif sys.platform.startswith('linux'):
652 return 'linux'
653 elif sys.platform == 'darwin':
654 return 'mac'
655 raise Error('Unknown platform: ' + sys.platform)
656
657
[email protected]cc968fe2014-06-23 17:30:32658def GetBuildtoolsPath():
659 """Returns the full path to the buildtools directory.
660 This is based on the root of the checkout containing the current directory."""
[email protected]0db9a142014-08-13 23:15:25661
662 # Overriding the build tools path by environment is highly unsupported and may
663 # break without warning. Do not rely on this for anything important.
664 override = os.environ.get('CHROMIUM_BUILDTOOLS_PATH')
665 if override is not None:
666 return override
667
[email protected]cc968fe2014-06-23 17:30:32668 gclient_root = FindGclientRoot(os.getcwd())
669 if not gclient_root:
[email protected]aaee92f2014-07-02 07:35:31670 # Some projects might not use .gclient. Try to see whether we're in a git
671 # checkout.
672 top_dir = [os.getcwd()]
673 def filter_fn(line):
674 top_dir[0] = os.path.normpath(line.rstrip('\n'))
675 try:
676 CheckCallAndFilter(["git", "rev-parse", "--show-toplevel"],
677 print_stdout=False, filter_fn=filter_fn)
678 except Exception:
679 pass
680 top_dir = top_dir[0]
681 if os.path.exists(os.path.join(top_dir, 'buildtools')):
682 return os.path.join(top_dir, 'buildtools')
[email protected]cc968fe2014-06-23 17:30:32683 return None
[email protected]f7facfa2014-09-05 12:40:28684
685 # Some projects' top directory is not named 'src'.
686 source_dir_name = GetGClientPrimarySolutionName(gclient_root) or 'src'
[email protected]43e91582014-11-12 22:38:51687 buildtools_path = os.path.join(gclient_root, source_dir_name, 'buildtools')
688 if not os.path.exists(buildtools_path):
689 # Buildtools may be in the gclient root.
690 buildtools_path = os.path.join(gclient_root, 'buildtools')
691 return buildtools_path
[email protected]cc968fe2014-06-23 17:30:32692
693
694def GetBuildtoolsPlatformBinaryPath():
695 """Returns the full path to the binary directory for the current platform."""
696 # Mac and Windows just have one directory, Linux has two according to whether
697 # it's 32 or 64 bits.
698 buildtools_path = GetBuildtoolsPath()
699 if not buildtools_path:
700 return None
701
702 if sys.platform.startswith(('cygwin', 'win')):
703 subdir = 'win'
704 elif sys.platform == 'darwin':
705 subdir = 'mac'
706 elif sys.platform.startswith('linux'):
707 if sys.maxsize > 2**32:
708 subdir = 'linux64'
709 else:
710 subdir = 'linux32'
711 else:
712 raise Error('Unknown platform: ' + sys.platform)
713 return os.path.join(buildtools_path, subdir)
714
715
[email protected]3ac1c4e2014-01-16 02:44:42716def GetExeSuffix():
717 """Returns '' or '.exe' depending on how executables work on this platform."""
718 if sys.platform.startswith(('cygwin', 'win')):
719 return '.exe'
720 return ''
721
722
[email protected]f7facfa2014-09-05 12:40:28723def GetGClientPrimarySolutionName(gclient_root_dir_path):
724 """Returns the name of the primary solution in the .gclient file specified."""
725 gclient_config_file = os.path.join(gclient_root_dir_path, '.gclient')
726 env = {}
727 execfile(gclient_config_file, env)
728 solutions = env.get('solutions', [])
729 if solutions:
730 return solutions[0].get('name')
731 return None
732
733
[email protected]f43d0192010-04-15 02:36:04734def GetGClientRootAndEntries(path=None):
735 """Returns the gclient root and the dict of entries."""
736 config_file = '.gclient_entries'
[email protected]93a9ee02011-10-18 18:23:58737 root = FindFileUpwards(config_file, path)
738 if not root:
[email protected]116704f2010-06-11 17:34:38739 print "Can't find %s" % config_file
[email protected]f43d0192010-04-15 02:36:04740 return None
[email protected]93a9ee02011-10-18 18:23:58741 config_path = os.path.join(root, config_file)
[email protected]f43d0192010-04-15 02:36:04742 env = {}
743 execfile(config_path, env)
744 config_dir = os.path.dirname(config_path)
745 return config_dir, env['entries']
[email protected]80cbe8b2010-08-13 13:53:07746
747
[email protected]6ca8bf82011-09-19 23:04:30748def lockedmethod(method):
749 """Method decorator that holds self.lock for the duration of the call."""
750 def inner(self, *args, **kwargs):
751 try:
752 try:
753 self.lock.acquire()
754 except KeyboardInterrupt:
755 print >> sys.stderr, 'Was deadlocked'
756 raise
757 return method(self, *args, **kwargs)
758 finally:
759 self.lock.release()
760 return inner
761
762
[email protected]80cbe8b2010-08-13 13:53:07763class WorkItem(object):
764 """One work item."""
[email protected]4901daf2011-10-20 14:34:47765 # On cygwin, creating a lock throwing randomly when nearing ~100 locks.
766 # As a workaround, use a single lock. Yep you read it right. Single lock for
767 # all the 100 objects.
768 lock = threading.Lock()
769
[email protected]6ca8bf82011-09-19 23:04:30770 def __init__(self, name):
[email protected]485dcab2011-09-14 12:48:47771 # A unique string representing this work item.
[email protected]6ca8bf82011-09-19 23:04:30772 self._name = name
[email protected]fe0d1902014-04-08 20:50:44773 self.outbuf = cStringIO.StringIO()
774 self.start = self.finish = None
[email protected]80cbe8b2010-08-13 13:53:07775
[email protected]77e4eca2010-09-21 13:23:07776 def run(self, work_queue):
777 """work_queue is passed as keyword argument so it should be
[email protected]3742c842010-09-09 19:27:14778 the last parameters of the function when you override it."""
[email protected]80cbe8b2010-08-13 13:53:07779 pass
780
[email protected]6ca8bf82011-09-19 23:04:30781 @property
782 def name(self):
783 return self._name
784
[email protected]80cbe8b2010-08-13 13:53:07785
786class ExecutionQueue(object):
[email protected]9e5317a2010-08-13 20:35:11787 """Runs a set of WorkItem that have interdependencies and were WorkItem are
788 added as they are processed.
[email protected]80cbe8b2010-08-13 13:53:07789
[email protected]9e5317a2010-08-13 20:35:11790 In gclient's case, Dependencies sometime needs to be run out of order due to
791 From() keyword. This class manages that all the required dependencies are run
792 before running each one.
[email protected]80cbe8b2010-08-13 13:53:07793
[email protected]9e5317a2010-08-13 20:35:11794 Methods of this class are thread safe.
[email protected]80cbe8b2010-08-13 13:53:07795 """
[email protected]fe0d1902014-04-08 20:50:44796 def __init__(self, jobs, progress, ignore_requirements, verbose=False):
[email protected]9e5317a2010-08-13 20:35:11797 """jobs specifies the number of concurrent tasks to allow. progress is a
798 Progress instance."""
799 # Set when a thread is done or a new item is enqueued.
800 self.ready_cond = threading.Condition()
801 # Maximum number of concurrent tasks.
802 self.jobs = jobs
803 # List of WorkItem, for gclient, these are Dependency instances.
[email protected]80cbe8b2010-08-13 13:53:07804 self.queued = []
805 # List of strings representing each Dependency.name that was run.
806 self.ran = []
807 # List of items currently running.
808 self.running = []
[email protected]9e5317a2010-08-13 20:35:11809 # Exceptions thrown if any.
[email protected]3742c842010-09-09 19:27:14810 self.exceptions = Queue.Queue()
811 # Progress status
[email protected]80cbe8b2010-08-13 13:53:07812 self.progress = progress
813 if self.progress:
[email protected]3742c842010-09-09 19:27:14814 self.progress.update(0)
[email protected]80cbe8b2010-08-13 13:53:07815
[email protected]f2ed3fb2012-11-09 23:39:49816 self.ignore_requirements = ignore_requirements
[email protected]fe0d1902014-04-08 20:50:44817 self.verbose = verbose
818 self.last_join = None
819 self.last_subproc_output = None
[email protected]f2ed3fb2012-11-09 23:39:49820
[email protected]80cbe8b2010-08-13 13:53:07821 def enqueue(self, d):
822 """Enqueue one Dependency to be executed later once its requirements are
823 satisfied.
824 """
825 assert isinstance(d, WorkItem)
[email protected]9e5317a2010-08-13 20:35:11826 self.ready_cond.acquire()
[email protected]80cbe8b2010-08-13 13:53:07827 try:
[email protected]80cbe8b2010-08-13 13:53:07828 self.queued.append(d)
829 total = len(self.queued) + len(self.ran) + len(self.running)
[email protected]e98e04c2014-07-25 00:28:06830 if self.jobs == 1:
831 total += 1
[email protected]9e5317a2010-08-13 20:35:11832 logging.debug('enqueued(%s)' % d.name)
833 if self.progress:
[email protected]e98e04c2014-07-25 00:28:06834 self.progress._total = total
[email protected]9e5317a2010-08-13 20:35:11835 self.progress.update(0)
836 self.ready_cond.notifyAll()
[email protected]80cbe8b2010-08-13 13:53:07837 finally:
[email protected]9e5317a2010-08-13 20:35:11838 self.ready_cond.release()
[email protected]80cbe8b2010-08-13 13:53:07839
[email protected]fe0d1902014-04-08 20:50:44840 def out_cb(self, _):
841 self.last_subproc_output = datetime.datetime.now()
842 return True
843
844 @staticmethod
845 def format_task_output(task, comment=''):
846 if comment:
847 comment = ' (%s)' % comment
848 if task.start and task.finish:
849 elapsed = ' (Elapsed: %s)' % (
850 str(task.finish - task.start).partition('.')[0])
851 else:
852 elapsed = ''
853 return """
854%s%s%s
855----------------------------------------
856%s
857----------------------------------------""" % (
[email protected]1f4e71b2014-04-09 19:45:40858 task.name, comment, elapsed, task.outbuf.getvalue().strip())
[email protected]fe0d1902014-04-08 20:50:44859
[email protected]80cbe8b2010-08-13 13:53:07860 def flush(self, *args, **kwargs):
861 """Runs all enqueued items until all are executed."""
[email protected]3742c842010-09-09 19:27:14862 kwargs['work_queue'] = self
[email protected]fe0d1902014-04-08 20:50:44863 self.last_subproc_output = self.last_join = datetime.datetime.now()
[email protected]9e5317a2010-08-13 20:35:11864 self.ready_cond.acquire()
[email protected]80cbe8b2010-08-13 13:53:07865 try:
[email protected]9e5317a2010-08-13 20:35:11866 while True:
867 # Check for task to run first, then wait.
868 while True:
[email protected]3742c842010-09-09 19:27:14869 if not self.exceptions.empty():
870 # Systematically flush the queue when an exception logged.
[email protected]9e5317a2010-08-13 20:35:11871 self.queued = []
[email protected]3742c842010-09-09 19:27:14872 self._flush_terminated_threads()
873 if (not self.queued and not self.running or
874 self.jobs == len(self.running)):
[email protected]1333cb32011-10-04 23:40:16875 logging.debug('No more worker threads or can\'t queue anything.')
[email protected]9e5317a2010-08-13 20:35:11876 break
[email protected]3742c842010-09-09 19:27:14877
878 # Check for new tasks to start.
[email protected]9e5317a2010-08-13 20:35:11879 for i in xrange(len(self.queued)):
880 # Verify its requirements.
[email protected]f2ed3fb2012-11-09 23:39:49881 if (self.ignore_requirements or
882 not (set(self.queued[i].requirements) - set(self.ran))):
[email protected]9e5317a2010-08-13 20:35:11883 # Start one work item: all its requirements are satisfied.
[email protected]3742c842010-09-09 19:27:14884 self._run_one_task(self.queued.pop(i), args, kwargs)
[email protected]9e5317a2010-08-13 20:35:11885 break
886 else:
887 # Couldn't find an item that could run. Break out the outher loop.
888 break
[email protected]3742c842010-09-09 19:27:14889
[email protected]9e5317a2010-08-13 20:35:11890 if not self.queued and not self.running:
[email protected]3742c842010-09-09 19:27:14891 # We're done.
[email protected]9e5317a2010-08-13 20:35:11892 break
893 # We need to poll here otherwise Ctrl-C isn't processed.
[email protected]485dcab2011-09-14 12:48:47894 try:
895 self.ready_cond.wait(10)
[email protected]fe0d1902014-04-08 20:50:44896 # If we haven't printed to terminal for a while, but we have received
897 # spew from a suprocess, let the user know we're still progressing.
898 now = datetime.datetime.now()
899 if (now - self.last_join > datetime.timedelta(seconds=60) and
900 self.last_subproc_output > self.last_join):
901 if self.progress:
902 print >> sys.stdout, ''
[email protected]4dfb8662014-04-25 22:21:24903 sys.stdout.flush()
[email protected]fe0d1902014-04-08 20:50:44904 elapsed = Elapsed()
905 print >> sys.stdout, '[%s] Still working on:' % elapsed
[email protected]4dfb8662014-04-25 22:21:24906 sys.stdout.flush()
[email protected]fe0d1902014-04-08 20:50:44907 for task in self.running:
908 print >> sys.stdout, '[%s] %s' % (elapsed, task.item.name)
[email protected]4dfb8662014-04-25 22:21:24909 sys.stdout.flush()
[email protected]485dcab2011-09-14 12:48:47910 except KeyboardInterrupt:
911 # Help debugging by printing some information:
912 print >> sys.stderr, (
913 ('\nAllowed parallel jobs: %d\n# queued: %d\nRan: %s\n'
914 'Running: %d') % (
915 self.jobs,
916 len(self.queued),
917 ', '.join(self.ran),
918 len(self.running)))
919 for i in self.queued:
[email protected]fe0d1902014-04-08 20:50:44920 print >> sys.stderr, '%s (not started): %s' % (
921 i.name, ', '.join(i.requirements))
922 for i in self.running:
923 print >> sys.stderr, self.format_task_output(i.item, 'interrupted')
[email protected]485dcab2011-09-14 12:48:47924 raise
[email protected]9e5317a2010-08-13 20:35:11925 # Something happened: self.enqueue() or a thread terminated. Loop again.
[email protected]80cbe8b2010-08-13 13:53:07926 finally:
[email protected]9e5317a2010-08-13 20:35:11927 self.ready_cond.release()
[email protected]3742c842010-09-09 19:27:14928
[email protected]9e5317a2010-08-13 20:35:11929 assert not self.running, 'Now guaranteed to be single-threaded'
[email protected]3742c842010-09-09 19:27:14930 if not self.exceptions.empty():
[email protected]fe0d1902014-04-08 20:50:44931 if self.progress:
932 print >> sys.stdout, ''
[email protected]c8d064b2010-08-16 16:46:14933 # To get back the stack location correctly, the raise a, b, c form must be
934 # used, passing a tuple as the first argument doesn't work.
[email protected]fe0d1902014-04-08 20:50:44935 e, task = self.exceptions.get()
936 print >> sys.stderr, self.format_task_output(task.item, 'ERROR')
[email protected]c8d064b2010-08-16 16:46:14937 raise e[0], e[1], e[2]
[email protected]fe0d1902014-04-08 20:50:44938 elif self.progress:
[email protected]80cbe8b2010-08-13 13:53:07939 self.progress.end()
[email protected]80cbe8b2010-08-13 13:53:07940
[email protected]3742c842010-09-09 19:27:14941 def _flush_terminated_threads(self):
942 """Flush threads that have terminated."""
943 running = self.running
944 self.running = []
945 for t in running:
946 if t.isAlive():
947 self.running.append(t)
948 else:
949 t.join()
[email protected]fe0d1902014-04-08 20:50:44950 self.last_join = datetime.datetime.now()
[email protected]042f0e72011-10-23 00:04:35951 sys.stdout.flush()
[email protected]fe0d1902014-04-08 20:50:44952 if self.verbose:
953 print >> sys.stdout, self.format_task_output(t.item)
[email protected]3742c842010-09-09 19:27:14954 if self.progress:
[email protected]55a2eb82010-10-06 23:35:18955 self.progress.update(1, t.item.name)
[email protected]f36c0ee2011-09-14 19:16:47956 if t.item.name in self.ran:
957 raise Error(
958 'gclient is confused, "%s" is already in "%s"' % (
959 t.item.name, ', '.join(self.ran)))
[email protected]acc45672010-09-09 21:21:21960 if not t.item.name in self.ran:
961 self.ran.append(t.item.name)
[email protected]3742c842010-09-09 19:27:14962
963 def _run_one_task(self, task_item, args, kwargs):
964 if self.jobs > 1:
965 # Start the thread.
966 index = len(self.ran) + len(self.running) + 1
[email protected]77e4eca2010-09-21 13:23:07967 new_thread = self._Worker(task_item, index, args, kwargs)
[email protected]3742c842010-09-09 19:27:14968 self.running.append(new_thread)
969 new_thread.start()
970 else:
971 # Run the 'thread' inside the main thread. Don't try to catch any
972 # exception.
[email protected]fe0d1902014-04-08 20:50:44973 try:
974 task_item.start = datetime.datetime.now()
975 print >> task_item.outbuf, '[%s] Started.' % Elapsed(task_item.start)
976 task_item.run(*args, **kwargs)
977 task_item.finish = datetime.datetime.now()
978 print >> task_item.outbuf, '[%s] Finished.' % Elapsed(task_item.finish)
979 self.ran.append(task_item.name)
980 if self.verbose:
981 if self.progress:
982 print >> sys.stdout, ''
983 print >> sys.stdout, self.format_task_output(task_item)
984 if self.progress:
985 self.progress.update(1, ', '.join(t.item.name for t in self.running))
986 except KeyboardInterrupt:
987 print >> sys.stderr, self.format_task_output(task_item, 'interrupted')
988 raise
989 except Exception:
990 print >> sys.stderr, self.format_task_output(task_item, 'ERROR')
991 raise
992
[email protected]3742c842010-09-09 19:27:14993
[email protected]9e5317a2010-08-13 20:35:11994 class _Worker(threading.Thread):
995 """One thread to execute one WorkItem."""
[email protected]4ed34182010-09-17 15:57:47996 def __init__(self, item, index, args, kwargs):
[email protected]9e5317a2010-08-13 20:35:11997 threading.Thread.__init__(self, name=item.name or 'Worker')
[email protected]1333cb32011-10-04 23:40:16998 logging.info('_Worker(%s) reqs:%s' % (item.name, item.requirements))
[email protected]9e5317a2010-08-13 20:35:11999 self.item = item
[email protected]4ed34182010-09-17 15:57:471000 self.index = index
[email protected]3742c842010-09-09 19:27:141001 self.args = args
1002 self.kwargs = kwargs
[email protected]2fd6c3f2013-05-03 21:57:551003 self.daemon = True
[email protected]80cbe8b2010-08-13 13:53:071004
[email protected]9e5317a2010-08-13 20:35:111005 def run(self):
1006 """Runs in its own thread."""
[email protected]1333cb32011-10-04 23:40:161007 logging.debug('_Worker.run(%s)' % self.item.name)
[email protected]3742c842010-09-09 19:27:141008 work_queue = self.kwargs['work_queue']
[email protected]9e5317a2010-08-13 20:35:111009 try:
[email protected]fe0d1902014-04-08 20:50:441010 self.item.start = datetime.datetime.now()
1011 print >> self.item.outbuf, '[%s] Started.' % Elapsed(self.item.start)
[email protected]9e5317a2010-08-13 20:35:111012 self.item.run(*self.args, **self.kwargs)
[email protected]fe0d1902014-04-08 20:50:441013 self.item.finish = datetime.datetime.now()
1014 print >> self.item.outbuf, '[%s] Finished.' % Elapsed(self.item.finish)
[email protected]2fd6c3f2013-05-03 21:57:551015 except KeyboardInterrupt:
[email protected]c144e062013-05-03 23:23:531016 logging.info('Caught KeyboardInterrupt in thread %s', self.item.name)
[email protected]2fd6c3f2013-05-03 21:57:551017 logging.info(str(sys.exc_info()))
[email protected]fe0d1902014-04-08 20:50:441018 work_queue.exceptions.put((sys.exc_info(), self))
[email protected]2fd6c3f2013-05-03 21:57:551019 raise
[email protected]c8d064b2010-08-16 16:46:141020 except Exception:
1021 # Catch exception location.
[email protected]c144e062013-05-03 23:23:531022 logging.info('Caught exception in thread %s', self.item.name)
[email protected]3742c842010-09-09 19:27:141023 logging.info(str(sys.exc_info()))
[email protected]fe0d1902014-04-08 20:50:441024 work_queue.exceptions.put((sys.exc_info(), self))
[email protected]9e5317a2010-08-13 20:35:111025 finally:
[email protected]c144e062013-05-03 23:23:531026 logging.info('_Worker.run(%s) done', self.item.name)
[email protected]2fd6c3f2013-05-03 21:57:551027 work_queue.ready_cond.acquire()
1028 try:
1029 work_queue.ready_cond.notifyAll()
1030 finally:
1031 work_queue.ready_cond.release()
[email protected]0e0436a2011-10-25 13:32:411032
1033
[email protected]615a2622013-05-03 13:20:141034def GetEditor(git, git_editor=None):
1035 """Returns the most plausible editor to use.
1036
1037 In order of preference:
1038 - GIT_EDITOR/SVN_EDITOR environment variable
1039 - core.editor git configuration variable (if supplied by git-cl)
1040 - VISUAL environment variable
1041 - EDITOR environment variable
[email protected]65621c72013-12-09 15:05:321042 - vi (non-Windows) or notepad (Windows)
[email protected]615a2622013-05-03 13:20:141043
1044 In the case of git-cl, this matches git's behaviour, except that it does not
1045 include dumb terminal detection.
1046
1047 In the case of gcl, this matches svn's behaviour, except that it does not
1048 accept a command-line flag or check the editor-cmd configuration variable.
1049 """
[email protected]0e0436a2011-10-25 13:32:411050 if git:
[email protected]615a2622013-05-03 13:20:141051 editor = os.environ.get('GIT_EDITOR') or git_editor
[email protected]0e0436a2011-10-25 13:32:411052 else:
1053 editor = os.environ.get('SVN_EDITOR')
1054 if not editor:
[email protected]615a2622013-05-03 13:20:141055 editor = os.environ.get('VISUAL')
1056 if not editor:
[email protected]0e0436a2011-10-25 13:32:411057 editor = os.environ.get('EDITOR')
1058 if not editor:
1059 if sys.platform.startswith('win'):
1060 editor = 'notepad'
1061 else:
[email protected]65621c72013-12-09 15:05:321062 editor = 'vi'
[email protected]0e0436a2011-10-25 13:32:411063 return editor
1064
1065
[email protected]615a2622013-05-03 13:20:141066def RunEditor(content, git, git_editor=None):
[email protected]0e0436a2011-10-25 13:32:411067 """Opens up the default editor in the system to get the CL description."""
[email protected]cbd760f2013-07-23 13:02:481068 file_handle, filename = tempfile.mkstemp(text=True, prefix='cl_description')
[email protected]0e0436a2011-10-25 13:32:411069 # Make sure CRLF is handled properly by requiring none.
1070 if '\r' in content:
[email protected]0eff22d2011-10-25 16:11:161071 print >> sys.stderr, (
1072 '!! Please remove \\r from your change description !!')
[email protected]0e0436a2011-10-25 13:32:411073 fileobj = os.fdopen(file_handle, 'w')
1074 # Still remove \r if present.
1075 fileobj.write(re.sub('\r?\n', '\n', content))
1076 fileobj.close()
1077
1078 try:
[email protected]615a2622013-05-03 13:20:141079 editor = GetEditor(git, git_editor=git_editor)
1080 if not editor:
1081 return None
1082 cmd = '%s %s' % (editor, filename)
[email protected]0e0436a2011-10-25 13:32:411083 if sys.platform == 'win32' and os.environ.get('TERM') == 'msys':
1084 # Msysgit requires the usage of 'env' to be present.
1085 cmd = 'env ' + cmd
1086 try:
1087 # shell=True to allow the shell to handle all forms of quotes in
1088 # $EDITOR.
1089 subprocess2.check_call(cmd, shell=True)
1090 except subprocess2.CalledProcessError:
1091 return None
1092 return FileRead(filename)
1093 finally:
1094 os.remove(filename)
[email protected]99ac1c52012-01-16 14:52:121095
1096
[email protected]eb5edbc2012-01-16 17:03:281097def UpgradeToHttps(url):
1098 """Upgrades random urls to https://.
1099
1100 Do not touch unknown urls like ssh:// or git://.
1101 Do not touch http:// urls with a port number,
1102 Fixes invalid GAE url.
1103 """
1104 if not url:
1105 return url
1106 if not re.match(r'[a-z\-]+\://.*', url):
1107 # Make sure it is a valid uri. Otherwise, urlparse() will consider it a
1108 # relative url and will use http:///foo. Note that it defaults to http://
1109 # for compatibility with naked url like "localhost:8080".
1110 url = 'http://%s' % url
1111 parsed = list(urlparse.urlparse(url))
1112 # Do not automatically upgrade http to https if a port number is provided.
1113 if parsed[0] == 'http' and not re.match(r'^.+?\:\d+$', parsed[1]):
1114 parsed[0] = 'https'
[email protected]eb5edbc2012-01-16 17:03:281115 return urlparse.urlunparse(parsed)
1116
1117
[email protected]99ac1c52012-01-16 14:52:121118def ParseCodereviewSettingsContent(content):
1119 """Process a codereview.settings file properly."""
1120 lines = (l for l in content.splitlines() if not l.strip().startswith("#"))
1121 try:
1122 keyvals = dict([x.strip() for x in l.split(':', 1)] for l in lines if l)
1123 except ValueError:
1124 raise Error(
1125 'Failed to process settings, please fix. Content:\n\n%s' % content)
[email protected]eb5edbc2012-01-16 17:03:281126 def fix_url(key):
1127 if keyvals.get(key):
1128 keyvals[key] = UpgradeToHttps(keyvals[key])
1129 fix_url('CODE_REVIEW_SERVER')
1130 fix_url('VIEW_VC')
[email protected]99ac1c52012-01-16 14:52:121131 return keyvals
[email protected]13691502012-10-16 04:26:371132
1133
1134def NumLocalCpus():
1135 """Returns the number of processors.
1136
1137 Python on OSX 10.6 raises a NotImplementedError exception.
1138 """
1139 try:
1140 import multiprocessing
1141 return multiprocessing.cpu_count()
1142 except: # pylint: disable=W0702
1143 # Mac OS 10.6 only
1144 # pylint: disable=E1101
1145 return int(os.sysconf('SC_NPROCESSORS_ONLN'))
[email protected]fc616382014-03-18 20:32:041146
1147def DefaultDeltaBaseCacheLimit():
1148 """Return a reasonable default for the git config core.deltaBaseCacheLimit.
1149
1150 The primary constraint is the address space of virtual memory. The cache
1151 size limit is per-thread, and 32-bit systems can hit OOM errors if this
1152 parameter is set too high.
1153 """
1154 if platform.architecture()[0].startswith('64'):
1155 return '2g'
1156 else:
1157 return '512m'
1158
[email protected]ff113292014-03-25 06:02:081159def DefaultIndexPackConfig(url=''):
[email protected]fc616382014-03-18 20:32:041160 """Return reasonable default values for configuring git-index-pack.
1161
1162 Experiments suggest that higher values for pack.threads don't improve
1163 performance."""
[email protected]ff113292014-03-25 06:02:081164 cache_limit = DefaultDeltaBaseCacheLimit()
1165 result = ['-c', 'core.deltaBaseCacheLimit=%s' % cache_limit]
1166 if url in THREADED_INDEX_PACK_BLACKLIST:
1167 result.extend(['-c', 'pack.threads=1'])
1168 return result