blob: 556018105cc8cd0c30e42f0ec6e235efbe900eeb [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
tandriidb8b8392016-08-30 08:11:00 -0700469 def __init__(self, timeout, child, child_info):
tandriif8757b72016-08-25 04:02:19 -0700470 self._timeout = timeout
471 self._child = child
tandriidb8b8392016-08-30 08:11:00 -0700472 self._child_info = child_info
tandriif8757b72016-08-25 04:02:19 -0700473
474 self._cv = threading.Condition()
475 # All items below are protected by condition above.
476 self._kill_at = None
tandriidb8b8392016-08-30 08:11:00 -0700477 self._killing_attempted = False
tandriif8757b72016-08-25 04:02:19 -0700478 self._working = True
479 self._thread = None
480
481 # Start the timer immediately.
482 if self._timeout:
483 self._kill_at = time.time() + self._timeout
484 self._thread = threading.Thread(name='_KillTimer', target=self._work)
485 self._thread.daemon = True
486 self._thread.start()
487
tandriidb8b8392016-08-30 08:11:00 -0700488 @property
489 def killing_attempted(self):
490 return self._killing_attempted
491
tandriif8757b72016-08-25 04:02:19 -0700492 def poke(self):
493 if not self._timeout:
494 return
495 with self._cv:
496 self._kill_at = time.time() + self._timeout
497
498 def cancel(self):
499 with self._cv:
500 self._working = False
501 self._cv.notifyAll()
502
503 def _work(self):
504 if not self._timeout:
505 return
506 while True:
507 with self._cv:
508 if not self._working:
509 return
510 left = self._kill_at - time.time()
511 if left > 0:
512 self._cv.wait(timeout=left)
513 continue
514 try:
tandriidb8b8392016-08-30 08:11:00 -0700515 logging.warn('killing child %s %s because of no output for %fs',
516 self._child.pid, self._child_info, self._timeout)
517 self._killing_attempted = True
tandriif8757b72016-08-25 04:02:19 -0700518 self._child.kill()
519 except OSError:
520 logging.exception('failed to kill child %s', self._child)
521 return
522
523
maruel@chromium.org17d01792010-09-01 18:07:10 +0000524def CheckCallAndFilter(args, stdout=None, filter_fn=None,
525 print_stdout=None, call_filter_on_first_line=False,
tandriif8757b72016-08-25 04:02:19 -0700526 retry=False, kill_timeout=None, **kwargs):
maruel@chromium.org17d01792010-09-01 18:07:10 +0000527 """Runs a command and calls back a filter function if needed.
528
maruel@chromium.org57bf78d2011-09-08 18:57:33 +0000529 Accepts all subprocess2.Popen() parameters plus:
maruel@chromium.org17d01792010-09-01 18:07:10 +0000530 print_stdout: If True, the command's stdout is forwarded to stdout.
531 filter_fn: A function taking a single string argument called with each line
maruel@chromium.org57bf78d2011-09-08 18:57:33 +0000532 of the subprocess2's output. Each line has the trailing newline
maruel@chromium.org17d01792010-09-01 18:07:10 +0000533 character trimmed.
534 stdout: Can be any bufferable output.
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000535 retry: If the process exits non-zero, sleep for a brief interval and try
536 again, up to RETRY_MAX times.
tandriif8757b72016-08-25 04:02:19 -0700537 kill_timeout: (float) if given, number of seconds after which process would
tandriidb8b8392016-08-30 08:11:00 -0700538 be killed. Must not be used with shell=True as only shell process
539 would be killed, but not processes spawned by shell.
maruel@chromium.org17d01792010-09-01 18:07:10 +0000540
541 stderr is always redirected to stdout.
542 """
543 assert print_stdout or filter_fn
tandriif8757b72016-08-25 04:02:19 -0700544 assert not kwargs.get('shell', False) or not kill_timeout, (
545 'kill_timeout should not be used with shell=True')
maruel@chromium.org17d01792010-09-01 18:07:10 +0000546 stdout = stdout or sys.stdout
hinoka@google.com267f33e2014-02-28 22:02:32 +0000547 output = cStringIO.StringIO()
maruel@chromium.org17d01792010-09-01 18:07:10 +0000548 filter_fn = filter_fn or (lambda x: None)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000549
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000550 sleep_interval = RETRY_INITIAL_SLEEP
551 run_cwd = kwargs.get('cwd', os.getcwd())
tandriidb8b8392016-08-30 08:11:00 -0700552 debug_kid_info = "'%s' in %s" % (' '.join('"%s"' % x for x in args), run_cwd)
553
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000554 for _ in xrange(RETRY_MAX + 1):
555 kid = subprocess2.Popen(
556 args, bufsize=0, stdout=subprocess2.PIPE, stderr=subprocess2.STDOUT,
557 **kwargs)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000558
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000559 GClientChildren.add(kid)
chase@chromium.org8ad1cee2010-08-16 19:12:27 +0000560
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000561 # Do a flush of stdout before we begin reading from the subprocess2's stdout
562 stdout.flush()
563
564 # Also, we need to forward stdout to prevent weird re-ordering of output.
565 # This has to be done on a per byte basis to make sure it is not buffered:
566 # normally buffering is done for each line, but if svn requests input, no
567 # end-of-line character is output after the prompt and it would not show up.
568 try:
tandriidb8b8392016-08-30 08:11:00 -0700569 timeout_killer = _KillTimer(kill_timeout, kid, debug_kid_info)
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000570 in_byte = kid.stdout.read(1)
571 if in_byte:
572 if call_filter_on_first_line:
573 filter_fn(None)
574 in_line = ''
575 while in_byte:
hinoka@google.com267f33e2014-02-28 22:02:32 +0000576 output.write(in_byte)
577 if print_stdout:
578 stdout.write(in_byte)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000579 if in_byte not in ['\r', '\n']:
580 in_line += in_byte
maruel@chromium.org109cb9d2011-09-14 20:03:11 +0000581 else:
582 filter_fn(in_line)
583 in_line = ''
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000584 in_byte = kid.stdout.read(1)
585 # Flush the rest of buffered output. This is only an issue with
586 # stdout/stderr not ending with a \n.
587 if len(in_line):
szager@google.com85d3e3a2011-10-07 17:12:00 +0000588 filter_fn(in_line)
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000589 rv = kid.wait()
tandriif8757b72016-08-25 04:02:19 -0700590 timeout_killer.cancel()
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000591
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000592 # Don't put this in a 'finally,' since the child may still run if we get
593 # an exception.
594 GClientChildren.remove(kid)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000595
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000596 except KeyboardInterrupt:
597 print >> sys.stderr, 'Failed while running "%s"' % ' '.join(args)
598 raise
maruel@chromium.org109cb9d2011-09-14 20:03:11 +0000599
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000600 if rv == 0:
hinoka@google.com267f33e2014-02-28 22:02:32 +0000601 return output.getvalue()
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000602 if not retry:
603 break
tandriidb8b8392016-08-30 08:11:00 -0700604 print ("WARNING: subprocess %s failed; will retry after a short nap..." %
605 debug_kid_info)
606 if timeout_killer.killing_attempted:
607 print('The subprocess above was likely killed because it looked hung. '
608 'Output thus far:\n> %s' %
609 ('\n> '.join(output.getvalue().splitlines())))
raphael.kubo.da.costa@intel.com91507f72013-10-22 12:18:25 +0000610 time.sleep(sleep_interval)
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000611 sleep_interval *= 2
612 raise subprocess2.CalledProcessError(
613 rv, args, kwargs.get('cwd', None), None, None)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000614
615
agable@chromium.org5a306a22014-02-24 22:13:59 +0000616class GitFilter(object):
617 """A filter_fn implementation for quieting down git output messages.
618
619 Allows a custom function to skip certain lines (predicate), and will throttle
620 the output of percentage completed lines to only output every X seconds.
621 """
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000622 PERCENT_RE = re.compile('(.*) ([0-9]{1,3})% .*')
agable@chromium.org5a306a22014-02-24 22:13:59 +0000623
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000624 def __init__(self, time_throttle=0, predicate=None, out_fh=None):
agable@chromium.org5a306a22014-02-24 22:13:59 +0000625 """
626 Args:
627 time_throttle (int): GitFilter will throttle 'noisy' output (such as the
628 XX% complete messages) to only be printed at least |time_throttle|
629 seconds apart.
630 predicate (f(line)): An optional function which is invoked for every line.
631 The line will be skipped if predicate(line) returns False.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000632 out_fh: File handle to write output to.
agable@chromium.org5a306a22014-02-24 22:13:59 +0000633 """
634 self.last_time = 0
635 self.time_throttle = time_throttle
636 self.predicate = predicate
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000637 self.out_fh = out_fh or sys.stdout
638 self.progress_prefix = None
agable@chromium.org5a306a22014-02-24 22:13:59 +0000639
640 def __call__(self, line):
641 # git uses an escape sequence to clear the line; elide it.
642 esc = line.find(unichr(033))
643 if esc > -1:
644 line = line[:esc]
645 if self.predicate and not self.predicate(line):
646 return
647 now = time.time()
648 match = self.PERCENT_RE.match(line)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000649 if match:
650 if match.group(1) != self.progress_prefix:
651 self.progress_prefix = match.group(1)
652 elif now - self.last_time < self.time_throttle:
653 return
654 self.last_time = now
655 self.out_fh.write('[%s] ' % Elapsed())
656 print >> self.out_fh, line
agable@chromium.org5a306a22014-02-24 22:13:59 +0000657
658
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000659def FindGclientRoot(from_dir, filename='.gclient'):
maruel@chromium.orga9371762009-12-22 18:27:38 +0000660 """Tries to find the gclient root."""
jochen@chromium.org20760a52010-09-08 08:47:28 +0000661 real_from_dir = os.path.realpath(from_dir)
662 path = real_from_dir
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000663 while not os.path.exists(os.path.join(path, filename)):
maruel@chromium.org3a292682010-08-23 18:54:55 +0000664 split_path = os.path.split(path)
665 if not split_path[1]:
maruel@chromium.orga9371762009-12-22 18:27:38 +0000666 return None
maruel@chromium.org3a292682010-08-23 18:54:55 +0000667 path = split_path[0]
jochen@chromium.org20760a52010-09-08 08:47:28 +0000668
669 # If we did not find the file in the current directory, make sure we are in a
670 # sub directory that is controlled by this configuration.
671 if path != real_from_dir:
672 entries_filename = os.path.join(path, filename + '_entries')
673 if not os.path.exists(entries_filename):
674 # If .gclient_entries does not exist, a previous call to gclient sync
675 # might have failed. In that case, we cannot verify that the .gclient
676 # is the one we want to use. In order to not to cause too much trouble,
677 # just issue a warning and return the path anyway.
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +0000678 print >> sys.stderr, ("%s file in parent directory %s might not be the "
jochen@chromium.org20760a52010-09-08 08:47:28 +0000679 "file you want to use" % (filename, path))
680 return path
681 scope = {}
682 try:
683 exec(FileRead(entries_filename), scope)
684 except SyntaxError, e:
685 SyntaxErrorToError(filename, e)
686 all_directories = scope['entries'].keys()
687 path_to_check = real_from_dir[len(path)+1:]
688 while path_to_check:
689 if path_to_check in all_directories:
690 return path
691 path_to_check = os.path.dirname(path_to_check)
692 return None
maruel@chromium.org3742c842010-09-09 19:27:14 +0000693
maruel@chromium.orgd9141bf2009-12-23 16:13:32 +0000694 logging.info('Found gclient root at ' + path)
maruel@chromium.orga9371762009-12-22 18:27:38 +0000695 return path
maruel@chromium.org3ccbf7e2009-12-22 20:46:42 +0000696
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000697
maruel@chromium.org3ccbf7e2009-12-22 20:46:42 +0000698def PathDifference(root, subpath):
699 """Returns the difference subpath minus root."""
700 root = os.path.realpath(root)
701 subpath = os.path.realpath(subpath)
702 if not subpath.startswith(root):
703 return None
704 # If the root does not have a trailing \ or /, we add it so the returned
705 # path starts immediately after the seperator regardless of whether it is
706 # provided.
707 root = os.path.join(root, '')
708 return subpath[len(root):]
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000709
710
711def FindFileUpwards(filename, path=None):
rcui@google.com13595ff2011-10-13 01:25:07 +0000712 """Search upwards from the a directory (default: current) to find a file.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000713
rcui@google.com13595ff2011-10-13 01:25:07 +0000714 Returns nearest upper-level directory with the passed in file.
715 """
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000716 if not path:
717 path = os.getcwd()
718 path = os.path.realpath(path)
719 while True:
720 file_path = os.path.join(path, filename)
rcui@google.com13595ff2011-10-13 01:25:07 +0000721 if os.path.exists(file_path):
722 return path
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000723 (new_path, _) = os.path.split(path)
724 if new_path == path:
725 return None
726 path = new_path
727
728
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +0000729def GetMacWinOrLinux():
730 """Returns 'mac', 'win', or 'linux', matching the current platform."""
731 if sys.platform.startswith(('cygwin', 'win')):
732 return 'win'
733 elif sys.platform.startswith('linux'):
734 return 'linux'
735 elif sys.platform == 'darwin':
736 return 'mac'
737 raise Error('Unknown platform: ' + sys.platform)
738
739
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +0000740def GetPrimarySolutionPath():
741 """Returns the full path to the primary solution. (gclient_root + src)"""
zturner@chromium.org0db9a142014-08-13 23:15:25 +0000742
brettw@chromium.orgcc968fe2014-06-23 17:30:32 +0000743 gclient_root = FindGclientRoot(os.getcwd())
744 if not gclient_root:
jochen@chromium.orgaaee92f2014-07-02 07:35:31 +0000745 # Some projects might not use .gclient. Try to see whether we're in a git
746 # checkout.
747 top_dir = [os.getcwd()]
748 def filter_fn(line):
hanpfei13f9c372016-08-08 22:05:56 -0700749 repo_root_path = os.path.normpath(line.rstrip('\n'))
750 if os.path.exists(repo_root_path):
751 top_dir[0] = repo_root_path
jochen@chromium.orgaaee92f2014-07-02 07:35:31 +0000752 try:
753 CheckCallAndFilter(["git", "rev-parse", "--show-toplevel"],
754 print_stdout=False, filter_fn=filter_fn)
755 except Exception:
756 pass
757 top_dir = top_dir[0]
758 if os.path.exists(os.path.join(top_dir, 'buildtools')):
jiangj@opera.comd6d15b82015-04-20 06:43:48 +0000759 return top_dir
brettw@chromium.orgcc968fe2014-06-23 17:30:32 +0000760 return None
kjellander@chromium.orgf7facfa2014-09-05 12:40:28 +0000761
762 # Some projects' top directory is not named 'src'.
763 source_dir_name = GetGClientPrimarySolutionName(gclient_root) or 'src'
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +0000764 return os.path.join(gclient_root, source_dir_name)
765
766
767def GetBuildtoolsPath():
768 """Returns the full path to the buildtools directory.
769 This is based on the root of the checkout containing the current directory."""
770
771 # Overriding the build tools path by environment is highly unsupported and may
772 # break without warning. Do not rely on this for anything important.
773 override = os.environ.get('CHROMIUM_BUILDTOOLS_PATH')
774 if override is not None:
775 return override
776
777 primary_solution = GetPrimarySolutionPath()
sbc@chromium.org9d0644d2015-06-05 23:16:54 +0000778 if not primary_solution:
779 return None
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +0000780 buildtools_path = os.path.join(primary_solution, 'buildtools')
ncbray@chromium.org43e91582014-11-12 22:38:51 +0000781 if not os.path.exists(buildtools_path):
782 # Buildtools may be in the gclient root.
erg@chromium.orge0a7c5d2015-02-23 20:30:08 +0000783 gclient_root = FindGclientRoot(os.getcwd())
ncbray@chromium.org43e91582014-11-12 22:38:51 +0000784 buildtools_path = os.path.join(gclient_root, 'buildtools')
785 return buildtools_path
brettw@chromium.orgcc968fe2014-06-23 17:30:32 +0000786
787
788def GetBuildtoolsPlatformBinaryPath():
789 """Returns the full path to the binary directory for the current platform."""
brettw@chromium.orgcc968fe2014-06-23 17:30:32 +0000790 buildtools_path = GetBuildtoolsPath()
791 if not buildtools_path:
792 return None
793
794 if sys.platform.startswith(('cygwin', 'win')):
795 subdir = 'win'
796 elif sys.platform == 'darwin':
797 subdir = 'mac'
798 elif sys.platform.startswith('linux'):
brettw@chromium.orgcc968fe2014-06-23 17:30:32 +0000799 subdir = 'linux64'
brettw@chromium.orgcc968fe2014-06-23 17:30:32 +0000800 else:
801 raise Error('Unknown platform: ' + sys.platform)
802 return os.path.join(buildtools_path, subdir)
803
804
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +0000805def GetExeSuffix():
806 """Returns '' or '.exe' depending on how executables work on this platform."""
807 if sys.platform.startswith(('cygwin', 'win')):
808 return '.exe'
809 return ''
810
811
kjellander@chromium.orgf7facfa2014-09-05 12:40:28 +0000812def GetGClientPrimarySolutionName(gclient_root_dir_path):
813 """Returns the name of the primary solution in the .gclient file specified."""
814 gclient_config_file = os.path.join(gclient_root_dir_path, '.gclient')
815 env = {}
816 execfile(gclient_config_file, env)
817 solutions = env.get('solutions', [])
818 if solutions:
819 return solutions[0].get('name')
820 return None
821
822
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000823def GetGClientRootAndEntries(path=None):
824 """Returns the gclient root and the dict of entries."""
825 config_file = '.gclient_entries'
maruel@chromium.org93a9ee02011-10-18 18:23:58 +0000826 root = FindFileUpwards(config_file, path)
827 if not root:
maruel@chromium.org116704f2010-06-11 17:34:38 +0000828 print "Can't find %s" % config_file
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000829 return None
maruel@chromium.org93a9ee02011-10-18 18:23:58 +0000830 config_path = os.path.join(root, config_file)
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000831 env = {}
832 execfile(config_path, env)
833 config_dir = os.path.dirname(config_path)
834 return config_dir, env['entries']
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000835
836
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000837def lockedmethod(method):
838 """Method decorator that holds self.lock for the duration of the call."""
839 def inner(self, *args, **kwargs):
840 try:
841 try:
842 self.lock.acquire()
843 except KeyboardInterrupt:
844 print >> sys.stderr, 'Was deadlocked'
845 raise
846 return method(self, *args, **kwargs)
847 finally:
848 self.lock.release()
849 return inner
850
851
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000852class WorkItem(object):
853 """One work item."""
maruel@chromium.org4901daf2011-10-20 14:34:47 +0000854 # On cygwin, creating a lock throwing randomly when nearing ~100 locks.
855 # As a workaround, use a single lock. Yep you read it right. Single lock for
856 # all the 100 objects.
857 lock = threading.Lock()
858
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000859 def __init__(self, name):
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000860 # A unique string representing this work item.
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000861 self._name = name
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000862 self.outbuf = cStringIO.StringIO()
863 self.start = self.finish = None
hinoka885e5b12016-06-08 14:40:09 -0700864 self.resources = [] # List of resources this work item requires.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000865
maruel@chromium.org77e4eca2010-09-21 13:23:07 +0000866 def run(self, work_queue):
867 """work_queue is passed as keyword argument so it should be
maruel@chromium.org3742c842010-09-09 19:27:14 +0000868 the last parameters of the function when you override it."""
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000869 pass
870
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000871 @property
872 def name(self):
873 return self._name
874
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000875
876class ExecutionQueue(object):
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000877 """Runs a set of WorkItem that have interdependencies and were WorkItem are
878 added as they are processed.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000879
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000880 In gclient's case, Dependencies sometime needs to be run out of order due to
881 From() keyword. This class manages that all the required dependencies are run
882 before running each one.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000883
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000884 Methods of this class are thread safe.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000885 """
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000886 def __init__(self, jobs, progress, ignore_requirements, verbose=False):
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000887 """jobs specifies the number of concurrent tasks to allow. progress is a
888 Progress instance."""
889 # Set when a thread is done or a new item is enqueued.
890 self.ready_cond = threading.Condition()
891 # Maximum number of concurrent tasks.
892 self.jobs = jobs
893 # List of WorkItem, for gclient, these are Dependency instances.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000894 self.queued = []
895 # List of strings representing each Dependency.name that was run.
896 self.ran = []
897 # List of items currently running.
898 self.running = []
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000899 # Exceptions thrown if any.
maruel@chromium.org3742c842010-09-09 19:27:14 +0000900 self.exceptions = Queue.Queue()
901 # Progress status
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000902 self.progress = progress
903 if self.progress:
maruel@chromium.org3742c842010-09-09 19:27:14 +0000904 self.progress.update(0)
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000905
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000906 self.ignore_requirements = ignore_requirements
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000907 self.verbose = verbose
908 self.last_join = None
909 self.last_subproc_output = None
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000910
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000911 def enqueue(self, d):
912 """Enqueue one Dependency to be executed later once its requirements are
913 satisfied.
914 """
915 assert isinstance(d, WorkItem)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000916 self.ready_cond.acquire()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000917 try:
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000918 self.queued.append(d)
919 total = len(self.queued) + len(self.ran) + len(self.running)
szager@chromium.orge98e04c2014-07-25 00:28:06 +0000920 if self.jobs == 1:
921 total += 1
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000922 logging.debug('enqueued(%s)' % d.name)
923 if self.progress:
szager@chromium.orge98e04c2014-07-25 00:28:06 +0000924 self.progress._total = total
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000925 self.progress.update(0)
926 self.ready_cond.notifyAll()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000927 finally:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000928 self.ready_cond.release()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000929
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000930 def out_cb(self, _):
931 self.last_subproc_output = datetime.datetime.now()
932 return True
933
934 @staticmethod
935 def format_task_output(task, comment=''):
936 if comment:
937 comment = ' (%s)' % comment
938 if task.start and task.finish:
939 elapsed = ' (Elapsed: %s)' % (
940 str(task.finish - task.start).partition('.')[0])
941 else:
942 elapsed = ''
943 return """
944%s%s%s
945----------------------------------------
946%s
947----------------------------------------""" % (
szager@chromium.org1f4e71b2014-04-09 19:45:40 +0000948 task.name, comment, elapsed, task.outbuf.getvalue().strip())
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000949
hinoka885e5b12016-06-08 14:40:09 -0700950 def _is_conflict(self, job):
951 """Checks to see if a job will conflict with another running job."""
952 for running_job in self.running:
953 for used_resource in running_job.item.resources:
954 logging.debug('Checking resource %s' % used_resource)
955 if used_resource in job.resources:
956 return True
957 return False
958
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000959 def flush(self, *args, **kwargs):
960 """Runs all enqueued items until all are executed."""
maruel@chromium.org3742c842010-09-09 19:27:14 +0000961 kwargs['work_queue'] = self
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000962 self.last_subproc_output = self.last_join = datetime.datetime.now()
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000963 self.ready_cond.acquire()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000964 try:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000965 while True:
966 # Check for task to run first, then wait.
967 while True:
maruel@chromium.org3742c842010-09-09 19:27:14 +0000968 if not self.exceptions.empty():
969 # Systematically flush the queue when an exception logged.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000970 self.queued = []
maruel@chromium.org3742c842010-09-09 19:27:14 +0000971 self._flush_terminated_threads()
972 if (not self.queued and not self.running or
973 self.jobs == len(self.running)):
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000974 logging.debug('No more worker threads or can\'t queue anything.')
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000975 break
maruel@chromium.org3742c842010-09-09 19:27:14 +0000976
977 # Check for new tasks to start.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000978 for i in xrange(len(self.queued)):
979 # Verify its requirements.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000980 if (self.ignore_requirements or
981 not (set(self.queued[i].requirements) - set(self.ran))):
hinoka885e5b12016-06-08 14:40:09 -0700982 if not self._is_conflict(self.queued[i]):
983 # Start one work item: all its requirements are satisfied.
984 self._run_one_task(self.queued.pop(i), args, kwargs)
985 break
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000986 else:
987 # Couldn't find an item that could run. Break out the outher loop.
988 break
maruel@chromium.org3742c842010-09-09 19:27:14 +0000989
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000990 if not self.queued and not self.running:
maruel@chromium.org3742c842010-09-09 19:27:14 +0000991 # We're done.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000992 break
993 # We need to poll here otherwise Ctrl-C isn't processed.
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000994 try:
995 self.ready_cond.wait(10)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000996 # If we haven't printed to terminal for a while, but we have received
997 # spew from a suprocess, let the user know we're still progressing.
998 now = datetime.datetime.now()
999 if (now - self.last_join > datetime.timedelta(seconds=60) and
1000 self.last_subproc_output > self.last_join):
1001 if self.progress:
1002 print >> sys.stdout, ''
hinoka@google.com4dfb8662014-04-25 22:21:24 +00001003 sys.stdout.flush()
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001004 elapsed = Elapsed()
1005 print >> sys.stdout, '[%s] Still working on:' % elapsed
hinoka@google.com4dfb8662014-04-25 22:21:24 +00001006 sys.stdout.flush()
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001007 for task in self.running:
1008 print >> sys.stdout, '[%s] %s' % (elapsed, task.item.name)
hinoka@google.com4dfb8662014-04-25 22:21:24 +00001009 sys.stdout.flush()
maruel@chromium.org485dcab2011-09-14 12:48:47 +00001010 except KeyboardInterrupt:
1011 # Help debugging by printing some information:
1012 print >> sys.stderr, (
1013 ('\nAllowed parallel jobs: %d\n# queued: %d\nRan: %s\n'
1014 'Running: %d') % (
1015 self.jobs,
1016 len(self.queued),
1017 ', '.join(self.ran),
1018 len(self.running)))
1019 for i in self.queued:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001020 print >> sys.stderr, '%s (not started): %s' % (
1021 i.name, ', '.join(i.requirements))
1022 for i in self.running:
1023 print >> sys.stderr, self.format_task_output(i.item, 'interrupted')
maruel@chromium.org485dcab2011-09-14 12:48:47 +00001024 raise
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001025 # Something happened: self.enqueue() or a thread terminated. Loop again.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +00001026 finally:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001027 self.ready_cond.release()
maruel@chromium.org3742c842010-09-09 19:27:14 +00001028
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001029 assert not self.running, 'Now guaranteed to be single-threaded'
maruel@chromium.org3742c842010-09-09 19:27:14 +00001030 if not self.exceptions.empty():
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001031 if self.progress:
1032 print >> sys.stdout, ''
maruel@chromium.orgc8d064b2010-08-16 16:46:14 +00001033 # To get back the stack location correctly, the raise a, b, c form must be
1034 # used, passing a tuple as the first argument doesn't work.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001035 e, task = self.exceptions.get()
1036 print >> sys.stderr, self.format_task_output(task.item, 'ERROR')
maruel@chromium.orgc8d064b2010-08-16 16:46:14 +00001037 raise e[0], e[1], e[2]
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001038 elif self.progress:
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +00001039 self.progress.end()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +00001040
maruel@chromium.org3742c842010-09-09 19:27:14 +00001041 def _flush_terminated_threads(self):
1042 """Flush threads that have terminated."""
1043 running = self.running
1044 self.running = []
1045 for t in running:
1046 if t.isAlive():
1047 self.running.append(t)
1048 else:
1049 t.join()
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001050 self.last_join = datetime.datetime.now()
maruel@chromium.org042f0e72011-10-23 00:04:35 +00001051 sys.stdout.flush()
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001052 if self.verbose:
1053 print >> sys.stdout, self.format_task_output(t.item)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001054 if self.progress:
maruel@chromium.org55a2eb82010-10-06 23:35:18 +00001055 self.progress.update(1, t.item.name)
maruel@chromium.orgf36c0ee2011-09-14 19:16:47 +00001056 if t.item.name in self.ran:
1057 raise Error(
1058 'gclient is confused, "%s" is already in "%s"' % (
1059 t.item.name, ', '.join(self.ran)))
maruel@chromium.orgacc45672010-09-09 21:21:21 +00001060 if not t.item.name in self.ran:
1061 self.ran.append(t.item.name)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001062
1063 def _run_one_task(self, task_item, args, kwargs):
1064 if self.jobs > 1:
1065 # Start the thread.
1066 index = len(self.ran) + len(self.running) + 1
maruel@chromium.org77e4eca2010-09-21 13:23:07 +00001067 new_thread = self._Worker(task_item, index, args, kwargs)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001068 self.running.append(new_thread)
1069 new_thread.start()
1070 else:
1071 # Run the 'thread' inside the main thread. Don't try to catch any
1072 # exception.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001073 try:
1074 task_item.start = datetime.datetime.now()
1075 print >> task_item.outbuf, '[%s] Started.' % Elapsed(task_item.start)
1076 task_item.run(*args, **kwargs)
1077 task_item.finish = datetime.datetime.now()
1078 print >> task_item.outbuf, '[%s] Finished.' % Elapsed(task_item.finish)
1079 self.ran.append(task_item.name)
1080 if self.verbose:
1081 if self.progress:
1082 print >> sys.stdout, ''
1083 print >> sys.stdout, self.format_task_output(task_item)
1084 if self.progress:
1085 self.progress.update(1, ', '.join(t.item.name for t in self.running))
1086 except KeyboardInterrupt:
1087 print >> sys.stderr, self.format_task_output(task_item, 'interrupted')
1088 raise
1089 except Exception:
1090 print >> sys.stderr, self.format_task_output(task_item, 'ERROR')
1091 raise
1092
maruel@chromium.org3742c842010-09-09 19:27:14 +00001093
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001094 class _Worker(threading.Thread):
1095 """One thread to execute one WorkItem."""
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001096 def __init__(self, item, index, args, kwargs):
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001097 threading.Thread.__init__(self, name=item.name or 'Worker')
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001098 logging.info('_Worker(%s) reqs:%s' % (item.name, item.requirements))
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001099 self.item = item
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001100 self.index = index
maruel@chromium.org3742c842010-09-09 19:27:14 +00001101 self.args = args
1102 self.kwargs = kwargs
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00001103 self.daemon = True
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +00001104
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001105 def run(self):
1106 """Runs in its own thread."""
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001107 logging.debug('_Worker.run(%s)' % self.item.name)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001108 work_queue = self.kwargs['work_queue']
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001109 try:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001110 self.item.start = datetime.datetime.now()
1111 print >> self.item.outbuf, '[%s] Started.' % Elapsed(self.item.start)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001112 self.item.run(*self.args, **self.kwargs)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001113 self.item.finish = datetime.datetime.now()
1114 print >> self.item.outbuf, '[%s] Finished.' % Elapsed(self.item.finish)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00001115 except KeyboardInterrupt:
xusydoc@chromium.orgc144e062013-05-03 23:23:53 +00001116 logging.info('Caught KeyboardInterrupt in thread %s', self.item.name)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00001117 logging.info(str(sys.exc_info()))
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001118 work_queue.exceptions.put((sys.exc_info(), self))
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00001119 raise
maruel@chromium.orgc8d064b2010-08-16 16:46:14 +00001120 except Exception:
1121 # Catch exception location.
xusydoc@chromium.orgc144e062013-05-03 23:23:53 +00001122 logging.info('Caught exception in thread %s', self.item.name)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001123 logging.info(str(sys.exc_info()))
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001124 work_queue.exceptions.put((sys.exc_info(), self))
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001125 finally:
xusydoc@chromium.orgc144e062013-05-03 23:23:53 +00001126 logging.info('_Worker.run(%s) done', self.item.name)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00001127 work_queue.ready_cond.acquire()
1128 try:
1129 work_queue.ready_cond.notifyAll()
1130 finally:
1131 work_queue.ready_cond.release()
maruel@chromium.org0e0436a2011-10-25 13:32:41 +00001132
1133
agable92bec4f2016-08-24 09:27:27 -07001134def GetEditor(git_editor=None):
jbroman@chromium.org615a2622013-05-03 13:20:14 +00001135 """Returns the most plausible editor to use.
1136
1137 In order of preference:
1138 - GIT_EDITOR/SVN_EDITOR environment variable
1139 - core.editor git configuration variable (if supplied by git-cl)
1140 - VISUAL environment variable
1141 - EDITOR environment variable
bratell@opera.com65621c72013-12-09 15:05:32 +00001142 - vi (non-Windows) or notepad (Windows)
jbroman@chromium.org615a2622013-05-03 13:20:14 +00001143
1144 In the case of git-cl, this matches git's behaviour, except that it does not
1145 include dumb terminal detection.
jbroman@chromium.org615a2622013-05-03 13:20:14 +00001146 """
agable92bec4f2016-08-24 09:27:27 -07001147 editor = os.environ.get('GIT_EDITOR') or git_editor
maruel@chromium.org0e0436a2011-10-25 13:32:41 +00001148 if not editor:
jbroman@chromium.org615a2622013-05-03 13:20:14 +00001149 editor = os.environ.get('VISUAL')
1150 if not editor:
maruel@chromium.org0e0436a2011-10-25 13:32:41 +00001151 editor = os.environ.get('EDITOR')
1152 if not editor:
1153 if sys.platform.startswith('win'):
1154 editor = 'notepad'
1155 else:
bratell@opera.com65621c72013-12-09 15:05:32 +00001156 editor = 'vi'
maruel@chromium.org0e0436a2011-10-25 13:32:41 +00001157 return editor
1158
1159
jbroman@chromium.org615a2622013-05-03 13:20:14 +00001160def RunEditor(content, git, git_editor=None):
maruel@chromium.org0e0436a2011-10-25 13:32:41 +00001161 """Opens up the default editor in the system to get the CL description."""
maruel@chromium.orgcbd760f2013-07-23 13:02:48 +00001162 file_handle, filename = tempfile.mkstemp(text=True, prefix='cl_description')
maruel@chromium.org0e0436a2011-10-25 13:32:41 +00001163 # Make sure CRLF is handled properly by requiring none.
1164 if '\r' in content:
asvitkine@chromium.org0eff22d2011-10-25 16:11:16 +00001165 print >> sys.stderr, (
1166 '!! Please remove \\r from your change description !!')
maruel@chromium.org0e0436a2011-10-25 13:32:41 +00001167 fileobj = os.fdopen(file_handle, 'w')
1168 # Still remove \r if present.
gab@chromium.orga3fe2902016-04-20 18:31:37 +00001169 content = re.sub('\r?\n', '\n', content)
1170 # Some editors complain when the file doesn't end in \n.
1171 if not content.endswith('\n'):
1172 content += '\n'
1173 fileobj.write(content)
maruel@chromium.org0e0436a2011-10-25 13:32:41 +00001174 fileobj.close()
1175
1176 try:
agable92bec4f2016-08-24 09:27:27 -07001177 editor = GetEditor(git_editor=git_editor)
jbroman@chromium.org615a2622013-05-03 13:20:14 +00001178 if not editor:
1179 return None
1180 cmd = '%s %s' % (editor, filename)
maruel@chromium.org0e0436a2011-10-25 13:32:41 +00001181 if sys.platform == 'win32' and os.environ.get('TERM') == 'msys':
1182 # Msysgit requires the usage of 'env' to be present.
1183 cmd = 'env ' + cmd
1184 try:
1185 # shell=True to allow the shell to handle all forms of quotes in
1186 # $EDITOR.
1187 subprocess2.check_call(cmd, shell=True)
1188 except subprocess2.CalledProcessError:
1189 return None
1190 return FileRead(filename)
1191 finally:
1192 os.remove(filename)
maruel@chromium.org99ac1c52012-01-16 14:52:12 +00001193
1194
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +00001195def UpgradeToHttps(url):
1196 """Upgrades random urls to https://.
1197
1198 Do not touch unknown urls like ssh:// or git://.
1199 Do not touch http:// urls with a port number,
1200 Fixes invalid GAE url.
1201 """
1202 if not url:
1203 return url
1204 if not re.match(r'[a-z\-]+\://.*', url):
1205 # Make sure it is a valid uri. Otherwise, urlparse() will consider it a
1206 # relative url and will use http:///foo. Note that it defaults to http://
1207 # for compatibility with naked url like "localhost:8080".
1208 url = 'http://%s' % url
1209 parsed = list(urlparse.urlparse(url))
1210 # Do not automatically upgrade http to https if a port number is provided.
1211 if parsed[0] == 'http' and not re.match(r'^.+?\:\d+$', parsed[1]):
1212 parsed[0] = 'https'
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +00001213 return urlparse.urlunparse(parsed)
1214
1215
maruel@chromium.org99ac1c52012-01-16 14:52:12 +00001216def ParseCodereviewSettingsContent(content):
1217 """Process a codereview.settings file properly."""
1218 lines = (l for l in content.splitlines() if not l.strip().startswith("#"))
1219 try:
1220 keyvals = dict([x.strip() for x in l.split(':', 1)] for l in lines if l)
1221 except ValueError:
1222 raise Error(
1223 'Failed to process settings, please fix. Content:\n\n%s' % content)
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +00001224 def fix_url(key):
1225 if keyvals.get(key):
1226 keyvals[key] = UpgradeToHttps(keyvals[key])
1227 fix_url('CODE_REVIEW_SERVER')
1228 fix_url('VIEW_VC')
maruel@chromium.org99ac1c52012-01-16 14:52:12 +00001229 return keyvals
ilevy@chromium.org13691502012-10-16 04:26:37 +00001230
1231
1232def NumLocalCpus():
1233 """Returns the number of processors.
1234
dnj@chromium.org530523b2015-01-07 19:54:57 +00001235 multiprocessing.cpu_count() is permitted to raise NotImplementedError, and
1236 is known to do this on some Windows systems and OSX 10.6. If we can't get the
1237 CPU count, we will fall back to '1'.
ilevy@chromium.org13691502012-10-16 04:26:37 +00001238 """
dnj@chromium.org530523b2015-01-07 19:54:57 +00001239 # Surround the entire thing in try/except; no failure here should stop gclient
1240 # from working.
ilevy@chromium.org13691502012-10-16 04:26:37 +00001241 try:
dnj@chromium.org530523b2015-01-07 19:54:57 +00001242 # Use multiprocessing to get CPU count. This may raise
1243 # NotImplementedError.
1244 try:
1245 import multiprocessing
1246 return multiprocessing.cpu_count()
1247 except NotImplementedError: # pylint: disable=W0702
1248 # (UNIX) Query 'os.sysconf'.
1249 # pylint: disable=E1101
1250 if hasattr(os, 'sysconf') and 'SC_NPROCESSORS_ONLN' in os.sysconf_names:
1251 return int(os.sysconf('SC_NPROCESSORS_ONLN'))
1252
1253 # (Windows) Query 'NUMBER_OF_PROCESSORS' environment variable.
1254 if 'NUMBER_OF_PROCESSORS' in os.environ:
1255 return int(os.environ['NUMBER_OF_PROCESSORS'])
1256 except Exception as e:
1257 logging.exception("Exception raised while probing CPU count: %s", e)
1258
1259 logging.debug('Failed to get CPU count. Defaulting to 1.')
1260 return 1
szager@chromium.orgfc616382014-03-18 20:32:04 +00001261
sbc@chromium.org9d0644d2015-06-05 23:16:54 +00001262
szager@chromium.orgfc616382014-03-18 20:32:04 +00001263def DefaultDeltaBaseCacheLimit():
1264 """Return a reasonable default for the git config core.deltaBaseCacheLimit.
1265
1266 The primary constraint is the address space of virtual memory. The cache
1267 size limit is per-thread, and 32-bit systems can hit OOM errors if this
1268 parameter is set too high.
1269 """
1270 if platform.architecture()[0].startswith('64'):
1271 return '2g'
1272 else:
1273 return '512m'
1274
sbc@chromium.org9d0644d2015-06-05 23:16:54 +00001275
szager@chromium.orgff113292014-03-25 06:02:08 +00001276def DefaultIndexPackConfig(url=''):
szager@chromium.orgfc616382014-03-18 20:32:04 +00001277 """Return reasonable default values for configuring git-index-pack.
1278
1279 Experiments suggest that higher values for pack.threads don't improve
1280 performance."""
szager@chromium.orgff113292014-03-25 06:02:08 +00001281 cache_limit = DefaultDeltaBaseCacheLimit()
1282 result = ['-c', 'core.deltaBaseCacheLimit=%s' % cache_limit]
1283 if url in THREADED_INDEX_PACK_BLACKLIST:
1284 result.extend(['-c', 'pack.threads=1'])
1285 return result
sbc@chromium.org9d0644d2015-06-05 23:16:54 +00001286
1287
1288def FindExecutable(executable):
1289 """This mimics the "which" utility."""
1290 path_folders = os.environ.get('PATH').split(os.pathsep)
1291
1292 for path_folder in path_folders:
1293 target = os.path.join(path_folder, executable)
1294 # Just incase we have some ~/blah paths.
1295 target = os.path.abspath(os.path.expanduser(target))
1296 if os.path.isfile(target) and os.access(target, os.X_OK):
1297 return target
1298 if sys.platform.startswith('win'):
1299 for suffix in ('.bat', '.cmd', '.exe'):
1300 alt_target = target + suffix
1301 if os.path.isfile(alt_target) and os.access(alt_target, os.X_OK):
1302 return alt_target
1303 return None