blob: 8385deb054a8b6823f13b5cfeef4816f45999304 [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
maruel@chromium.orgd9141bf2009-12-23 16:13:32 +00008import logging
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00009import os
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +000010import pipes
maruel@chromium.org3742c842010-09-09 19:27:14 +000011import Queue
msb@chromium.orgac915bb2009-11-13 17:03:01 +000012import re
bradnelson@google.com8f9c69f2009-09-17 00:48:28 +000013import stat
borenet@google.com6b4a2ab2013-04-18 15:50:27 +000014import subprocess
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000015import sys
maruel@chromium.org0e0436a2011-10-25 13:32:41 +000016import tempfile
maruel@chromium.org9e5317a2010-08-13 20:35:11 +000017import threading
maruel@chromium.org167b9e62009-09-17 17:41:02 +000018import time
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +000019import urlparse
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000020
maruel@chromium.orgca0f8392011-09-08 17:15:15 +000021import subprocess2
22
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000023
maruel@chromium.org66c83e62010-09-07 14:18:45 +000024class Error(Exception):
25 """gclient exception class."""
szager@chromium.org4a3c17e2013-05-24 23:59:29 +000026 def __init__(self, msg, *args, **kwargs):
27 index = getattr(threading.currentThread(), 'index', 0)
28 if index:
29 msg = '\n'.join('%d> %s' % (index, l) for l in msg.splitlines())
30 super(Error, self).__init__(msg, *args, **kwargs)
maruel@chromium.org66c83e62010-09-07 14:18:45 +000031
msb@chromium.orgac915bb2009-11-13 17:03:01 +000032def SplitUrlRevision(url):
33 """Splits url and returns a two-tuple: url, rev"""
34 if url.startswith('ssh:'):
maruel@chromium.org78b8cd12010-10-26 12:47:07 +000035 # Make sure ssh://user-name@example.com/~/test.git@stable works
36 regex = r'(ssh://(?:[-\w]+@)?[-\w:\.]+/[-~\w\./]+)(?:@(.+))?'
msb@chromium.orgac915bb2009-11-13 17:03:01 +000037 components = re.search(regex, url).groups()
38 else:
maruel@chromium.org116704f2010-06-11 17:34:38 +000039 components = url.split('@', 1)
msb@chromium.orgac915bb2009-11-13 17:03:01 +000040 if len(components) == 1:
41 components += [None]
42 return tuple(components)
43
44
floitsch@google.comeaab7842011-04-28 09:07:58 +000045def IsDateRevision(revision):
46 """Returns true if the given revision is of the form "{ ... }"."""
47 return bool(revision and re.match(r'^\{.+\}$', str(revision)))
48
49
50def MakeDateRevision(date):
51 """Returns a revision representing the latest revision before the given
52 date."""
53 return "{" + date + "}"
54
55
maruel@chromium.org5990f9d2010-07-07 18:02:58 +000056def SyntaxErrorToError(filename, e):
57 """Raises a gclient_utils.Error exception with the human readable message"""
58 try:
59 # Try to construct a human readable error message
60 if filename:
61 error_message = 'There is a syntax error in %s\n' % filename
62 else:
63 error_message = 'There is a syntax error\n'
64 error_message += 'Line #%s, character %s: "%s"' % (
65 e.lineno, e.offset, re.sub(r'[\r\n]*$', '', e.text))
66 except:
67 # Something went wrong, re-raise the original exception
68 raise e
69 else:
70 raise Error(error_message)
71
72
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000073class PrintableObject(object):
74 def __str__(self):
75 output = ''
76 for i in dir(self):
77 if i.startswith('__'):
78 continue
79 output += '%s = %s\n' % (i, str(getattr(self, i, '')))
80 return output
81
82
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +000083def FileRead(filename, mode='rU'):
maruel@chromium.org51e84fb2012-07-03 23:06:21 +000084 with open(filename, mode=mode) as f:
maruel@chromium.orgc3cd5372012-07-11 17:39:24 +000085 # codecs.open() has different behavior than open() on python 2.6 so use
86 # open() and decode manually.
chrisha@chromium.org2b99d432012-07-12 18:10:28 +000087 s = f.read()
88 try:
89 return s.decode('utf-8')
90 except UnicodeDecodeError:
91 return s
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000092
93
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +000094def FileWrite(filename, content, mode='w'):
maruel@chromium.orgdae209f2012-07-03 16:08:15 +000095 with codecs.open(filename, mode=mode, encoding='utf-8') as f:
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000096 f.write(content)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000097
98
maruel@chromium.orgf9040722011-03-09 14:47:51 +000099def rmtree(path):
100 """shutil.rmtree() on steroids.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000101
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000102 Recursively removes a directory, even if it's marked read-only.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000103
104 shutil.rmtree() doesn't work on Windows if any of the files or directories
105 are read-only, which svn repositories and some .svn files are. We need to
106 be able to force the files to be writable (i.e., deletable) as we traverse
107 the tree.
108
109 Even with all this, Windows still sometimes fails to delete a file, citing
110 a permission error (maybe something to do with antivirus scans or disk
111 indexing). The best suggestion any of the user forums had was to wait a
112 bit and try again, so we do that too. It's hand-waving, but sometimes it
113 works. :/
114
115 On POSIX systems, things are a little bit simpler. The modes of the files
116 to be deleted doesn't matter, only the modes of the directories containing
117 them are significant. As the directory tree is traversed, each directory
118 has its mode set appropriately before descending into it. This should
119 result in the entire tree being removed, with the possible exception of
120 *path itself, because nothing attempts to change the mode of its parent.
121 Doing so would be hazardous, as it's not a directory slated for removal.
122 In the ordinary case, this is not a problem: for our purposes, the user
123 will never lack write permission on *path's parent.
124 """
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000125 if not os.path.exists(path):
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000126 return
127
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000128 if os.path.islink(path) or not os.path.isdir(path):
129 raise Error('Called rmtree(%s) in non-directory' % path)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000130
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000131 if sys.platform == 'win32':
borenet@google.com6b4a2ab2013-04-18 15:50:27 +0000132 # Give up and use cmd.exe's rd command.
133 path = os.path.normcase(path)
134 for _ in xrange(3):
135 exitcode = subprocess.call(['cmd.exe', '/c', 'rd', '/q', '/s', path])
136 if exitcode == 0:
137 return
138 else:
139 print >> sys.stderr, 'rd exited with code %d' % exitcode
140 time.sleep(3)
141 raise Exception('Failed to remove path %s' % path)
142
143 # On POSIX systems, we need the x-bit set on the directory to access it,
144 # the r-bit to see its contents, and the w-bit to remove files from it.
145 # The actual modes of the files within the directory is irrelevant.
146 os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000147
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000148 def remove(func, subpath):
borenet@google.com6b4a2ab2013-04-18 15:50:27 +0000149 func(subpath)
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000150
151 for fn in os.listdir(path):
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000152 # If fullpath is a symbolic link that points to a directory, isdir will
153 # be True, but we don't want to descend into that as a directory, we just
154 # want to remove the link. Check islink and treat links as ordinary files
155 # would be treated regardless of what they reference.
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000156 fullpath = os.path.join(path, fn)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000157 if os.path.islink(fullpath) or not os.path.isdir(fullpath):
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000158 remove(os.remove, fullpath)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000159 else:
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000160 # Recurse.
161 rmtree(fullpath)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000162
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000163 remove(os.rmdir, path)
164
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000165
maruel@chromium.org6c48a302011-10-20 23:44:20 +0000166def safe_makedirs(tree):
167 """Creates the directory in a safe manner.
168
169 Because multiple threads can create these directories concurently, trap the
170 exception and pass on.
171 """
172 count = 0
173 while not os.path.exists(tree):
174 count += 1
175 try:
176 os.makedirs(tree)
177 except OSError, e:
178 # 17 POSIX, 183 Windows
179 if e.errno not in (17, 183):
180 raise
181 if count > 40:
182 # Give up.
183 raise
184
185
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000186def CommandToStr(args):
187 """Converts an arg list into a shell escaped string."""
188 return ' '.join(pipes.quote(arg) for arg in args)
189
190
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000191def CheckCallAndFilterAndHeader(args, always=False, header=None, **kwargs):
maruel@chromium.org17d01792010-09-01 18:07:10 +0000192 """Adds 'header' support to CheckCallAndFilter.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000193
maruel@chromium.org17d01792010-09-01 18:07:10 +0000194 If |always| is True, a message indicating what is being done
195 is printed to stdout all the time even if not output is generated. Otherwise
196 the message header is printed only if the call generated any ouput.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000197 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000198 stdout = kwargs.setdefault('stdout', sys.stdout)
199 if header is None:
200 header = "\n________ running '%s' in '%s'\n" % (
ilevy@chromium.org4aad1852013-07-12 21:32:51 +0000201 ' '.join(args), kwargs.get('cwd', '.'))
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000202
maruel@chromium.org17d01792010-09-01 18:07:10 +0000203 if always:
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000204 stdout.write(header)
maruel@chromium.org17d01792010-09-01 18:07:10 +0000205 else:
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000206 filter_fn = kwargs.get('filter_fn')
maruel@chromium.org17d01792010-09-01 18:07:10 +0000207 def filter_msg(line):
208 if line is None:
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000209 stdout.write(header)
maruel@chromium.org17d01792010-09-01 18:07:10 +0000210 elif filter_fn:
211 filter_fn(line)
212 kwargs['filter_fn'] = filter_msg
213 kwargs['call_filter_on_first_line'] = True
214 # Obviously.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000215 kwargs.setdefault('print_stdout', True)
maruel@chromium.org17d01792010-09-01 18:07:10 +0000216 return CheckCallAndFilter(args, **kwargs)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000217
maruel@chromium.org17d01792010-09-01 18:07:10 +0000218
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000219class Wrapper(object):
220 """Wraps an object, acting as a transparent proxy for all properties by
221 default.
222 """
223 def __init__(self, wrapped):
224 self._wrapped = wrapped
225
226 def __getattr__(self, name):
227 return getattr(self._wrapped, name)
maruel@chromium.orgdb111f72010-09-08 13:36:53 +0000228
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +0000229
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000230class AutoFlush(Wrapper):
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +0000231 """Creates a file object clone to automatically flush after N seconds."""
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000232 def __init__(self, wrapped, delay):
233 super(AutoFlush, self).__init__(wrapped)
234 if not hasattr(self, 'lock'):
235 self.lock = threading.Lock()
236 self.__last_flushed_at = time.time()
237 self.delay = delay
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +0000238
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000239 @property
240 def autoflush(self):
241 return self
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +0000242
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000243 def write(self, out, *args, **kwargs):
244 self._wrapped.write(out, *args, **kwargs)
maruel@chromium.orgdb111f72010-09-08 13:36:53 +0000245 should_flush = False
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000246 self.lock.acquire()
maruel@chromium.org9c531262010-09-08 13:41:13 +0000247 try:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000248 if self.delay and (time.time() - self.__last_flushed_at) > self.delay:
maruel@chromium.orgdb111f72010-09-08 13:36:53 +0000249 should_flush = True
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000250 self.__last_flushed_at = time.time()
maruel@chromium.org9c531262010-09-08 13:41:13 +0000251 finally:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000252 self.lock.release()
maruel@chromium.orgdb111f72010-09-08 13:36:53 +0000253 if should_flush:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000254 self.flush()
maruel@chromium.orgdb111f72010-09-08 13:36:53 +0000255
256
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000257class Annotated(Wrapper):
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000258 """Creates a file object clone to automatically prepends every line in worker
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000259 threads with a NN> prefix.
260 """
261 def __init__(self, wrapped, include_zero=False):
262 super(Annotated, self).__init__(wrapped)
263 if not hasattr(self, 'lock'):
264 self.lock = threading.Lock()
265 self.__output_buffers = {}
266 self.__include_zero = include_zero
maruel@chromium.orgcb1e97a2010-09-09 20:09:20 +0000267
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000268 @property
269 def annotated(self):
270 return self
maruel@chromium.orgcb1e97a2010-09-09 20:09:20 +0000271
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000272 def write(self, out):
273 index = getattr(threading.currentThread(), 'index', 0)
274 if not index and not self.__include_zero:
275 # Unindexed threads aren't buffered.
276 return self._wrapped.write(out)
maruel@chromium.orgcb1e97a2010-09-09 20:09:20 +0000277
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000278 self.lock.acquire()
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000279 try:
280 # Use a dummy array to hold the string so the code can be lockless.
281 # Strings are immutable, requiring to keep a lock for the whole dictionary
282 # otherwise. Using an array is faster than using a dummy object.
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000283 if not index in self.__output_buffers:
284 obj = self.__output_buffers[index] = ['']
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000285 else:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000286 obj = self.__output_buffers[index]
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000287 finally:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000288 self.lock.release()
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000289
290 # Continue lockless.
291 obj[0] += out
292 while '\n' in obj[0]:
293 line, remaining = obj[0].split('\n', 1)
nsylvain@google.come939bb52011-06-01 22:59:15 +0000294 if line:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000295 self._wrapped.write('%d>%s\n' % (index, line))
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000296 obj[0] = remaining
297
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000298 def flush(self):
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000299 """Flush buffered output."""
300 orphans = []
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000301 self.lock.acquire()
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000302 try:
303 # Detect threads no longer existing.
304 indexes = (getattr(t, 'index', None) for t in threading.enumerate())
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +0000305 indexes = filter(None, indexes)
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000306 for index in self.__output_buffers:
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000307 if not index in indexes:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000308 orphans.append((index, self.__output_buffers[index][0]))
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000309 for orphan in orphans:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000310 del self.__output_buffers[orphan[0]]
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000311 finally:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000312 self.lock.release()
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000313
314 # Don't keep the lock while writting. Will append \n when it shouldn't.
315 for orphan in orphans:
nsylvain@google.come939bb52011-06-01 22:59:15 +0000316 if orphan[1]:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000317 self._wrapped.write('%d>%s\n' % (orphan[0], orphan[1]))
318 return self._wrapped.flush()
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000319
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000320
321def MakeFileAutoFlush(fileobj, delay=10):
322 autoflush = getattr(fileobj, 'autoflush', None)
323 if autoflush:
324 autoflush.delay = delay
325 return fileobj
326 return AutoFlush(fileobj, delay)
327
328
329def MakeFileAnnotated(fileobj, include_zero=False):
330 if getattr(fileobj, 'annotated', None):
331 return fileobj
332 return Annotated(fileobj)
maruel@chromium.orgcb1e97a2010-09-09 20:09:20 +0000333
334
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000335GCLIENT_CHILDREN = []
336GCLIENT_CHILDREN_LOCK = threading.Lock()
337
338
339class GClientChildren(object):
340 @staticmethod
341 def add(popen_obj):
342 with GCLIENT_CHILDREN_LOCK:
343 GCLIENT_CHILDREN.append(popen_obj)
344
345 @staticmethod
346 def remove(popen_obj):
347 with GCLIENT_CHILDREN_LOCK:
348 GCLIENT_CHILDREN.remove(popen_obj)
349
350 @staticmethod
351 def _attemptToKillChildren():
352 global GCLIENT_CHILDREN
353 with GCLIENT_CHILDREN_LOCK:
354 zombies = [c for c in GCLIENT_CHILDREN if c.poll() is None]
355
356 for zombie in zombies:
357 try:
358 zombie.kill()
359 except OSError:
360 pass
361
362 with GCLIENT_CHILDREN_LOCK:
363 GCLIENT_CHILDREN = [k for k in GCLIENT_CHILDREN if k.poll() is not None]
364
365 @staticmethod
366 def _areZombies():
367 with GCLIENT_CHILDREN_LOCK:
368 return bool(GCLIENT_CHILDREN)
369
370 @staticmethod
371 def KillAllRemainingChildren():
372 GClientChildren._attemptToKillChildren()
373
374 if GClientChildren._areZombies():
375 time.sleep(0.5)
376 GClientChildren._attemptToKillChildren()
377
378 with GCLIENT_CHILDREN_LOCK:
379 if GCLIENT_CHILDREN:
380 print >> sys.stderr, 'Could not kill the following subprocesses:'
381 for zombie in GCLIENT_CHILDREN:
382 print >> sys.stderr, ' ', zombie.pid
383
384
maruel@chromium.org17d01792010-09-01 18:07:10 +0000385def CheckCallAndFilter(args, stdout=None, filter_fn=None,
386 print_stdout=None, call_filter_on_first_line=False,
szager@chromium.org12b07e72013-05-03 22:06:34 +0000387 nag_timer=None, nag_max=None, **kwargs):
maruel@chromium.org17d01792010-09-01 18:07:10 +0000388 """Runs a command and calls back a filter function if needed.
389
maruel@chromium.org57bf78d2011-09-08 18:57:33 +0000390 Accepts all subprocess2.Popen() parameters plus:
maruel@chromium.org17d01792010-09-01 18:07:10 +0000391 print_stdout: If True, the command's stdout is forwarded to stdout.
392 filter_fn: A function taking a single string argument called with each line
maruel@chromium.org57bf78d2011-09-08 18:57:33 +0000393 of the subprocess2's output. Each line has the trailing newline
maruel@chromium.org17d01792010-09-01 18:07:10 +0000394 character trimmed.
395 stdout: Can be any bufferable output.
396
397 stderr is always redirected to stdout.
398 """
399 assert print_stdout or filter_fn
400 stdout = stdout or sys.stdout
401 filter_fn = filter_fn or (lambda x: None)
maruel@chromium.orga82a8ee2011-09-08 18:41:37 +0000402 kid = subprocess2.Popen(
403 args, bufsize=0, stdout=subprocess2.PIPE, stderr=subprocess2.STDOUT,
404 **kwargs)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000405
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000406 GClientChildren.add(kid)
407
maruel@chromium.org57bf78d2011-09-08 18:57:33 +0000408 # Do a flush of stdout before we begin reading from the subprocess2's stdout
maruel@chromium.org559c3f82010-08-23 19:26:08 +0000409 stdout.flush()
chase@chromium.org8ad1cee2010-08-16 19:12:27 +0000410
szager@chromium.org12b07e72013-05-03 22:06:34 +0000411 nag = None
412 if nag_timer:
413 # Hack thread.index to force correct annotation.
414 index = getattr(threading.currentThread(), 'index', 0)
415 def _nag_cb(elapsed):
416 setattr(threading.currentThread(), 'index', index)
417 stdout.write(' No output for %.0f seconds from command:\n' % elapsed)
418 stdout.write(' %s\n' % kid.cmd_str)
419 if (nag_max and
420 int('%.0f' % (elapsed / nag_timer)) >= nag_max):
421 stdout.write(' ... killing it!\n')
422 kid.kill()
423 nag = subprocess2.NagTimer(nag_timer, _nag_cb)
424 nag.start()
425
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000426 # Also, we need to forward stdout to prevent weird re-ordering of output.
427 # This has to be done on a per byte basis to make sure it is not buffered:
428 # normally buffering is done for each line, but if svn requests input, no
429 # end-of-line character is output after the prompt and it would not show up.
maruel@chromium.org109cb9d2011-09-14 20:03:11 +0000430 try:
431 in_byte = kid.stdout.read(1)
432 if in_byte:
szager@chromium.org12b07e72013-05-03 22:06:34 +0000433 if nag:
434 nag.event()
maruel@chromium.org109cb9d2011-09-14 20:03:11 +0000435 if call_filter_on_first_line:
436 filter_fn(None)
437 in_line = ''
438 while in_byte:
439 if in_byte != '\r':
440 if print_stdout:
441 stdout.write(in_byte)
442 if in_byte != '\n':
443 in_line += in_byte
444 else:
445 filter_fn(in_line)
446 in_line = ''
szager@google.com85d3e3a2011-10-07 17:12:00 +0000447 else:
448 filter_fn(in_line)
449 in_line = ''
maruel@chromium.org109cb9d2011-09-14 20:03:11 +0000450 in_byte = kid.stdout.read(1)
szager@chromium.org12b07e72013-05-03 22:06:34 +0000451 if in_byte and nag:
452 nag.event()
maruel@chromium.org109cb9d2011-09-14 20:03:11 +0000453 # Flush the rest of buffered output. This is only an issue with
454 # stdout/stderr not ending with a \n.
455 if len(in_line):
456 filter_fn(in_line)
457 rv = kid.wait()
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000458
459 # Don't put this in a 'finally,' since the child may still run if we get an
460 # exception.
461 GClientChildren.remove(kid)
462
maruel@chromium.org109cb9d2011-09-14 20:03:11 +0000463 except KeyboardInterrupt:
464 print >> sys.stderr, 'Failed while running "%s"' % ' '.join(args)
465 raise
szager@chromium.org12b07e72013-05-03 22:06:34 +0000466 finally:
467 if nag:
468 nag.cancel()
maruel@chromium.org109cb9d2011-09-14 20:03:11 +0000469
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000470 if rv:
maruel@chromium.orga82a8ee2011-09-08 18:41:37 +0000471 raise subprocess2.CalledProcessError(
472 rv, args, kwargs.get('cwd', None), None, None)
maruel@chromium.org17d01792010-09-01 18:07:10 +0000473 return 0
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000474
475
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000476def FindGclientRoot(from_dir, filename='.gclient'):
maruel@chromium.orga9371762009-12-22 18:27:38 +0000477 """Tries to find the gclient root."""
jochen@chromium.org20760a52010-09-08 08:47:28 +0000478 real_from_dir = os.path.realpath(from_dir)
479 path = real_from_dir
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000480 while not os.path.exists(os.path.join(path, filename)):
maruel@chromium.org3a292682010-08-23 18:54:55 +0000481 split_path = os.path.split(path)
482 if not split_path[1]:
maruel@chromium.orga9371762009-12-22 18:27:38 +0000483 return None
maruel@chromium.org3a292682010-08-23 18:54:55 +0000484 path = split_path[0]
jochen@chromium.org20760a52010-09-08 08:47:28 +0000485
486 # If we did not find the file in the current directory, make sure we are in a
487 # sub directory that is controlled by this configuration.
488 if path != real_from_dir:
489 entries_filename = os.path.join(path, filename + '_entries')
490 if not os.path.exists(entries_filename):
491 # If .gclient_entries does not exist, a previous call to gclient sync
492 # might have failed. In that case, we cannot verify that the .gclient
493 # is the one we want to use. In order to not to cause too much trouble,
494 # just issue a warning and return the path anyway.
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +0000495 print >> sys.stderr, ("%s file in parent directory %s might not be the "
jochen@chromium.org20760a52010-09-08 08:47:28 +0000496 "file you want to use" % (filename, path))
497 return path
498 scope = {}
499 try:
500 exec(FileRead(entries_filename), scope)
501 except SyntaxError, e:
502 SyntaxErrorToError(filename, e)
503 all_directories = scope['entries'].keys()
504 path_to_check = real_from_dir[len(path)+1:]
505 while path_to_check:
506 if path_to_check in all_directories:
507 return path
508 path_to_check = os.path.dirname(path_to_check)
509 return None
maruel@chromium.org3742c842010-09-09 19:27:14 +0000510
maruel@chromium.orgd9141bf2009-12-23 16:13:32 +0000511 logging.info('Found gclient root at ' + path)
maruel@chromium.orga9371762009-12-22 18:27:38 +0000512 return path
maruel@chromium.org3ccbf7e2009-12-22 20:46:42 +0000513
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000514
maruel@chromium.org3ccbf7e2009-12-22 20:46:42 +0000515def PathDifference(root, subpath):
516 """Returns the difference subpath minus root."""
517 root = os.path.realpath(root)
518 subpath = os.path.realpath(subpath)
519 if not subpath.startswith(root):
520 return None
521 # If the root does not have a trailing \ or /, we add it so the returned
522 # path starts immediately after the seperator regardless of whether it is
523 # provided.
524 root = os.path.join(root, '')
525 return subpath[len(root):]
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000526
527
528def FindFileUpwards(filename, path=None):
rcui@google.com13595ff2011-10-13 01:25:07 +0000529 """Search upwards from the a directory (default: current) to find a file.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000530
rcui@google.com13595ff2011-10-13 01:25:07 +0000531 Returns nearest upper-level directory with the passed in file.
532 """
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000533 if not path:
534 path = os.getcwd()
535 path = os.path.realpath(path)
536 while True:
537 file_path = os.path.join(path, filename)
rcui@google.com13595ff2011-10-13 01:25:07 +0000538 if os.path.exists(file_path):
539 return path
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000540 (new_path, _) = os.path.split(path)
541 if new_path == path:
542 return None
543 path = new_path
544
545
546def GetGClientRootAndEntries(path=None):
547 """Returns the gclient root and the dict of entries."""
548 config_file = '.gclient_entries'
maruel@chromium.org93a9ee02011-10-18 18:23:58 +0000549 root = FindFileUpwards(config_file, path)
550 if not root:
maruel@chromium.org116704f2010-06-11 17:34:38 +0000551 print "Can't find %s" % config_file
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000552 return None
maruel@chromium.org93a9ee02011-10-18 18:23:58 +0000553 config_path = os.path.join(root, config_file)
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000554 env = {}
555 execfile(config_path, env)
556 config_dir = os.path.dirname(config_path)
557 return config_dir, env['entries']
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000558
559
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000560def lockedmethod(method):
561 """Method decorator that holds self.lock for the duration of the call."""
562 def inner(self, *args, **kwargs):
563 try:
564 try:
565 self.lock.acquire()
566 except KeyboardInterrupt:
567 print >> sys.stderr, 'Was deadlocked'
568 raise
569 return method(self, *args, **kwargs)
570 finally:
571 self.lock.release()
572 return inner
573
574
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000575class WorkItem(object):
576 """One work item."""
maruel@chromium.org4901daf2011-10-20 14:34:47 +0000577 # On cygwin, creating a lock throwing randomly when nearing ~100 locks.
578 # As a workaround, use a single lock. Yep you read it right. Single lock for
579 # all the 100 objects.
580 lock = threading.Lock()
581
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000582 def __init__(self, name):
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000583 # A unique string representing this work item.
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000584 self._name = name
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000585
maruel@chromium.org77e4eca2010-09-21 13:23:07 +0000586 def run(self, work_queue):
587 """work_queue is passed as keyword argument so it should be
maruel@chromium.org3742c842010-09-09 19:27:14 +0000588 the last parameters of the function when you override it."""
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000589 pass
590
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000591 @property
592 def name(self):
593 return self._name
594
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000595
596class ExecutionQueue(object):
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000597 """Runs a set of WorkItem that have interdependencies and were WorkItem are
598 added as they are processed.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000599
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000600 In gclient's case, Dependencies sometime needs to be run out of order due to
601 From() keyword. This class manages that all the required dependencies are run
602 before running each one.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000603
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000604 Methods of this class are thread safe.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000605 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000606 def __init__(self, jobs, progress, ignore_requirements):
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000607 """jobs specifies the number of concurrent tasks to allow. progress is a
608 Progress instance."""
609 # Set when a thread is done or a new item is enqueued.
610 self.ready_cond = threading.Condition()
611 # Maximum number of concurrent tasks.
612 self.jobs = jobs
613 # List of WorkItem, for gclient, these are Dependency instances.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000614 self.queued = []
615 # List of strings representing each Dependency.name that was run.
616 self.ran = []
617 # List of items currently running.
618 self.running = []
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000619 # Exceptions thrown if any.
maruel@chromium.org3742c842010-09-09 19:27:14 +0000620 self.exceptions = Queue.Queue()
621 # Progress status
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000622 self.progress = progress
623 if self.progress:
maruel@chromium.org3742c842010-09-09 19:27:14 +0000624 self.progress.update(0)
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000625
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000626 self.ignore_requirements = ignore_requirements
627
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000628 def enqueue(self, d):
629 """Enqueue one Dependency to be executed later once its requirements are
630 satisfied.
631 """
632 assert isinstance(d, WorkItem)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000633 self.ready_cond.acquire()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000634 try:
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000635 self.queued.append(d)
636 total = len(self.queued) + len(self.ran) + len(self.running)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000637 logging.debug('enqueued(%s)' % d.name)
638 if self.progress:
639 self.progress._total = total + 1
640 self.progress.update(0)
641 self.ready_cond.notifyAll()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000642 finally:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000643 self.ready_cond.release()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000644
645 def flush(self, *args, **kwargs):
646 """Runs all enqueued items until all are executed."""
maruel@chromium.org3742c842010-09-09 19:27:14 +0000647 kwargs['work_queue'] = self
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000648 self.ready_cond.acquire()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000649 try:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000650 while True:
651 # Check for task to run first, then wait.
652 while True:
maruel@chromium.org3742c842010-09-09 19:27:14 +0000653 if not self.exceptions.empty():
654 # Systematically flush the queue when an exception logged.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000655 self.queued = []
maruel@chromium.org3742c842010-09-09 19:27:14 +0000656 self._flush_terminated_threads()
657 if (not self.queued and not self.running or
658 self.jobs == len(self.running)):
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000659 logging.debug('No more worker threads or can\'t queue anything.')
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000660 break
maruel@chromium.org3742c842010-09-09 19:27:14 +0000661
662 # Check for new tasks to start.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000663 for i in xrange(len(self.queued)):
664 # Verify its requirements.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000665 if (self.ignore_requirements or
666 not (set(self.queued[i].requirements) - set(self.ran))):
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000667 # Start one work item: all its requirements are satisfied.
maruel@chromium.org3742c842010-09-09 19:27:14 +0000668 self._run_one_task(self.queued.pop(i), args, kwargs)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000669 break
670 else:
671 # Couldn't find an item that could run. Break out the outher loop.
672 break
maruel@chromium.org3742c842010-09-09 19:27:14 +0000673
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000674 if not self.queued and not self.running:
maruel@chromium.org3742c842010-09-09 19:27:14 +0000675 # We're done.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000676 break
677 # We need to poll here otherwise Ctrl-C isn't processed.
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000678 try:
679 self.ready_cond.wait(10)
680 except KeyboardInterrupt:
681 # Help debugging by printing some information:
682 print >> sys.stderr, (
683 ('\nAllowed parallel jobs: %d\n# queued: %d\nRan: %s\n'
684 'Running: %d') % (
685 self.jobs,
686 len(self.queued),
687 ', '.join(self.ran),
688 len(self.running)))
689 for i in self.queued:
690 print >> sys.stderr, '%s: %s' % (i.name, ', '.join(i.requirements))
691 raise
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000692 # Something happened: self.enqueue() or a thread terminated. Loop again.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000693 finally:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000694 self.ready_cond.release()
maruel@chromium.org3742c842010-09-09 19:27:14 +0000695
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000696 assert not self.running, 'Now guaranteed to be single-threaded'
maruel@chromium.org3742c842010-09-09 19:27:14 +0000697 if not self.exceptions.empty():
maruel@chromium.orgc8d064b2010-08-16 16:46:14 +0000698 # To get back the stack location correctly, the raise a, b, c form must be
699 # used, passing a tuple as the first argument doesn't work.
maruel@chromium.org3742c842010-09-09 19:27:14 +0000700 e = self.exceptions.get()
maruel@chromium.orgc8d064b2010-08-16 16:46:14 +0000701 raise e[0], e[1], e[2]
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000702 if self.progress:
703 self.progress.end()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000704
maruel@chromium.org3742c842010-09-09 19:27:14 +0000705 def _flush_terminated_threads(self):
706 """Flush threads that have terminated."""
707 running = self.running
708 self.running = []
709 for t in running:
710 if t.isAlive():
711 self.running.append(t)
712 else:
713 t.join()
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000714 sys.stdout.flush()
maruel@chromium.org3742c842010-09-09 19:27:14 +0000715 if self.progress:
maruel@chromium.org55a2eb82010-10-06 23:35:18 +0000716 self.progress.update(1, t.item.name)
maruel@chromium.orgf36c0ee2011-09-14 19:16:47 +0000717 if t.item.name in self.ran:
718 raise Error(
719 'gclient is confused, "%s" is already in "%s"' % (
720 t.item.name, ', '.join(self.ran)))
maruel@chromium.orgacc45672010-09-09 21:21:21 +0000721 if not t.item.name in self.ran:
722 self.ran.append(t.item.name)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000723
724 def _run_one_task(self, task_item, args, kwargs):
725 if self.jobs > 1:
726 # Start the thread.
727 index = len(self.ran) + len(self.running) + 1
maruel@chromium.org77e4eca2010-09-21 13:23:07 +0000728 new_thread = self._Worker(task_item, index, args, kwargs)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000729 self.running.append(new_thread)
730 new_thread.start()
731 else:
732 # Run the 'thread' inside the main thread. Don't try to catch any
733 # exception.
734 task_item.run(*args, **kwargs)
735 self.ran.append(task_item.name)
736 if self.progress:
maruel@chromium.org55a2eb82010-10-06 23:35:18 +0000737 self.progress.update(1, ', '.join(t.item.name for t in self.running))
maruel@chromium.org3742c842010-09-09 19:27:14 +0000738
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000739 class _Worker(threading.Thread):
740 """One thread to execute one WorkItem."""
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000741 def __init__(self, item, index, args, kwargs):
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000742 threading.Thread.__init__(self, name=item.name or 'Worker')
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000743 logging.info('_Worker(%s) reqs:%s' % (item.name, item.requirements))
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000744 self.item = item
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000745 self.index = index
maruel@chromium.org3742c842010-09-09 19:27:14 +0000746 self.args = args
747 self.kwargs = kwargs
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000748 self.daemon = True
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000749
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000750 def run(self):
751 """Runs in its own thread."""
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000752 logging.debug('_Worker.run(%s)' % self.item.name)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000753 work_queue = self.kwargs['work_queue']
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000754 try:
755 self.item.run(*self.args, **self.kwargs)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000756 except KeyboardInterrupt:
xusydoc@chromium.orgc144e062013-05-03 23:23:53 +0000757 logging.info('Caught KeyboardInterrupt in thread %s', self.item.name)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000758 logging.info(str(sys.exc_info()))
759 work_queue.exceptions.put(sys.exc_info())
760 raise
maruel@chromium.orgc8d064b2010-08-16 16:46:14 +0000761 except Exception:
762 # Catch exception location.
xusydoc@chromium.orgc144e062013-05-03 23:23:53 +0000763 logging.info('Caught exception in thread %s', self.item.name)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000764 logging.info(str(sys.exc_info()))
765 work_queue.exceptions.put(sys.exc_info())
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000766 finally:
xusydoc@chromium.orgc144e062013-05-03 23:23:53 +0000767 logging.info('_Worker.run(%s) done', self.item.name)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000768 work_queue.ready_cond.acquire()
769 try:
770 work_queue.ready_cond.notifyAll()
771 finally:
772 work_queue.ready_cond.release()
maruel@chromium.org0e0436a2011-10-25 13:32:41 +0000773
774
jbroman@chromium.org615a2622013-05-03 13:20:14 +0000775def GetEditor(git, git_editor=None):
776 """Returns the most plausible editor to use.
777
778 In order of preference:
779 - GIT_EDITOR/SVN_EDITOR environment variable
780 - core.editor git configuration variable (if supplied by git-cl)
781 - VISUAL environment variable
782 - EDITOR environment variable
783 - vim (non-Windows) or notepad (Windows)
784
785 In the case of git-cl, this matches git's behaviour, except that it does not
786 include dumb terminal detection.
787
788 In the case of gcl, this matches svn's behaviour, except that it does not
789 accept a command-line flag or check the editor-cmd configuration variable.
790 """
maruel@chromium.org0e0436a2011-10-25 13:32:41 +0000791 if git:
jbroman@chromium.org615a2622013-05-03 13:20:14 +0000792 editor = os.environ.get('GIT_EDITOR') or git_editor
maruel@chromium.org0e0436a2011-10-25 13:32:41 +0000793 else:
794 editor = os.environ.get('SVN_EDITOR')
795 if not editor:
jbroman@chromium.org615a2622013-05-03 13:20:14 +0000796 editor = os.environ.get('VISUAL')
797 if not editor:
maruel@chromium.org0e0436a2011-10-25 13:32:41 +0000798 editor = os.environ.get('EDITOR')
799 if not editor:
800 if sys.platform.startswith('win'):
801 editor = 'notepad'
802 else:
803 editor = 'vim'
804 return editor
805
806
jbroman@chromium.org615a2622013-05-03 13:20:14 +0000807def RunEditor(content, git, git_editor=None):
maruel@chromium.org0e0436a2011-10-25 13:32:41 +0000808 """Opens up the default editor in the system to get the CL description."""
maruel@chromium.orgcbd760f2013-07-23 13:02:48 +0000809 file_handle, filename = tempfile.mkstemp(text=True, prefix='cl_description')
maruel@chromium.org0e0436a2011-10-25 13:32:41 +0000810 # Make sure CRLF is handled properly by requiring none.
811 if '\r' in content:
asvitkine@chromium.org0eff22d2011-10-25 16:11:16 +0000812 print >> sys.stderr, (
813 '!! Please remove \\r from your change description !!')
maruel@chromium.org0e0436a2011-10-25 13:32:41 +0000814 fileobj = os.fdopen(file_handle, 'w')
815 # Still remove \r if present.
816 fileobj.write(re.sub('\r?\n', '\n', content))
817 fileobj.close()
818
819 try:
jbroman@chromium.org615a2622013-05-03 13:20:14 +0000820 editor = GetEditor(git, git_editor=git_editor)
821 if not editor:
822 return None
823 cmd = '%s %s' % (editor, filename)
maruel@chromium.org0e0436a2011-10-25 13:32:41 +0000824 if sys.platform == 'win32' and os.environ.get('TERM') == 'msys':
825 # Msysgit requires the usage of 'env' to be present.
826 cmd = 'env ' + cmd
827 try:
828 # shell=True to allow the shell to handle all forms of quotes in
829 # $EDITOR.
830 subprocess2.check_call(cmd, shell=True)
831 except subprocess2.CalledProcessError:
832 return None
833 return FileRead(filename)
834 finally:
835 os.remove(filename)
maruel@chromium.org99ac1c52012-01-16 14:52:12 +0000836
837
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +0000838def UpgradeToHttps(url):
839 """Upgrades random urls to https://.
840
841 Do not touch unknown urls like ssh:// or git://.
842 Do not touch http:// urls with a port number,
843 Fixes invalid GAE url.
844 """
845 if not url:
846 return url
847 if not re.match(r'[a-z\-]+\://.*', url):
848 # Make sure it is a valid uri. Otherwise, urlparse() will consider it a
849 # relative url and will use http:///foo. Note that it defaults to http://
850 # for compatibility with naked url like "localhost:8080".
851 url = 'http://%s' % url
852 parsed = list(urlparse.urlparse(url))
853 # Do not automatically upgrade http to https if a port number is provided.
854 if parsed[0] == 'http' and not re.match(r'^.+?\:\d+$', parsed[1]):
855 parsed[0] = 'https'
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +0000856 return urlparse.urlunparse(parsed)
857
858
maruel@chromium.org99ac1c52012-01-16 14:52:12 +0000859def ParseCodereviewSettingsContent(content):
860 """Process a codereview.settings file properly."""
861 lines = (l for l in content.splitlines() if not l.strip().startswith("#"))
862 try:
863 keyvals = dict([x.strip() for x in l.split(':', 1)] for l in lines if l)
864 except ValueError:
865 raise Error(
866 'Failed to process settings, please fix. Content:\n\n%s' % content)
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +0000867 def fix_url(key):
868 if keyvals.get(key):
869 keyvals[key] = UpgradeToHttps(keyvals[key])
870 fix_url('CODE_REVIEW_SERVER')
871 fix_url('VIEW_VC')
maruel@chromium.org99ac1c52012-01-16 14:52:12 +0000872 return keyvals
ilevy@chromium.org13691502012-10-16 04:26:37 +0000873
874
875def NumLocalCpus():
876 """Returns the number of processors.
877
878 Python on OSX 10.6 raises a NotImplementedError exception.
879 """
880 try:
881 import multiprocessing
882 return multiprocessing.cpu_count()
883 except: # pylint: disable=W0702
884 # Mac OS 10.6 only
885 # pylint: disable=E1101
886 return int(os.sysconf('SC_NPROCESSORS_ONLN'))