blob: d2f0af683986ca573c1c67d1cb663f0593dbee3c [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]67b59e92014-12-25 13:48:37169def rm_file_or_tree(path):
170 if os.path.isfile(path):
171 os.remove(path)
172 else:
173 rmtree(path)
174
175
[email protected]f9040722011-03-09 14:47:51176def rmtree(path):
177 """shutil.rmtree() on steroids.
[email protected]5f3eee32009-09-17 00:34:30178
[email protected]f9040722011-03-09 14:47:51179 Recursively removes a directory, even if it's marked read-only.
[email protected]5f3eee32009-09-17 00:34:30180
181 shutil.rmtree() doesn't work on Windows if any of the files or directories
182 are read-only, which svn repositories and some .svn files are. We need to
183 be able to force the files to be writable (i.e., deletable) as we traverse
184 the tree.
185
186 Even with all this, Windows still sometimes fails to delete a file, citing
187 a permission error (maybe something to do with antivirus scans or disk
188 indexing). The best suggestion any of the user forums had was to wait a
189 bit and try again, so we do that too. It's hand-waving, but sometimes it
190 works. :/
191
192 On POSIX systems, things are a little bit simpler. The modes of the files
193 to be deleted doesn't matter, only the modes of the directories containing
194 them are significant. As the directory tree is traversed, each directory
195 has its mode set appropriately before descending into it. This should
196 result in the entire tree being removed, with the possible exception of
197 *path itself, because nothing attempts to change the mode of its parent.
198 Doing so would be hazardous, as it's not a directory slated for removal.
199 In the ordinary case, this is not a problem: for our purposes, the user
200 will never lack write permission on *path's parent.
201 """
[email protected]f9040722011-03-09 14:47:51202 if not os.path.exists(path):
[email protected]5f3eee32009-09-17 00:34:30203 return
204
[email protected]f9040722011-03-09 14:47:51205 if os.path.islink(path) or not os.path.isdir(path):
206 raise Error('Called rmtree(%s) in non-directory' % path)
[email protected]5f3eee32009-09-17 00:34:30207
[email protected]5f3eee32009-09-17 00:34:30208 if sys.platform == 'win32':
[email protected]6b4a2ab2013-04-18 15:50:27209 # Give up and use cmd.exe's rd command.
210 path = os.path.normcase(path)
211 for _ in xrange(3):
212 exitcode = subprocess.call(['cmd.exe', '/c', 'rd', '/q', '/s', path])
213 if exitcode == 0:
214 return
215 else:
216 print >> sys.stderr, 'rd exited with code %d' % exitcode
217 time.sleep(3)
218 raise Exception('Failed to remove path %s' % path)
219
220 # On POSIX systems, we need the x-bit set on the directory to access it,
221 # the r-bit to see its contents, and the w-bit to remove files from it.
222 # The actual modes of the files within the directory is irrelevant.
223 os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
[email protected]5f3eee32009-09-17 00:34:30224
[email protected]f9040722011-03-09 14:47:51225 def remove(func, subpath):
[email protected]6b4a2ab2013-04-18 15:50:27226 func(subpath)
[email protected]f9040722011-03-09 14:47:51227
228 for fn in os.listdir(path):
[email protected]5f3eee32009-09-17 00:34:30229 # If fullpath is a symbolic link that points to a directory, isdir will
230 # be True, but we don't want to descend into that as a directory, we just
231 # want to remove the link. Check islink and treat links as ordinary files
232 # would be treated regardless of what they reference.
[email protected]f9040722011-03-09 14:47:51233 fullpath = os.path.join(path, fn)
[email protected]5f3eee32009-09-17 00:34:30234 if os.path.islink(fullpath) or not os.path.isdir(fullpath):
[email protected]f9040722011-03-09 14:47:51235 remove(os.remove, fullpath)
[email protected]5f3eee32009-09-17 00:34:30236 else:
[email protected]f9040722011-03-09 14:47:51237 # Recurse.
238 rmtree(fullpath)
[email protected]5f3eee32009-09-17 00:34:30239
[email protected]f9040722011-03-09 14:47:51240 remove(os.rmdir, path)
241
[email protected]5f3eee32009-09-17 00:34:30242
[email protected]6c48a302011-10-20 23:44:20243def safe_makedirs(tree):
244 """Creates the directory in a safe manner.
245
246 Because multiple threads can create these directories concurently, trap the
247 exception and pass on.
248 """
249 count = 0
250 while not os.path.exists(tree):
251 count += 1
252 try:
253 os.makedirs(tree)
254 except OSError, e:
255 # 17 POSIX, 183 Windows
256 if e.errno not in (17, 183):
257 raise
258 if count > 40:
259 # Give up.
260 raise
261
262
[email protected]c28d3772013-07-12 19:42:37263def CommandToStr(args):
264 """Converts an arg list into a shell escaped string."""
265 return ' '.join(pipes.quote(arg) for arg in args)
266
267
[email protected]f2ed3fb2012-11-09 23:39:49268def CheckCallAndFilterAndHeader(args, always=False, header=None, **kwargs):
[email protected]17d01792010-09-01 18:07:10269 """Adds 'header' support to CheckCallAndFilter.
[email protected]5f3eee32009-09-17 00:34:30270
[email protected]17d01792010-09-01 18:07:10271 If |always| is True, a message indicating what is being done
272 is printed to stdout all the time even if not output is generated. Otherwise
273 the message header is printed only if the call generated any ouput.
[email protected]5f3eee32009-09-17 00:34:30274 """
[email protected]f2ed3fb2012-11-09 23:39:49275 stdout = kwargs.setdefault('stdout', sys.stdout)
276 if header is None:
277 header = "\n________ running '%s' in '%s'\n" % (
[email protected]4aad1852013-07-12 21:32:51278 ' '.join(args), kwargs.get('cwd', '.'))
[email protected]f2ed3fb2012-11-09 23:39:49279
[email protected]17d01792010-09-01 18:07:10280 if always:
[email protected]f2ed3fb2012-11-09 23:39:49281 stdout.write(header)
[email protected]17d01792010-09-01 18:07:10282 else:
[email protected]f2ed3fb2012-11-09 23:39:49283 filter_fn = kwargs.get('filter_fn')
[email protected]17d01792010-09-01 18:07:10284 def filter_msg(line):
285 if line is None:
[email protected]f2ed3fb2012-11-09 23:39:49286 stdout.write(header)
[email protected]17d01792010-09-01 18:07:10287 elif filter_fn:
288 filter_fn(line)
289 kwargs['filter_fn'] = filter_msg
290 kwargs['call_filter_on_first_line'] = True
291 # Obviously.
[email protected]f2ed3fb2012-11-09 23:39:49292 kwargs.setdefault('print_stdout', True)
[email protected]17d01792010-09-01 18:07:10293 return CheckCallAndFilter(args, **kwargs)
[email protected]5f3eee32009-09-17 00:34:30294
[email protected]17d01792010-09-01 18:07:10295
[email protected]042f0e72011-10-23 00:04:35296class Wrapper(object):
297 """Wraps an object, acting as a transparent proxy for all properties by
298 default.
299 """
300 def __init__(self, wrapped):
301 self._wrapped = wrapped
302
303 def __getattr__(self, name):
304 return getattr(self._wrapped, name)
[email protected]db111f72010-09-08 13:36:53305
[email protected]e0de9cb2010-09-17 15:07:14306
[email protected]042f0e72011-10-23 00:04:35307class AutoFlush(Wrapper):
[email protected]e0de9cb2010-09-17 15:07:14308 """Creates a file object clone to automatically flush after N seconds."""
[email protected]042f0e72011-10-23 00:04:35309 def __init__(self, wrapped, delay):
310 super(AutoFlush, self).__init__(wrapped)
311 if not hasattr(self, 'lock'):
312 self.lock = threading.Lock()
313 self.__last_flushed_at = time.time()
314 self.delay = delay
[email protected]e0de9cb2010-09-17 15:07:14315
[email protected]042f0e72011-10-23 00:04:35316 @property
317 def autoflush(self):
318 return self
[email protected]e0de9cb2010-09-17 15:07:14319
[email protected]042f0e72011-10-23 00:04:35320 def write(self, out, *args, **kwargs):
321 self._wrapped.write(out, *args, **kwargs)
[email protected]db111f72010-09-08 13:36:53322 should_flush = False
[email protected]042f0e72011-10-23 00:04:35323 self.lock.acquire()
[email protected]9c531262010-09-08 13:41:13324 try:
[email protected]042f0e72011-10-23 00:04:35325 if self.delay and (time.time() - self.__last_flushed_at) > self.delay:
[email protected]db111f72010-09-08 13:36:53326 should_flush = True
[email protected]042f0e72011-10-23 00:04:35327 self.__last_flushed_at = time.time()
[email protected]9c531262010-09-08 13:41:13328 finally:
[email protected]042f0e72011-10-23 00:04:35329 self.lock.release()
[email protected]db111f72010-09-08 13:36:53330 if should_flush:
[email protected]042f0e72011-10-23 00:04:35331 self.flush()
[email protected]db111f72010-09-08 13:36:53332
333
[email protected]042f0e72011-10-23 00:04:35334class Annotated(Wrapper):
[email protected]4ed34182010-09-17 15:57:47335 """Creates a file object clone to automatically prepends every line in worker
[email protected]042f0e72011-10-23 00:04:35336 threads with a NN> prefix.
337 """
338 def __init__(self, wrapped, include_zero=False):
339 super(Annotated, self).__init__(wrapped)
340 if not hasattr(self, 'lock'):
341 self.lock = threading.Lock()
342 self.__output_buffers = {}
343 self.__include_zero = include_zero
[email protected]cb1e97a2010-09-09 20:09:20344
[email protected]042f0e72011-10-23 00:04:35345 @property
346 def annotated(self):
347 return self
[email protected]cb1e97a2010-09-09 20:09:20348
[email protected]042f0e72011-10-23 00:04:35349 def write(self, out):
350 index = getattr(threading.currentThread(), 'index', 0)
351 if not index and not self.__include_zero:
352 # Unindexed threads aren't buffered.
353 return self._wrapped.write(out)
[email protected]cb1e97a2010-09-09 20:09:20354
[email protected]042f0e72011-10-23 00:04:35355 self.lock.acquire()
[email protected]4ed34182010-09-17 15:57:47356 try:
357 # Use a dummy array to hold the string so the code can be lockless.
358 # Strings are immutable, requiring to keep a lock for the whole dictionary
359 # otherwise. Using an array is faster than using a dummy object.
[email protected]042f0e72011-10-23 00:04:35360 if not index in self.__output_buffers:
361 obj = self.__output_buffers[index] = ['']
[email protected]4ed34182010-09-17 15:57:47362 else:
[email protected]042f0e72011-10-23 00:04:35363 obj = self.__output_buffers[index]
[email protected]4ed34182010-09-17 15:57:47364 finally:
[email protected]042f0e72011-10-23 00:04:35365 self.lock.release()
[email protected]4ed34182010-09-17 15:57:47366
367 # Continue lockless.
368 obj[0] += out
369 while '\n' in obj[0]:
370 line, remaining = obj[0].split('\n', 1)
[email protected]e939bb52011-06-01 22:59:15371 if line:
[email protected]042f0e72011-10-23 00:04:35372 self._wrapped.write('%d>%s\n' % (index, line))
[email protected]4ed34182010-09-17 15:57:47373 obj[0] = remaining
374
[email protected]042f0e72011-10-23 00:04:35375 def flush(self):
[email protected]4ed34182010-09-17 15:57:47376 """Flush buffered output."""
377 orphans = []
[email protected]042f0e72011-10-23 00:04:35378 self.lock.acquire()
[email protected]4ed34182010-09-17 15:57:47379 try:
380 # Detect threads no longer existing.
381 indexes = (getattr(t, 'index', None) for t in threading.enumerate())
[email protected]cb2985f2010-11-03 14:08:31382 indexes = filter(None, indexes)
[email protected]042f0e72011-10-23 00:04:35383 for index in self.__output_buffers:
[email protected]4ed34182010-09-17 15:57:47384 if not index in indexes:
[email protected]042f0e72011-10-23 00:04:35385 orphans.append((index, self.__output_buffers[index][0]))
[email protected]4ed34182010-09-17 15:57:47386 for orphan in orphans:
[email protected]042f0e72011-10-23 00:04:35387 del self.__output_buffers[orphan[0]]
[email protected]4ed34182010-09-17 15:57:47388 finally:
[email protected]042f0e72011-10-23 00:04:35389 self.lock.release()
[email protected]4ed34182010-09-17 15:57:47390
391 # Don't keep the lock while writting. Will append \n when it shouldn't.
392 for orphan in orphans:
[email protected]e939bb52011-06-01 22:59:15393 if orphan[1]:
[email protected]042f0e72011-10-23 00:04:35394 self._wrapped.write('%d>%s\n' % (orphan[0], orphan[1]))
395 return self._wrapped.flush()
[email protected]4ed34182010-09-17 15:57:47396
[email protected]042f0e72011-10-23 00:04:35397
398def MakeFileAutoFlush(fileobj, delay=10):
399 autoflush = getattr(fileobj, 'autoflush', None)
400 if autoflush:
401 autoflush.delay = delay
402 return fileobj
403 return AutoFlush(fileobj, delay)
404
405
406def MakeFileAnnotated(fileobj, include_zero=False):
407 if getattr(fileobj, 'annotated', None):
408 return fileobj
409 return Annotated(fileobj)
[email protected]cb1e97a2010-09-09 20:09:20410
411
[email protected]2fd6c3f2013-05-03 21:57:55412GCLIENT_CHILDREN = []
413GCLIENT_CHILDREN_LOCK = threading.Lock()
414
415
416class GClientChildren(object):
417 @staticmethod
418 def add(popen_obj):
419 with GCLIENT_CHILDREN_LOCK:
420 GCLIENT_CHILDREN.append(popen_obj)
421
422 @staticmethod
423 def remove(popen_obj):
424 with GCLIENT_CHILDREN_LOCK:
425 GCLIENT_CHILDREN.remove(popen_obj)
426
427 @staticmethod
428 def _attemptToKillChildren():
429 global GCLIENT_CHILDREN
430 with GCLIENT_CHILDREN_LOCK:
431 zombies = [c for c in GCLIENT_CHILDREN if c.poll() is None]
432
433 for zombie in zombies:
434 try:
435 zombie.kill()
436 except OSError:
437 pass
438
439 with GCLIENT_CHILDREN_LOCK:
440 GCLIENT_CHILDREN = [k for k in GCLIENT_CHILDREN if k.poll() is not None]
441
442 @staticmethod
443 def _areZombies():
444 with GCLIENT_CHILDREN_LOCK:
445 return bool(GCLIENT_CHILDREN)
446
447 @staticmethod
448 def KillAllRemainingChildren():
449 GClientChildren._attemptToKillChildren()
450
451 if GClientChildren._areZombies():
452 time.sleep(0.5)
453 GClientChildren._attemptToKillChildren()
454
455 with GCLIENT_CHILDREN_LOCK:
456 if GCLIENT_CHILDREN:
457 print >> sys.stderr, 'Could not kill the following subprocesses:'
458 for zombie in GCLIENT_CHILDREN:
459 print >> sys.stderr, ' ', zombie.pid
460
461
[email protected]17d01792010-09-01 18:07:10462def CheckCallAndFilter(args, stdout=None, filter_fn=None,
463 print_stdout=None, call_filter_on_first_line=False,
[email protected]f2d7d6b2013-10-17 20:41:43464 retry=False, **kwargs):
[email protected]17d01792010-09-01 18:07:10465 """Runs a command and calls back a filter function if needed.
466
[email protected]57bf78d2011-09-08 18:57:33467 Accepts all subprocess2.Popen() parameters plus:
[email protected]17d01792010-09-01 18:07:10468 print_stdout: If True, the command's stdout is forwarded to stdout.
469 filter_fn: A function taking a single string argument called with each line
[email protected]57bf78d2011-09-08 18:57:33470 of the subprocess2's output. Each line has the trailing newline
[email protected]17d01792010-09-01 18:07:10471 character trimmed.
472 stdout: Can be any bufferable output.
[email protected]f2d7d6b2013-10-17 20:41:43473 retry: If the process exits non-zero, sleep for a brief interval and try
474 again, up to RETRY_MAX times.
[email protected]17d01792010-09-01 18:07:10475
476 stderr is always redirected to stdout.
477 """
478 assert print_stdout or filter_fn
479 stdout = stdout or sys.stdout
[email protected]267f33e2014-02-28 22:02:32480 output = cStringIO.StringIO()
[email protected]17d01792010-09-01 18:07:10481 filter_fn = filter_fn or (lambda x: None)
[email protected]5f3eee32009-09-17 00:34:30482
[email protected]f2d7d6b2013-10-17 20:41:43483 sleep_interval = RETRY_INITIAL_SLEEP
484 run_cwd = kwargs.get('cwd', os.getcwd())
485 for _ in xrange(RETRY_MAX + 1):
486 kid = subprocess2.Popen(
487 args, bufsize=0, stdout=subprocess2.PIPE, stderr=subprocess2.STDOUT,
488 **kwargs)
[email protected]2fd6c3f2013-05-03 21:57:55489
[email protected]f2d7d6b2013-10-17 20:41:43490 GClientChildren.add(kid)
[email protected]8ad1cee2010-08-16 19:12:27491
[email protected]f2d7d6b2013-10-17 20:41:43492 # Do a flush of stdout before we begin reading from the subprocess2's stdout
493 stdout.flush()
494
495 # Also, we need to forward stdout to prevent weird re-ordering of output.
496 # This has to be done on a per byte basis to make sure it is not buffered:
497 # normally buffering is done for each line, but if svn requests input, no
498 # end-of-line character is output after the prompt and it would not show up.
499 try:
500 in_byte = kid.stdout.read(1)
501 if in_byte:
502 if call_filter_on_first_line:
503 filter_fn(None)
504 in_line = ''
505 while in_byte:
[email protected]267f33e2014-02-28 22:02:32506 output.write(in_byte)
507 if print_stdout:
508 stdout.write(in_byte)
[email protected]fe0d1902014-04-08 20:50:44509 if in_byte not in ['\r', '\n']:
510 in_line += in_byte
[email protected]109cb9d2011-09-14 20:03:11511 else:
512 filter_fn(in_line)
513 in_line = ''
[email protected]f2d7d6b2013-10-17 20:41:43514 in_byte = kid.stdout.read(1)
515 # Flush the rest of buffered output. This is only an issue with
516 # stdout/stderr not ending with a \n.
517 if len(in_line):
[email protected]85d3e3a2011-10-07 17:12:00518 filter_fn(in_line)
[email protected]f2d7d6b2013-10-17 20:41:43519 rv = kid.wait()
[email protected]2fd6c3f2013-05-03 21:57:55520
[email protected]f2d7d6b2013-10-17 20:41:43521 # Don't put this in a 'finally,' since the child may still run if we get
522 # an exception.
523 GClientChildren.remove(kid)
[email protected]2fd6c3f2013-05-03 21:57:55524
[email protected]f2d7d6b2013-10-17 20:41:43525 except KeyboardInterrupt:
526 print >> sys.stderr, 'Failed while running "%s"' % ' '.join(args)
527 raise
[email protected]109cb9d2011-09-14 20:03:11528
[email protected]f2d7d6b2013-10-17 20:41:43529 if rv == 0:
[email protected]267f33e2014-02-28 22:02:32530 return output.getvalue()
[email protected]f2d7d6b2013-10-17 20:41:43531 if not retry:
532 break
533 print ("WARNING: subprocess '%s' in %s failed; will retry after a short "
534 'nap...' % (' '.join('"%s"' % x for x in args), run_cwd))
[email protected]91507f72013-10-22 12:18:25535 time.sleep(sleep_interval)
[email protected]f2d7d6b2013-10-17 20:41:43536 sleep_interval *= 2
537 raise subprocess2.CalledProcessError(
538 rv, args, kwargs.get('cwd', None), None, None)
[email protected]5f3eee32009-09-17 00:34:30539
540
[email protected]5a306a22014-02-24 22:13:59541class GitFilter(object):
542 """A filter_fn implementation for quieting down git output messages.
543
544 Allows a custom function to skip certain lines (predicate), and will throttle
545 the output of percentage completed lines to only output every X seconds.
546 """
[email protected]fe0d1902014-04-08 20:50:44547 PERCENT_RE = re.compile('(.*) ([0-9]{1,3})% .*')
[email protected]5a306a22014-02-24 22:13:59548
[email protected]fe0d1902014-04-08 20:50:44549 def __init__(self, time_throttle=0, predicate=None, out_fh=None):
[email protected]5a306a22014-02-24 22:13:59550 """
551 Args:
552 time_throttle (int): GitFilter will throttle 'noisy' output (such as the
553 XX% complete messages) to only be printed at least |time_throttle|
554 seconds apart.
555 predicate (f(line)): An optional function which is invoked for every line.
556 The line will be skipped if predicate(line) returns False.
[email protected]fe0d1902014-04-08 20:50:44557 out_fh: File handle to write output to.
[email protected]5a306a22014-02-24 22:13:59558 """
559 self.last_time = 0
560 self.time_throttle = time_throttle
561 self.predicate = predicate
[email protected]fe0d1902014-04-08 20:50:44562 self.out_fh = out_fh or sys.stdout
563 self.progress_prefix = None
[email protected]5a306a22014-02-24 22:13:59564
565 def __call__(self, line):
566 # git uses an escape sequence to clear the line; elide it.
567 esc = line.find(unichr(033))
568 if esc > -1:
569 line = line[:esc]
570 if self.predicate and not self.predicate(line):
571 return
572 now = time.time()
573 match = self.PERCENT_RE.match(line)
[email protected]fe0d1902014-04-08 20:50:44574 if match:
575 if match.group(1) != self.progress_prefix:
576 self.progress_prefix = match.group(1)
577 elif now - self.last_time < self.time_throttle:
578 return
579 self.last_time = now
580 self.out_fh.write('[%s] ' % Elapsed())
581 print >> self.out_fh, line
[email protected]5a306a22014-02-24 22:13:59582
583
[email protected]9eda4112010-06-11 18:56:10584def FindGclientRoot(from_dir, filename='.gclient'):
[email protected]a9371762009-12-22 18:27:38585 """Tries to find the gclient root."""
[email protected]20760a52010-09-08 08:47:28586 real_from_dir = os.path.realpath(from_dir)
587 path = real_from_dir
[email protected]9eda4112010-06-11 18:56:10588 while not os.path.exists(os.path.join(path, filename)):
[email protected]3a292682010-08-23 18:54:55589 split_path = os.path.split(path)
590 if not split_path[1]:
[email protected]a9371762009-12-22 18:27:38591 return None
[email protected]3a292682010-08-23 18:54:55592 path = split_path[0]
[email protected]20760a52010-09-08 08:47:28593
594 # If we did not find the file in the current directory, make sure we are in a
595 # sub directory that is controlled by this configuration.
596 if path != real_from_dir:
597 entries_filename = os.path.join(path, filename + '_entries')
598 if not os.path.exists(entries_filename):
599 # If .gclient_entries does not exist, a previous call to gclient sync
600 # might have failed. In that case, we cannot verify that the .gclient
601 # is the one we want to use. In order to not to cause too much trouble,
602 # just issue a warning and return the path anyway.
[email protected]cb2985f2010-11-03 14:08:31603 print >> sys.stderr, ("%s file in parent directory %s might not be the "
[email protected]20760a52010-09-08 08:47:28604 "file you want to use" % (filename, path))
605 return path
606 scope = {}
607 try:
608 exec(FileRead(entries_filename), scope)
609 except SyntaxError, e:
610 SyntaxErrorToError(filename, e)
611 all_directories = scope['entries'].keys()
612 path_to_check = real_from_dir[len(path)+1:]
613 while path_to_check:
614 if path_to_check in all_directories:
615 return path
616 path_to_check = os.path.dirname(path_to_check)
617 return None
[email protected]3742c842010-09-09 19:27:14618
[email protected]d9141bf2009-12-23 16:13:32619 logging.info('Found gclient root at ' + path)
[email protected]a9371762009-12-22 18:27:38620 return path
[email protected]3ccbf7e2009-12-22 20:46:42621
[email protected]9eda4112010-06-11 18:56:10622
[email protected]3ccbf7e2009-12-22 20:46:42623def PathDifference(root, subpath):
624 """Returns the difference subpath minus root."""
625 root = os.path.realpath(root)
626 subpath = os.path.realpath(subpath)
627 if not subpath.startswith(root):
628 return None
629 # If the root does not have a trailing \ or /, we add it so the returned
630 # path starts immediately after the seperator regardless of whether it is
631 # provided.
632 root = os.path.join(root, '')
633 return subpath[len(root):]
[email protected]f43d0192010-04-15 02:36:04634
635
636def FindFileUpwards(filename, path=None):
[email protected]13595ff2011-10-13 01:25:07637 """Search upwards from the a directory (default: current) to find a file.
[email protected]f2ed3fb2012-11-09 23:39:49638
[email protected]13595ff2011-10-13 01:25:07639 Returns nearest upper-level directory with the passed in file.
640 """
[email protected]f43d0192010-04-15 02:36:04641 if not path:
642 path = os.getcwd()
643 path = os.path.realpath(path)
644 while True:
645 file_path = os.path.join(path, filename)
[email protected]13595ff2011-10-13 01:25:07646 if os.path.exists(file_path):
647 return path
[email protected]f43d0192010-04-15 02:36:04648 (new_path, _) = os.path.split(path)
649 if new_path == path:
650 return None
651 path = new_path
652
653
[email protected]3ac1c4e2014-01-16 02:44:42654def GetMacWinOrLinux():
655 """Returns 'mac', 'win', or 'linux', matching the current platform."""
656 if sys.platform.startswith(('cygwin', 'win')):
657 return 'win'
658 elif sys.platform.startswith('linux'):
659 return 'linux'
660 elif sys.platform == 'darwin':
661 return 'mac'
662 raise Error('Unknown platform: ' + sys.platform)
663
664
[email protected]e0a7c5d2015-02-23 20:30:08665def GetPrimarySolutionPath():
666 """Returns the full path to the primary solution. (gclient_root + src)"""
[email protected]0db9a142014-08-13 23:15:25667
[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')):
[email protected]d6d15b82015-04-20 06:43:48682 return top_dir
[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]e0a7c5d2015-02-23 20:30:08687 return os.path.join(gclient_root, source_dir_name)
688
689
690def GetBuildtoolsPath():
691 """Returns the full path to the buildtools directory.
692 This is based on the root of the checkout containing the current directory."""
693
694 # Overriding the build tools path by environment is highly unsupported and may
695 # break without warning. Do not rely on this for anything important.
696 override = os.environ.get('CHROMIUM_BUILDTOOLS_PATH')
697 if override is not None:
698 return override
699
700 primary_solution = GetPrimarySolutionPath()
701 buildtools_path = os.path.join(primary_solution, 'buildtools')
[email protected]43e91582014-11-12 22:38:51702 if not os.path.exists(buildtools_path):
703 # Buildtools may be in the gclient root.
[email protected]e0a7c5d2015-02-23 20:30:08704 gclient_root = FindGclientRoot(os.getcwd())
[email protected]43e91582014-11-12 22:38:51705 buildtools_path = os.path.join(gclient_root, 'buildtools')
706 return buildtools_path
[email protected]cc968fe2014-06-23 17:30:32707
708
709def GetBuildtoolsPlatformBinaryPath():
710 """Returns the full path to the binary directory for the current platform."""
711 # Mac and Windows just have one directory, Linux has two according to whether
712 # it's 32 or 64 bits.
713 buildtools_path = GetBuildtoolsPath()
714 if not buildtools_path:
715 return None
716
717 if sys.platform.startswith(('cygwin', 'win')):
718 subdir = 'win'
719 elif sys.platform == 'darwin':
720 subdir = 'mac'
721 elif sys.platform.startswith('linux'):
722 if sys.maxsize > 2**32:
723 subdir = 'linux64'
724 else:
725 subdir = 'linux32'
726 else:
727 raise Error('Unknown platform: ' + sys.platform)
728 return os.path.join(buildtools_path, subdir)
729
730
[email protected]3ac1c4e2014-01-16 02:44:42731def GetExeSuffix():
732 """Returns '' or '.exe' depending on how executables work on this platform."""
733 if sys.platform.startswith(('cygwin', 'win')):
734 return '.exe'
735 return ''
736
737
[email protected]f7facfa2014-09-05 12:40:28738def GetGClientPrimarySolutionName(gclient_root_dir_path):
739 """Returns the name of the primary solution in the .gclient file specified."""
740 gclient_config_file = os.path.join(gclient_root_dir_path, '.gclient')
741 env = {}
742 execfile(gclient_config_file, env)
743 solutions = env.get('solutions', [])
744 if solutions:
745 return solutions[0].get('name')
746 return None
747
748
[email protected]f43d0192010-04-15 02:36:04749def GetGClientRootAndEntries(path=None):
750 """Returns the gclient root and the dict of entries."""
751 config_file = '.gclient_entries'
[email protected]93a9ee02011-10-18 18:23:58752 root = FindFileUpwards(config_file, path)
753 if not root:
[email protected]116704f2010-06-11 17:34:38754 print "Can't find %s" % config_file
[email protected]f43d0192010-04-15 02:36:04755 return None
[email protected]93a9ee02011-10-18 18:23:58756 config_path = os.path.join(root, config_file)
[email protected]f43d0192010-04-15 02:36:04757 env = {}
758 execfile(config_path, env)
759 config_dir = os.path.dirname(config_path)
760 return config_dir, env['entries']
[email protected]80cbe8b2010-08-13 13:53:07761
762
[email protected]6ca8bf82011-09-19 23:04:30763def lockedmethod(method):
764 """Method decorator that holds self.lock for the duration of the call."""
765 def inner(self, *args, **kwargs):
766 try:
767 try:
768 self.lock.acquire()
769 except KeyboardInterrupt:
770 print >> sys.stderr, 'Was deadlocked'
771 raise
772 return method(self, *args, **kwargs)
773 finally:
774 self.lock.release()
775 return inner
776
777
[email protected]80cbe8b2010-08-13 13:53:07778class WorkItem(object):
779 """One work item."""
[email protected]4901daf2011-10-20 14:34:47780 # On cygwin, creating a lock throwing randomly when nearing ~100 locks.
781 # As a workaround, use a single lock. Yep you read it right. Single lock for
782 # all the 100 objects.
783 lock = threading.Lock()
784
[email protected]6ca8bf82011-09-19 23:04:30785 def __init__(self, name):
[email protected]485dcab2011-09-14 12:48:47786 # A unique string representing this work item.
[email protected]6ca8bf82011-09-19 23:04:30787 self._name = name
[email protected]fe0d1902014-04-08 20:50:44788 self.outbuf = cStringIO.StringIO()
789 self.start = self.finish = None
[email protected]80cbe8b2010-08-13 13:53:07790
[email protected]77e4eca2010-09-21 13:23:07791 def run(self, work_queue):
792 """work_queue is passed as keyword argument so it should be
[email protected]3742c842010-09-09 19:27:14793 the last parameters of the function when you override it."""
[email protected]80cbe8b2010-08-13 13:53:07794 pass
795
[email protected]6ca8bf82011-09-19 23:04:30796 @property
797 def name(self):
798 return self._name
799
[email protected]80cbe8b2010-08-13 13:53:07800
801class ExecutionQueue(object):
[email protected]9e5317a2010-08-13 20:35:11802 """Runs a set of WorkItem that have interdependencies and were WorkItem are
803 added as they are processed.
[email protected]80cbe8b2010-08-13 13:53:07804
[email protected]9e5317a2010-08-13 20:35:11805 In gclient's case, Dependencies sometime needs to be run out of order due to
806 From() keyword. This class manages that all the required dependencies are run
807 before running each one.
[email protected]80cbe8b2010-08-13 13:53:07808
[email protected]9e5317a2010-08-13 20:35:11809 Methods of this class are thread safe.
[email protected]80cbe8b2010-08-13 13:53:07810 """
[email protected]fe0d1902014-04-08 20:50:44811 def __init__(self, jobs, progress, ignore_requirements, verbose=False):
[email protected]9e5317a2010-08-13 20:35:11812 """jobs specifies the number of concurrent tasks to allow. progress is a
813 Progress instance."""
814 # Set when a thread is done or a new item is enqueued.
815 self.ready_cond = threading.Condition()
816 # Maximum number of concurrent tasks.
817 self.jobs = jobs
818 # List of WorkItem, for gclient, these are Dependency instances.
[email protected]80cbe8b2010-08-13 13:53:07819 self.queued = []
820 # List of strings representing each Dependency.name that was run.
821 self.ran = []
822 # List of items currently running.
823 self.running = []
[email protected]9e5317a2010-08-13 20:35:11824 # Exceptions thrown if any.
[email protected]3742c842010-09-09 19:27:14825 self.exceptions = Queue.Queue()
826 # Progress status
[email protected]80cbe8b2010-08-13 13:53:07827 self.progress = progress
828 if self.progress:
[email protected]3742c842010-09-09 19:27:14829 self.progress.update(0)
[email protected]80cbe8b2010-08-13 13:53:07830
[email protected]f2ed3fb2012-11-09 23:39:49831 self.ignore_requirements = ignore_requirements
[email protected]fe0d1902014-04-08 20:50:44832 self.verbose = verbose
833 self.last_join = None
834 self.last_subproc_output = None
[email protected]f2ed3fb2012-11-09 23:39:49835
[email protected]80cbe8b2010-08-13 13:53:07836 def enqueue(self, d):
837 """Enqueue one Dependency to be executed later once its requirements are
838 satisfied.
839 """
840 assert isinstance(d, WorkItem)
[email protected]9e5317a2010-08-13 20:35:11841 self.ready_cond.acquire()
[email protected]80cbe8b2010-08-13 13:53:07842 try:
[email protected]80cbe8b2010-08-13 13:53:07843 self.queued.append(d)
844 total = len(self.queued) + len(self.ran) + len(self.running)
[email protected]e98e04c2014-07-25 00:28:06845 if self.jobs == 1:
846 total += 1
[email protected]9e5317a2010-08-13 20:35:11847 logging.debug('enqueued(%s)' % d.name)
848 if self.progress:
[email protected]e98e04c2014-07-25 00:28:06849 self.progress._total = total
[email protected]9e5317a2010-08-13 20:35:11850 self.progress.update(0)
851 self.ready_cond.notifyAll()
[email protected]80cbe8b2010-08-13 13:53:07852 finally:
[email protected]9e5317a2010-08-13 20:35:11853 self.ready_cond.release()
[email protected]80cbe8b2010-08-13 13:53:07854
[email protected]fe0d1902014-04-08 20:50:44855 def out_cb(self, _):
856 self.last_subproc_output = datetime.datetime.now()
857 return True
858
859 @staticmethod
860 def format_task_output(task, comment=''):
861 if comment:
862 comment = ' (%s)' % comment
863 if task.start and task.finish:
864 elapsed = ' (Elapsed: %s)' % (
865 str(task.finish - task.start).partition('.')[0])
866 else:
867 elapsed = ''
868 return """
869%s%s%s
870----------------------------------------
871%s
872----------------------------------------""" % (
[email protected]1f4e71b2014-04-09 19:45:40873 task.name, comment, elapsed, task.outbuf.getvalue().strip())
[email protected]fe0d1902014-04-08 20:50:44874
[email protected]80cbe8b2010-08-13 13:53:07875 def flush(self, *args, **kwargs):
876 """Runs all enqueued items until all are executed."""
[email protected]3742c842010-09-09 19:27:14877 kwargs['work_queue'] = self
[email protected]fe0d1902014-04-08 20:50:44878 self.last_subproc_output = self.last_join = datetime.datetime.now()
[email protected]9e5317a2010-08-13 20:35:11879 self.ready_cond.acquire()
[email protected]80cbe8b2010-08-13 13:53:07880 try:
[email protected]9e5317a2010-08-13 20:35:11881 while True:
882 # Check for task to run first, then wait.
883 while True:
[email protected]3742c842010-09-09 19:27:14884 if not self.exceptions.empty():
885 # Systematically flush the queue when an exception logged.
[email protected]9e5317a2010-08-13 20:35:11886 self.queued = []
[email protected]3742c842010-09-09 19:27:14887 self._flush_terminated_threads()
888 if (not self.queued and not self.running or
889 self.jobs == len(self.running)):
[email protected]1333cb32011-10-04 23:40:16890 logging.debug('No more worker threads or can\'t queue anything.')
[email protected]9e5317a2010-08-13 20:35:11891 break
[email protected]3742c842010-09-09 19:27:14892
893 # Check for new tasks to start.
[email protected]9e5317a2010-08-13 20:35:11894 for i in xrange(len(self.queued)):
895 # Verify its requirements.
[email protected]f2ed3fb2012-11-09 23:39:49896 if (self.ignore_requirements or
897 not (set(self.queued[i].requirements) - set(self.ran))):
[email protected]9e5317a2010-08-13 20:35:11898 # Start one work item: all its requirements are satisfied.
[email protected]3742c842010-09-09 19:27:14899 self._run_one_task(self.queued.pop(i), args, kwargs)
[email protected]9e5317a2010-08-13 20:35:11900 break
901 else:
902 # Couldn't find an item that could run. Break out the outher loop.
903 break
[email protected]3742c842010-09-09 19:27:14904
[email protected]9e5317a2010-08-13 20:35:11905 if not self.queued and not self.running:
[email protected]3742c842010-09-09 19:27:14906 # We're done.
[email protected]9e5317a2010-08-13 20:35:11907 break
908 # We need to poll here otherwise Ctrl-C isn't processed.
[email protected]485dcab2011-09-14 12:48:47909 try:
910 self.ready_cond.wait(10)
[email protected]fe0d1902014-04-08 20:50:44911 # If we haven't printed to terminal for a while, but we have received
912 # spew from a suprocess, let the user know we're still progressing.
913 now = datetime.datetime.now()
914 if (now - self.last_join > datetime.timedelta(seconds=60) and
915 self.last_subproc_output > self.last_join):
916 if self.progress:
917 print >> sys.stdout, ''
[email protected]4dfb8662014-04-25 22:21:24918 sys.stdout.flush()
[email protected]fe0d1902014-04-08 20:50:44919 elapsed = Elapsed()
920 print >> sys.stdout, '[%s] Still working on:' % elapsed
[email protected]4dfb8662014-04-25 22:21:24921 sys.stdout.flush()
[email protected]fe0d1902014-04-08 20:50:44922 for task in self.running:
923 print >> sys.stdout, '[%s] %s' % (elapsed, task.item.name)
[email protected]4dfb8662014-04-25 22:21:24924 sys.stdout.flush()
[email protected]485dcab2011-09-14 12:48:47925 except KeyboardInterrupt:
926 # Help debugging by printing some information:
927 print >> sys.stderr, (
928 ('\nAllowed parallel jobs: %d\n# queued: %d\nRan: %s\n'
929 'Running: %d') % (
930 self.jobs,
931 len(self.queued),
932 ', '.join(self.ran),
933 len(self.running)))
934 for i in self.queued:
[email protected]fe0d1902014-04-08 20:50:44935 print >> sys.stderr, '%s (not started): %s' % (
936 i.name, ', '.join(i.requirements))
937 for i in self.running:
938 print >> sys.stderr, self.format_task_output(i.item, 'interrupted')
[email protected]485dcab2011-09-14 12:48:47939 raise
[email protected]9e5317a2010-08-13 20:35:11940 # Something happened: self.enqueue() or a thread terminated. Loop again.
[email protected]80cbe8b2010-08-13 13:53:07941 finally:
[email protected]9e5317a2010-08-13 20:35:11942 self.ready_cond.release()
[email protected]3742c842010-09-09 19:27:14943
[email protected]9e5317a2010-08-13 20:35:11944 assert not self.running, 'Now guaranteed to be single-threaded'
[email protected]3742c842010-09-09 19:27:14945 if not self.exceptions.empty():
[email protected]fe0d1902014-04-08 20:50:44946 if self.progress:
947 print >> sys.stdout, ''
[email protected]c8d064b2010-08-16 16:46:14948 # To get back the stack location correctly, the raise a, b, c form must be
949 # used, passing a tuple as the first argument doesn't work.
[email protected]fe0d1902014-04-08 20:50:44950 e, task = self.exceptions.get()
951 print >> sys.stderr, self.format_task_output(task.item, 'ERROR')
[email protected]c8d064b2010-08-16 16:46:14952 raise e[0], e[1], e[2]
[email protected]fe0d1902014-04-08 20:50:44953 elif self.progress:
[email protected]80cbe8b2010-08-13 13:53:07954 self.progress.end()
[email protected]80cbe8b2010-08-13 13:53:07955
[email protected]3742c842010-09-09 19:27:14956 def _flush_terminated_threads(self):
957 """Flush threads that have terminated."""
958 running = self.running
959 self.running = []
960 for t in running:
961 if t.isAlive():
962 self.running.append(t)
963 else:
964 t.join()
[email protected]fe0d1902014-04-08 20:50:44965 self.last_join = datetime.datetime.now()
[email protected]042f0e72011-10-23 00:04:35966 sys.stdout.flush()
[email protected]fe0d1902014-04-08 20:50:44967 if self.verbose:
968 print >> sys.stdout, self.format_task_output(t.item)
[email protected]3742c842010-09-09 19:27:14969 if self.progress:
[email protected]55a2eb82010-10-06 23:35:18970 self.progress.update(1, t.item.name)
[email protected]f36c0ee2011-09-14 19:16:47971 if t.item.name in self.ran:
972 raise Error(
973 'gclient is confused, "%s" is already in "%s"' % (
974 t.item.name, ', '.join(self.ran)))
[email protected]acc45672010-09-09 21:21:21975 if not t.item.name in self.ran:
976 self.ran.append(t.item.name)
[email protected]3742c842010-09-09 19:27:14977
978 def _run_one_task(self, task_item, args, kwargs):
979 if self.jobs > 1:
980 # Start the thread.
981 index = len(self.ran) + len(self.running) + 1
[email protected]77e4eca2010-09-21 13:23:07982 new_thread = self._Worker(task_item, index, args, kwargs)
[email protected]3742c842010-09-09 19:27:14983 self.running.append(new_thread)
984 new_thread.start()
985 else:
986 # Run the 'thread' inside the main thread. Don't try to catch any
987 # exception.
[email protected]fe0d1902014-04-08 20:50:44988 try:
989 task_item.start = datetime.datetime.now()
990 print >> task_item.outbuf, '[%s] Started.' % Elapsed(task_item.start)
991 task_item.run(*args, **kwargs)
992 task_item.finish = datetime.datetime.now()
993 print >> task_item.outbuf, '[%s] Finished.' % Elapsed(task_item.finish)
994 self.ran.append(task_item.name)
995 if self.verbose:
996 if self.progress:
997 print >> sys.stdout, ''
998 print >> sys.stdout, self.format_task_output(task_item)
999 if self.progress:
1000 self.progress.update(1, ', '.join(t.item.name for t in self.running))
1001 except KeyboardInterrupt:
1002 print >> sys.stderr, self.format_task_output(task_item, 'interrupted')
1003 raise
1004 except Exception:
1005 print >> sys.stderr, self.format_task_output(task_item, 'ERROR')
1006 raise
1007
[email protected]3742c842010-09-09 19:27:141008
[email protected]9e5317a2010-08-13 20:35:111009 class _Worker(threading.Thread):
1010 """One thread to execute one WorkItem."""
[email protected]4ed34182010-09-17 15:57:471011 def __init__(self, item, index, args, kwargs):
[email protected]9e5317a2010-08-13 20:35:111012 threading.Thread.__init__(self, name=item.name or 'Worker')
[email protected]1333cb32011-10-04 23:40:161013 logging.info('_Worker(%s) reqs:%s' % (item.name, item.requirements))
[email protected]9e5317a2010-08-13 20:35:111014 self.item = item
[email protected]4ed34182010-09-17 15:57:471015 self.index = index
[email protected]3742c842010-09-09 19:27:141016 self.args = args
1017 self.kwargs = kwargs
[email protected]2fd6c3f2013-05-03 21:57:551018 self.daemon = True
[email protected]80cbe8b2010-08-13 13:53:071019
[email protected]9e5317a2010-08-13 20:35:111020 def run(self):
1021 """Runs in its own thread."""
[email protected]1333cb32011-10-04 23:40:161022 logging.debug('_Worker.run(%s)' % self.item.name)
[email protected]3742c842010-09-09 19:27:141023 work_queue = self.kwargs['work_queue']
[email protected]9e5317a2010-08-13 20:35:111024 try:
[email protected]fe0d1902014-04-08 20:50:441025 self.item.start = datetime.datetime.now()
1026 print >> self.item.outbuf, '[%s] Started.' % Elapsed(self.item.start)
[email protected]9e5317a2010-08-13 20:35:111027 self.item.run(*self.args, **self.kwargs)
[email protected]fe0d1902014-04-08 20:50:441028 self.item.finish = datetime.datetime.now()
1029 print >> self.item.outbuf, '[%s] Finished.' % Elapsed(self.item.finish)
[email protected]2fd6c3f2013-05-03 21:57:551030 except KeyboardInterrupt:
[email protected]c144e062013-05-03 23:23:531031 logging.info('Caught KeyboardInterrupt in thread %s', self.item.name)
[email protected]2fd6c3f2013-05-03 21:57:551032 logging.info(str(sys.exc_info()))
[email protected]fe0d1902014-04-08 20:50:441033 work_queue.exceptions.put((sys.exc_info(), self))
[email protected]2fd6c3f2013-05-03 21:57:551034 raise
[email protected]c8d064b2010-08-16 16:46:141035 except Exception:
1036 # Catch exception location.
[email protected]c144e062013-05-03 23:23:531037 logging.info('Caught exception in thread %s', self.item.name)
[email protected]3742c842010-09-09 19:27:141038 logging.info(str(sys.exc_info()))
[email protected]fe0d1902014-04-08 20:50:441039 work_queue.exceptions.put((sys.exc_info(), self))
[email protected]9e5317a2010-08-13 20:35:111040 finally:
[email protected]c144e062013-05-03 23:23:531041 logging.info('_Worker.run(%s) done', self.item.name)
[email protected]2fd6c3f2013-05-03 21:57:551042 work_queue.ready_cond.acquire()
1043 try:
1044 work_queue.ready_cond.notifyAll()
1045 finally:
1046 work_queue.ready_cond.release()
[email protected]0e0436a2011-10-25 13:32:411047
1048
[email protected]615a2622013-05-03 13:20:141049def GetEditor(git, git_editor=None):
1050 """Returns the most plausible editor to use.
1051
1052 In order of preference:
1053 - GIT_EDITOR/SVN_EDITOR environment variable
1054 - core.editor git configuration variable (if supplied by git-cl)
1055 - VISUAL environment variable
1056 - EDITOR environment variable
[email protected]65621c72013-12-09 15:05:321057 - vi (non-Windows) or notepad (Windows)
[email protected]615a2622013-05-03 13:20:141058
1059 In the case of git-cl, this matches git's behaviour, except that it does not
1060 include dumb terminal detection.
1061
1062 In the case of gcl, this matches svn's behaviour, except that it does not
1063 accept a command-line flag or check the editor-cmd configuration variable.
1064 """
[email protected]0e0436a2011-10-25 13:32:411065 if git:
[email protected]615a2622013-05-03 13:20:141066 editor = os.environ.get('GIT_EDITOR') or git_editor
[email protected]0e0436a2011-10-25 13:32:411067 else:
1068 editor = os.environ.get('SVN_EDITOR')
1069 if not editor:
[email protected]615a2622013-05-03 13:20:141070 editor = os.environ.get('VISUAL')
1071 if not editor:
[email protected]0e0436a2011-10-25 13:32:411072 editor = os.environ.get('EDITOR')
1073 if not editor:
1074 if sys.platform.startswith('win'):
1075 editor = 'notepad'
1076 else:
[email protected]65621c72013-12-09 15:05:321077 editor = 'vi'
[email protected]0e0436a2011-10-25 13:32:411078 return editor
1079
1080
[email protected]615a2622013-05-03 13:20:141081def RunEditor(content, git, git_editor=None):
[email protected]0e0436a2011-10-25 13:32:411082 """Opens up the default editor in the system to get the CL description."""
[email protected]cbd760f2013-07-23 13:02:481083 file_handle, filename = tempfile.mkstemp(text=True, prefix='cl_description')
[email protected]0e0436a2011-10-25 13:32:411084 # Make sure CRLF is handled properly by requiring none.
1085 if '\r' in content:
[email protected]0eff22d2011-10-25 16:11:161086 print >> sys.stderr, (
1087 '!! Please remove \\r from your change description !!')
[email protected]0e0436a2011-10-25 13:32:411088 fileobj = os.fdopen(file_handle, 'w')
1089 # Still remove \r if present.
1090 fileobj.write(re.sub('\r?\n', '\n', content))
1091 fileobj.close()
1092
1093 try:
[email protected]615a2622013-05-03 13:20:141094 editor = GetEditor(git, git_editor=git_editor)
1095 if not editor:
1096 return None
1097 cmd = '%s %s' % (editor, filename)
[email protected]0e0436a2011-10-25 13:32:411098 if sys.platform == 'win32' and os.environ.get('TERM') == 'msys':
1099 # Msysgit requires the usage of 'env' to be present.
1100 cmd = 'env ' + cmd
1101 try:
1102 # shell=True to allow the shell to handle all forms of quotes in
1103 # $EDITOR.
1104 subprocess2.check_call(cmd, shell=True)
1105 except subprocess2.CalledProcessError:
1106 return None
1107 return FileRead(filename)
1108 finally:
1109 os.remove(filename)
[email protected]99ac1c52012-01-16 14:52:121110
1111
[email protected]eb5edbc2012-01-16 17:03:281112def UpgradeToHttps(url):
1113 """Upgrades random urls to https://.
1114
1115 Do not touch unknown urls like ssh:// or git://.
1116 Do not touch http:// urls with a port number,
1117 Fixes invalid GAE url.
1118 """
1119 if not url:
1120 return url
1121 if not re.match(r'[a-z\-]+\://.*', url):
1122 # Make sure it is a valid uri. Otherwise, urlparse() will consider it a
1123 # relative url and will use http:///foo. Note that it defaults to http://
1124 # for compatibility with naked url like "localhost:8080".
1125 url = 'http://%s' % url
1126 parsed = list(urlparse.urlparse(url))
1127 # Do not automatically upgrade http to https if a port number is provided.
1128 if parsed[0] == 'http' and not re.match(r'^.+?\:\d+$', parsed[1]):
1129 parsed[0] = 'https'
[email protected]eb5edbc2012-01-16 17:03:281130 return urlparse.urlunparse(parsed)
1131
1132
[email protected]99ac1c52012-01-16 14:52:121133def ParseCodereviewSettingsContent(content):
1134 """Process a codereview.settings file properly."""
1135 lines = (l for l in content.splitlines() if not l.strip().startswith("#"))
1136 try:
1137 keyvals = dict([x.strip() for x in l.split(':', 1)] for l in lines if l)
1138 except ValueError:
1139 raise Error(
1140 'Failed to process settings, please fix. Content:\n\n%s' % content)
[email protected]eb5edbc2012-01-16 17:03:281141 def fix_url(key):
1142 if keyvals.get(key):
1143 keyvals[key] = UpgradeToHttps(keyvals[key])
1144 fix_url('CODE_REVIEW_SERVER')
1145 fix_url('VIEW_VC')
[email protected]99ac1c52012-01-16 14:52:121146 return keyvals
[email protected]13691502012-10-16 04:26:371147
1148
1149def NumLocalCpus():
1150 """Returns the number of processors.
1151
[email protected]530523b2015-01-07 19:54:571152 multiprocessing.cpu_count() is permitted to raise NotImplementedError, and
1153 is known to do this on some Windows systems and OSX 10.6. If we can't get the
1154 CPU count, we will fall back to '1'.
[email protected]13691502012-10-16 04:26:371155 """
[email protected]530523b2015-01-07 19:54:571156 # Surround the entire thing in try/except; no failure here should stop gclient
1157 # from working.
[email protected]13691502012-10-16 04:26:371158 try:
[email protected]530523b2015-01-07 19:54:571159 # Use multiprocessing to get CPU count. This may raise
1160 # NotImplementedError.
1161 try:
1162 import multiprocessing
1163 return multiprocessing.cpu_count()
1164 except NotImplementedError: # pylint: disable=W0702
1165 # (UNIX) Query 'os.sysconf'.
1166 # pylint: disable=E1101
1167 if hasattr(os, 'sysconf') and 'SC_NPROCESSORS_ONLN' in os.sysconf_names:
1168 return int(os.sysconf('SC_NPROCESSORS_ONLN'))
1169
1170 # (Windows) Query 'NUMBER_OF_PROCESSORS' environment variable.
1171 if 'NUMBER_OF_PROCESSORS' in os.environ:
1172 return int(os.environ['NUMBER_OF_PROCESSORS'])
1173 except Exception as e:
1174 logging.exception("Exception raised while probing CPU count: %s", e)
1175
1176 logging.debug('Failed to get CPU count. Defaulting to 1.')
1177 return 1
[email protected]fc616382014-03-18 20:32:041178
1179def DefaultDeltaBaseCacheLimit():
1180 """Return a reasonable default for the git config core.deltaBaseCacheLimit.
1181
1182 The primary constraint is the address space of virtual memory. The cache
1183 size limit is per-thread, and 32-bit systems can hit OOM errors if this
1184 parameter is set too high.
1185 """
1186 if platform.architecture()[0].startswith('64'):
1187 return '2g'
1188 else:
1189 return '512m'
1190
[email protected]ff113292014-03-25 06:02:081191def DefaultIndexPackConfig(url=''):
[email protected]fc616382014-03-18 20:32:041192 """Return reasonable default values for configuring git-index-pack.
1193
1194 Experiments suggest that higher values for pack.threads don't improve
1195 performance."""
[email protected]ff113292014-03-25 06:02:081196 cache_limit = DefaultDeltaBaseCacheLimit()
1197 result = ['-c', 'core.deltaBaseCacheLimit=%s' % cache_limit]
1198 if url in THREADED_INDEX_PACK_BLACKLIST:
1199 result.extend(['-c', 'pack.threads=1'])
1200 return result