blob: 3517946435a6efe346af97bb539f15f46f624166 [file] [log] [blame]
maruel@chromium.org99ac1c52012-01-16 14:52:12 +00001# Copyright (c) 2012 The Chromium Authors. All rights reserved.
maruel@chromium.org06617272010-11-04 13:50:50 +00002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00004
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +00005"""Generic utils."""
6
maruel@chromium.orgdae209f2012-07-03 16:08:15 +00007import codecs
hinoka@google.com267f33e2014-02-28 22:02:32 +00008import cStringIO
maruel@chromium.orgd9141bf2009-12-23 16:13:32 +00009import logging
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000010import os
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +000011import pipes
szager@chromium.orgfc616382014-03-18 20:32:04 +000012import platform
maruel@chromium.org3742c842010-09-09 19:27:14 +000013import Queue
msb@chromium.orgac915bb2009-11-13 17:03:01 +000014import re
bradnelson@google.com8f9c69f2009-09-17 00:48:28 +000015import stat
borenet@google.com6b4a2ab2013-04-18 15:50:27 +000016import subprocess
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000017import sys
maruel@chromium.org0e0436a2011-10-25 13:32:41 +000018import tempfile
maruel@chromium.org9e5317a2010-08-13 20:35:11 +000019import threading
maruel@chromium.org167b9e62009-09-17 17:41:02 +000020import time
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +000021import urlparse
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000022
maruel@chromium.orgca0f8392011-09-08 17:15:15 +000023import subprocess2
24
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000025
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +000026RETRY_MAX = 3
27RETRY_INITIAL_SLEEP = 0.5
28
29
borenet@google.com6a9b1682014-03-24 18:35:23 +000030_WARNINGS = []
31
32
szager@chromium.orgff113292014-03-25 06:02:08 +000033# These repos are known to cause OOM errors on 32-bit platforms, due the the
34# very large objects they contain. It is not safe to use threaded index-pack
35# when cloning/fetching them.
36THREADED_INDEX_PACK_BLACKLIST = [
37 'https://chromium.googlesource.com/chromium/reference_builds/chrome_win.git'
38]
39
40
maruel@chromium.org66c83e62010-09-07 14:18:45 +000041class Error(Exception):
42 """gclient exception class."""
szager@chromium.org4a3c17e2013-05-24 23:59:29 +000043 def __init__(self, msg, *args, **kwargs):
44 index = getattr(threading.currentThread(), 'index', 0)
45 if index:
46 msg = '\n'.join('%d> %s' % (index, l) for l in msg.splitlines())
47 super(Error, self).__init__(msg, *args, **kwargs)
maruel@chromium.org66c83e62010-09-07 14:18:45 +000048
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +000049
borenet@google.com6a9b1682014-03-24 18:35:23 +000050def PrintWarnings():
51 """Prints any accumulated warnings."""
52 if _WARNINGS:
53 print >> sys.stderr, '\n\nWarnings:'
54 for warning in _WARNINGS:
55 print >> sys.stderr, warning
56
57
58def AddWarning(msg):
59 """Adds the given warning message to the list of accumulated warnings."""
60 _WARNINGS.append(msg)
61
62
msb@chromium.orgac915bb2009-11-13 17:03:01 +000063def SplitUrlRevision(url):
64 """Splits url and returns a two-tuple: url, rev"""
65 if url.startswith('ssh:'):
maruel@chromium.org78b8cd12010-10-26 12:47:07 +000066 # Make sure ssh://user-name@example.com/~/test.git@stable works
kangil.han@samsung.com71b13572013-10-16 17:28:11 +000067 regex = r'(ssh://(?:[-.\w]+@)?[-\w:\.]+/[-~\w\./]+)(?:@(.+))?'
msb@chromium.orgac915bb2009-11-13 17:03:01 +000068 components = re.search(regex, url).groups()
69 else:
maruel@chromium.org116704f2010-06-11 17:34:38 +000070 components = url.split('@', 1)
msb@chromium.orgac915bb2009-11-13 17:03:01 +000071 if len(components) == 1:
72 components += [None]
73 return tuple(components)
74
75
floitsch@google.comeaab7842011-04-28 09:07:58 +000076def IsDateRevision(revision):
77 """Returns true if the given revision is of the form "{ ... }"."""
78 return bool(revision and re.match(r'^\{.+\}$', str(revision)))
79
80
81def MakeDateRevision(date):
82 """Returns a revision representing the latest revision before the given
83 date."""
84 return "{" + date + "}"
85
86
maruel@chromium.org5990f9d2010-07-07 18:02:58 +000087def SyntaxErrorToError(filename, e):
88 """Raises a gclient_utils.Error exception with the human readable message"""
89 try:
90 # Try to construct a human readable error message
91 if filename:
92 error_message = 'There is a syntax error in %s\n' % filename
93 else:
94 error_message = 'There is a syntax error\n'
95 error_message += 'Line #%s, character %s: "%s"' % (
96 e.lineno, e.offset, re.sub(r'[\r\n]*$', '', e.text))
97 except:
98 # Something went wrong, re-raise the original exception
99 raise e
100 else:
101 raise Error(error_message)
102
103
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000104class PrintableObject(object):
105 def __str__(self):
106 output = ''
107 for i in dir(self):
108 if i.startswith('__'):
109 continue
110 output += '%s = %s\n' % (i, str(getattr(self, i, '')))
111 return output
112
113
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000114def FileRead(filename, mode='rU'):
maruel@chromium.org51e84fb2012-07-03 23:06:21 +0000115 with open(filename, mode=mode) as f:
maruel@chromium.orgc3cd5372012-07-11 17:39:24 +0000116 # codecs.open() has different behavior than open() on python 2.6 so use
117 # open() and decode manually.
chrisha@chromium.org2b99d432012-07-12 18:10:28 +0000118 s = f.read()
119 try:
120 return s.decode('utf-8')
121 except UnicodeDecodeError:
122 return s
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000123
124
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000125def FileWrite(filename, content, mode='w'):
maruel@chromium.orgdae209f2012-07-03 16:08:15 +0000126 with codecs.open(filename, mode=mode, encoding='utf-8') as f:
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000127 f.write(content)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000128
129
cyrille@nnamrak.orgef509e42013-09-20 13:19:08 +0000130def safe_rename(old, new):
131 """Renames a file reliably.
132
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +0000133 Sometimes os.rename does not work because a dying git process keeps a handle
134 on it for a few seconds. An exception is then thrown, which make the program
cyrille@nnamrak.orgef509e42013-09-20 13:19:08 +0000135 give up what it was doing and remove what was deleted.
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +0000136 The only solution is to catch the exception and try again until it works.
cyrille@nnamrak.orgef509e42013-09-20 13:19:08 +0000137 """
138 # roughly 10s
139 retries = 100
140 for i in range(retries):
141 try:
142 os.rename(old, new)
143 break
144 except OSError:
145 if i == (retries - 1):
146 # Give up.
147 raise
148 # retry
149 logging.debug("Renaming failed from %s to %s. Retrying ..." % (old, new))
150 time.sleep(0.1)
151
152
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000153def rmtree(path):
154 """shutil.rmtree() on steroids.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000155
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000156 Recursively removes a directory, even if it's marked read-only.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000157
158 shutil.rmtree() doesn't work on Windows if any of the files or directories
159 are read-only, which svn repositories and some .svn files are. We need to
160 be able to force the files to be writable (i.e., deletable) as we traverse
161 the tree.
162
163 Even with all this, Windows still sometimes fails to delete a file, citing
164 a permission error (maybe something to do with antivirus scans or disk
165 indexing). The best suggestion any of the user forums had was to wait a
166 bit and try again, so we do that too. It's hand-waving, but sometimes it
167 works. :/
168
169 On POSIX systems, things are a little bit simpler. The modes of the files
170 to be deleted doesn't matter, only the modes of the directories containing
171 them are significant. As the directory tree is traversed, each directory
172 has its mode set appropriately before descending into it. This should
173 result in the entire tree being removed, with the possible exception of
174 *path itself, because nothing attempts to change the mode of its parent.
175 Doing so would be hazardous, as it's not a directory slated for removal.
176 In the ordinary case, this is not a problem: for our purposes, the user
177 will never lack write permission on *path's parent.
178 """
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000179 if not os.path.exists(path):
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000180 return
181
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000182 if os.path.islink(path) or not os.path.isdir(path):
183 raise Error('Called rmtree(%s) in non-directory' % path)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000184
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000185 if sys.platform == 'win32':
borenet@google.com6b4a2ab2013-04-18 15:50:27 +0000186 # Give up and use cmd.exe's rd command.
187 path = os.path.normcase(path)
188 for _ in xrange(3):
189 exitcode = subprocess.call(['cmd.exe', '/c', 'rd', '/q', '/s', path])
190 if exitcode == 0:
191 return
192 else:
193 print >> sys.stderr, 'rd exited with code %d' % exitcode
194 time.sleep(3)
195 raise Exception('Failed to remove path %s' % path)
196
197 # On POSIX systems, we need the x-bit set on the directory to access it,
198 # the r-bit to see its contents, and the w-bit to remove files from it.
199 # The actual modes of the files within the directory is irrelevant.
200 os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000201
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000202 def remove(func, subpath):
borenet@google.com6b4a2ab2013-04-18 15:50:27 +0000203 func(subpath)
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000204
205 for fn in os.listdir(path):
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000206 # If fullpath is a symbolic link that points to a directory, isdir will
207 # be True, but we don't want to descend into that as a directory, we just
208 # want to remove the link. Check islink and treat links as ordinary files
209 # would be treated regardless of what they reference.
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000210 fullpath = os.path.join(path, fn)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000211 if os.path.islink(fullpath) or not os.path.isdir(fullpath):
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000212 remove(os.remove, fullpath)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000213 else:
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000214 # Recurse.
215 rmtree(fullpath)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000216
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000217 remove(os.rmdir, path)
218
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000219
maruel@chromium.org6c48a302011-10-20 23:44:20 +0000220def safe_makedirs(tree):
221 """Creates the directory in a safe manner.
222
223 Because multiple threads can create these directories concurently, trap the
224 exception and pass on.
225 """
226 count = 0
227 while not os.path.exists(tree):
228 count += 1
229 try:
230 os.makedirs(tree)
231 except OSError, e:
232 # 17 POSIX, 183 Windows
233 if e.errno not in (17, 183):
234 raise
235 if count > 40:
236 # Give up.
237 raise
238
239
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000240def CommandToStr(args):
241 """Converts an arg list into a shell escaped string."""
242 return ' '.join(pipes.quote(arg) for arg in args)
243
244
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000245def CheckCallAndFilterAndHeader(args, always=False, header=None, **kwargs):
maruel@chromium.org17d01792010-09-01 18:07:10 +0000246 """Adds 'header' support to CheckCallAndFilter.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000247
maruel@chromium.org17d01792010-09-01 18:07:10 +0000248 If |always| is True, a message indicating what is being done
249 is printed to stdout all the time even if not output is generated. Otherwise
250 the message header is printed only if the call generated any ouput.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000251 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000252 stdout = kwargs.setdefault('stdout', sys.stdout)
253 if header is None:
254 header = "\n________ running '%s' in '%s'\n" % (
ilevy@chromium.org4aad1852013-07-12 21:32:51 +0000255 ' '.join(args), kwargs.get('cwd', '.'))
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000256
maruel@chromium.org17d01792010-09-01 18:07:10 +0000257 if always:
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000258 stdout.write(header)
maruel@chromium.org17d01792010-09-01 18:07:10 +0000259 else:
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000260 filter_fn = kwargs.get('filter_fn')
maruel@chromium.org17d01792010-09-01 18:07:10 +0000261 def filter_msg(line):
262 if line is None:
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000263 stdout.write(header)
maruel@chromium.org17d01792010-09-01 18:07:10 +0000264 elif filter_fn:
265 filter_fn(line)
266 kwargs['filter_fn'] = filter_msg
267 kwargs['call_filter_on_first_line'] = True
268 # Obviously.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000269 kwargs.setdefault('print_stdout', True)
maruel@chromium.org17d01792010-09-01 18:07:10 +0000270 return CheckCallAndFilter(args, **kwargs)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000271
maruel@chromium.org17d01792010-09-01 18:07:10 +0000272
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000273class Wrapper(object):
274 """Wraps an object, acting as a transparent proxy for all properties by
275 default.
276 """
277 def __init__(self, wrapped):
278 self._wrapped = wrapped
279
280 def __getattr__(self, name):
281 return getattr(self._wrapped, name)
maruel@chromium.orgdb111f72010-09-08 13:36:53 +0000282
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +0000283
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000284class AutoFlush(Wrapper):
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +0000285 """Creates a file object clone to automatically flush after N seconds."""
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000286 def __init__(self, wrapped, delay):
287 super(AutoFlush, self).__init__(wrapped)
288 if not hasattr(self, 'lock'):
289 self.lock = threading.Lock()
290 self.__last_flushed_at = time.time()
291 self.delay = delay
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +0000292
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000293 @property
294 def autoflush(self):
295 return self
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +0000296
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000297 def write(self, out, *args, **kwargs):
298 self._wrapped.write(out, *args, **kwargs)
maruel@chromium.orgdb111f72010-09-08 13:36:53 +0000299 should_flush = False
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000300 self.lock.acquire()
maruel@chromium.org9c531262010-09-08 13:41:13 +0000301 try:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000302 if self.delay and (time.time() - self.__last_flushed_at) > self.delay:
maruel@chromium.orgdb111f72010-09-08 13:36:53 +0000303 should_flush = True
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000304 self.__last_flushed_at = time.time()
maruel@chromium.org9c531262010-09-08 13:41:13 +0000305 finally:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000306 self.lock.release()
maruel@chromium.orgdb111f72010-09-08 13:36:53 +0000307 if should_flush:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000308 self.flush()
maruel@chromium.orgdb111f72010-09-08 13:36:53 +0000309
310
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000311class Annotated(Wrapper):
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000312 """Creates a file object clone to automatically prepends every line in worker
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000313 threads with a NN> prefix.
314 """
315 def __init__(self, wrapped, include_zero=False):
316 super(Annotated, self).__init__(wrapped)
317 if not hasattr(self, 'lock'):
318 self.lock = threading.Lock()
319 self.__output_buffers = {}
320 self.__include_zero = include_zero
maruel@chromium.orgcb1e97a2010-09-09 20:09:20 +0000321
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000322 @property
323 def annotated(self):
324 return self
maruel@chromium.orgcb1e97a2010-09-09 20:09:20 +0000325
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000326 def write(self, out):
327 index = getattr(threading.currentThread(), 'index', 0)
328 if not index and not self.__include_zero:
329 # Unindexed threads aren't buffered.
330 return self._wrapped.write(out)
maruel@chromium.orgcb1e97a2010-09-09 20:09:20 +0000331
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000332 self.lock.acquire()
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000333 try:
334 # Use a dummy array to hold the string so the code can be lockless.
335 # Strings are immutable, requiring to keep a lock for the whole dictionary
336 # otherwise. Using an array is faster than using a dummy object.
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000337 if not index in self.__output_buffers:
338 obj = self.__output_buffers[index] = ['']
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000339 else:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000340 obj = self.__output_buffers[index]
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000341 finally:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000342 self.lock.release()
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000343
344 # Continue lockless.
345 obj[0] += out
346 while '\n' in obj[0]:
347 line, remaining = obj[0].split('\n', 1)
nsylvain@google.come939bb52011-06-01 22:59:15 +0000348 if line:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000349 self._wrapped.write('%d>%s\n' % (index, line))
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000350 obj[0] = remaining
351
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000352 def flush(self):
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000353 """Flush buffered output."""
354 orphans = []
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000355 self.lock.acquire()
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000356 try:
357 # Detect threads no longer existing.
358 indexes = (getattr(t, 'index', None) for t in threading.enumerate())
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +0000359 indexes = filter(None, indexes)
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000360 for index in self.__output_buffers:
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000361 if not index in indexes:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000362 orphans.append((index, self.__output_buffers[index][0]))
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000363 for orphan in orphans:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000364 del self.__output_buffers[orphan[0]]
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000365 finally:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000366 self.lock.release()
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000367
368 # Don't keep the lock while writting. Will append \n when it shouldn't.
369 for orphan in orphans:
nsylvain@google.come939bb52011-06-01 22:59:15 +0000370 if orphan[1]:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000371 self._wrapped.write('%d>%s\n' % (orphan[0], orphan[1]))
372 return self._wrapped.flush()
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000373
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000374
375def MakeFileAutoFlush(fileobj, delay=10):
376 autoflush = getattr(fileobj, 'autoflush', None)
377 if autoflush:
378 autoflush.delay = delay
379 return fileobj
380 return AutoFlush(fileobj, delay)
381
382
383def MakeFileAnnotated(fileobj, include_zero=False):
384 if getattr(fileobj, 'annotated', None):
385 return fileobj
386 return Annotated(fileobj)
maruel@chromium.orgcb1e97a2010-09-09 20:09:20 +0000387
388
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000389GCLIENT_CHILDREN = []
390GCLIENT_CHILDREN_LOCK = threading.Lock()
391
392
393class GClientChildren(object):
394 @staticmethod
395 def add(popen_obj):
396 with GCLIENT_CHILDREN_LOCK:
397 GCLIENT_CHILDREN.append(popen_obj)
398
399 @staticmethod
400 def remove(popen_obj):
401 with GCLIENT_CHILDREN_LOCK:
402 GCLIENT_CHILDREN.remove(popen_obj)
403
404 @staticmethod
405 def _attemptToKillChildren():
406 global GCLIENT_CHILDREN
407 with GCLIENT_CHILDREN_LOCK:
408 zombies = [c for c in GCLIENT_CHILDREN if c.poll() is None]
409
410 for zombie in zombies:
411 try:
412 zombie.kill()
413 except OSError:
414 pass
415
416 with GCLIENT_CHILDREN_LOCK:
417 GCLIENT_CHILDREN = [k for k in GCLIENT_CHILDREN if k.poll() is not None]
418
419 @staticmethod
420 def _areZombies():
421 with GCLIENT_CHILDREN_LOCK:
422 return bool(GCLIENT_CHILDREN)
423
424 @staticmethod
425 def KillAllRemainingChildren():
426 GClientChildren._attemptToKillChildren()
427
428 if GClientChildren._areZombies():
429 time.sleep(0.5)
430 GClientChildren._attemptToKillChildren()
431
432 with GCLIENT_CHILDREN_LOCK:
433 if GCLIENT_CHILDREN:
434 print >> sys.stderr, 'Could not kill the following subprocesses:'
435 for zombie in GCLIENT_CHILDREN:
436 print >> sys.stderr, ' ', zombie.pid
437
438
maruel@chromium.org17d01792010-09-01 18:07:10 +0000439def CheckCallAndFilter(args, stdout=None, filter_fn=None,
440 print_stdout=None, call_filter_on_first_line=False,
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000441 retry=False, **kwargs):
maruel@chromium.org17d01792010-09-01 18:07:10 +0000442 """Runs a command and calls back a filter function if needed.
443
maruel@chromium.org57bf78d2011-09-08 18:57:33 +0000444 Accepts all subprocess2.Popen() parameters plus:
maruel@chromium.org17d01792010-09-01 18:07:10 +0000445 print_stdout: If True, the command's stdout is forwarded to stdout.
446 filter_fn: A function taking a single string argument called with each line
maruel@chromium.org57bf78d2011-09-08 18:57:33 +0000447 of the subprocess2's output. Each line has the trailing newline
maruel@chromium.org17d01792010-09-01 18:07:10 +0000448 character trimmed.
449 stdout: Can be any bufferable output.
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000450 retry: If the process exits non-zero, sleep for a brief interval and try
451 again, up to RETRY_MAX times.
maruel@chromium.org17d01792010-09-01 18:07:10 +0000452
453 stderr is always redirected to stdout.
454 """
455 assert print_stdout or filter_fn
456 stdout = stdout or sys.stdout
hinoka@google.com267f33e2014-02-28 22:02:32 +0000457 output = cStringIO.StringIO()
maruel@chromium.org17d01792010-09-01 18:07:10 +0000458 filter_fn = filter_fn or (lambda x: None)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000459
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000460 sleep_interval = RETRY_INITIAL_SLEEP
461 run_cwd = kwargs.get('cwd', os.getcwd())
462 for _ in xrange(RETRY_MAX + 1):
463 kid = subprocess2.Popen(
464 args, bufsize=0, stdout=subprocess2.PIPE, stderr=subprocess2.STDOUT,
465 **kwargs)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000466
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000467 GClientChildren.add(kid)
chase@chromium.org8ad1cee2010-08-16 19:12:27 +0000468
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000469 # Do a flush of stdout before we begin reading from the subprocess2's stdout
470 stdout.flush()
471
472 # Also, we need to forward stdout to prevent weird re-ordering of output.
473 # This has to be done on a per byte basis to make sure it is not buffered:
474 # normally buffering is done for each line, but if svn requests input, no
475 # end-of-line character is output after the prompt and it would not show up.
476 try:
477 in_byte = kid.stdout.read(1)
478 if in_byte:
479 if call_filter_on_first_line:
480 filter_fn(None)
481 in_line = ''
482 while in_byte:
hinoka@google.com267f33e2014-02-28 22:02:32 +0000483 output.write(in_byte)
484 if print_stdout:
485 stdout.write(in_byte)
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000486 if in_byte != '\r':
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000487 if in_byte != '\n':
488 in_line += in_byte
489 else:
490 filter_fn(in_line)
491 in_line = ''
maruel@chromium.org109cb9d2011-09-14 20:03:11 +0000492 else:
493 filter_fn(in_line)
494 in_line = ''
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000495 in_byte = kid.stdout.read(1)
496 # Flush the rest of buffered output. This is only an issue with
497 # stdout/stderr not ending with a \n.
498 if len(in_line):
szager@google.com85d3e3a2011-10-07 17:12:00 +0000499 filter_fn(in_line)
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000500 rv = kid.wait()
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000501
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000502 # Don't put this in a 'finally,' since the child may still run if we get
503 # an exception.
504 GClientChildren.remove(kid)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000505
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000506 except KeyboardInterrupt:
507 print >> sys.stderr, 'Failed while running "%s"' % ' '.join(args)
508 raise
maruel@chromium.org109cb9d2011-09-14 20:03:11 +0000509
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000510 if rv == 0:
hinoka@google.com267f33e2014-02-28 22:02:32 +0000511 return output.getvalue()
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000512 if not retry:
513 break
514 print ("WARNING: subprocess '%s' in %s failed; will retry after a short "
515 'nap...' % (' '.join('"%s"' % x for x in args), run_cwd))
raphael.kubo.da.costa@intel.com91507f72013-10-22 12:18:25 +0000516 time.sleep(sleep_interval)
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000517 sleep_interval *= 2
518 raise subprocess2.CalledProcessError(
519 rv, args, kwargs.get('cwd', None), None, None)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000520
521
agable@chromium.org5a306a22014-02-24 22:13:59 +0000522class GitFilter(object):
523 """A filter_fn implementation for quieting down git output messages.
524
525 Allows a custom function to skip certain lines (predicate), and will throttle
526 the output of percentage completed lines to only output every X seconds.
527 """
528 PERCENT_RE = re.compile('.* ([0-9]{1,2})% .*')
529
530 def __init__(self, time_throttle=0, predicate=None):
531 """
532 Args:
533 time_throttle (int): GitFilter will throttle 'noisy' output (such as the
534 XX% complete messages) to only be printed at least |time_throttle|
535 seconds apart.
536 predicate (f(line)): An optional function which is invoked for every line.
537 The line will be skipped if predicate(line) returns False.
538 """
539 self.last_time = 0
540 self.time_throttle = time_throttle
541 self.predicate = predicate
542
543 def __call__(self, line):
544 # git uses an escape sequence to clear the line; elide it.
545 esc = line.find(unichr(033))
546 if esc > -1:
547 line = line[:esc]
548 if self.predicate and not self.predicate(line):
549 return
550 now = time.time()
551 match = self.PERCENT_RE.match(line)
552 if not match:
553 self.last_time = 0
554 if (now - self.last_time) >= self.time_throttle:
555 self.last_time = now
556 print line
557
558
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000559def FindGclientRoot(from_dir, filename='.gclient'):
maruel@chromium.orga9371762009-12-22 18:27:38 +0000560 """Tries to find the gclient root."""
jochen@chromium.org20760a52010-09-08 08:47:28 +0000561 real_from_dir = os.path.realpath(from_dir)
562 path = real_from_dir
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000563 while not os.path.exists(os.path.join(path, filename)):
maruel@chromium.org3a292682010-08-23 18:54:55 +0000564 split_path = os.path.split(path)
565 if not split_path[1]:
maruel@chromium.orga9371762009-12-22 18:27:38 +0000566 return None
maruel@chromium.org3a292682010-08-23 18:54:55 +0000567 path = split_path[0]
jochen@chromium.org20760a52010-09-08 08:47:28 +0000568
569 # If we did not find the file in the current directory, make sure we are in a
570 # sub directory that is controlled by this configuration.
571 if path != real_from_dir:
572 entries_filename = os.path.join(path, filename + '_entries')
573 if not os.path.exists(entries_filename):
574 # If .gclient_entries does not exist, a previous call to gclient sync
575 # might have failed. In that case, we cannot verify that the .gclient
576 # is the one we want to use. In order to not to cause too much trouble,
577 # just issue a warning and return the path anyway.
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +0000578 print >> sys.stderr, ("%s file in parent directory %s might not be the "
jochen@chromium.org20760a52010-09-08 08:47:28 +0000579 "file you want to use" % (filename, path))
580 return path
581 scope = {}
582 try:
583 exec(FileRead(entries_filename), scope)
584 except SyntaxError, e:
585 SyntaxErrorToError(filename, e)
586 all_directories = scope['entries'].keys()
587 path_to_check = real_from_dir[len(path)+1:]
588 while path_to_check:
589 if path_to_check in all_directories:
590 return path
591 path_to_check = os.path.dirname(path_to_check)
592 return None
maruel@chromium.org3742c842010-09-09 19:27:14 +0000593
maruel@chromium.orgd9141bf2009-12-23 16:13:32 +0000594 logging.info('Found gclient root at ' + path)
maruel@chromium.orga9371762009-12-22 18:27:38 +0000595 return path
maruel@chromium.org3ccbf7e2009-12-22 20:46:42 +0000596
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000597
maruel@chromium.org3ccbf7e2009-12-22 20:46:42 +0000598def PathDifference(root, subpath):
599 """Returns the difference subpath minus root."""
600 root = os.path.realpath(root)
601 subpath = os.path.realpath(subpath)
602 if not subpath.startswith(root):
603 return None
604 # If the root does not have a trailing \ or /, we add it so the returned
605 # path starts immediately after the seperator regardless of whether it is
606 # provided.
607 root = os.path.join(root, '')
608 return subpath[len(root):]
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000609
610
611def FindFileUpwards(filename, path=None):
rcui@google.com13595ff2011-10-13 01:25:07 +0000612 """Search upwards from the a directory (default: current) to find a file.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000613
rcui@google.com13595ff2011-10-13 01:25:07 +0000614 Returns nearest upper-level directory with the passed in file.
615 """
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000616 if not path:
617 path = os.getcwd()
618 path = os.path.realpath(path)
619 while True:
620 file_path = os.path.join(path, filename)
rcui@google.com13595ff2011-10-13 01:25:07 +0000621 if os.path.exists(file_path):
622 return path
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000623 (new_path, _) = os.path.split(path)
624 if new_path == path:
625 return None
626 path = new_path
627
628
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +0000629def GetMacWinOrLinux():
630 """Returns 'mac', 'win', or 'linux', matching the current platform."""
631 if sys.platform.startswith(('cygwin', 'win')):
632 return 'win'
633 elif sys.platform.startswith('linux'):
634 return 'linux'
635 elif sys.platform == 'darwin':
636 return 'mac'
637 raise Error('Unknown platform: ' + sys.platform)
638
639
640def GetExeSuffix():
641 """Returns '' or '.exe' depending on how executables work on this platform."""
642 if sys.platform.startswith(('cygwin', 'win')):
643 return '.exe'
644 return ''
645
646
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000647def GetGClientRootAndEntries(path=None):
648 """Returns the gclient root and the dict of entries."""
649 config_file = '.gclient_entries'
maruel@chromium.org93a9ee02011-10-18 18:23:58 +0000650 root = FindFileUpwards(config_file, path)
651 if not root:
maruel@chromium.org116704f2010-06-11 17:34:38 +0000652 print "Can't find %s" % config_file
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000653 return None
maruel@chromium.org93a9ee02011-10-18 18:23:58 +0000654 config_path = os.path.join(root, config_file)
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000655 env = {}
656 execfile(config_path, env)
657 config_dir = os.path.dirname(config_path)
658 return config_dir, env['entries']
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000659
660
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000661def lockedmethod(method):
662 """Method decorator that holds self.lock for the duration of the call."""
663 def inner(self, *args, **kwargs):
664 try:
665 try:
666 self.lock.acquire()
667 except KeyboardInterrupt:
668 print >> sys.stderr, 'Was deadlocked'
669 raise
670 return method(self, *args, **kwargs)
671 finally:
672 self.lock.release()
673 return inner
674
675
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000676class WorkItem(object):
677 """One work item."""
maruel@chromium.org4901daf2011-10-20 14:34:47 +0000678 # On cygwin, creating a lock throwing randomly when nearing ~100 locks.
679 # As a workaround, use a single lock. Yep you read it right. Single lock for
680 # all the 100 objects.
681 lock = threading.Lock()
682
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000683 def __init__(self, name):
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000684 # A unique string representing this work item.
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000685 self._name = name
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000686
maruel@chromium.org77e4eca2010-09-21 13:23:07 +0000687 def run(self, work_queue):
688 """work_queue is passed as keyword argument so it should be
maruel@chromium.org3742c842010-09-09 19:27:14 +0000689 the last parameters of the function when you override it."""
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000690 pass
691
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000692 @property
693 def name(self):
694 return self._name
695
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000696
697class ExecutionQueue(object):
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000698 """Runs a set of WorkItem that have interdependencies and were WorkItem are
699 added as they are processed.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000700
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000701 In gclient's case, Dependencies sometime needs to be run out of order due to
702 From() keyword. This class manages that all the required dependencies are run
703 before running each one.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000704
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000705 Methods of this class are thread safe.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000706 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000707 def __init__(self, jobs, progress, ignore_requirements):
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000708 """jobs specifies the number of concurrent tasks to allow. progress is a
709 Progress instance."""
710 # Set when a thread is done or a new item is enqueued.
711 self.ready_cond = threading.Condition()
712 # Maximum number of concurrent tasks.
713 self.jobs = jobs
714 # List of WorkItem, for gclient, these are Dependency instances.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000715 self.queued = []
716 # List of strings representing each Dependency.name that was run.
717 self.ran = []
718 # List of items currently running.
719 self.running = []
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000720 # Exceptions thrown if any.
maruel@chromium.org3742c842010-09-09 19:27:14 +0000721 self.exceptions = Queue.Queue()
722 # Progress status
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000723 self.progress = progress
724 if self.progress:
maruel@chromium.org3742c842010-09-09 19:27:14 +0000725 self.progress.update(0)
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000726
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000727 self.ignore_requirements = ignore_requirements
728
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000729 def enqueue(self, d):
730 """Enqueue one Dependency to be executed later once its requirements are
731 satisfied.
732 """
733 assert isinstance(d, WorkItem)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000734 self.ready_cond.acquire()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000735 try:
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000736 self.queued.append(d)
737 total = len(self.queued) + len(self.ran) + len(self.running)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000738 logging.debug('enqueued(%s)' % d.name)
739 if self.progress:
740 self.progress._total = total + 1
741 self.progress.update(0)
742 self.ready_cond.notifyAll()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000743 finally:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000744 self.ready_cond.release()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000745
746 def flush(self, *args, **kwargs):
747 """Runs all enqueued items until all are executed."""
maruel@chromium.org3742c842010-09-09 19:27:14 +0000748 kwargs['work_queue'] = self
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000749 self.ready_cond.acquire()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000750 try:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000751 while True:
752 # Check for task to run first, then wait.
753 while True:
maruel@chromium.org3742c842010-09-09 19:27:14 +0000754 if not self.exceptions.empty():
755 # Systematically flush the queue when an exception logged.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000756 self.queued = []
maruel@chromium.org3742c842010-09-09 19:27:14 +0000757 self._flush_terminated_threads()
758 if (not self.queued and not self.running or
759 self.jobs == len(self.running)):
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000760 logging.debug('No more worker threads or can\'t queue anything.')
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000761 break
maruel@chromium.org3742c842010-09-09 19:27:14 +0000762
763 # Check for new tasks to start.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000764 for i in xrange(len(self.queued)):
765 # Verify its requirements.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000766 if (self.ignore_requirements or
767 not (set(self.queued[i].requirements) - set(self.ran))):
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000768 # Start one work item: all its requirements are satisfied.
maruel@chromium.org3742c842010-09-09 19:27:14 +0000769 self._run_one_task(self.queued.pop(i), args, kwargs)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000770 break
771 else:
772 # Couldn't find an item that could run. Break out the outher loop.
773 break
maruel@chromium.org3742c842010-09-09 19:27:14 +0000774
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000775 if not self.queued and not self.running:
maruel@chromium.org3742c842010-09-09 19:27:14 +0000776 # We're done.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000777 break
778 # We need to poll here otherwise Ctrl-C isn't processed.
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000779 try:
780 self.ready_cond.wait(10)
781 except KeyboardInterrupt:
782 # Help debugging by printing some information:
783 print >> sys.stderr, (
784 ('\nAllowed parallel jobs: %d\n# queued: %d\nRan: %s\n'
785 'Running: %d') % (
786 self.jobs,
787 len(self.queued),
788 ', '.join(self.ran),
789 len(self.running)))
790 for i in self.queued:
791 print >> sys.stderr, '%s: %s' % (i.name, ', '.join(i.requirements))
792 raise
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000793 # Something happened: self.enqueue() or a thread terminated. Loop again.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000794 finally:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000795 self.ready_cond.release()
maruel@chromium.org3742c842010-09-09 19:27:14 +0000796
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000797 assert not self.running, 'Now guaranteed to be single-threaded'
maruel@chromium.org3742c842010-09-09 19:27:14 +0000798 if not self.exceptions.empty():
maruel@chromium.orgc8d064b2010-08-16 16:46:14 +0000799 # To get back the stack location correctly, the raise a, b, c form must be
800 # used, passing a tuple as the first argument doesn't work.
maruel@chromium.org3742c842010-09-09 19:27:14 +0000801 e = self.exceptions.get()
maruel@chromium.orgc8d064b2010-08-16 16:46:14 +0000802 raise e[0], e[1], e[2]
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000803 if self.progress:
804 self.progress.end()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000805
maruel@chromium.org3742c842010-09-09 19:27:14 +0000806 def _flush_terminated_threads(self):
807 """Flush threads that have terminated."""
808 running = self.running
809 self.running = []
810 for t in running:
811 if t.isAlive():
812 self.running.append(t)
813 else:
814 t.join()
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000815 sys.stdout.flush()
maruel@chromium.org3742c842010-09-09 19:27:14 +0000816 if self.progress:
maruel@chromium.org55a2eb82010-10-06 23:35:18 +0000817 self.progress.update(1, t.item.name)
maruel@chromium.orgf36c0ee2011-09-14 19:16:47 +0000818 if t.item.name in self.ran:
819 raise Error(
820 'gclient is confused, "%s" is already in "%s"' % (
821 t.item.name, ', '.join(self.ran)))
maruel@chromium.orgacc45672010-09-09 21:21:21 +0000822 if not t.item.name in self.ran:
823 self.ran.append(t.item.name)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000824
825 def _run_one_task(self, task_item, args, kwargs):
826 if self.jobs > 1:
827 # Start the thread.
828 index = len(self.ran) + len(self.running) + 1
maruel@chromium.org77e4eca2010-09-21 13:23:07 +0000829 new_thread = self._Worker(task_item, index, args, kwargs)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000830 self.running.append(new_thread)
831 new_thread.start()
832 else:
833 # Run the 'thread' inside the main thread. Don't try to catch any
834 # exception.
835 task_item.run(*args, **kwargs)
836 self.ran.append(task_item.name)
837 if self.progress:
maruel@chromium.org55a2eb82010-10-06 23:35:18 +0000838 self.progress.update(1, ', '.join(t.item.name for t in self.running))
maruel@chromium.org3742c842010-09-09 19:27:14 +0000839
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000840 class _Worker(threading.Thread):
841 """One thread to execute one WorkItem."""
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000842 def __init__(self, item, index, args, kwargs):
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000843 threading.Thread.__init__(self, name=item.name or 'Worker')
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000844 logging.info('_Worker(%s) reqs:%s' % (item.name, item.requirements))
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000845 self.item = item
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000846 self.index = index
maruel@chromium.org3742c842010-09-09 19:27:14 +0000847 self.args = args
848 self.kwargs = kwargs
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000849 self.daemon = True
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000850
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000851 def run(self):
852 """Runs in its own thread."""
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000853 logging.debug('_Worker.run(%s)' % self.item.name)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000854 work_queue = self.kwargs['work_queue']
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000855 try:
856 self.item.run(*self.args, **self.kwargs)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000857 except KeyboardInterrupt:
xusydoc@chromium.orgc144e062013-05-03 23:23:53 +0000858 logging.info('Caught KeyboardInterrupt in thread %s', self.item.name)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000859 logging.info(str(sys.exc_info()))
860 work_queue.exceptions.put(sys.exc_info())
861 raise
maruel@chromium.orgc8d064b2010-08-16 16:46:14 +0000862 except Exception:
863 # Catch exception location.
xusydoc@chromium.orgc144e062013-05-03 23:23:53 +0000864 logging.info('Caught exception in thread %s', self.item.name)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000865 logging.info(str(sys.exc_info()))
866 work_queue.exceptions.put(sys.exc_info())
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000867 finally:
xusydoc@chromium.orgc144e062013-05-03 23:23:53 +0000868 logging.info('_Worker.run(%s) done', self.item.name)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000869 work_queue.ready_cond.acquire()
870 try:
871 work_queue.ready_cond.notifyAll()
872 finally:
873 work_queue.ready_cond.release()
maruel@chromium.org0e0436a2011-10-25 13:32:41 +0000874
875
jbroman@chromium.org615a2622013-05-03 13:20:14 +0000876def GetEditor(git, git_editor=None):
877 """Returns the most plausible editor to use.
878
879 In order of preference:
880 - GIT_EDITOR/SVN_EDITOR environment variable
881 - core.editor git configuration variable (if supplied by git-cl)
882 - VISUAL environment variable
883 - EDITOR environment variable
bratell@opera.com65621c72013-12-09 15:05:32 +0000884 - vi (non-Windows) or notepad (Windows)
jbroman@chromium.org615a2622013-05-03 13:20:14 +0000885
886 In the case of git-cl, this matches git's behaviour, except that it does not
887 include dumb terminal detection.
888
889 In the case of gcl, this matches svn's behaviour, except that it does not
890 accept a command-line flag or check the editor-cmd configuration variable.
891 """
maruel@chromium.org0e0436a2011-10-25 13:32:41 +0000892 if git:
jbroman@chromium.org615a2622013-05-03 13:20:14 +0000893 editor = os.environ.get('GIT_EDITOR') or git_editor
maruel@chromium.org0e0436a2011-10-25 13:32:41 +0000894 else:
895 editor = os.environ.get('SVN_EDITOR')
896 if not editor:
jbroman@chromium.org615a2622013-05-03 13:20:14 +0000897 editor = os.environ.get('VISUAL')
898 if not editor:
maruel@chromium.org0e0436a2011-10-25 13:32:41 +0000899 editor = os.environ.get('EDITOR')
900 if not editor:
901 if sys.platform.startswith('win'):
902 editor = 'notepad'
903 else:
bratell@opera.com65621c72013-12-09 15:05:32 +0000904 editor = 'vi'
maruel@chromium.org0e0436a2011-10-25 13:32:41 +0000905 return editor
906
907
jbroman@chromium.org615a2622013-05-03 13:20:14 +0000908def RunEditor(content, git, git_editor=None):
maruel@chromium.org0e0436a2011-10-25 13:32:41 +0000909 """Opens up the default editor in the system to get the CL description."""
maruel@chromium.orgcbd760f2013-07-23 13:02:48 +0000910 file_handle, filename = tempfile.mkstemp(text=True, prefix='cl_description')
maruel@chromium.org0e0436a2011-10-25 13:32:41 +0000911 # Make sure CRLF is handled properly by requiring none.
912 if '\r' in content:
asvitkine@chromium.org0eff22d2011-10-25 16:11:16 +0000913 print >> sys.stderr, (
914 '!! Please remove \\r from your change description !!')
maruel@chromium.org0e0436a2011-10-25 13:32:41 +0000915 fileobj = os.fdopen(file_handle, 'w')
916 # Still remove \r if present.
917 fileobj.write(re.sub('\r?\n', '\n', content))
918 fileobj.close()
919
920 try:
jbroman@chromium.org615a2622013-05-03 13:20:14 +0000921 editor = GetEditor(git, git_editor=git_editor)
922 if not editor:
923 return None
924 cmd = '%s %s' % (editor, filename)
maruel@chromium.org0e0436a2011-10-25 13:32:41 +0000925 if sys.platform == 'win32' and os.environ.get('TERM') == 'msys':
926 # Msysgit requires the usage of 'env' to be present.
927 cmd = 'env ' + cmd
928 try:
929 # shell=True to allow the shell to handle all forms of quotes in
930 # $EDITOR.
931 subprocess2.check_call(cmd, shell=True)
932 except subprocess2.CalledProcessError:
933 return None
934 return FileRead(filename)
935 finally:
936 os.remove(filename)
maruel@chromium.org99ac1c52012-01-16 14:52:12 +0000937
938
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +0000939def UpgradeToHttps(url):
940 """Upgrades random urls to https://.
941
942 Do not touch unknown urls like ssh:// or git://.
943 Do not touch http:// urls with a port number,
944 Fixes invalid GAE url.
945 """
946 if not url:
947 return url
948 if not re.match(r'[a-z\-]+\://.*', url):
949 # Make sure it is a valid uri. Otherwise, urlparse() will consider it a
950 # relative url and will use http:///foo. Note that it defaults to http://
951 # for compatibility with naked url like "localhost:8080".
952 url = 'http://%s' % url
953 parsed = list(urlparse.urlparse(url))
954 # Do not automatically upgrade http to https if a port number is provided.
955 if parsed[0] == 'http' and not re.match(r'^.+?\:\d+$', parsed[1]):
956 parsed[0] = 'https'
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +0000957 return urlparse.urlunparse(parsed)
958
959
maruel@chromium.org99ac1c52012-01-16 14:52:12 +0000960def ParseCodereviewSettingsContent(content):
961 """Process a codereview.settings file properly."""
962 lines = (l for l in content.splitlines() if not l.strip().startswith("#"))
963 try:
964 keyvals = dict([x.strip() for x in l.split(':', 1)] for l in lines if l)
965 except ValueError:
966 raise Error(
967 'Failed to process settings, please fix. Content:\n\n%s' % content)
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +0000968 def fix_url(key):
969 if keyvals.get(key):
970 keyvals[key] = UpgradeToHttps(keyvals[key])
971 fix_url('CODE_REVIEW_SERVER')
972 fix_url('VIEW_VC')
maruel@chromium.org99ac1c52012-01-16 14:52:12 +0000973 return keyvals
ilevy@chromium.org13691502012-10-16 04:26:37 +0000974
975
976def NumLocalCpus():
977 """Returns the number of processors.
978
979 Python on OSX 10.6 raises a NotImplementedError exception.
980 """
981 try:
982 import multiprocessing
983 return multiprocessing.cpu_count()
984 except: # pylint: disable=W0702
985 # Mac OS 10.6 only
986 # pylint: disable=E1101
987 return int(os.sysconf('SC_NPROCESSORS_ONLN'))
szager@chromium.orgfc616382014-03-18 20:32:04 +0000988
989def DefaultDeltaBaseCacheLimit():
990 """Return a reasonable default for the git config core.deltaBaseCacheLimit.
991
992 The primary constraint is the address space of virtual memory. The cache
993 size limit is per-thread, and 32-bit systems can hit OOM errors if this
994 parameter is set too high.
995 """
996 if platform.architecture()[0].startswith('64'):
997 return '2g'
998 else:
999 return '512m'
1000
szager@chromium.orgff113292014-03-25 06:02:08 +00001001def DefaultIndexPackConfig(url=''):
szager@chromium.orgfc616382014-03-18 20:32:04 +00001002 """Return reasonable default values for configuring git-index-pack.
1003
1004 Experiments suggest that higher values for pack.threads don't improve
1005 performance."""
szager@chromium.orgff113292014-03-25 06:02:08 +00001006 cache_limit = DefaultDeltaBaseCacheLimit()
1007 result = ['-c', 'core.deltaBaseCacheLimit=%s' % cache_limit]
1008 if url in THREADED_INDEX_PACK_BLACKLIST:
1009 result.extend(['-c', 'pack.threads=1'])
1010 return result