blob: 794fc023bb21ed1c59797be895424461bb787c4d [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
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00009import datetime
maruel@chromium.orgd9141bf2009-12-23 16:13:32 +000010import logging
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000011import os
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +000012import pipes
szager@chromium.orgfc616382014-03-18 20:32:04 +000013import platform
maruel@chromium.org3742c842010-09-09 19:27:14 +000014import Queue
msb@chromium.orgac915bb2009-11-13 17:03:01 +000015import re
bradnelson@google.com8f9c69f2009-09-17 00:48:28 +000016import stat
borenet@google.com6b4a2ab2013-04-18 15:50:27 +000017import subprocess
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000018import sys
maruel@chromium.org0e0436a2011-10-25 13:32:41 +000019import tempfile
maruel@chromium.org9e5317a2010-08-13 20:35:11 +000020import threading
maruel@chromium.org167b9e62009-09-17 17:41:02 +000021import time
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +000022import urlparse
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000023
maruel@chromium.orgca0f8392011-09-08 17:15:15 +000024import subprocess2
25
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000026
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +000027RETRY_MAX = 3
28RETRY_INITIAL_SLEEP = 0.5
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000029START = datetime.datetime.now()
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +000030
31
borenet@google.com6a9b1682014-03-24 18:35:23 +000032_WARNINGS = []
33
34
szager@chromium.orgff113292014-03-25 06:02:08 +000035# These repos are known to cause OOM errors on 32-bit platforms, due the the
36# very large objects they contain. It is not safe to use threaded index-pack
37# when cloning/fetching them.
38THREADED_INDEX_PACK_BLACKLIST = [
39 'https://chromium.googlesource.com/chromium/reference_builds/chrome_win.git'
40]
41
42
maruel@chromium.org66c83e62010-09-07 14:18:45 +000043class Error(Exception):
44 """gclient exception class."""
szager@chromium.org4a3c17e2013-05-24 23:59:29 +000045 def __init__(self, msg, *args, **kwargs):
46 index = getattr(threading.currentThread(), 'index', 0)
47 if index:
48 msg = '\n'.join('%d> %s' % (index, l) for l in msg.splitlines())
49 super(Error, self).__init__(msg, *args, **kwargs)
maruel@chromium.org66c83e62010-09-07 14:18:45 +000050
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +000051
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000052def Elapsed(until=None):
53 if until is None:
54 until = datetime.datetime.now()
55 return str(until - START).partition('.')[0]
56
57
borenet@google.com6a9b1682014-03-24 18:35:23 +000058def PrintWarnings():
59 """Prints any accumulated warnings."""
60 if _WARNINGS:
61 print >> sys.stderr, '\n\nWarnings:'
62 for warning in _WARNINGS:
63 print >> sys.stderr, warning
64
65
66def AddWarning(msg):
67 """Adds the given warning message to the list of accumulated warnings."""
68 _WARNINGS.append(msg)
69
70
msb@chromium.orgac915bb2009-11-13 17:03:01 +000071def SplitUrlRevision(url):
72 """Splits url and returns a two-tuple: url, rev"""
73 if url.startswith('ssh:'):
maruel@chromium.org78b8cd12010-10-26 12:47:07 +000074 # Make sure ssh://user-name@example.com/~/test.git@stable works
kangil.han@samsung.com71b13572013-10-16 17:28:11 +000075 regex = r'(ssh://(?:[-.\w]+@)?[-\w:\.]+/[-~\w\./]+)(?:@(.+))?'
msb@chromium.orgac915bb2009-11-13 17:03:01 +000076 components = re.search(regex, url).groups()
77 else:
scr@chromium.orgf1eccaf2014-04-11 15:51:33 +000078 components = url.rsplit('@', 1)
79 if re.match(r'^\w+\@', url) and '@' not in components[0]:
80 components = [url]
81
msb@chromium.orgac915bb2009-11-13 17:03:01 +000082 if len(components) == 1:
83 components += [None]
84 return tuple(components)
85
86
primiano@chromium.org5439ea52014-08-06 17:18:18 +000087def IsGitSha(revision):
88 """Returns true if the given string is a valid hex-encoded sha"""
89 return re.match('^[a-fA-F0-9]{6,40}$', revision) is not None
90
91
floitsch@google.comeaab7842011-04-28 09:07:58 +000092def IsDateRevision(revision):
93 """Returns true if the given revision is of the form "{ ... }"."""
94 return bool(revision and re.match(r'^\{.+\}$', str(revision)))
95
96
97def MakeDateRevision(date):
98 """Returns a revision representing the latest revision before the given
99 date."""
100 return "{" + date + "}"
101
102
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000103def SyntaxErrorToError(filename, e):
104 """Raises a gclient_utils.Error exception with the human readable message"""
105 try:
106 # Try to construct a human readable error message
107 if filename:
108 error_message = 'There is a syntax error in %s\n' % filename
109 else:
110 error_message = 'There is a syntax error\n'
111 error_message += 'Line #%s, character %s: "%s"' % (
112 e.lineno, e.offset, re.sub(r'[\r\n]*$', '', e.text))
113 except:
114 # Something went wrong, re-raise the original exception
115 raise e
116 else:
117 raise Error(error_message)
118
119
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000120class PrintableObject(object):
121 def __str__(self):
122 output = ''
123 for i in dir(self):
124 if i.startswith('__'):
125 continue
126 output += '%s = %s\n' % (i, str(getattr(self, i, '')))
127 return output
128
129
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000130def FileRead(filename, mode='rU'):
maruel@chromium.org51e84fb2012-07-03 23:06:21 +0000131 with open(filename, mode=mode) as f:
maruel@chromium.orgc3cd5372012-07-11 17:39:24 +0000132 # codecs.open() has different behavior than open() on python 2.6 so use
133 # open() and decode manually.
chrisha@chromium.org2b99d432012-07-12 18:10:28 +0000134 s = f.read()
135 try:
136 return s.decode('utf-8')
137 except UnicodeDecodeError:
138 return s
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000139
140
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000141def FileWrite(filename, content, mode='w'):
maruel@chromium.orgdae209f2012-07-03 16:08:15 +0000142 with codecs.open(filename, mode=mode, encoding='utf-8') as f:
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000143 f.write(content)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000144
145
cyrille@nnamrak.orgef509e42013-09-20 13:19:08 +0000146def safe_rename(old, new):
147 """Renames a file reliably.
148
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +0000149 Sometimes os.rename does not work because a dying git process keeps a handle
150 on it for a few seconds. An exception is then thrown, which make the program
cyrille@nnamrak.orgef509e42013-09-20 13:19:08 +0000151 give up what it was doing and remove what was deleted.
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +0000152 The only solution is to catch the exception and try again until it works.
cyrille@nnamrak.orgef509e42013-09-20 13:19:08 +0000153 """
154 # roughly 10s
155 retries = 100
156 for i in range(retries):
157 try:
158 os.rename(old, new)
159 break
160 except OSError:
161 if i == (retries - 1):
162 # Give up.
163 raise
164 # retry
165 logging.debug("Renaming failed from %s to %s. Retrying ..." % (old, new))
166 time.sleep(0.1)
167
168
loislo@chromium.org67b59e92014-12-25 13:48:37 +0000169def rm_file_or_tree(path):
170 if os.path.isfile(path):
171 os.remove(path)
172 else:
173 rmtree(path)
174
175
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000176def rmtree(path):
177 """shutil.rmtree() on steroids.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000178
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000179 Recursively removes a directory, even if it's marked read-only.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000180
181 shutil.rmtree() doesn't work on Windows if any of the files or directories
182 are read-only, which svn repositories and some .svn files are. We need to
183 be able to force the files to be writable (i.e., deletable) as we traverse
184 the tree.
185
186 Even with all this, Windows still sometimes fails to delete a file, citing
187 a permission error (maybe something to do with antivirus scans or disk
188 indexing). The best suggestion any of the user forums had was to wait a
189 bit and try again, so we do that too. It's hand-waving, but sometimes it
190 works. :/
191
192 On POSIX systems, things are a little bit simpler. The modes of the files
193 to be deleted doesn't matter, only the modes of the directories containing
194 them are significant. As the directory tree is traversed, each directory
195 has its mode set appropriately before descending into it. This should
196 result in the entire tree being removed, with the possible exception of
197 *path itself, because nothing attempts to change the mode of its parent.
198 Doing so would be hazardous, as it's not a directory slated for removal.
199 In the ordinary case, this is not a problem: for our purposes, the user
200 will never lack write permission on *path's parent.
201 """
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000202 if not os.path.exists(path):
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000203 return
204
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000205 if os.path.islink(path) or not os.path.isdir(path):
206 raise Error('Called rmtree(%s) in non-directory' % path)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000207
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000208 if sys.platform == 'win32':
borenet@google.com6b4a2ab2013-04-18 15:50:27 +0000209 # Give up and use cmd.exe's rd command.
210 path = os.path.normcase(path)
211 for _ in xrange(3):
212 exitcode = subprocess.call(['cmd.exe', '/c', 'rd', '/q', '/s', path])
213 if exitcode == 0:
214 return
215 else:
216 print >> sys.stderr, 'rd exited with code %d' % exitcode
217 time.sleep(3)
218 raise Exception('Failed to remove path %s' % path)
219
220 # On POSIX systems, we need the x-bit set on the directory to access it,
221 # the r-bit to see its contents, and the w-bit to remove files from it.
222 # The actual modes of the files within the directory is irrelevant.
223 os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000224
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000225 def remove(func, subpath):
borenet@google.com6b4a2ab2013-04-18 15:50:27 +0000226 func(subpath)
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000227
228 for fn in os.listdir(path):
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000229 # If fullpath is a symbolic link that points to a directory, isdir will
230 # be True, but we don't want to descend into that as a directory, we just
231 # want to remove the link. Check islink and treat links as ordinary files
232 # would be treated regardless of what they reference.
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000233 fullpath = os.path.join(path, fn)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000234 if os.path.islink(fullpath) or not os.path.isdir(fullpath):
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000235 remove(os.remove, fullpath)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000236 else:
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000237 # Recurse.
238 rmtree(fullpath)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000239
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000240 remove(os.rmdir, path)
241
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000242
maruel@chromium.org6c48a302011-10-20 23:44:20 +0000243def safe_makedirs(tree):
244 """Creates the directory in a safe manner.
245
246 Because multiple threads can create these directories concurently, trap the
247 exception and pass on.
248 """
249 count = 0
250 while not os.path.exists(tree):
251 count += 1
252 try:
253 os.makedirs(tree)
254 except OSError, e:
255 # 17 POSIX, 183 Windows
256 if e.errno not in (17, 183):
257 raise
258 if count > 40:
259 # Give up.
260 raise
261
262
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000263def CommandToStr(args):
264 """Converts an arg list into a shell escaped string."""
265 return ' '.join(pipes.quote(arg) for arg in args)
266
267
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000268def CheckCallAndFilterAndHeader(args, always=False, header=None, **kwargs):
maruel@chromium.org17d01792010-09-01 18:07:10 +0000269 """Adds 'header' support to CheckCallAndFilter.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000270
maruel@chromium.org17d01792010-09-01 18:07:10 +0000271 If |always| is True, a message indicating what is being done
272 is printed to stdout all the time even if not output is generated. Otherwise
273 the message header is printed only if the call generated any ouput.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000274 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000275 stdout = kwargs.setdefault('stdout', sys.stdout)
276 if header is None:
277 header = "\n________ running '%s' in '%s'\n" % (
ilevy@chromium.org4aad1852013-07-12 21:32:51 +0000278 ' '.join(args), kwargs.get('cwd', '.'))
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000279
maruel@chromium.org17d01792010-09-01 18:07:10 +0000280 if always:
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000281 stdout.write(header)
maruel@chromium.org17d01792010-09-01 18:07:10 +0000282 else:
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000283 filter_fn = kwargs.get('filter_fn')
maruel@chromium.org17d01792010-09-01 18:07:10 +0000284 def filter_msg(line):
285 if line is None:
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000286 stdout.write(header)
maruel@chromium.org17d01792010-09-01 18:07:10 +0000287 elif filter_fn:
288 filter_fn(line)
289 kwargs['filter_fn'] = filter_msg
290 kwargs['call_filter_on_first_line'] = True
291 # Obviously.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000292 kwargs.setdefault('print_stdout', True)
maruel@chromium.org17d01792010-09-01 18:07:10 +0000293 return CheckCallAndFilter(args, **kwargs)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000294
maruel@chromium.org17d01792010-09-01 18:07:10 +0000295
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000296class Wrapper(object):
297 """Wraps an object, acting as a transparent proxy for all properties by
298 default.
299 """
300 def __init__(self, wrapped):
301 self._wrapped = wrapped
302
303 def __getattr__(self, name):
304 return getattr(self._wrapped, name)
maruel@chromium.orgdb111f72010-09-08 13:36:53 +0000305
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +0000306
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000307class AutoFlush(Wrapper):
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +0000308 """Creates a file object clone to automatically flush after N seconds."""
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000309 def __init__(self, wrapped, delay):
310 super(AutoFlush, self).__init__(wrapped)
311 if not hasattr(self, 'lock'):
312 self.lock = threading.Lock()
313 self.__last_flushed_at = time.time()
314 self.delay = delay
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +0000315
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000316 @property
317 def autoflush(self):
318 return self
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +0000319
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000320 def write(self, out, *args, **kwargs):
321 self._wrapped.write(out, *args, **kwargs)
maruel@chromium.orgdb111f72010-09-08 13:36:53 +0000322 should_flush = False
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000323 self.lock.acquire()
maruel@chromium.org9c531262010-09-08 13:41:13 +0000324 try:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000325 if self.delay and (time.time() - self.__last_flushed_at) > self.delay:
maruel@chromium.orgdb111f72010-09-08 13:36:53 +0000326 should_flush = True
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000327 self.__last_flushed_at = time.time()
maruel@chromium.org9c531262010-09-08 13:41:13 +0000328 finally:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000329 self.lock.release()
maruel@chromium.orgdb111f72010-09-08 13:36:53 +0000330 if should_flush:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000331 self.flush()
maruel@chromium.orgdb111f72010-09-08 13:36:53 +0000332
333
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000334class Annotated(Wrapper):
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000335 """Creates a file object clone to automatically prepends every line in worker
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000336 threads with a NN> prefix.
337 """
338 def __init__(self, wrapped, include_zero=False):
339 super(Annotated, self).__init__(wrapped)
340 if not hasattr(self, 'lock'):
341 self.lock = threading.Lock()
342 self.__output_buffers = {}
343 self.__include_zero = include_zero
maruel@chromium.orgcb1e97a2010-09-09 20:09:20 +0000344
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000345 @property
346 def annotated(self):
347 return self
maruel@chromium.orgcb1e97a2010-09-09 20:09:20 +0000348
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000349 def write(self, out):
350 index = getattr(threading.currentThread(), 'index', 0)
351 if not index and not self.__include_zero:
352 # Unindexed threads aren't buffered.
353 return self._wrapped.write(out)
maruel@chromium.orgcb1e97a2010-09-09 20:09:20 +0000354
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000355 self.lock.acquire()
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000356 try:
357 # Use a dummy array to hold the string so the code can be lockless.
358 # Strings are immutable, requiring to keep a lock for the whole dictionary
359 # otherwise. Using an array is faster than using a dummy object.
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000360 if not index in self.__output_buffers:
361 obj = self.__output_buffers[index] = ['']
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000362 else:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000363 obj = self.__output_buffers[index]
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000364 finally:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000365 self.lock.release()
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000366
367 # Continue lockless.
368 obj[0] += out
369 while '\n' in obj[0]:
370 line, remaining = obj[0].split('\n', 1)
nsylvain@google.come939bb52011-06-01 22:59:15 +0000371 if line:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000372 self._wrapped.write('%d>%s\n' % (index, line))
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000373 obj[0] = remaining
374
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000375 def flush(self):
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000376 """Flush buffered output."""
377 orphans = []
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000378 self.lock.acquire()
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000379 try:
380 # Detect threads no longer existing.
381 indexes = (getattr(t, 'index', None) for t in threading.enumerate())
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +0000382 indexes = filter(None, indexes)
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000383 for index in self.__output_buffers:
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000384 if not index in indexes:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000385 orphans.append((index, self.__output_buffers[index][0]))
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000386 for orphan in orphans:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000387 del self.__output_buffers[orphan[0]]
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000388 finally:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000389 self.lock.release()
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000390
391 # Don't keep the lock while writting. Will append \n when it shouldn't.
392 for orphan in orphans:
nsylvain@google.come939bb52011-06-01 22:59:15 +0000393 if orphan[1]:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000394 self._wrapped.write('%d>%s\n' % (orphan[0], orphan[1]))
395 return self._wrapped.flush()
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000396
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000397
398def MakeFileAutoFlush(fileobj, delay=10):
399 autoflush = getattr(fileobj, 'autoflush', None)
400 if autoflush:
401 autoflush.delay = delay
402 return fileobj
403 return AutoFlush(fileobj, delay)
404
405
406def MakeFileAnnotated(fileobj, include_zero=False):
407 if getattr(fileobj, 'annotated', None):
408 return fileobj
409 return Annotated(fileobj)
maruel@chromium.orgcb1e97a2010-09-09 20:09:20 +0000410
411
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000412GCLIENT_CHILDREN = []
413GCLIENT_CHILDREN_LOCK = threading.Lock()
414
415
416class GClientChildren(object):
417 @staticmethod
418 def add(popen_obj):
419 with GCLIENT_CHILDREN_LOCK:
420 GCLIENT_CHILDREN.append(popen_obj)
421
422 @staticmethod
423 def remove(popen_obj):
424 with GCLIENT_CHILDREN_LOCK:
425 GCLIENT_CHILDREN.remove(popen_obj)
426
427 @staticmethod
428 def _attemptToKillChildren():
429 global GCLIENT_CHILDREN
430 with GCLIENT_CHILDREN_LOCK:
431 zombies = [c for c in GCLIENT_CHILDREN if c.poll() is None]
432
433 for zombie in zombies:
434 try:
435 zombie.kill()
436 except OSError:
437 pass
438
439 with GCLIENT_CHILDREN_LOCK:
440 GCLIENT_CHILDREN = [k for k in GCLIENT_CHILDREN if k.poll() is not None]
441
442 @staticmethod
443 def _areZombies():
444 with GCLIENT_CHILDREN_LOCK:
445 return bool(GCLIENT_CHILDREN)
446
447 @staticmethod
448 def KillAllRemainingChildren():
449 GClientChildren._attemptToKillChildren()
450
451 if GClientChildren._areZombies():
452 time.sleep(0.5)
453 GClientChildren._attemptToKillChildren()
454
455 with GCLIENT_CHILDREN_LOCK:
456 if GCLIENT_CHILDREN:
457 print >> sys.stderr, 'Could not kill the following subprocesses:'
458 for zombie in GCLIENT_CHILDREN:
459 print >> sys.stderr, ' ', zombie.pid
460
461
tandriif8757b72016-08-25 04:02:19 -0700462class _KillTimer(object):
463 """Timer that kills child process after certain interval since last poke or
464 creation.
465 """
466 # TODO(tandrii): we really want to make use of subprocess42 here, and not
467 # re-invent the wheel, but it's too much work :(
468
tandrii30d95622016-10-11 05:20:26 -0700469 def __init__(self, timeout, child):
tandriif8757b72016-08-25 04:02:19 -0700470 self._timeout = timeout
471 self._child = child
472
473 self._cv = threading.Condition()
474 # All items below are protected by condition above.
475 self._kill_at = None
476 self._working = True
477 self._thread = None
478
479 # Start the timer immediately.
480 if self._timeout:
481 self._kill_at = time.time() + self._timeout
482 self._thread = threading.Thread(name='_KillTimer', target=self._work)
483 self._thread.daemon = True
484 self._thread.start()
485
486 def poke(self):
487 if not self._timeout:
488 return
489 with self._cv:
490 self._kill_at = time.time() + self._timeout
491
492 def cancel(self):
493 with self._cv:
494 self._working = False
495 self._cv.notifyAll()
496
497 def _work(self):
498 if not self._timeout:
499 return
500 while True:
501 with self._cv:
502 if not self._working:
503 return
504 left = self._kill_at - time.time()
505 if left > 0:
506 self._cv.wait(timeout=left)
507 continue
508 try:
tandrii30d95622016-10-11 05:20:26 -0700509 logging.warn('killing child %s because of no output for %fs',
510 self._child, self._timeout)
tandriif8757b72016-08-25 04:02:19 -0700511 self._child.kill()
512 except OSError:
513 logging.exception('failed to kill child %s', self._child)
514 return
515
516
maruel@chromium.org17d01792010-09-01 18:07:10 +0000517def CheckCallAndFilter(args, stdout=None, filter_fn=None,
518 print_stdout=None, call_filter_on_first_line=False,
tandriif8757b72016-08-25 04:02:19 -0700519 retry=False, kill_timeout=None, **kwargs):
maruel@chromium.org17d01792010-09-01 18:07:10 +0000520 """Runs a command and calls back a filter function if needed.
521
maruel@chromium.org57bf78d2011-09-08 18:57:33 +0000522 Accepts all subprocess2.Popen() parameters plus:
maruel@chromium.org17d01792010-09-01 18:07:10 +0000523 print_stdout: If True, the command's stdout is forwarded to stdout.
524 filter_fn: A function taking a single string argument called with each line
maruel@chromium.org57bf78d2011-09-08 18:57:33 +0000525 of the subprocess2's output. Each line has the trailing newline
maruel@chromium.org17d01792010-09-01 18:07:10 +0000526 character trimmed.
527 stdout: Can be any bufferable output.
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000528 retry: If the process exits non-zero, sleep for a brief interval and try
529 again, up to RETRY_MAX times.
tandriif8757b72016-08-25 04:02:19 -0700530 kill_timeout: (float) if given, number of seconds after which process would
tandrii30d95622016-10-11 05:20:26 -0700531 be killed if there is no output. Must not be used with shell=True as
532 only shell process would be killed, but not processes spawned by
533 shell.
maruel@chromium.org17d01792010-09-01 18:07:10 +0000534
535 stderr is always redirected to stdout.
536 """
537 assert print_stdout or filter_fn
tandriif8757b72016-08-25 04:02:19 -0700538 assert not kwargs.get('shell', False) or not kill_timeout, (
539 'kill_timeout should not be used with shell=True')
maruel@chromium.org17d01792010-09-01 18:07:10 +0000540 stdout = stdout or sys.stdout
hinoka@google.com267f33e2014-02-28 22:02:32 +0000541 output = cStringIO.StringIO()
maruel@chromium.org17d01792010-09-01 18:07:10 +0000542 filter_fn = filter_fn or (lambda x: None)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000543
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000544 sleep_interval = RETRY_INITIAL_SLEEP
545 run_cwd = kwargs.get('cwd', os.getcwd())
546 for _ in xrange(RETRY_MAX + 1):
547 kid = subprocess2.Popen(
548 args, bufsize=0, stdout=subprocess2.PIPE, stderr=subprocess2.STDOUT,
549 **kwargs)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000550
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000551 GClientChildren.add(kid)
chase@chromium.org8ad1cee2010-08-16 19:12:27 +0000552
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000553 # Do a flush of stdout before we begin reading from the subprocess2's stdout
554 stdout.flush()
555
556 # Also, we need to forward stdout to prevent weird re-ordering of output.
557 # This has to be done on a per byte basis to make sure it is not buffered:
558 # normally buffering is done for each line, but if svn requests input, no
559 # end-of-line character is output after the prompt and it would not show up.
560 try:
tandrii30d95622016-10-11 05:20:26 -0700561 timeout_killer = _KillTimer(kill_timeout, kid)
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000562 in_byte = kid.stdout.read(1)
563 if in_byte:
564 if call_filter_on_first_line:
565 filter_fn(None)
566 in_line = ''
567 while in_byte:
tandrii30d95622016-10-11 05:20:26 -0700568 timeout_killer.poke()
hinoka@google.com267f33e2014-02-28 22:02:32 +0000569 output.write(in_byte)
570 if print_stdout:
571 stdout.write(in_byte)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000572 if in_byte not in ['\r', '\n']:
573 in_line += in_byte
maruel@chromium.org109cb9d2011-09-14 20:03:11 +0000574 else:
575 filter_fn(in_line)
576 in_line = ''
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000577 in_byte = kid.stdout.read(1)
578 # Flush the rest of buffered output. This is only an issue with
579 # stdout/stderr not ending with a \n.
580 if len(in_line):
szager@google.com85d3e3a2011-10-07 17:12:00 +0000581 filter_fn(in_line)
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000582 rv = kid.wait()
tandriif8757b72016-08-25 04:02:19 -0700583 timeout_killer.cancel()
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000584
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000585 # Don't put this in a 'finally,' since the child may still run if we get
586 # an exception.
587 GClientChildren.remove(kid)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000588
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000589 except KeyboardInterrupt:
590 print >> sys.stderr, 'Failed while running "%s"' % ' '.join(args)
591 raise
maruel@chromium.org109cb9d2011-09-14 20:03:11 +0000592
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000593 if rv == 0:
hinoka@google.com267f33e2014-02-28 22:02:32 +0000594 return output.getvalue()
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000595 if not retry:
596 break
tandrii30d95622016-10-11 05:20:26 -0700597 print ("WARNING: subprocess '%s' in %s failed; will retry after a short "
598 'nap...' % (' '.join('"%s"' % x for x in args), run_cwd))
raphael.kubo.da.costa@intel.com91507f72013-10-22 12:18:25 +0000599 time.sleep(sleep_interval)
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000600 sleep_interval *= 2
601 raise subprocess2.CalledProcessError(
602 rv, args, kwargs.get('cwd', None), None, None)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000603
604
agable@chromium.org5a306a22014-02-24 22:13:59 +0000605class GitFilter(object):
606 """A filter_fn implementation for quieting down git output messages.
607
608 Allows a custom function to skip certain lines (predicate), and will throttle
609 the output of percentage completed lines to only output every X seconds.
610 """
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000611 PERCENT_RE = re.compile('(.*) ([0-9]{1,3})% .*')
agable@chromium.org5a306a22014-02-24 22:13:59 +0000612
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000613 def __init__(self, time_throttle=0, predicate=None, out_fh=None):
agable@chromium.org5a306a22014-02-24 22:13:59 +0000614 """
615 Args:
616 time_throttle (int): GitFilter will throttle 'noisy' output (such as the
617 XX% complete messages) to only be printed at least |time_throttle|
618 seconds apart.
619 predicate (f(line)): An optional function which is invoked for every line.
620 The line will be skipped if predicate(line) returns False.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000621 out_fh: File handle to write output to.
agable@chromium.org5a306a22014-02-24 22:13:59 +0000622 """
623 self.last_time = 0
624 self.time_throttle = time_throttle
625 self.predicate = predicate
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000626 self.out_fh = out_fh or sys.stdout
627 self.progress_prefix = None
agable@chromium.org5a306a22014-02-24 22:13:59 +0000628
629 def __call__(self, line):
630 # git uses an escape sequence to clear the line; elide it.
631 esc = line.find(unichr(033))
632 if esc > -1:
633 line = line[:esc]
634 if self.predicate and not self.predicate(line):
635 return
636 now = time.time()
637 match = self.PERCENT_RE.match(line)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000638 if match:
639 if match.group(1) != self.progress_prefix:
640 self.progress_prefix = match.group(1)
641 elif now - self.last_time < self.time_throttle:
642 return
643 self.last_time = now
644 self.out_fh.write('[%s] ' % Elapsed())
645 print >> self.out_fh, line
agable@chromium.org5a306a22014-02-24 22:13:59 +0000646
647
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000648def FindGclientRoot(from_dir, filename='.gclient'):
maruel@chromium.orga9371762009-12-22 18:27:38 +0000649 """Tries to find the gclient root."""
jochen@chromium.org20760a52010-09-08 08:47:28 +0000650 real_from_dir = os.path.realpath(from_dir)
651 path = real_from_dir
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000652 while not os.path.exists(os.path.join(path, filename)):
maruel@chromium.org3a292682010-08-23 18:54:55 +0000653 split_path = os.path.split(path)
654 if not split_path[1]:
maruel@chromium.orga9371762009-12-22 18:27:38 +0000655 return None
maruel@chromium.org3a292682010-08-23 18:54:55 +0000656 path = split_path[0]
jochen@chromium.org20760a52010-09-08 08:47:28 +0000657
658 # If we did not find the file in the current directory, make sure we are in a
659 # sub directory that is controlled by this configuration.
660 if path != real_from_dir:
661 entries_filename = os.path.join(path, filename + '_entries')
662 if not os.path.exists(entries_filename):
663 # If .gclient_entries does not exist, a previous call to gclient sync
664 # might have failed. In that case, we cannot verify that the .gclient
665 # is the one we want to use. In order to not to cause too much trouble,
666 # just issue a warning and return the path anyway.
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +0000667 print >> sys.stderr, ("%s file in parent directory %s might not be the "
jochen@chromium.org20760a52010-09-08 08:47:28 +0000668 "file you want to use" % (filename, path))
669 return path
670 scope = {}
671 try:
672 exec(FileRead(entries_filename), scope)
673 except SyntaxError, e:
674 SyntaxErrorToError(filename, e)
675 all_directories = scope['entries'].keys()
676 path_to_check = real_from_dir[len(path)+1:]
677 while path_to_check:
678 if path_to_check in all_directories:
679 return path
680 path_to_check = os.path.dirname(path_to_check)
681 return None
maruel@chromium.org3742c842010-09-09 19:27:14 +0000682
maruel@chromium.orgd9141bf2009-12-23 16:13:32 +0000683 logging.info('Found gclient root at ' + path)
maruel@chromium.orga9371762009-12-22 18:27:38 +0000684 return path
maruel@chromium.org3ccbf7e2009-12-22 20:46:42 +0000685
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000686
maruel@chromium.org3ccbf7e2009-12-22 20:46:42 +0000687def PathDifference(root, subpath):
688 """Returns the difference subpath minus root."""
689 root = os.path.realpath(root)
690 subpath = os.path.realpath(subpath)
691 if not subpath.startswith(root):
692 return None
693 # If the root does not have a trailing \ or /, we add it so the returned
694 # path starts immediately after the seperator regardless of whether it is
695 # provided.
696 root = os.path.join(root, '')
697 return subpath[len(root):]
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000698
699
700def FindFileUpwards(filename, path=None):
rcui@google.com13595ff2011-10-13 01:25:07 +0000701 """Search upwards from the a directory (default: current) to find a file.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000702
rcui@google.com13595ff2011-10-13 01:25:07 +0000703 Returns nearest upper-level directory with the passed in file.
704 """
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000705 if not path:
706 path = os.getcwd()
707 path = os.path.realpath(path)
708 while True:
709 file_path = os.path.join(path, filename)
rcui@google.com13595ff2011-10-13 01:25:07 +0000710 if os.path.exists(file_path):
711 return path
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000712 (new_path, _) = os.path.split(path)
713 if new_path == path:
714 return None
715 path = new_path
716
717
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +0000718def GetMacWinOrLinux():
719 """Returns 'mac', 'win', or 'linux', matching the current platform."""
720 if sys.platform.startswith(('cygwin', 'win')):
721 return 'win'
722 elif sys.platform.startswith('linux'):
723 return 'linux'
724 elif sys.platform == 'darwin':
725 return 'mac'
726 raise Error('Unknown platform: ' + sys.platform)
727
728
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +0000729def GetPrimarySolutionPath():
730 """Returns the full path to the primary solution. (gclient_root + src)"""
zturner@chromium.org0db9a142014-08-13 23:15:25 +0000731
brettw@chromium.orgcc968fe2014-06-23 17:30:32 +0000732 gclient_root = FindGclientRoot(os.getcwd())
733 if not gclient_root:
jochen@chromium.orgaaee92f2014-07-02 07:35:31 +0000734 # Some projects might not use .gclient. Try to see whether we're in a git
735 # checkout.
736 top_dir = [os.getcwd()]
737 def filter_fn(line):
hanpfei13f9c372016-08-08 22:05:56 -0700738 repo_root_path = os.path.normpath(line.rstrip('\n'))
739 if os.path.exists(repo_root_path):
740 top_dir[0] = repo_root_path
jochen@chromium.orgaaee92f2014-07-02 07:35:31 +0000741 try:
742 CheckCallAndFilter(["git", "rev-parse", "--show-toplevel"],
743 print_stdout=False, filter_fn=filter_fn)
744 except Exception:
745 pass
746 top_dir = top_dir[0]
747 if os.path.exists(os.path.join(top_dir, 'buildtools')):
jiangj@opera.comd6d15b82015-04-20 06:43:48 +0000748 return top_dir
brettw@chromium.orgcc968fe2014-06-23 17:30:32 +0000749 return None
kjellander@chromium.orgf7facfa2014-09-05 12:40:28 +0000750
751 # Some projects' top directory is not named 'src'.
752 source_dir_name = GetGClientPrimarySolutionName(gclient_root) or 'src'
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +0000753 return os.path.join(gclient_root, source_dir_name)
754
755
756def GetBuildtoolsPath():
757 """Returns the full path to the buildtools directory.
758 This is based on the root of the checkout containing the current directory."""
759
760 # Overriding the build tools path by environment is highly unsupported and may
761 # break without warning. Do not rely on this for anything important.
762 override = os.environ.get('CHROMIUM_BUILDTOOLS_PATH')
763 if override is not None:
764 return override
765
766 primary_solution = GetPrimarySolutionPath()
sbc@chromium.org9d0644d2015-06-05 23:16:54 +0000767 if not primary_solution:
768 return None
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +0000769 buildtools_path = os.path.join(primary_solution, 'buildtools')
ncbray@chromium.org43e91582014-11-12 22:38:51 +0000770 if not os.path.exists(buildtools_path):
771 # Buildtools may be in the gclient root.
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +0000772 gclient_root = FindGclientRoot(os.getcwd())
ncbray@chromium.org43e91582014-11-12 22:38:51 +0000773 buildtools_path = os.path.join(gclient_root, 'buildtools')
774 return buildtools_path
brettw@chromium.orgcc968fe2014-06-23 17:30:32 +0000775
776
777def GetBuildtoolsPlatformBinaryPath():
778 """Returns the full path to the binary directory for the current platform."""
brettw@chromium.orgcc968fe2014-06-23 17:30:32 +0000779 buildtools_path = GetBuildtoolsPath()
780 if not buildtools_path:
781 return None
782
783 if sys.platform.startswith(('cygwin', 'win')):
784 subdir = 'win'
785 elif sys.platform == 'darwin':
786 subdir = 'mac'
787 elif sys.platform.startswith('linux'):
brettw@chromium.orgcc968fe2014-06-23 17:30:32 +0000788 subdir = 'linux64'
brettw@chromium.orgcc968fe2014-06-23 17:30:32 +0000789 else:
790 raise Error('Unknown platform: ' + sys.platform)
791 return os.path.join(buildtools_path, subdir)
792
793
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +0000794def GetExeSuffix():
795 """Returns '' or '.exe' depending on how executables work on this platform."""
796 if sys.platform.startswith(('cygwin', 'win')):
797 return '.exe'
798 return ''
799
800
kjellander@chromium.orgf7facfa2014-09-05 12:40:28 +0000801def GetGClientPrimarySolutionName(gclient_root_dir_path):
802 """Returns the name of the primary solution in the .gclient file specified."""
803 gclient_config_file = os.path.join(gclient_root_dir_path, '.gclient')
804 env = {}
805 execfile(gclient_config_file, env)
806 solutions = env.get('solutions', [])
807 if solutions:
808 return solutions[0].get('name')
809 return None
810
811
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000812def GetGClientRootAndEntries(path=None):
813 """Returns the gclient root and the dict of entries."""
814 config_file = '.gclient_entries'
maruel@chromium.org93a9ee02011-10-18 18:23:58 +0000815 root = FindFileUpwards(config_file, path)
816 if not root:
maruel@chromium.org116704f2010-06-11 17:34:38 +0000817 print "Can't find %s" % config_file
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000818 return None
maruel@chromium.org93a9ee02011-10-18 18:23:58 +0000819 config_path = os.path.join(root, config_file)
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000820 env = {}
821 execfile(config_path, env)
822 config_dir = os.path.dirname(config_path)
823 return config_dir, env['entries']
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000824
825
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000826def lockedmethod(method):
827 """Method decorator that holds self.lock for the duration of the call."""
828 def inner(self, *args, **kwargs):
829 try:
830 try:
831 self.lock.acquire()
832 except KeyboardInterrupt:
833 print >> sys.stderr, 'Was deadlocked'
834 raise
835 return method(self, *args, **kwargs)
836 finally:
837 self.lock.release()
838 return inner
839
840
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000841class WorkItem(object):
842 """One work item."""
maruel@chromium.org4901daf2011-10-20 14:34:47 +0000843 # On cygwin, creating a lock throwing randomly when nearing ~100 locks.
844 # As a workaround, use a single lock. Yep you read it right. Single lock for
845 # all the 100 objects.
846 lock = threading.Lock()
847
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000848 def __init__(self, name):
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000849 # A unique string representing this work item.
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000850 self._name = name
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000851 self.outbuf = cStringIO.StringIO()
852 self.start = self.finish = None
hinoka885e5b12016-06-08 14:40:09 -0700853 self.resources = [] # List of resources this work item requires.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000854
maruel@chromium.org77e4eca2010-09-21 13:23:07 +0000855 def run(self, work_queue):
856 """work_queue is passed as keyword argument so it should be
maruel@chromium.org3742c842010-09-09 19:27:14 +0000857 the last parameters of the function when you override it."""
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000858 pass
859
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000860 @property
861 def name(self):
862 return self._name
863
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000864
865class ExecutionQueue(object):
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000866 """Runs a set of WorkItem that have interdependencies and were WorkItem are
867 added as they are processed.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000868
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000869 In gclient's case, Dependencies sometime needs to be run out of order due to
870 From() keyword. This class manages that all the required dependencies are run
871 before running each one.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000872
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000873 Methods of this class are thread safe.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000874 """
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000875 def __init__(self, jobs, progress, ignore_requirements, verbose=False):
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000876 """jobs specifies the number of concurrent tasks to allow. progress is a
877 Progress instance."""
878 # Set when a thread is done or a new item is enqueued.
879 self.ready_cond = threading.Condition()
880 # Maximum number of concurrent tasks.
881 self.jobs = jobs
882 # List of WorkItem, for gclient, these are Dependency instances.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000883 self.queued = []
884 # List of strings representing each Dependency.name that was run.
885 self.ran = []
886 # List of items currently running.
887 self.running = []
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000888 # Exceptions thrown if any.
maruel@chromium.org3742c842010-09-09 19:27:14 +0000889 self.exceptions = Queue.Queue()
890 # Progress status
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000891 self.progress = progress
892 if self.progress:
maruel@chromium.org3742c842010-09-09 19:27:14 +0000893 self.progress.update(0)
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000894
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000895 self.ignore_requirements = ignore_requirements
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000896 self.verbose = verbose
897 self.last_join = None
898 self.last_subproc_output = None
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000899
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000900 def enqueue(self, d):
901 """Enqueue one Dependency to be executed later once its requirements are
902 satisfied.
903 """
904 assert isinstance(d, WorkItem)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000905 self.ready_cond.acquire()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000906 try:
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000907 self.queued.append(d)
908 total = len(self.queued) + len(self.ran) + len(self.running)
szager@chromium.orge98e04c2014-07-25 00:28:06 +0000909 if self.jobs == 1:
910 total += 1
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000911 logging.debug('enqueued(%s)' % d.name)
912 if self.progress:
szager@chromium.orge98e04c2014-07-25 00:28:06 +0000913 self.progress._total = total
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000914 self.progress.update(0)
915 self.ready_cond.notifyAll()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000916 finally:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000917 self.ready_cond.release()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000918
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000919 def out_cb(self, _):
920 self.last_subproc_output = datetime.datetime.now()
921 return True
922
923 @staticmethod
924 def format_task_output(task, comment=''):
925 if comment:
926 comment = ' (%s)' % comment
927 if task.start and task.finish:
928 elapsed = ' (Elapsed: %s)' % (
929 str(task.finish - task.start).partition('.')[0])
930 else:
931 elapsed = ''
932 return """
933%s%s%s
934----------------------------------------
935%s
936----------------------------------------""" % (
szager@chromium.org1f4e71b2014-04-09 19:45:40 +0000937 task.name, comment, elapsed, task.outbuf.getvalue().strip())
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000938
hinoka885e5b12016-06-08 14:40:09 -0700939 def _is_conflict(self, job):
940 """Checks to see if a job will conflict with another running job."""
941 for running_job in self.running:
942 for used_resource in running_job.item.resources:
943 logging.debug('Checking resource %s' % used_resource)
944 if used_resource in job.resources:
945 return True
946 return False
947
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000948 def flush(self, *args, **kwargs):
949 """Runs all enqueued items until all are executed."""
maruel@chromium.org3742c842010-09-09 19:27:14 +0000950 kwargs['work_queue'] = self
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000951 self.last_subproc_output = self.last_join = datetime.datetime.now()
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000952 self.ready_cond.acquire()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000953 try:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000954 while True:
955 # Check for task to run first, then wait.
956 while True:
maruel@chromium.org3742c842010-09-09 19:27:14 +0000957 if not self.exceptions.empty():
958 # Systematically flush the queue when an exception logged.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000959 self.queued = []
maruel@chromium.org3742c842010-09-09 19:27:14 +0000960 self._flush_terminated_threads()
961 if (not self.queued and not self.running or
962 self.jobs == len(self.running)):
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000963 logging.debug('No more worker threads or can\'t queue anything.')
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000964 break
maruel@chromium.org3742c842010-09-09 19:27:14 +0000965
966 # Check for new tasks to start.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000967 for i in xrange(len(self.queued)):
968 # Verify its requirements.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000969 if (self.ignore_requirements or
970 not (set(self.queued[i].requirements) - set(self.ran))):
hinoka885e5b12016-06-08 14:40:09 -0700971 if not self._is_conflict(self.queued[i]):
972 # Start one work item: all its requirements are satisfied.
973 self._run_one_task(self.queued.pop(i), args, kwargs)
974 break
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000975 else:
976 # Couldn't find an item that could run. Break out the outher loop.
977 break
maruel@chromium.org3742c842010-09-09 19:27:14 +0000978
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000979 if not self.queued and not self.running:
maruel@chromium.org3742c842010-09-09 19:27:14 +0000980 # We're done.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000981 break
982 # We need to poll here otherwise Ctrl-C isn't processed.
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000983 try:
984 self.ready_cond.wait(10)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000985 # If we haven't printed to terminal for a while, but we have received
986 # spew from a suprocess, let the user know we're still progressing.
987 now = datetime.datetime.now()
988 if (now - self.last_join > datetime.timedelta(seconds=60) and
989 self.last_subproc_output > self.last_join):
990 if self.progress:
991 print >> sys.stdout, ''
hinoka@google.com4dfb8662014-04-25 22:21:24 +0000992 sys.stdout.flush()
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000993 elapsed = Elapsed()
994 print >> sys.stdout, '[%s] Still working on:' % elapsed
hinoka@google.com4dfb8662014-04-25 22:21:24 +0000995 sys.stdout.flush()
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000996 for task in self.running:
997 print >> sys.stdout, '[%s] %s' % (elapsed, task.item.name)
hinoka@google.com4dfb8662014-04-25 22:21:24 +0000998 sys.stdout.flush()
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000999 except KeyboardInterrupt:
1000 # Help debugging by printing some information:
1001 print >> sys.stderr, (
1002 ('\nAllowed parallel jobs: %d\n# queued: %d\nRan: %s\n'
1003 'Running: %d') % (
1004 self.jobs,
1005 len(self.queued),
1006 ', '.join(self.ran),
1007 len(self.running)))
1008 for i in self.queued:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001009 print >> sys.stderr, '%s (not started): %s' % (
1010 i.name, ', '.join(i.requirements))
1011 for i in self.running:
1012 print >> sys.stderr, self.format_task_output(i.item, 'interrupted')
maruel@chromium.org485dcab2011-09-14 12:48:47 +00001013 raise
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001014 # Something happened: self.enqueue() or a thread terminated. Loop again.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +00001015 finally:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001016 self.ready_cond.release()
maruel@chromium.org3742c842010-09-09 19:27:14 +00001017
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001018 assert not self.running, 'Now guaranteed to be single-threaded'
maruel@chromium.org3742c842010-09-09 19:27:14 +00001019 if not self.exceptions.empty():
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001020 if self.progress:
1021 print >> sys.stdout, ''
maruel@chromium.orgc8d064b2010-08-16 16:46:14 +00001022 # To get back the stack location correctly, the raise a, b, c form must be
1023 # used, passing a tuple as the first argument doesn't work.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001024 e, task = self.exceptions.get()
1025 print >> sys.stderr, self.format_task_output(task.item, 'ERROR')
maruel@chromium.orgc8d064b2010-08-16 16:46:14 +00001026 raise e[0], e[1], e[2]
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001027 elif self.progress:
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +00001028 self.progress.end()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +00001029
maruel@chromium.org3742c842010-09-09 19:27:14 +00001030 def _flush_terminated_threads(self):
1031 """Flush threads that have terminated."""
1032 running = self.running
1033 self.running = []
1034 for t in running:
1035 if t.isAlive():
1036 self.running.append(t)
1037 else:
1038 t.join()
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001039 self.last_join = datetime.datetime.now()
maruel@chromium.org042f0e72011-10-23 00:04:35 +00001040 sys.stdout.flush()
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001041 if self.verbose:
1042 print >> sys.stdout, self.format_task_output(t.item)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001043 if self.progress:
maruel@chromium.org55a2eb82010-10-06 23:35:18 +00001044 self.progress.update(1, t.item.name)
maruel@chromium.orgf36c0ee2011-09-14 19:16:47 +00001045 if t.item.name in self.ran:
1046 raise Error(
1047 'gclient is confused, "%s" is already in "%s"' % (
1048 t.item.name, ', '.join(self.ran)))
maruel@chromium.orgacc45672010-09-09 21:21:21 +00001049 if not t.item.name in self.ran:
1050 self.ran.append(t.item.name)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001051
1052 def _run_one_task(self, task_item, args, kwargs):
1053 if self.jobs > 1:
1054 # Start the thread.
1055 index = len(self.ran) + len(self.running) + 1
maruel@chromium.org77e4eca2010-09-21 13:23:07 +00001056 new_thread = self._Worker(task_item, index, args, kwargs)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001057 self.running.append(new_thread)
1058 new_thread.start()
1059 else:
1060 # Run the 'thread' inside the main thread. Don't try to catch any
1061 # exception.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001062 try:
1063 task_item.start = datetime.datetime.now()
1064 print >> task_item.outbuf, '[%s] Started.' % Elapsed(task_item.start)
1065 task_item.run(*args, **kwargs)
1066 task_item.finish = datetime.datetime.now()
1067 print >> task_item.outbuf, '[%s] Finished.' % Elapsed(task_item.finish)
1068 self.ran.append(task_item.name)
1069 if self.verbose:
1070 if self.progress:
1071 print >> sys.stdout, ''
1072 print >> sys.stdout, self.format_task_output(task_item)
1073 if self.progress:
1074 self.progress.update(1, ', '.join(t.item.name for t in self.running))
1075 except KeyboardInterrupt:
1076 print >> sys.stderr, self.format_task_output(task_item, 'interrupted')
1077 raise
1078 except Exception:
1079 print >> sys.stderr, self.format_task_output(task_item, 'ERROR')
1080 raise
1081
maruel@chromium.org3742c842010-09-09 19:27:14 +00001082
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001083 class _Worker(threading.Thread):
1084 """One thread to execute one WorkItem."""
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001085 def __init__(self, item, index, args, kwargs):
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001086 threading.Thread.__init__(self, name=item.name or 'Worker')
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001087 logging.info('_Worker(%s) reqs:%s' % (item.name, item.requirements))
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001088 self.item = item
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001089 self.index = index
maruel@chromium.org3742c842010-09-09 19:27:14 +00001090 self.args = args
1091 self.kwargs = kwargs
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00001092 self.daemon = True
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +00001093
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001094 def run(self):
1095 """Runs in its own thread."""
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001096 logging.debug('_Worker.run(%s)' % self.item.name)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001097 work_queue = self.kwargs['work_queue']
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001098 try:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001099 self.item.start = datetime.datetime.now()
1100 print >> self.item.outbuf, '[%s] Started.' % Elapsed(self.item.start)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001101 self.item.run(*self.args, **self.kwargs)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001102 self.item.finish = datetime.datetime.now()
1103 print >> self.item.outbuf, '[%s] Finished.' % Elapsed(self.item.finish)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00001104 except KeyboardInterrupt:
xusydoc@chromium.orgc144e062013-05-03 23:23:53 +00001105 logging.info('Caught KeyboardInterrupt in thread %s', self.item.name)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00001106 logging.info(str(sys.exc_info()))
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001107 work_queue.exceptions.put((sys.exc_info(), self))
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00001108 raise
maruel@chromium.orgc8d064b2010-08-16 16:46:14 +00001109 except Exception:
1110 # Catch exception location.
xusydoc@chromium.orgc144e062013-05-03 23:23:53 +00001111 logging.info('Caught exception in thread %s', self.item.name)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001112 logging.info(str(sys.exc_info()))
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001113 work_queue.exceptions.put((sys.exc_info(), self))
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001114 finally:
xusydoc@chromium.orgc144e062013-05-03 23:23:53 +00001115 logging.info('_Worker.run(%s) done', self.item.name)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00001116 work_queue.ready_cond.acquire()
1117 try:
1118 work_queue.ready_cond.notifyAll()
1119 finally:
1120 work_queue.ready_cond.release()
maruel@chromium.org0e0436a2011-10-25 13:32:41 +00001121
1122
agable92bec4f2016-08-24 09:27:27 -07001123def GetEditor(git_editor=None):
jbroman@chromium.org615a2622013-05-03 13:20:14 +00001124 """Returns the most plausible editor to use.
1125
1126 In order of preference:
1127 - GIT_EDITOR/SVN_EDITOR environment variable
1128 - core.editor git configuration variable (if supplied by git-cl)
1129 - VISUAL environment variable
1130 - EDITOR environment variable
bratell@opera.com65621c72013-12-09 15:05:32 +00001131 - vi (non-Windows) or notepad (Windows)
jbroman@chromium.org615a2622013-05-03 13:20:14 +00001132
1133 In the case of git-cl, this matches git's behaviour, except that it does not
1134 include dumb terminal detection.
jbroman@chromium.org615a2622013-05-03 13:20:14 +00001135 """
agable92bec4f2016-08-24 09:27:27 -07001136 editor = os.environ.get('GIT_EDITOR') or git_editor
maruel@chromium.org0e0436a2011-10-25 13:32:41 +00001137 if not editor:
jbroman@chromium.org615a2622013-05-03 13:20:14 +00001138 editor = os.environ.get('VISUAL')
1139 if not editor:
maruel@chromium.org0e0436a2011-10-25 13:32:41 +00001140 editor = os.environ.get('EDITOR')
1141 if not editor:
1142 if sys.platform.startswith('win'):
1143 editor = 'notepad'
1144 else:
bratell@opera.com65621c72013-12-09 15:05:32 +00001145 editor = 'vi'
maruel@chromium.org0e0436a2011-10-25 13:32:41 +00001146 return editor
1147
1148
jbroman@chromium.org615a2622013-05-03 13:20:14 +00001149def RunEditor(content, git, git_editor=None):
maruel@chromium.org0e0436a2011-10-25 13:32:41 +00001150 """Opens up the default editor in the system to get the CL description."""
maruel@chromium.orgcbd760f2013-07-23 13:02:48 +00001151 file_handle, filename = tempfile.mkstemp(text=True, prefix='cl_description')
maruel@chromium.org0e0436a2011-10-25 13:32:41 +00001152 # Make sure CRLF is handled properly by requiring none.
1153 if '\r' in content:
asvitkine@chromium.org0eff22d2011-10-25 16:11:16 +00001154 print >> sys.stderr, (
1155 '!! Please remove \\r from your change description !!')
maruel@chromium.org0e0436a2011-10-25 13:32:41 +00001156 fileobj = os.fdopen(file_handle, 'w')
1157 # Still remove \r if present.
gab@chromium.orga3fe2902016-04-20 18:31:37 +00001158 content = re.sub('\r?\n', '\n', content)
1159 # Some editors complain when the file doesn't end in \n.
1160 if not content.endswith('\n'):
1161 content += '\n'
1162 fileobj.write(content)
maruel@chromium.org0e0436a2011-10-25 13:32:41 +00001163 fileobj.close()
1164
1165 try:
agable92bec4f2016-08-24 09:27:27 -07001166 editor = GetEditor(git_editor=git_editor)
jbroman@chromium.org615a2622013-05-03 13:20:14 +00001167 if not editor:
1168 return None
1169 cmd = '%s %s' % (editor, filename)
maruel@chromium.org0e0436a2011-10-25 13:32:41 +00001170 if sys.platform == 'win32' and os.environ.get('TERM') == 'msys':
1171 # Msysgit requires the usage of 'env' to be present.
1172 cmd = 'env ' + cmd
1173 try:
1174 # shell=True to allow the shell to handle all forms of quotes in
1175 # $EDITOR.
1176 subprocess2.check_call(cmd, shell=True)
1177 except subprocess2.CalledProcessError:
1178 return None
1179 return FileRead(filename)
1180 finally:
1181 os.remove(filename)
maruel@chromium.org99ac1c52012-01-16 14:52:12 +00001182
1183
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +00001184def UpgradeToHttps(url):
1185 """Upgrades random urls to https://.
1186
1187 Do not touch unknown urls like ssh:// or git://.
1188 Do not touch http:// urls with a port number,
1189 Fixes invalid GAE url.
1190 """
1191 if not url:
1192 return url
1193 if not re.match(r'[a-z\-]+\://.*', url):
1194 # Make sure it is a valid uri. Otherwise, urlparse() will consider it a
1195 # relative url and will use http:///foo. Note that it defaults to http://
1196 # for compatibility with naked url like "localhost:8080".
1197 url = 'http://%s' % url
1198 parsed = list(urlparse.urlparse(url))
1199 # Do not automatically upgrade http to https if a port number is provided.
1200 if parsed[0] == 'http' and not re.match(r'^.+?\:\d+$', parsed[1]):
1201 parsed[0] = 'https'
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +00001202 return urlparse.urlunparse(parsed)
1203
1204
maruel@chromium.org99ac1c52012-01-16 14:52:12 +00001205def ParseCodereviewSettingsContent(content):
1206 """Process a codereview.settings file properly."""
1207 lines = (l for l in content.splitlines() if not l.strip().startswith("#"))
1208 try:
1209 keyvals = dict([x.strip() for x in l.split(':', 1)] for l in lines if l)
1210 except ValueError:
1211 raise Error(
1212 'Failed to process settings, please fix. Content:\n\n%s' % content)
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +00001213 def fix_url(key):
1214 if keyvals.get(key):
1215 keyvals[key] = UpgradeToHttps(keyvals[key])
1216 fix_url('CODE_REVIEW_SERVER')
1217 fix_url('VIEW_VC')
maruel@chromium.org99ac1c52012-01-16 14:52:12 +00001218 return keyvals
ilevy@chromium.org13691502012-10-16 04:26:37 +00001219
1220
1221def NumLocalCpus():
1222 """Returns the number of processors.
1223
dnj@chromium.org530523b2015-01-07 19:54:57 +00001224 multiprocessing.cpu_count() is permitted to raise NotImplementedError, and
1225 is known to do this on some Windows systems and OSX 10.6. If we can't get the
1226 CPU count, we will fall back to '1'.
ilevy@chromium.org13691502012-10-16 04:26:37 +00001227 """
dnj@chromium.org530523b2015-01-07 19:54:57 +00001228 # Surround the entire thing in try/except; no failure here should stop gclient
1229 # from working.
ilevy@chromium.org13691502012-10-16 04:26:37 +00001230 try:
dnj@chromium.org530523b2015-01-07 19:54:57 +00001231 # Use multiprocessing to get CPU count. This may raise
1232 # NotImplementedError.
1233 try:
1234 import multiprocessing
1235 return multiprocessing.cpu_count()
1236 except NotImplementedError: # pylint: disable=W0702
1237 # (UNIX) Query 'os.sysconf'.
1238 # pylint: disable=E1101
1239 if hasattr(os, 'sysconf') and 'SC_NPROCESSORS_ONLN' in os.sysconf_names:
1240 return int(os.sysconf('SC_NPROCESSORS_ONLN'))
1241
1242 # (Windows) Query 'NUMBER_OF_PROCESSORS' environment variable.
1243 if 'NUMBER_OF_PROCESSORS' in os.environ:
1244 return int(os.environ['NUMBER_OF_PROCESSORS'])
1245 except Exception as e:
1246 logging.exception("Exception raised while probing CPU count: %s", e)
1247
1248 logging.debug('Failed to get CPU count. Defaulting to 1.')
1249 return 1
szager@chromium.orgfc616382014-03-18 20:32:04 +00001250
sbc@chromium.org9d0644d2015-06-05 23:16:54 +00001251
szager@chromium.orgfc616382014-03-18 20:32:04 +00001252def DefaultDeltaBaseCacheLimit():
1253 """Return a reasonable default for the git config core.deltaBaseCacheLimit.
1254
1255 The primary constraint is the address space of virtual memory. The cache
1256 size limit is per-thread, and 32-bit systems can hit OOM errors if this
1257 parameter is set too high.
1258 """
1259 if platform.architecture()[0].startswith('64'):
1260 return '2g'
1261 else:
1262 return '512m'
1263
sbc@chromium.org9d0644d2015-06-05 23:16:54 +00001264
szager@chromium.orgff113292014-03-25 06:02:08 +00001265def DefaultIndexPackConfig(url=''):
szager@chromium.orgfc616382014-03-18 20:32:04 +00001266 """Return reasonable default values for configuring git-index-pack.
1267
1268 Experiments suggest that higher values for pack.threads don't improve
1269 performance."""
szager@chromium.orgff113292014-03-25 06:02:08 +00001270 cache_limit = DefaultDeltaBaseCacheLimit()
1271 result = ['-c', 'core.deltaBaseCacheLimit=%s' % cache_limit]
1272 if url in THREADED_INDEX_PACK_BLACKLIST:
1273 result.extend(['-c', 'pack.threads=1'])
1274 return result
sbc@chromium.org9d0644d2015-06-05 23:16:54 +00001275
1276
1277def FindExecutable(executable):
1278 """This mimics the "which" utility."""
1279 path_folders = os.environ.get('PATH').split(os.pathsep)
1280
1281 for path_folder in path_folders:
1282 target = os.path.join(path_folder, executable)
1283 # Just incase we have some ~/blah paths.
1284 target = os.path.abspath(os.path.expanduser(target))
1285 if os.path.isfile(target) and os.access(target, os.X_OK):
1286 return target
1287 if sys.platform.startswith('win'):
1288 for suffix in ('.bat', '.cmd', '.exe'):
1289 alt_target = target + suffix
1290 if os.path.isfile(alt_target) and os.access(alt_target, os.X_OK):
1291 return alt_target
1292 return None