blob: 81bed14b68e6e36b2d1c59defed5aa077a1479d0 [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
Paweł Hajdan, Jr7e502612017-06-12 14:58:388import collections
Andrii Shyshkalov351c61d2017-01-21 20:40:169import contextlib
[email protected]267f33e2014-02-28 22:02:3210import cStringIO
[email protected]fe0d1902014-04-08 20:50:4411import datetime
[email protected]d9141bf2009-12-23 16:13:3212import logging
Paweł Hajdan, Jr7e502612017-06-12 14:58:3813import operator
[email protected]5f3eee32009-09-17 00:34:3014import os
[email protected]c28d3772013-07-12 19:42:3715import pipes
[email protected]fc616382014-03-18 20:32:0416import platform
[email protected]3742c842010-09-09 19:27:1417import Queue
[email protected]ac915bb2009-11-13 17:03:0118import re
[email protected]8f9c69f2009-09-17 00:48:2819import stat
[email protected]6b4a2ab2013-04-18 15:50:2720import subprocess
[email protected]5f3eee32009-09-17 00:34:3021import sys
[email protected]0e0436a2011-10-25 13:32:4122import tempfile
[email protected]9e5317a2010-08-13 20:35:1123import threading
[email protected]167b9e62009-09-17 17:41:0224import time
[email protected]eb5edbc2012-01-16 17:03:2825import urlparse
[email protected]5f3eee32009-09-17 00:34:3026
[email protected]ca0f8392011-09-08 17:15:1527import subprocess2
28
[email protected]5f3eee32009-09-17 00:34:3029
[email protected]f2d7d6b2013-10-17 20:41:4330RETRY_MAX = 3
31RETRY_INITIAL_SLEEP = 0.5
[email protected]fe0d1902014-04-08 20:50:4432START = datetime.datetime.now()
[email protected]f2d7d6b2013-10-17 20:41:4333
34
[email protected]6a9b1682014-03-24 18:35:2335_WARNINGS = []
36
37
[email protected]ff113292014-03-25 06:02:0838# These repos are known to cause OOM errors on 32-bit platforms, due the the
39# very large objects they contain. It is not safe to use threaded index-pack
40# when cloning/fetching them.
41THREADED_INDEX_PACK_BLACKLIST = [
42 'https://chromium.googlesource.com/chromium/reference_builds/chrome_win.git'
43]
44
45
[email protected]66c83e62010-09-07 14:18:4546class Error(Exception):
47 """gclient exception class."""
[email protected]4a3c17e2013-05-24 23:59:2948 def __init__(self, msg, *args, **kwargs):
49 index = getattr(threading.currentThread(), 'index', 0)
50 if index:
51 msg = '\n'.join('%d> %s' % (index, l) for l in msg.splitlines())
52 super(Error, self).__init__(msg, *args, **kwargs)
[email protected]66c83e62010-09-07 14:18:4553
[email protected]3ac1c4e2014-01-16 02:44:4254
[email protected]fe0d1902014-04-08 20:50:4455def Elapsed(until=None):
56 if until is None:
57 until = datetime.datetime.now()
58 return str(until - START).partition('.')[0]
59
60
[email protected]6a9b1682014-03-24 18:35:2361def PrintWarnings():
62 """Prints any accumulated warnings."""
63 if _WARNINGS:
64 print >> sys.stderr, '\n\nWarnings:'
65 for warning in _WARNINGS:
66 print >> sys.stderr, warning
67
68
69def AddWarning(msg):
70 """Adds the given warning message to the list of accumulated warnings."""
71 _WARNINGS.append(msg)
72
73
[email protected]ac915bb2009-11-13 17:03:0174def SplitUrlRevision(url):
75 """Splits url and returns a two-tuple: url, rev"""
76 if url.startswith('ssh:'):
[email protected]78b8cd12010-10-26 12:47:0777 # Make sure ssh://[email protected]/~/test.git@stable works
[email protected]71b13572013-10-16 17:28:1178 regex = r'(ssh://(?:[-.\w]+@)?[-\w:\.]+/[-~\w\./]+)(?:@(.+))?'
[email protected]ac915bb2009-11-13 17:03:0179 components = re.search(regex, url).groups()
80 else:
[email protected]f1eccaf2014-04-11 15:51:3381 components = url.rsplit('@', 1)
82 if re.match(r'^\w+\@', url) and '@' not in components[0]:
83 components = [url]
84
[email protected]ac915bb2009-11-13 17:03:0185 if len(components) == 1:
86 components += [None]
87 return tuple(components)
88
89
[email protected]5439ea52014-08-06 17:18:1890def IsGitSha(revision):
91 """Returns true if the given string is a valid hex-encoded sha"""
92 return re.match('^[a-fA-F0-9]{6,40}$', revision) is not None
93
94
Paweł Hajdan, Jr5ec77132017-08-16 17:21:0695def IsFullGitSha(revision):
96 """Returns true if the given string is a valid hex-encoded full sha"""
97 return re.match('^[a-fA-F0-9]{40}$', revision) is not None
98
99
[email protected]eaab7842011-04-28 09:07:58100def IsDateRevision(revision):
101 """Returns true if the given revision is of the form "{ ... }"."""
102 return bool(revision and re.match(r'^\{.+\}$', str(revision)))
103
104
105def MakeDateRevision(date):
106 """Returns a revision representing the latest revision before the given
107 date."""
108 return "{" + date + "}"
109
110
[email protected]5990f9d2010-07-07 18:02:58111def SyntaxErrorToError(filename, e):
112 """Raises a gclient_utils.Error exception with the human readable message"""
113 try:
114 # Try to construct a human readable error message
115 if filename:
116 error_message = 'There is a syntax error in %s\n' % filename
117 else:
118 error_message = 'There is a syntax error\n'
119 error_message += 'Line #%s, character %s: "%s"' % (
120 e.lineno, e.offset, re.sub(r'[\r\n]*$', '', e.text))
121 except:
122 # Something went wrong, re-raise the original exception
123 raise e
124 else:
125 raise Error(error_message)
126
127
[email protected]5f3eee32009-09-17 00:34:30128class PrintableObject(object):
129 def __str__(self):
130 output = ''
131 for i in dir(self):
132 if i.startswith('__'):
133 continue
134 output += '%s = %s\n' % (i, str(getattr(self, i, '')))
135 return output
136
137
[email protected]5aeb7dd2009-11-17 18:09:01138def FileRead(filename, mode='rU'):
[email protected]51e84fb2012-07-03 23:06:21139 with open(filename, mode=mode) as f:
[email protected]c3cd5372012-07-11 17:39:24140 # codecs.open() has different behavior than open() on python 2.6 so use
141 # open() and decode manually.
[email protected]2b99d432012-07-12 18:10:28142 s = f.read()
143 try:
144 return s.decode('utf-8')
145 except UnicodeDecodeError:
146 return s
[email protected]5f3eee32009-09-17 00:34:30147
148
[email protected]5aeb7dd2009-11-17 18:09:01149def FileWrite(filename, content, mode='w'):
[email protected]dae209f2012-07-03 16:08:15150 with codecs.open(filename, mode=mode, encoding='utf-8') as f:
[email protected]5f3eee32009-09-17 00:34:30151 f.write(content)
[email protected]5f3eee32009-09-17 00:34:30152
153
Andrii Shyshkalov351c61d2017-01-21 20:40:16154@contextlib.contextmanager
155def temporary_directory(**kwargs):
156 tdir = tempfile.mkdtemp(**kwargs)
157 try:
158 yield tdir
159 finally:
160 if tdir:
161 rmtree(tdir)
162
163
[email protected]ef509e42013-09-20 13:19:08164def safe_rename(old, new):
165 """Renames a file reliably.
166
[email protected]3ac1c4e2014-01-16 02:44:42167 Sometimes os.rename does not work because a dying git process keeps a handle
168 on it for a few seconds. An exception is then thrown, which make the program
[email protected]ef509e42013-09-20 13:19:08169 give up what it was doing and remove what was deleted.
[email protected]3ac1c4e2014-01-16 02:44:42170 The only solution is to catch the exception and try again until it works.
[email protected]ef509e42013-09-20 13:19:08171 """
172 # roughly 10s
173 retries = 100
174 for i in range(retries):
175 try:
176 os.rename(old, new)
177 break
178 except OSError:
179 if i == (retries - 1):
180 # Give up.
181 raise
182 # retry
183 logging.debug("Renaming failed from %s to %s. Retrying ..." % (old, new))
184 time.sleep(0.1)
185
186
[email protected]67b59e92014-12-25 13:48:37187def rm_file_or_tree(path):
188 if os.path.isfile(path):
189 os.remove(path)
190 else:
191 rmtree(path)
192
193
[email protected]f9040722011-03-09 14:47:51194def rmtree(path):
195 """shutil.rmtree() on steroids.
[email protected]5f3eee32009-09-17 00:34:30196
[email protected]f9040722011-03-09 14:47:51197 Recursively removes a directory, even if it's marked read-only.
[email protected]5f3eee32009-09-17 00:34:30198
199 shutil.rmtree() doesn't work on Windows if any of the files or directories
agable41e3a6c2016-10-20 18:36:56200 are read-only. We need to be able to force the files to be writable (i.e.,
201 deletable) as we traverse the tree.
[email protected]5f3eee32009-09-17 00:34:30202
203 Even with all this, Windows still sometimes fails to delete a file, citing
204 a permission error (maybe something to do with antivirus scans or disk
205 indexing). The best suggestion any of the user forums had was to wait a
206 bit and try again, so we do that too. It's hand-waving, but sometimes it
207 works. :/
208
209 On POSIX systems, things are a little bit simpler. The modes of the files
210 to be deleted doesn't matter, only the modes of the directories containing
211 them are significant. As the directory tree is traversed, each directory
212 has its mode set appropriately before descending into it. This should
213 result in the entire tree being removed, with the possible exception of
214 *path itself, because nothing attempts to change the mode of its parent.
215 Doing so would be hazardous, as it's not a directory slated for removal.
216 In the ordinary case, this is not a problem: for our purposes, the user
217 will never lack write permission on *path's parent.
218 """
[email protected]f9040722011-03-09 14:47:51219 if not os.path.exists(path):
[email protected]5f3eee32009-09-17 00:34:30220 return
221
[email protected]f9040722011-03-09 14:47:51222 if os.path.islink(path) or not os.path.isdir(path):
223 raise Error('Called rmtree(%s) in non-directory' % path)
[email protected]5f3eee32009-09-17 00:34:30224
[email protected]5f3eee32009-09-17 00:34:30225 if sys.platform == 'win32':
[email protected]6b4a2ab2013-04-18 15:50:27226 # Give up and use cmd.exe's rd command.
227 path = os.path.normcase(path)
228 for _ in xrange(3):
229 exitcode = subprocess.call(['cmd.exe', '/c', 'rd', '/q', '/s', path])
230 if exitcode == 0:
231 return
232 else:
233 print >> sys.stderr, 'rd exited with code %d' % exitcode
234 time.sleep(3)
235 raise Exception('Failed to remove path %s' % path)
236
237 # On POSIX systems, we need the x-bit set on the directory to access it,
238 # the r-bit to see its contents, and the w-bit to remove files from it.
239 # The actual modes of the files within the directory is irrelevant.
240 os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
[email protected]5f3eee32009-09-17 00:34:30241
[email protected]f9040722011-03-09 14:47:51242 def remove(func, subpath):
[email protected]6b4a2ab2013-04-18 15:50:27243 func(subpath)
[email protected]f9040722011-03-09 14:47:51244
245 for fn in os.listdir(path):
[email protected]5f3eee32009-09-17 00:34:30246 # If fullpath is a symbolic link that points to a directory, isdir will
247 # be True, but we don't want to descend into that as a directory, we just
248 # want to remove the link. Check islink and treat links as ordinary files
249 # would be treated regardless of what they reference.
[email protected]f9040722011-03-09 14:47:51250 fullpath = os.path.join(path, fn)
[email protected]5f3eee32009-09-17 00:34:30251 if os.path.islink(fullpath) or not os.path.isdir(fullpath):
[email protected]f9040722011-03-09 14:47:51252 remove(os.remove, fullpath)
[email protected]5f3eee32009-09-17 00:34:30253 else:
[email protected]f9040722011-03-09 14:47:51254 # Recurse.
255 rmtree(fullpath)
[email protected]5f3eee32009-09-17 00:34:30256
[email protected]f9040722011-03-09 14:47:51257 remove(os.rmdir, path)
258
[email protected]5f3eee32009-09-17 00:34:30259
[email protected]6c48a302011-10-20 23:44:20260def safe_makedirs(tree):
261 """Creates the directory in a safe manner.
262
263 Because multiple threads can create these directories concurently, trap the
264 exception and pass on.
265 """
266 count = 0
267 while not os.path.exists(tree):
268 count += 1
269 try:
270 os.makedirs(tree)
271 except OSError, e:
272 # 17 POSIX, 183 Windows
273 if e.errno not in (17, 183):
274 raise
275 if count > 40:
276 # Give up.
277 raise
278
279
[email protected]c28d3772013-07-12 19:42:37280def CommandToStr(args):
281 """Converts an arg list into a shell escaped string."""
282 return ' '.join(pipes.quote(arg) for arg in args)
283
284
[email protected]f2ed3fb2012-11-09 23:39:49285def CheckCallAndFilterAndHeader(args, always=False, header=None, **kwargs):
[email protected]17d01792010-09-01 18:07:10286 """Adds 'header' support to CheckCallAndFilter.
[email protected]5f3eee32009-09-17 00:34:30287
[email protected]17d01792010-09-01 18:07:10288 If |always| is True, a message indicating what is being done
289 is printed to stdout all the time even if not output is generated. Otherwise
290 the message header is printed only if the call generated any ouput.
[email protected]5f3eee32009-09-17 00:34:30291 """
[email protected]f2ed3fb2012-11-09 23:39:49292 stdout = kwargs.setdefault('stdout', sys.stdout)
293 if header is None:
Daniel Chenga0c5f082017-10-19 20:35:19294 # The automatically generated header only prepends newline if always is
295 # false: always is usually set to false if there's an external progress
296 # display, and it's better not to clobber it in that case.
297 header = "%s________ running '%s' in '%s'\n" % (
298 '' if always else '\n',
[email protected]4aad1852013-07-12 21:32:51299 ' '.join(args), kwargs.get('cwd', '.'))
[email protected]f2ed3fb2012-11-09 23:39:49300
[email protected]17d01792010-09-01 18:07:10301 if always:
[email protected]f2ed3fb2012-11-09 23:39:49302 stdout.write(header)
[email protected]17d01792010-09-01 18:07:10303 else:
[email protected]f2ed3fb2012-11-09 23:39:49304 filter_fn = kwargs.get('filter_fn')
[email protected]17d01792010-09-01 18:07:10305 def filter_msg(line):
306 if line is None:
[email protected]f2ed3fb2012-11-09 23:39:49307 stdout.write(header)
[email protected]17d01792010-09-01 18:07:10308 elif filter_fn:
309 filter_fn(line)
310 kwargs['filter_fn'] = filter_msg
311 kwargs['call_filter_on_first_line'] = True
312 # Obviously.
[email protected]f2ed3fb2012-11-09 23:39:49313 kwargs.setdefault('print_stdout', True)
[email protected]17d01792010-09-01 18:07:10314 return CheckCallAndFilter(args, **kwargs)
[email protected]5f3eee32009-09-17 00:34:30315
[email protected]17d01792010-09-01 18:07:10316
[email protected]042f0e72011-10-23 00:04:35317class Wrapper(object):
318 """Wraps an object, acting as a transparent proxy for all properties by
319 default.
320 """
321 def __init__(self, wrapped):
322 self._wrapped = wrapped
323
324 def __getattr__(self, name):
325 return getattr(self._wrapped, name)
[email protected]db111f72010-09-08 13:36:53326
[email protected]e0de9cb2010-09-17 15:07:14327
Edward Lemur231f5ea2018-01-31 18:02:36328class WriteToStdout(Wrapper):
329 """Creates a file object clone to also print to sys.stdout."""
330 def __init__(self, wrapped):
331 super(WriteToStdout, self).__init__(wrapped)
332 if not hasattr(self, 'lock'):
333 self.lock = threading.Lock()
334
335 def write(self, out, *args, **kwargs):
336 self._wrapped.write(out, *args, **kwargs)
337 self.lock.acquire()
338 try:
339 sys.stdout.write(out, *args, **kwargs)
340 finally:
341 self.lock.release()
342
343
[email protected]042f0e72011-10-23 00:04:35344class AutoFlush(Wrapper):
[email protected]e0de9cb2010-09-17 15:07:14345 """Creates a file object clone to automatically flush after N seconds."""
[email protected]042f0e72011-10-23 00:04:35346 def __init__(self, wrapped, delay):
347 super(AutoFlush, self).__init__(wrapped)
348 if not hasattr(self, 'lock'):
349 self.lock = threading.Lock()
350 self.__last_flushed_at = time.time()
351 self.delay = delay
[email protected]e0de9cb2010-09-17 15:07:14352
[email protected]042f0e72011-10-23 00:04:35353 @property
354 def autoflush(self):
355 return self
[email protected]e0de9cb2010-09-17 15:07:14356
[email protected]042f0e72011-10-23 00:04:35357 def write(self, out, *args, **kwargs):
358 self._wrapped.write(out, *args, **kwargs)
[email protected]db111f72010-09-08 13:36:53359 should_flush = False
[email protected]042f0e72011-10-23 00:04:35360 self.lock.acquire()
[email protected]9c531262010-09-08 13:41:13361 try:
[email protected]042f0e72011-10-23 00:04:35362 if self.delay and (time.time() - self.__last_flushed_at) > self.delay:
[email protected]db111f72010-09-08 13:36:53363 should_flush = True
[email protected]042f0e72011-10-23 00:04:35364 self.__last_flushed_at = time.time()
[email protected]9c531262010-09-08 13:41:13365 finally:
[email protected]042f0e72011-10-23 00:04:35366 self.lock.release()
[email protected]db111f72010-09-08 13:36:53367 if should_flush:
[email protected]042f0e72011-10-23 00:04:35368 self.flush()
[email protected]db111f72010-09-08 13:36:53369
370
[email protected]042f0e72011-10-23 00:04:35371class Annotated(Wrapper):
[email protected]4ed34182010-09-17 15:57:47372 """Creates a file object clone to automatically prepends every line in worker
[email protected]042f0e72011-10-23 00:04:35373 threads with a NN> prefix.
374 """
375 def __init__(self, wrapped, include_zero=False):
376 super(Annotated, self).__init__(wrapped)
377 if not hasattr(self, 'lock'):
378 self.lock = threading.Lock()
379 self.__output_buffers = {}
380 self.__include_zero = include_zero
[email protected]cb1e97a2010-09-09 20:09:20381
[email protected]042f0e72011-10-23 00:04:35382 @property
383 def annotated(self):
384 return self
[email protected]cb1e97a2010-09-09 20:09:20385
[email protected]042f0e72011-10-23 00:04:35386 def write(self, out):
387 index = getattr(threading.currentThread(), 'index', 0)
388 if not index and not self.__include_zero:
389 # Unindexed threads aren't buffered.
390 return self._wrapped.write(out)
[email protected]cb1e97a2010-09-09 20:09:20391
[email protected]042f0e72011-10-23 00:04:35392 self.lock.acquire()
[email protected]4ed34182010-09-17 15:57:47393 try:
394 # Use a dummy array to hold the string so the code can be lockless.
395 # Strings are immutable, requiring to keep a lock for the whole dictionary
396 # otherwise. Using an array is faster than using a dummy object.
[email protected]042f0e72011-10-23 00:04:35397 if not index in self.__output_buffers:
398 obj = self.__output_buffers[index] = ['']
[email protected]4ed34182010-09-17 15:57:47399 else:
[email protected]042f0e72011-10-23 00:04:35400 obj = self.__output_buffers[index]
[email protected]4ed34182010-09-17 15:57:47401 finally:
[email protected]042f0e72011-10-23 00:04:35402 self.lock.release()
[email protected]4ed34182010-09-17 15:57:47403
404 # Continue lockless.
405 obj[0] += out
406 while '\n' in obj[0]:
407 line, remaining = obj[0].split('\n', 1)
[email protected]e939bb52011-06-01 22:59:15408 if line:
[email protected]042f0e72011-10-23 00:04:35409 self._wrapped.write('%d>%s\n' % (index, line))
[email protected]4ed34182010-09-17 15:57:47410 obj[0] = remaining
411
[email protected]042f0e72011-10-23 00:04:35412 def flush(self):
[email protected]4ed34182010-09-17 15:57:47413 """Flush buffered output."""
414 orphans = []
[email protected]042f0e72011-10-23 00:04:35415 self.lock.acquire()
[email protected]4ed34182010-09-17 15:57:47416 try:
417 # Detect threads no longer existing.
418 indexes = (getattr(t, 'index', None) for t in threading.enumerate())
[email protected]cb2985f2010-11-03 14:08:31419 indexes = filter(None, indexes)
[email protected]042f0e72011-10-23 00:04:35420 for index in self.__output_buffers:
[email protected]4ed34182010-09-17 15:57:47421 if not index in indexes:
[email protected]042f0e72011-10-23 00:04:35422 orphans.append((index, self.__output_buffers[index][0]))
[email protected]4ed34182010-09-17 15:57:47423 for orphan in orphans:
[email protected]042f0e72011-10-23 00:04:35424 del self.__output_buffers[orphan[0]]
[email protected]4ed34182010-09-17 15:57:47425 finally:
[email protected]042f0e72011-10-23 00:04:35426 self.lock.release()
[email protected]4ed34182010-09-17 15:57:47427
428 # Don't keep the lock while writting. Will append \n when it shouldn't.
429 for orphan in orphans:
[email protected]e939bb52011-06-01 22:59:15430 if orphan[1]:
[email protected]042f0e72011-10-23 00:04:35431 self._wrapped.write('%d>%s\n' % (orphan[0], orphan[1]))
432 return self._wrapped.flush()
[email protected]4ed34182010-09-17 15:57:47433
[email protected]042f0e72011-10-23 00:04:35434
435def MakeFileAutoFlush(fileobj, delay=10):
436 autoflush = getattr(fileobj, 'autoflush', None)
437 if autoflush:
438 autoflush.delay = delay
439 return fileobj
440 return AutoFlush(fileobj, delay)
441
442
443def MakeFileAnnotated(fileobj, include_zero=False):
444 if getattr(fileobj, 'annotated', None):
445 return fileobj
446 return Annotated(fileobj)
[email protected]cb1e97a2010-09-09 20:09:20447
448
[email protected]2fd6c3f2013-05-03 21:57:55449GCLIENT_CHILDREN = []
450GCLIENT_CHILDREN_LOCK = threading.Lock()
451
452
453class GClientChildren(object):
454 @staticmethod
455 def add(popen_obj):
456 with GCLIENT_CHILDREN_LOCK:
457 GCLIENT_CHILDREN.append(popen_obj)
458
459 @staticmethod
460 def remove(popen_obj):
461 with GCLIENT_CHILDREN_LOCK:
462 GCLIENT_CHILDREN.remove(popen_obj)
463
464 @staticmethod
465 def _attemptToKillChildren():
466 global GCLIENT_CHILDREN
467 with GCLIENT_CHILDREN_LOCK:
468 zombies = [c for c in GCLIENT_CHILDREN if c.poll() is None]
469
470 for zombie in zombies:
471 try:
472 zombie.kill()
473 except OSError:
474 pass
475
476 with GCLIENT_CHILDREN_LOCK:
477 GCLIENT_CHILDREN = [k for k in GCLIENT_CHILDREN if k.poll() is not None]
478
479 @staticmethod
480 def _areZombies():
481 with GCLIENT_CHILDREN_LOCK:
482 return bool(GCLIENT_CHILDREN)
483
484 @staticmethod
485 def KillAllRemainingChildren():
486 GClientChildren._attemptToKillChildren()
487
488 if GClientChildren._areZombies():
489 time.sleep(0.5)
490 GClientChildren._attemptToKillChildren()
491
492 with GCLIENT_CHILDREN_LOCK:
493 if GCLIENT_CHILDREN:
494 print >> sys.stderr, 'Could not kill the following subprocesses:'
495 for zombie in GCLIENT_CHILDREN:
496 print >> sys.stderr, ' ', zombie.pid
497
498
[email protected]17d01792010-09-01 18:07:10499def CheckCallAndFilter(args, stdout=None, filter_fn=None,
500 print_stdout=None, call_filter_on_first_line=False,
tandrii64103db2016-10-11 12:30:05501 retry=False, **kwargs):
[email protected]17d01792010-09-01 18:07:10502 """Runs a command and calls back a filter function if needed.
503
[email protected]57bf78d2011-09-08 18:57:33504 Accepts all subprocess2.Popen() parameters plus:
[email protected]17d01792010-09-01 18:07:10505 print_stdout: If True, the command's stdout is forwarded to stdout.
506 filter_fn: A function taking a single string argument called with each line
[email protected]57bf78d2011-09-08 18:57:33507 of the subprocess2's output. Each line has the trailing newline
[email protected]17d01792010-09-01 18:07:10508 character trimmed.
509 stdout: Can be any bufferable output.
[email protected]f2d7d6b2013-10-17 20:41:43510 retry: If the process exits non-zero, sleep for a brief interval and try
511 again, up to RETRY_MAX times.
[email protected]17d01792010-09-01 18:07:10512
513 stderr is always redirected to stdout.
514 """
515 assert print_stdout or filter_fn
516 stdout = stdout or sys.stdout
[email protected]267f33e2014-02-28 22:02:32517 output = cStringIO.StringIO()
[email protected]17d01792010-09-01 18:07:10518 filter_fn = filter_fn or (lambda x: None)
[email protected]5f3eee32009-09-17 00:34:30519
[email protected]f2d7d6b2013-10-17 20:41:43520 sleep_interval = RETRY_INITIAL_SLEEP
521 run_cwd = kwargs.get('cwd', os.getcwd())
522 for _ in xrange(RETRY_MAX + 1):
523 kid = subprocess2.Popen(
524 args, bufsize=0, stdout=subprocess2.PIPE, stderr=subprocess2.STDOUT,
525 **kwargs)
[email protected]2fd6c3f2013-05-03 21:57:55526
[email protected]f2d7d6b2013-10-17 20:41:43527 GClientChildren.add(kid)
[email protected]8ad1cee2010-08-16 19:12:27528
[email protected]f2d7d6b2013-10-17 20:41:43529 # Do a flush of stdout before we begin reading from the subprocess2's stdout
530 stdout.flush()
531
532 # Also, we need to forward stdout to prevent weird re-ordering of output.
533 # This has to be done on a per byte basis to make sure it is not buffered:
agable41e3a6c2016-10-20 18:36:56534 # normally buffering is done for each line, but if the process requests
535 # input, no end-of-line character is output after the prompt and it would
536 # not show up.
[email protected]f2d7d6b2013-10-17 20:41:43537 try:
538 in_byte = kid.stdout.read(1)
539 if in_byte:
540 if call_filter_on_first_line:
541 filter_fn(None)
542 in_line = ''
543 while in_byte:
[email protected]267f33e2014-02-28 22:02:32544 output.write(in_byte)
545 if print_stdout:
546 stdout.write(in_byte)
[email protected]fe0d1902014-04-08 20:50:44547 if in_byte not in ['\r', '\n']:
548 in_line += in_byte
[email protected]109cb9d2011-09-14 20:03:11549 else:
550 filter_fn(in_line)
551 in_line = ''
[email protected]f2d7d6b2013-10-17 20:41:43552 in_byte = kid.stdout.read(1)
553 # Flush the rest of buffered output. This is only an issue with
554 # stdout/stderr not ending with a \n.
555 if len(in_line):
[email protected]85d3e3a2011-10-07 17:12:00556 filter_fn(in_line)
[email protected]f2d7d6b2013-10-17 20:41:43557 rv = kid.wait()
[email protected]2fd6c3f2013-05-03 21:57:55558
[email protected]f2d7d6b2013-10-17 20:41:43559 # Don't put this in a 'finally,' since the child may still run if we get
560 # an exception.
561 GClientChildren.remove(kid)
[email protected]2fd6c3f2013-05-03 21:57:55562
[email protected]f2d7d6b2013-10-17 20:41:43563 except KeyboardInterrupt:
564 print >> sys.stderr, 'Failed while running "%s"' % ' '.join(args)
565 raise
[email protected]109cb9d2011-09-14 20:03:11566
[email protected]f2d7d6b2013-10-17 20:41:43567 if rv == 0:
[email protected]267f33e2014-02-28 22:02:32568 return output.getvalue()
[email protected]f2d7d6b2013-10-17 20:41:43569 if not retry:
570 break
tandrii30d95622016-10-11 12:20:26571 print ("WARNING: subprocess '%s' in %s failed; will retry after a short "
572 'nap...' % (' '.join('"%s"' % x for x in args), run_cwd))
[email protected]91507f72013-10-22 12:18:25573 time.sleep(sleep_interval)
[email protected]f2d7d6b2013-10-17 20:41:43574 sleep_interval *= 2
575 raise subprocess2.CalledProcessError(
576 rv, args, kwargs.get('cwd', None), None, None)
[email protected]5f3eee32009-09-17 00:34:30577
578
[email protected]5a306a22014-02-24 22:13:59579class GitFilter(object):
580 """A filter_fn implementation for quieting down git output messages.
581
582 Allows a custom function to skip certain lines (predicate), and will throttle
583 the output of percentage completed lines to only output every X seconds.
584 """
[email protected]fe0d1902014-04-08 20:50:44585 PERCENT_RE = re.compile('(.*) ([0-9]{1,3})% .*')
[email protected]5a306a22014-02-24 22:13:59586
[email protected]fe0d1902014-04-08 20:50:44587 def __init__(self, time_throttle=0, predicate=None, out_fh=None):
[email protected]5a306a22014-02-24 22:13:59588 """
589 Args:
590 time_throttle (int): GitFilter will throttle 'noisy' output (such as the
591 XX% complete messages) to only be printed at least |time_throttle|
592 seconds apart.
593 predicate (f(line)): An optional function which is invoked for every line.
594 The line will be skipped if predicate(line) returns False.
[email protected]fe0d1902014-04-08 20:50:44595 out_fh: File handle to write output to.
[email protected]5a306a22014-02-24 22:13:59596 """
597 self.last_time = 0
598 self.time_throttle = time_throttle
599 self.predicate = predicate
[email protected]fe0d1902014-04-08 20:50:44600 self.out_fh = out_fh or sys.stdout
601 self.progress_prefix = None
[email protected]5a306a22014-02-24 22:13:59602
603 def __call__(self, line):
604 # git uses an escape sequence to clear the line; elide it.
605 esc = line.find(unichr(033))
606 if esc > -1:
607 line = line[:esc]
608 if self.predicate and not self.predicate(line):
609 return
610 now = time.time()
611 match = self.PERCENT_RE.match(line)
[email protected]fe0d1902014-04-08 20:50:44612 if match:
613 if match.group(1) != self.progress_prefix:
614 self.progress_prefix = match.group(1)
615 elif now - self.last_time < self.time_throttle:
616 return
617 self.last_time = now
618 self.out_fh.write('[%s] ' % Elapsed())
619 print >> self.out_fh, line
[email protected]5a306a22014-02-24 22:13:59620
621
[email protected]9eda4112010-06-11 18:56:10622def FindGclientRoot(from_dir, filename='.gclient'):
[email protected]a9371762009-12-22 18:27:38623 """Tries to find the gclient root."""
[email protected]20760a52010-09-08 08:47:28624 real_from_dir = os.path.realpath(from_dir)
625 path = real_from_dir
[email protected]9eda4112010-06-11 18:56:10626 while not os.path.exists(os.path.join(path, filename)):
[email protected]3a292682010-08-23 18:54:55627 split_path = os.path.split(path)
628 if not split_path[1]:
[email protected]a9371762009-12-22 18:27:38629 return None
[email protected]3a292682010-08-23 18:54:55630 path = split_path[0]
[email protected]20760a52010-09-08 08:47:28631
632 # If we did not find the file in the current directory, make sure we are in a
633 # sub directory that is controlled by this configuration.
634 if path != real_from_dir:
635 entries_filename = os.path.join(path, filename + '_entries')
636 if not os.path.exists(entries_filename):
637 # If .gclient_entries does not exist, a previous call to gclient sync
638 # might have failed. In that case, we cannot verify that the .gclient
639 # is the one we want to use. In order to not to cause too much trouble,
640 # just issue a warning and return the path anyway.
Bruce Dawson1c5c1182017-06-13 17:34:06641 print >> sys.stderr, ("%s missing, %s file in parent directory %s might "
642 "not be the file you want to use." %
643 (entries_filename, filename, path))
[email protected]20760a52010-09-08 08:47:28644 return path
645 scope = {}
646 try:
647 exec(FileRead(entries_filename), scope)
648 except SyntaxError, e:
649 SyntaxErrorToError(filename, e)
650 all_directories = scope['entries'].keys()
651 path_to_check = real_from_dir[len(path)+1:]
652 while path_to_check:
653 if path_to_check in all_directories:
654 return path
655 path_to_check = os.path.dirname(path_to_check)
656 return None
[email protected]3742c842010-09-09 19:27:14657
[email protected]d9141bf2009-12-23 16:13:32658 logging.info('Found gclient root at ' + path)
[email protected]a9371762009-12-22 18:27:38659 return path
[email protected]3ccbf7e2009-12-22 20:46:42660
[email protected]9eda4112010-06-11 18:56:10661
[email protected]3ccbf7e2009-12-22 20:46:42662def PathDifference(root, subpath):
663 """Returns the difference subpath minus root."""
664 root = os.path.realpath(root)
665 subpath = os.path.realpath(subpath)
666 if not subpath.startswith(root):
667 return None
668 # If the root does not have a trailing \ or /, we add it so the returned
669 # path starts immediately after the seperator regardless of whether it is
670 # provided.
671 root = os.path.join(root, '')
672 return subpath[len(root):]
[email protected]f43d0192010-04-15 02:36:04673
674
675def FindFileUpwards(filename, path=None):
[email protected]13595ff2011-10-13 01:25:07676 """Search upwards from the a directory (default: current) to find a file.
[email protected]f2ed3fb2012-11-09 23:39:49677
[email protected]13595ff2011-10-13 01:25:07678 Returns nearest upper-level directory with the passed in file.
679 """
[email protected]f43d0192010-04-15 02:36:04680 if not path:
681 path = os.getcwd()
682 path = os.path.realpath(path)
683 while True:
684 file_path = os.path.join(path, filename)
[email protected]13595ff2011-10-13 01:25:07685 if os.path.exists(file_path):
686 return path
[email protected]f43d0192010-04-15 02:36:04687 (new_path, _) = os.path.split(path)
688 if new_path == path:
689 return None
690 path = new_path
691
692
[email protected]3ac1c4e2014-01-16 02:44:42693def GetMacWinOrLinux():
694 """Returns 'mac', 'win', or 'linux', matching the current platform."""
695 if sys.platform.startswith(('cygwin', 'win')):
696 return 'win'
697 elif sys.platform.startswith('linux'):
698 return 'linux'
699 elif sys.platform == 'darwin':
700 return 'mac'
701 raise Error('Unknown platform: ' + sys.platform)
702
703
[email protected]e0a7c5d2015-02-23 20:30:08704def GetPrimarySolutionPath():
705 """Returns the full path to the primary solution. (gclient_root + src)"""
[email protected]0db9a142014-08-13 23:15:25706
[email protected]cc968fe2014-06-23 17:30:32707 gclient_root = FindGclientRoot(os.getcwd())
708 if not gclient_root:
[email protected]aaee92f2014-07-02 07:35:31709 # Some projects might not use .gclient. Try to see whether we're in a git
710 # checkout.
711 top_dir = [os.getcwd()]
712 def filter_fn(line):
hanpfei13f9c372016-08-09 05:05:56713 repo_root_path = os.path.normpath(line.rstrip('\n'))
714 if os.path.exists(repo_root_path):
715 top_dir[0] = repo_root_path
[email protected]aaee92f2014-07-02 07:35:31716 try:
717 CheckCallAndFilter(["git", "rev-parse", "--show-toplevel"],
718 print_stdout=False, filter_fn=filter_fn)
719 except Exception:
720 pass
721 top_dir = top_dir[0]
722 if os.path.exists(os.path.join(top_dir, 'buildtools')):
[email protected]d6d15b82015-04-20 06:43:48723 return top_dir
[email protected]cc968fe2014-06-23 17:30:32724 return None
[email protected]f7facfa2014-09-05 12:40:28725
726 # Some projects' top directory is not named 'src'.
727 source_dir_name = GetGClientPrimarySolutionName(gclient_root) or 'src'
[email protected]e0a7c5d2015-02-23 20:30:08728 return os.path.join(gclient_root, source_dir_name)
729
730
731def GetBuildtoolsPath():
732 """Returns the full path to the buildtools directory.
733 This is based on the root of the checkout containing the current directory."""
734
735 # Overriding the build tools path by environment is highly unsupported and may
736 # break without warning. Do not rely on this for anything important.
737 override = os.environ.get('CHROMIUM_BUILDTOOLS_PATH')
738 if override is not None:
739 return override
740
741 primary_solution = GetPrimarySolutionPath()
[email protected]9d0644d2015-06-05 23:16:54742 if not primary_solution:
743 return None
[email protected]e0a7c5d2015-02-23 20:30:08744 buildtools_path = os.path.join(primary_solution, 'buildtools')
[email protected]43e91582014-11-12 22:38:51745 if not os.path.exists(buildtools_path):
746 # Buildtools may be in the gclient root.
[email protected]e0a7c5d2015-02-23 20:30:08747 gclient_root = FindGclientRoot(os.getcwd())
[email protected]43e91582014-11-12 22:38:51748 buildtools_path = os.path.join(gclient_root, 'buildtools')
749 return buildtools_path
[email protected]cc968fe2014-06-23 17:30:32750
751
752def GetBuildtoolsPlatformBinaryPath():
753 """Returns the full path to the binary directory for the current platform."""
[email protected]cc968fe2014-06-23 17:30:32754 buildtools_path = GetBuildtoolsPath()
755 if not buildtools_path:
756 return None
757
758 if sys.platform.startswith(('cygwin', 'win')):
759 subdir = 'win'
760 elif sys.platform == 'darwin':
761 subdir = 'mac'
762 elif sys.platform.startswith('linux'):
[email protected]cc968fe2014-06-23 17:30:32763 subdir = 'linux64'
[email protected]cc968fe2014-06-23 17:30:32764 else:
765 raise Error('Unknown platform: ' + sys.platform)
766 return os.path.join(buildtools_path, subdir)
767
768
[email protected]3ac1c4e2014-01-16 02:44:42769def GetExeSuffix():
770 """Returns '' or '.exe' depending on how executables work on this platform."""
771 if sys.platform.startswith(('cygwin', 'win')):
772 return '.exe'
773 return ''
774
775
[email protected]f7facfa2014-09-05 12:40:28776def GetGClientPrimarySolutionName(gclient_root_dir_path):
777 """Returns the name of the primary solution in the .gclient file specified."""
778 gclient_config_file = os.path.join(gclient_root_dir_path, '.gclient')
779 env = {}
780 execfile(gclient_config_file, env)
781 solutions = env.get('solutions', [])
782 if solutions:
783 return solutions[0].get('name')
784 return None
785
786
[email protected]f43d0192010-04-15 02:36:04787def GetGClientRootAndEntries(path=None):
788 """Returns the gclient root and the dict of entries."""
789 config_file = '.gclient_entries'
[email protected]93a9ee02011-10-18 18:23:58790 root = FindFileUpwards(config_file, path)
791 if not root:
[email protected]116704f2010-06-11 17:34:38792 print "Can't find %s" % config_file
[email protected]f43d0192010-04-15 02:36:04793 return None
[email protected]93a9ee02011-10-18 18:23:58794 config_path = os.path.join(root, config_file)
[email protected]f43d0192010-04-15 02:36:04795 env = {}
796 execfile(config_path, env)
797 config_dir = os.path.dirname(config_path)
798 return config_dir, env['entries']
[email protected]80cbe8b2010-08-13 13:53:07799
800
[email protected]6ca8bf82011-09-19 23:04:30801def lockedmethod(method):
802 """Method decorator that holds self.lock for the duration of the call."""
803 def inner(self, *args, **kwargs):
804 try:
805 try:
806 self.lock.acquire()
807 except KeyboardInterrupt:
808 print >> sys.stderr, 'Was deadlocked'
809 raise
810 return method(self, *args, **kwargs)
811 finally:
812 self.lock.release()
813 return inner
814
815
[email protected]80cbe8b2010-08-13 13:53:07816class WorkItem(object):
817 """One work item."""
[email protected]4901daf2011-10-20 14:34:47818 # On cygwin, creating a lock throwing randomly when nearing ~100 locks.
819 # As a workaround, use a single lock. Yep you read it right. Single lock for
820 # all the 100 objects.
821 lock = threading.Lock()
822
[email protected]6ca8bf82011-09-19 23:04:30823 def __init__(self, name):
[email protected]485dcab2011-09-14 12:48:47824 # A unique string representing this work item.
[email protected]6ca8bf82011-09-19 23:04:30825 self._name = name
[email protected]fe0d1902014-04-08 20:50:44826 self.outbuf = cStringIO.StringIO()
827 self.start = self.finish = None
hinoka885e5b12016-06-08 21:40:09828 self.resources = [] # List of resources this work item requires.
[email protected]80cbe8b2010-08-13 13:53:07829
[email protected]77e4eca2010-09-21 13:23:07830 def run(self, work_queue):
831 """work_queue is passed as keyword argument so it should be
[email protected]3742c842010-09-09 19:27:14832 the last parameters of the function when you override it."""
[email protected]80cbe8b2010-08-13 13:53:07833 pass
834
[email protected]6ca8bf82011-09-19 23:04:30835 @property
836 def name(self):
837 return self._name
838
[email protected]80cbe8b2010-08-13 13:53:07839
840class ExecutionQueue(object):
[email protected]9e5317a2010-08-13 20:35:11841 """Runs a set of WorkItem that have interdependencies and were WorkItem are
842 added as they are processed.
[email protected]80cbe8b2010-08-13 13:53:07843
Paweł Hajdan, Jr7e9303b2017-05-23 12:38:27844 This class manages that all the required dependencies are run
[email protected]9e5317a2010-08-13 20:35:11845 before running each one.
[email protected]80cbe8b2010-08-13 13:53:07846
[email protected]9e5317a2010-08-13 20:35:11847 Methods of this class are thread safe.
[email protected]80cbe8b2010-08-13 13:53:07848 """
[email protected]fe0d1902014-04-08 20:50:44849 def __init__(self, jobs, progress, ignore_requirements, verbose=False):
[email protected]9e5317a2010-08-13 20:35:11850 """jobs specifies the number of concurrent tasks to allow. progress is a
851 Progress instance."""
852 # Set when a thread is done or a new item is enqueued.
853 self.ready_cond = threading.Condition()
854 # Maximum number of concurrent tasks.
855 self.jobs = jobs
856 # List of WorkItem, for gclient, these are Dependency instances.
[email protected]80cbe8b2010-08-13 13:53:07857 self.queued = []
858 # List of strings representing each Dependency.name that was run.
859 self.ran = []
860 # List of items currently running.
861 self.running = []
[email protected]9e5317a2010-08-13 20:35:11862 # Exceptions thrown if any.
[email protected]3742c842010-09-09 19:27:14863 self.exceptions = Queue.Queue()
864 # Progress status
[email protected]80cbe8b2010-08-13 13:53:07865 self.progress = progress
866 if self.progress:
[email protected]3742c842010-09-09 19:27:14867 self.progress.update(0)
[email protected]80cbe8b2010-08-13 13:53:07868
[email protected]f2ed3fb2012-11-09 23:39:49869 self.ignore_requirements = ignore_requirements
[email protected]fe0d1902014-04-08 20:50:44870 self.verbose = verbose
871 self.last_join = None
872 self.last_subproc_output = None
[email protected]f2ed3fb2012-11-09 23:39:49873
[email protected]80cbe8b2010-08-13 13:53:07874 def enqueue(self, d):
875 """Enqueue one Dependency to be executed later once its requirements are
876 satisfied.
877 """
878 assert isinstance(d, WorkItem)
[email protected]9e5317a2010-08-13 20:35:11879 self.ready_cond.acquire()
[email protected]80cbe8b2010-08-13 13:53:07880 try:
[email protected]80cbe8b2010-08-13 13:53:07881 self.queued.append(d)
882 total = len(self.queued) + len(self.ran) + len(self.running)
[email protected]e98e04c2014-07-25 00:28:06883 if self.jobs == 1:
884 total += 1
[email protected]9e5317a2010-08-13 20:35:11885 logging.debug('enqueued(%s)' % d.name)
886 if self.progress:
[email protected]e98e04c2014-07-25 00:28:06887 self.progress._total = total
[email protected]9e5317a2010-08-13 20:35:11888 self.progress.update(0)
889 self.ready_cond.notifyAll()
[email protected]80cbe8b2010-08-13 13:53:07890 finally:
[email protected]9e5317a2010-08-13 20:35:11891 self.ready_cond.release()
[email protected]80cbe8b2010-08-13 13:53:07892
[email protected]fe0d1902014-04-08 20:50:44893 def out_cb(self, _):
894 self.last_subproc_output = datetime.datetime.now()
895 return True
896
897 @staticmethod
898 def format_task_output(task, comment=''):
899 if comment:
900 comment = ' (%s)' % comment
901 if task.start and task.finish:
902 elapsed = ' (Elapsed: %s)' % (
903 str(task.finish - task.start).partition('.')[0])
904 else:
905 elapsed = ''
906 return """
907%s%s%s
908----------------------------------------
909%s
910----------------------------------------""" % (
[email protected]1f4e71b2014-04-09 19:45:40911 task.name, comment, elapsed, task.outbuf.getvalue().strip())
[email protected]fe0d1902014-04-08 20:50:44912
hinoka885e5b12016-06-08 21:40:09913 def _is_conflict(self, job):
914 """Checks to see if a job will conflict with another running job."""
915 for running_job in self.running:
916 for used_resource in running_job.item.resources:
917 logging.debug('Checking resource %s' % used_resource)
918 if used_resource in job.resources:
919 return True
920 return False
921
[email protected]80cbe8b2010-08-13 13:53:07922 def flush(self, *args, **kwargs):
923 """Runs all enqueued items until all are executed."""
[email protected]3742c842010-09-09 19:27:14924 kwargs['work_queue'] = self
[email protected]fe0d1902014-04-08 20:50:44925 self.last_subproc_output = self.last_join = datetime.datetime.now()
[email protected]9e5317a2010-08-13 20:35:11926 self.ready_cond.acquire()
[email protected]80cbe8b2010-08-13 13:53:07927 try:
[email protected]9e5317a2010-08-13 20:35:11928 while True:
929 # Check for task to run first, then wait.
930 while True:
[email protected]3742c842010-09-09 19:27:14931 if not self.exceptions.empty():
932 # Systematically flush the queue when an exception logged.
[email protected]9e5317a2010-08-13 20:35:11933 self.queued = []
[email protected]3742c842010-09-09 19:27:14934 self._flush_terminated_threads()
935 if (not self.queued and not self.running or
936 self.jobs == len(self.running)):
[email protected]1333cb32011-10-04 23:40:16937 logging.debug('No more worker threads or can\'t queue anything.')
[email protected]9e5317a2010-08-13 20:35:11938 break
[email protected]3742c842010-09-09 19:27:14939
940 # Check for new tasks to start.
[email protected]9e5317a2010-08-13 20:35:11941 for i in xrange(len(self.queued)):
942 # Verify its requirements.
[email protected]f2ed3fb2012-11-09 23:39:49943 if (self.ignore_requirements or
944 not (set(self.queued[i].requirements) - set(self.ran))):
hinoka885e5b12016-06-08 21:40:09945 if not self._is_conflict(self.queued[i]):
946 # Start one work item: all its requirements are satisfied.
947 self._run_one_task(self.queued.pop(i), args, kwargs)
948 break
[email protected]9e5317a2010-08-13 20:35:11949 else:
950 # Couldn't find an item that could run. Break out the outher loop.
951 break
[email protected]3742c842010-09-09 19:27:14952
[email protected]9e5317a2010-08-13 20:35:11953 if not self.queued and not self.running:
[email protected]3742c842010-09-09 19:27:14954 # We're done.
[email protected]9e5317a2010-08-13 20:35:11955 break
956 # We need to poll here otherwise Ctrl-C isn't processed.
[email protected]485dcab2011-09-14 12:48:47957 try:
958 self.ready_cond.wait(10)
[email protected]fe0d1902014-04-08 20:50:44959 # If we haven't printed to terminal for a while, but we have received
960 # spew from a suprocess, let the user know we're still progressing.
961 now = datetime.datetime.now()
962 if (now - self.last_join > datetime.timedelta(seconds=60) and
963 self.last_subproc_output > self.last_join):
964 if self.progress:
965 print >> sys.stdout, ''
[email protected]4dfb8662014-04-25 22:21:24966 sys.stdout.flush()
[email protected]fe0d1902014-04-08 20:50:44967 elapsed = Elapsed()
968 print >> sys.stdout, '[%s] Still working on:' % elapsed
[email protected]4dfb8662014-04-25 22:21:24969 sys.stdout.flush()
[email protected]fe0d1902014-04-08 20:50:44970 for task in self.running:
971 print >> sys.stdout, '[%s] %s' % (elapsed, task.item.name)
[email protected]4dfb8662014-04-25 22:21:24972 sys.stdout.flush()
[email protected]485dcab2011-09-14 12:48:47973 except KeyboardInterrupt:
974 # Help debugging by printing some information:
975 print >> sys.stderr, (
976 ('\nAllowed parallel jobs: %d\n# queued: %d\nRan: %s\n'
977 'Running: %d') % (
978 self.jobs,
979 len(self.queued),
980 ', '.join(self.ran),
981 len(self.running)))
982 for i in self.queued:
[email protected]fe0d1902014-04-08 20:50:44983 print >> sys.stderr, '%s (not started): %s' % (
984 i.name, ', '.join(i.requirements))
985 for i in self.running:
986 print >> sys.stderr, self.format_task_output(i.item, 'interrupted')
[email protected]485dcab2011-09-14 12:48:47987 raise
[email protected]9e5317a2010-08-13 20:35:11988 # Something happened: self.enqueue() or a thread terminated. Loop again.
[email protected]80cbe8b2010-08-13 13:53:07989 finally:
[email protected]9e5317a2010-08-13 20:35:11990 self.ready_cond.release()
[email protected]3742c842010-09-09 19:27:14991
[email protected]9e5317a2010-08-13 20:35:11992 assert not self.running, 'Now guaranteed to be single-threaded'
[email protected]3742c842010-09-09 19:27:14993 if not self.exceptions.empty():
[email protected]fe0d1902014-04-08 20:50:44994 if self.progress:
995 print >> sys.stdout, ''
[email protected]c8d064b2010-08-16 16:46:14996 # To get back the stack location correctly, the raise a, b, c form must be
997 # used, passing a tuple as the first argument doesn't work.
[email protected]fe0d1902014-04-08 20:50:44998 e, task = self.exceptions.get()
999 print >> sys.stderr, self.format_task_output(task.item, 'ERROR')
[email protected]c8d064b2010-08-16 16:46:141000 raise e[0], e[1], e[2]
[email protected]fe0d1902014-04-08 20:50:441001 elif self.progress:
[email protected]80cbe8b2010-08-13 13:53:071002 self.progress.end()
[email protected]80cbe8b2010-08-13 13:53:071003
[email protected]3742c842010-09-09 19:27:141004 def _flush_terminated_threads(self):
1005 """Flush threads that have terminated."""
1006 running = self.running
1007 self.running = []
1008 for t in running:
1009 if t.isAlive():
1010 self.running.append(t)
1011 else:
1012 t.join()
[email protected]fe0d1902014-04-08 20:50:441013 self.last_join = datetime.datetime.now()
[email protected]042f0e72011-10-23 00:04:351014 sys.stdout.flush()
[email protected]fe0d1902014-04-08 20:50:441015 if self.verbose:
1016 print >> sys.stdout, self.format_task_output(t.item)
[email protected]3742c842010-09-09 19:27:141017 if self.progress:
[email protected]55a2eb82010-10-06 23:35:181018 self.progress.update(1, t.item.name)
[email protected]f36c0ee2011-09-14 19:16:471019 if t.item.name in self.ran:
1020 raise Error(
1021 'gclient is confused, "%s" is already in "%s"' % (
1022 t.item.name, ', '.join(self.ran)))
[email protected]acc45672010-09-09 21:21:211023 if not t.item.name in self.ran:
1024 self.ran.append(t.item.name)
[email protected]3742c842010-09-09 19:27:141025
1026 def _run_one_task(self, task_item, args, kwargs):
1027 if self.jobs > 1:
1028 # Start the thread.
1029 index = len(self.ran) + len(self.running) + 1
[email protected]77e4eca2010-09-21 13:23:071030 new_thread = self._Worker(task_item, index, args, kwargs)
[email protected]3742c842010-09-09 19:27:141031 self.running.append(new_thread)
1032 new_thread.start()
1033 else:
1034 # Run the 'thread' inside the main thread. Don't try to catch any
1035 # exception.
[email protected]fe0d1902014-04-08 20:50:441036 try:
1037 task_item.start = datetime.datetime.now()
1038 print >> task_item.outbuf, '[%s] Started.' % Elapsed(task_item.start)
1039 task_item.run(*args, **kwargs)
1040 task_item.finish = datetime.datetime.now()
1041 print >> task_item.outbuf, '[%s] Finished.' % Elapsed(task_item.finish)
1042 self.ran.append(task_item.name)
1043 if self.verbose:
1044 if self.progress:
1045 print >> sys.stdout, ''
1046 print >> sys.stdout, self.format_task_output(task_item)
1047 if self.progress:
1048 self.progress.update(1, ', '.join(t.item.name for t in self.running))
1049 except KeyboardInterrupt:
1050 print >> sys.stderr, self.format_task_output(task_item, 'interrupted')
1051 raise
1052 except Exception:
1053 print >> sys.stderr, self.format_task_output(task_item, 'ERROR')
1054 raise
1055
[email protected]3742c842010-09-09 19:27:141056
[email protected]9e5317a2010-08-13 20:35:111057 class _Worker(threading.Thread):
1058 """One thread to execute one WorkItem."""
[email protected]4ed34182010-09-17 15:57:471059 def __init__(self, item, index, args, kwargs):
[email protected]9e5317a2010-08-13 20:35:111060 threading.Thread.__init__(self, name=item.name or 'Worker')
[email protected]1333cb32011-10-04 23:40:161061 logging.info('_Worker(%s) reqs:%s' % (item.name, item.requirements))
[email protected]9e5317a2010-08-13 20:35:111062 self.item = item
[email protected]4ed34182010-09-17 15:57:471063 self.index = index
[email protected]3742c842010-09-09 19:27:141064 self.args = args
1065 self.kwargs = kwargs
[email protected]2fd6c3f2013-05-03 21:57:551066 self.daemon = True
[email protected]80cbe8b2010-08-13 13:53:071067
[email protected]9e5317a2010-08-13 20:35:111068 def run(self):
1069 """Runs in its own thread."""
[email protected]1333cb32011-10-04 23:40:161070 logging.debug('_Worker.run(%s)' % self.item.name)
[email protected]3742c842010-09-09 19:27:141071 work_queue = self.kwargs['work_queue']
[email protected]9e5317a2010-08-13 20:35:111072 try:
[email protected]fe0d1902014-04-08 20:50:441073 self.item.start = datetime.datetime.now()
1074 print >> self.item.outbuf, '[%s] Started.' % Elapsed(self.item.start)
[email protected]9e5317a2010-08-13 20:35:111075 self.item.run(*self.args, **self.kwargs)
[email protected]fe0d1902014-04-08 20:50:441076 self.item.finish = datetime.datetime.now()
1077 print >> self.item.outbuf, '[%s] Finished.' % Elapsed(self.item.finish)
[email protected]2fd6c3f2013-05-03 21:57:551078 except KeyboardInterrupt:
[email protected]c144e062013-05-03 23:23:531079 logging.info('Caught KeyboardInterrupt in thread %s', self.item.name)
[email protected]2fd6c3f2013-05-03 21:57:551080 logging.info(str(sys.exc_info()))
[email protected]fe0d1902014-04-08 20:50:441081 work_queue.exceptions.put((sys.exc_info(), self))
[email protected]2fd6c3f2013-05-03 21:57:551082 raise
[email protected]c8d064b2010-08-16 16:46:141083 except Exception:
1084 # Catch exception location.
[email protected]c144e062013-05-03 23:23:531085 logging.info('Caught exception in thread %s', self.item.name)
[email protected]3742c842010-09-09 19:27:141086 logging.info(str(sys.exc_info()))
[email protected]fe0d1902014-04-08 20:50:441087 work_queue.exceptions.put((sys.exc_info(), self))
[email protected]9e5317a2010-08-13 20:35:111088 finally:
[email protected]c144e062013-05-03 23:23:531089 logging.info('_Worker.run(%s) done', self.item.name)
[email protected]2fd6c3f2013-05-03 21:57:551090 work_queue.ready_cond.acquire()
1091 try:
1092 work_queue.ready_cond.notifyAll()
1093 finally:
1094 work_queue.ready_cond.release()
[email protected]0e0436a2011-10-25 13:32:411095
1096
agable92bec4f2016-08-24 16:27:271097def GetEditor(git_editor=None):
[email protected]615a2622013-05-03 13:20:141098 """Returns the most plausible editor to use.
1099
1100 In order of preference:
agable41e3a6c2016-10-20 18:36:561101 - GIT_EDITOR environment variable
[email protected]615a2622013-05-03 13:20:141102 - core.editor git configuration variable (if supplied by git-cl)
1103 - VISUAL environment variable
1104 - EDITOR environment variable
[email protected]65621c72013-12-09 15:05:321105 - vi (non-Windows) or notepad (Windows)
[email protected]615a2622013-05-03 13:20:141106
1107 In the case of git-cl, this matches git's behaviour, except that it does not
1108 include dumb terminal detection.
[email protected]615a2622013-05-03 13:20:141109 """
agable92bec4f2016-08-24 16:27:271110 editor = os.environ.get('GIT_EDITOR') or git_editor
[email protected]0e0436a2011-10-25 13:32:411111 if not editor:
[email protected]615a2622013-05-03 13:20:141112 editor = os.environ.get('VISUAL')
1113 if not editor:
[email protected]0e0436a2011-10-25 13:32:411114 editor = os.environ.get('EDITOR')
1115 if not editor:
1116 if sys.platform.startswith('win'):
1117 editor = 'notepad'
1118 else:
[email protected]65621c72013-12-09 15:05:321119 editor = 'vi'
[email protected]0e0436a2011-10-25 13:32:411120 return editor
1121
1122
[email protected]615a2622013-05-03 13:20:141123def RunEditor(content, git, git_editor=None):
[email protected]0e0436a2011-10-25 13:32:411124 """Opens up the default editor in the system to get the CL description."""
[email protected]cbd760f2013-07-23 13:02:481125 file_handle, filename = tempfile.mkstemp(text=True, prefix='cl_description')
[email protected]0e0436a2011-10-25 13:32:411126 # Make sure CRLF is handled properly by requiring none.
1127 if '\r' in content:
[email protected]0eff22d2011-10-25 16:11:161128 print >> sys.stderr, (
1129 '!! Please remove \\r from your change description !!')
[email protected]0e0436a2011-10-25 13:32:411130 fileobj = os.fdopen(file_handle, 'w')
1131 # Still remove \r if present.
[email protected]a3fe2902016-04-20 18:31:371132 content = re.sub('\r?\n', '\n', content)
1133 # Some editors complain when the file doesn't end in \n.
1134 if not content.endswith('\n'):
1135 content += '\n'
1136 fileobj.write(content)
[email protected]0e0436a2011-10-25 13:32:411137 fileobj.close()
1138
1139 try:
agable92bec4f2016-08-24 16:27:271140 editor = GetEditor(git_editor=git_editor)
[email protected]615a2622013-05-03 13:20:141141 if not editor:
1142 return None
1143 cmd = '%s %s' % (editor, filename)
[email protected]0e0436a2011-10-25 13:32:411144 if sys.platform == 'win32' and os.environ.get('TERM') == 'msys':
1145 # Msysgit requires the usage of 'env' to be present.
1146 cmd = 'env ' + cmd
1147 try:
1148 # shell=True to allow the shell to handle all forms of quotes in
1149 # $EDITOR.
1150 subprocess2.check_call(cmd, shell=True)
1151 except subprocess2.CalledProcessError:
1152 return None
1153 return FileRead(filename)
1154 finally:
1155 os.remove(filename)
[email protected]99ac1c52012-01-16 14:52:121156
1157
[email protected]eb5edbc2012-01-16 17:03:281158def UpgradeToHttps(url):
1159 """Upgrades random urls to https://.
1160
1161 Do not touch unknown urls like ssh:// or git://.
1162 Do not touch http:// urls with a port number,
1163 Fixes invalid GAE url.
1164 """
1165 if not url:
1166 return url
1167 if not re.match(r'[a-z\-]+\://.*', url):
1168 # Make sure it is a valid uri. Otherwise, urlparse() will consider it a
1169 # relative url and will use http:///foo. Note that it defaults to http://
1170 # for compatibility with naked url like "localhost:8080".
1171 url = 'http://%s' % url
1172 parsed = list(urlparse.urlparse(url))
1173 # Do not automatically upgrade http to https if a port number is provided.
1174 if parsed[0] == 'http' and not re.match(r'^.+?\:\d+$', parsed[1]):
1175 parsed[0] = 'https'
[email protected]eb5edbc2012-01-16 17:03:281176 return urlparse.urlunparse(parsed)
1177
1178
[email protected]99ac1c52012-01-16 14:52:121179def ParseCodereviewSettingsContent(content):
1180 """Process a codereview.settings file properly."""
1181 lines = (l for l in content.splitlines() if not l.strip().startswith("#"))
1182 try:
1183 keyvals = dict([x.strip() for x in l.split(':', 1)] for l in lines if l)
1184 except ValueError:
1185 raise Error(
1186 'Failed to process settings, please fix. Content:\n\n%s' % content)
[email protected]eb5edbc2012-01-16 17:03:281187 def fix_url(key):
1188 if keyvals.get(key):
1189 keyvals[key] = UpgradeToHttps(keyvals[key])
1190 fix_url('CODE_REVIEW_SERVER')
1191 fix_url('VIEW_VC')
[email protected]99ac1c52012-01-16 14:52:121192 return keyvals
[email protected]13691502012-10-16 04:26:371193
1194
1195def NumLocalCpus():
1196 """Returns the number of processors.
1197
[email protected]530523b2015-01-07 19:54:571198 multiprocessing.cpu_count() is permitted to raise NotImplementedError, and
1199 is known to do this on some Windows systems and OSX 10.6. If we can't get the
1200 CPU count, we will fall back to '1'.
[email protected]13691502012-10-16 04:26:371201 """
[email protected]530523b2015-01-07 19:54:571202 # Surround the entire thing in try/except; no failure here should stop gclient
1203 # from working.
[email protected]13691502012-10-16 04:26:371204 try:
[email protected]530523b2015-01-07 19:54:571205 # Use multiprocessing to get CPU count. This may raise
1206 # NotImplementedError.
1207 try:
1208 import multiprocessing
1209 return multiprocessing.cpu_count()
Quinten Yearsleyb2cc4a92016-12-15 21:53:261210 except NotImplementedError: # pylint: disable=bare-except
[email protected]530523b2015-01-07 19:54:571211 # (UNIX) Query 'os.sysconf'.
Quinten Yearsleyb2cc4a92016-12-15 21:53:261212 # pylint: disable=no-member
[email protected]530523b2015-01-07 19:54:571213 if hasattr(os, 'sysconf') and 'SC_NPROCESSORS_ONLN' in os.sysconf_names:
1214 return int(os.sysconf('SC_NPROCESSORS_ONLN'))
1215
1216 # (Windows) Query 'NUMBER_OF_PROCESSORS' environment variable.
1217 if 'NUMBER_OF_PROCESSORS' in os.environ:
1218 return int(os.environ['NUMBER_OF_PROCESSORS'])
1219 except Exception as e:
1220 logging.exception("Exception raised while probing CPU count: %s", e)
1221
1222 logging.debug('Failed to get CPU count. Defaulting to 1.')
1223 return 1
[email protected]fc616382014-03-18 20:32:041224
[email protected]9d0644d2015-06-05 23:16:541225
[email protected]fc616382014-03-18 20:32:041226def DefaultDeltaBaseCacheLimit():
1227 """Return a reasonable default for the git config core.deltaBaseCacheLimit.
1228
1229 The primary constraint is the address space of virtual memory. The cache
1230 size limit is per-thread, and 32-bit systems can hit OOM errors if this
1231 parameter is set too high.
1232 """
1233 if platform.architecture()[0].startswith('64'):
1234 return '2g'
1235 else:
1236 return '512m'
1237
[email protected]9d0644d2015-06-05 23:16:541238
[email protected]ff113292014-03-25 06:02:081239def DefaultIndexPackConfig(url=''):
[email protected]fc616382014-03-18 20:32:041240 """Return reasonable default values for configuring git-index-pack.
1241
1242 Experiments suggest that higher values for pack.threads don't improve
1243 performance."""
[email protected]ff113292014-03-25 06:02:081244 cache_limit = DefaultDeltaBaseCacheLimit()
1245 result = ['-c', 'core.deltaBaseCacheLimit=%s' % cache_limit]
1246 if url in THREADED_INDEX_PACK_BLACKLIST:
1247 result.extend(['-c', 'pack.threads=1'])
1248 return result
[email protected]9d0644d2015-06-05 23:16:541249
1250
1251def FindExecutable(executable):
1252 """This mimics the "which" utility."""
1253 path_folders = os.environ.get('PATH').split(os.pathsep)
1254
1255 for path_folder in path_folders:
1256 target = os.path.join(path_folder, executable)
1257 # Just incase we have some ~/blah paths.
1258 target = os.path.abspath(os.path.expanduser(target))
1259 if os.path.isfile(target) and os.access(target, os.X_OK):
1260 return target
1261 if sys.platform.startswith('win'):
1262 for suffix in ('.bat', '.cmd', '.exe'):
1263 alt_target = target + suffix
1264 if os.path.isfile(alt_target) and os.access(alt_target, os.X_OK):
1265 return alt_target
1266 return None
Paweł Hajdan, Jr7e502612017-06-12 14:58:381267
1268
1269def freeze(obj):
1270 """Takes a generic object ``obj``, and returns an immutable version of it.
1271
1272 Supported types:
1273 * dict / OrderedDict -> FrozenDict
1274 * list -> tuple
1275 * set -> frozenset
1276 * any object with a working __hash__ implementation (assumes that hashable
1277 means immutable)
1278
1279 Will raise TypeError if you pass an object which is not hashable.
1280 """
Edward Lesmes6f64a052018-03-20 21:35:491281 if isinstance(obj, collections.Mapping):
Paweł Hajdan, Jr7e502612017-06-12 14:58:381282 return FrozenDict((freeze(k), freeze(v)) for k, v in obj.iteritems())
1283 elif isinstance(obj, (list, tuple)):
1284 return tuple(freeze(i) for i in obj)
1285 elif isinstance(obj, set):
1286 return frozenset(freeze(i) for i in obj)
1287 else:
1288 hash(obj)
1289 return obj
1290
1291
1292class FrozenDict(collections.Mapping):
1293 """An immutable OrderedDict.
1294
1295 Modified From: http://stackoverflow.com/a/2704866
1296 """
1297 def __init__(self, *args, **kwargs):
1298 self._d = collections.OrderedDict(*args, **kwargs)
1299
1300 # Calculate the hash immediately so that we know all the items are
1301 # hashable too.
1302 self._hash = reduce(operator.xor,
1303 (hash(i) for i in enumerate(self._d.iteritems())), 0)
1304
1305 def __eq__(self, other):
1306 if not isinstance(other, collections.Mapping):
1307 return NotImplemented
1308 if self is other:
1309 return True
1310 if len(self) != len(other):
1311 return False
1312 for k, v in self.iteritems():
1313 if k not in other or other[k] != v:
1314 return False
1315 return True
1316
1317 def __iter__(self):
1318 return iter(self._d)
1319
1320 def __len__(self):
1321 return len(self._d)
1322
1323 def __getitem__(self, key):
1324 return self._d[key]
1325
1326 def __hash__(self):
1327 return self._hash
1328
1329 def __repr__(self):
1330 return 'FrozenDict(%r)' % (self._d.items(),)