blob: d86f23c1d140466ec7acd435907811f23ba4b9b4 [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
maruel@chromium.org3742c842010-09-09 19:27:14 +000010import Queue
msb@chromium.orgac915bb2009-11-13 17:03:01 +000011import re
bradnelson@google.com8f9c69f2009-09-17 00:48:28 +000012import stat
borenet@google.com6b4a2ab2013-04-18 15:50:27 +000013import subprocess
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000014import sys
maruel@chromium.org0e0436a2011-10-25 13:32:41 +000015import tempfile
maruel@chromium.org9e5317a2010-08-13 20:35:11 +000016import threading
maruel@chromium.org167b9e62009-09-17 17:41:02 +000017import time
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +000018import urlparse
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000019
maruel@chromium.orgca0f8392011-09-08 17:15:15 +000020import subprocess2
21
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000022
maruel@chromium.org66c83e62010-09-07 14:18:45 +000023class Error(Exception):
24 """gclient exception class."""
szager@chromium.org4a3c17e2013-05-24 23:59:29 +000025 def __init__(self, msg, *args, **kwargs):
26 index = getattr(threading.currentThread(), 'index', 0)
27 if index:
28 msg = '\n'.join('%d> %s' % (index, l) for l in msg.splitlines())
29 super(Error, self).__init__(msg, *args, **kwargs)
maruel@chromium.org66c83e62010-09-07 14:18:45 +000030
msb@chromium.orgac915bb2009-11-13 17:03:01 +000031def SplitUrlRevision(url):
32 """Splits url and returns a two-tuple: url, rev"""
33 if url.startswith('ssh:'):
maruel@chromium.org78b8cd12010-10-26 12:47:07 +000034 # Make sure ssh://user-name@example.com/~/test.git@stable works
35 regex = r'(ssh://(?:[-\w]+@)?[-\w:\.]+/[-~\w\./]+)(?:@(.+))?'
msb@chromium.orgac915bb2009-11-13 17:03:01 +000036 components = re.search(regex, url).groups()
37 else:
maruel@chromium.org116704f2010-06-11 17:34:38 +000038 components = url.split('@', 1)
msb@chromium.orgac915bb2009-11-13 17:03:01 +000039 if len(components) == 1:
40 components += [None]
41 return tuple(components)
42
43
floitsch@google.comeaab7842011-04-28 09:07:58 +000044def IsDateRevision(revision):
45 """Returns true if the given revision is of the form "{ ... }"."""
46 return bool(revision and re.match(r'^\{.+\}$', str(revision)))
47
48
49def MakeDateRevision(date):
50 """Returns a revision representing the latest revision before the given
51 date."""
52 return "{" + date + "}"
53
54
maruel@chromium.org5990f9d2010-07-07 18:02:58 +000055def SyntaxErrorToError(filename, e):
56 """Raises a gclient_utils.Error exception with the human readable message"""
57 try:
58 # Try to construct a human readable error message
59 if filename:
60 error_message = 'There is a syntax error in %s\n' % filename
61 else:
62 error_message = 'There is a syntax error\n'
63 error_message += 'Line #%s, character %s: "%s"' % (
64 e.lineno, e.offset, re.sub(r'[\r\n]*$', '', e.text))
65 except:
66 # Something went wrong, re-raise the original exception
67 raise e
68 else:
69 raise Error(error_message)
70
71
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000072class PrintableObject(object):
73 def __str__(self):
74 output = ''
75 for i in dir(self):
76 if i.startswith('__'):
77 continue
78 output += '%s = %s\n' % (i, str(getattr(self, i, '')))
79 return output
80
81
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +000082def FileRead(filename, mode='rU'):
maruel@chromium.org51e84fb2012-07-03 23:06:21 +000083 with open(filename, mode=mode) as f:
maruel@chromium.orgc3cd5372012-07-11 17:39:24 +000084 # codecs.open() has different behavior than open() on python 2.6 so use
85 # open() and decode manually.
chrisha@chromium.org2b99d432012-07-12 18:10:28 +000086 s = f.read()
87 try:
88 return s.decode('utf-8')
89 except UnicodeDecodeError:
90 return s
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000091
92
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +000093def FileWrite(filename, content, mode='w'):
maruel@chromium.orgdae209f2012-07-03 16:08:15 +000094 with codecs.open(filename, mode=mode, encoding='utf-8') as f:
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000095 f.write(content)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000096
97
maruel@chromium.orgf9040722011-03-09 14:47:51 +000098def rmtree(path):
99 """shutil.rmtree() on steroids.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000100
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000101 Recursively removes a directory, even if it's marked read-only.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000102
103 shutil.rmtree() doesn't work on Windows if any of the files or directories
104 are read-only, which svn repositories and some .svn files are. We need to
105 be able to force the files to be writable (i.e., deletable) as we traverse
106 the tree.
107
108 Even with all this, Windows still sometimes fails to delete a file, citing
109 a permission error (maybe something to do with antivirus scans or disk
110 indexing). The best suggestion any of the user forums had was to wait a
111 bit and try again, so we do that too. It's hand-waving, but sometimes it
112 works. :/
113
114 On POSIX systems, things are a little bit simpler. The modes of the files
115 to be deleted doesn't matter, only the modes of the directories containing
116 them are significant. As the directory tree is traversed, each directory
117 has its mode set appropriately before descending into it. This should
118 result in the entire tree being removed, with the possible exception of
119 *path itself, because nothing attempts to change the mode of its parent.
120 Doing so would be hazardous, as it's not a directory slated for removal.
121 In the ordinary case, this is not a problem: for our purposes, the user
122 will never lack write permission on *path's parent.
123 """
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000124 if not os.path.exists(path):
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000125 return
126
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000127 if os.path.islink(path) or not os.path.isdir(path):
128 raise Error('Called rmtree(%s) in non-directory' % path)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000129
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000130 if sys.platform == 'win32':
borenet@google.com6b4a2ab2013-04-18 15:50:27 +0000131 # Give up and use cmd.exe's rd command.
132 path = os.path.normcase(path)
133 for _ in xrange(3):
134 exitcode = subprocess.call(['cmd.exe', '/c', 'rd', '/q', '/s', path])
135 if exitcode == 0:
136 return
137 else:
138 print >> sys.stderr, 'rd exited with code %d' % exitcode
139 time.sleep(3)
140 raise Exception('Failed to remove path %s' % path)
141
142 # On POSIX systems, we need the x-bit set on the directory to access it,
143 # the r-bit to see its contents, and the w-bit to remove files from it.
144 # The actual modes of the files within the directory is irrelevant.
145 os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000146
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000147 def remove(func, subpath):
borenet@google.com6b4a2ab2013-04-18 15:50:27 +0000148 func(subpath)
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000149
150 for fn in os.listdir(path):
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000151 # If fullpath is a symbolic link that points to a directory, isdir will
152 # be True, but we don't want to descend into that as a directory, we just
153 # want to remove the link. Check islink and treat links as ordinary files
154 # would be treated regardless of what they reference.
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000155 fullpath = os.path.join(path, fn)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000156 if os.path.islink(fullpath) or not os.path.isdir(fullpath):
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000157 remove(os.remove, fullpath)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000158 else:
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000159 # Recurse.
160 rmtree(fullpath)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000161
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000162 remove(os.rmdir, path)
163
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000164
maruel@chromium.org6c48a302011-10-20 23:44:20 +0000165def safe_makedirs(tree):
166 """Creates the directory in a safe manner.
167
168 Because multiple threads can create these directories concurently, trap the
169 exception and pass on.
170 """
171 count = 0
172 while not os.path.exists(tree):
173 count += 1
174 try:
175 os.makedirs(tree)
176 except OSError, e:
177 # 17 POSIX, 183 Windows
178 if e.errno not in (17, 183):
179 raise
180 if count > 40:
181 # Give up.
182 raise
183
184
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000185def CheckCallAndFilterAndHeader(args, always=False, header=None, **kwargs):
maruel@chromium.org17d01792010-09-01 18:07:10 +0000186 """Adds 'header' support to CheckCallAndFilter.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000187
maruel@chromium.org17d01792010-09-01 18:07:10 +0000188 If |always| is True, a message indicating what is being done
189 is printed to stdout all the time even if not output is generated. Otherwise
190 the message header is printed only if the call generated any ouput.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000191 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000192 stdout = kwargs.setdefault('stdout', sys.stdout)
193 if header is None:
194 header = "\n________ running '%s' in '%s'\n" % (
195 ' '.join(args), kwargs.get('cwd', '.'))
196
maruel@chromium.org17d01792010-09-01 18:07:10 +0000197 if always:
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000198 stdout.write(header)
maruel@chromium.org17d01792010-09-01 18:07:10 +0000199 else:
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000200 filter_fn = kwargs.get('filter_fn')
maruel@chromium.org17d01792010-09-01 18:07:10 +0000201 def filter_msg(line):
202 if line is None:
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000203 stdout.write(header)
maruel@chromium.org17d01792010-09-01 18:07:10 +0000204 elif filter_fn:
205 filter_fn(line)
206 kwargs['filter_fn'] = filter_msg
207 kwargs['call_filter_on_first_line'] = True
208 # Obviously.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000209 kwargs.setdefault('print_stdout', True)
maruel@chromium.org17d01792010-09-01 18:07:10 +0000210 return CheckCallAndFilter(args, **kwargs)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000211
maruel@chromium.org17d01792010-09-01 18:07:10 +0000212
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000213class Wrapper(object):
214 """Wraps an object, acting as a transparent proxy for all properties by
215 default.
216 """
217 def __init__(self, wrapped):
218 self._wrapped = wrapped
219
220 def __getattr__(self, name):
221 return getattr(self._wrapped, name)
maruel@chromium.orgdb111f72010-09-08 13:36:53 +0000222
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +0000223
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000224class AutoFlush(Wrapper):
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +0000225 """Creates a file object clone to automatically flush after N seconds."""
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000226 def __init__(self, wrapped, delay):
227 super(AutoFlush, self).__init__(wrapped)
228 if not hasattr(self, 'lock'):
229 self.lock = threading.Lock()
230 self.__last_flushed_at = time.time()
231 self.delay = delay
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +0000232
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000233 @property
234 def autoflush(self):
235 return self
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +0000236
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000237 def write(self, out, *args, **kwargs):
238 self._wrapped.write(out, *args, **kwargs)
maruel@chromium.orgdb111f72010-09-08 13:36:53 +0000239 should_flush = False
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000240 self.lock.acquire()
maruel@chromium.org9c531262010-09-08 13:41:13 +0000241 try:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000242 if self.delay and (time.time() - self.__last_flushed_at) > self.delay:
maruel@chromium.orgdb111f72010-09-08 13:36:53 +0000243 should_flush = True
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000244 self.__last_flushed_at = time.time()
maruel@chromium.org9c531262010-09-08 13:41:13 +0000245 finally:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000246 self.lock.release()
maruel@chromium.orgdb111f72010-09-08 13:36:53 +0000247 if should_flush:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000248 self.flush()
maruel@chromium.orgdb111f72010-09-08 13:36:53 +0000249
250
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000251class Annotated(Wrapper):
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000252 """Creates a file object clone to automatically prepends every line in worker
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000253 threads with a NN> prefix.
254 """
255 def __init__(self, wrapped, include_zero=False):
256 super(Annotated, self).__init__(wrapped)
257 if not hasattr(self, 'lock'):
258 self.lock = threading.Lock()
259 self.__output_buffers = {}
260 self.__include_zero = include_zero
maruel@chromium.orgcb1e97a2010-09-09 20:09:20 +0000261
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000262 @property
263 def annotated(self):
264 return self
maruel@chromium.orgcb1e97a2010-09-09 20:09:20 +0000265
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000266 def write(self, out):
267 index = getattr(threading.currentThread(), 'index', 0)
268 if not index and not self.__include_zero:
269 # Unindexed threads aren't buffered.
270 return self._wrapped.write(out)
maruel@chromium.orgcb1e97a2010-09-09 20:09:20 +0000271
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000272 self.lock.acquire()
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000273 try:
274 # Use a dummy array to hold the string so the code can be lockless.
275 # Strings are immutable, requiring to keep a lock for the whole dictionary
276 # otherwise. Using an array is faster than using a dummy object.
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000277 if not index in self.__output_buffers:
278 obj = self.__output_buffers[index] = ['']
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000279 else:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000280 obj = self.__output_buffers[index]
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000281 finally:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000282 self.lock.release()
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000283
284 # Continue lockless.
285 obj[0] += out
286 while '\n' in obj[0]:
287 line, remaining = obj[0].split('\n', 1)
nsylvain@google.come939bb52011-06-01 22:59:15 +0000288 if line:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000289 self._wrapped.write('%d>%s\n' % (index, line))
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000290 obj[0] = remaining
291
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000292 def flush(self):
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000293 """Flush buffered output."""
294 orphans = []
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000295 self.lock.acquire()
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000296 try:
297 # Detect threads no longer existing.
298 indexes = (getattr(t, 'index', None) for t in threading.enumerate())
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +0000299 indexes = filter(None, indexes)
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000300 for index in self.__output_buffers:
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000301 if not index in indexes:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000302 orphans.append((index, self.__output_buffers[index][0]))
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000303 for orphan in orphans:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000304 del self.__output_buffers[orphan[0]]
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000305 finally:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000306 self.lock.release()
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000307
308 # Don't keep the lock while writting. Will append \n when it shouldn't.
309 for orphan in orphans:
nsylvain@google.come939bb52011-06-01 22:59:15 +0000310 if orphan[1]:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000311 self._wrapped.write('%d>%s\n' % (orphan[0], orphan[1]))
312 return self._wrapped.flush()
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000313
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000314
315def MakeFileAutoFlush(fileobj, delay=10):
316 autoflush = getattr(fileobj, 'autoflush', None)
317 if autoflush:
318 autoflush.delay = delay
319 return fileobj
320 return AutoFlush(fileobj, delay)
321
322
323def MakeFileAnnotated(fileobj, include_zero=False):
324 if getattr(fileobj, 'annotated', None):
325 return fileobj
326 return Annotated(fileobj)
maruel@chromium.orgcb1e97a2010-09-09 20:09:20 +0000327
328
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000329GCLIENT_CHILDREN = []
330GCLIENT_CHILDREN_LOCK = threading.Lock()
331
332
333class GClientChildren(object):
334 @staticmethod
335 def add(popen_obj):
336 with GCLIENT_CHILDREN_LOCK:
337 GCLIENT_CHILDREN.append(popen_obj)
338
339 @staticmethod
340 def remove(popen_obj):
341 with GCLIENT_CHILDREN_LOCK:
342 GCLIENT_CHILDREN.remove(popen_obj)
343
344 @staticmethod
345 def _attemptToKillChildren():
346 global GCLIENT_CHILDREN
347 with GCLIENT_CHILDREN_LOCK:
348 zombies = [c for c in GCLIENT_CHILDREN if c.poll() is None]
349
350 for zombie in zombies:
351 try:
352 zombie.kill()
353 except OSError:
354 pass
355
356 with GCLIENT_CHILDREN_LOCK:
357 GCLIENT_CHILDREN = [k for k in GCLIENT_CHILDREN if k.poll() is not None]
358
359 @staticmethod
360 def _areZombies():
361 with GCLIENT_CHILDREN_LOCK:
362 return bool(GCLIENT_CHILDREN)
363
364 @staticmethod
365 def KillAllRemainingChildren():
366 GClientChildren._attemptToKillChildren()
367
368 if GClientChildren._areZombies():
369 time.sleep(0.5)
370 GClientChildren._attemptToKillChildren()
371
372 with GCLIENT_CHILDREN_LOCK:
373 if GCLIENT_CHILDREN:
374 print >> sys.stderr, 'Could not kill the following subprocesses:'
375 for zombie in GCLIENT_CHILDREN:
376 print >> sys.stderr, ' ', zombie.pid
377
378
maruel@chromium.org17d01792010-09-01 18:07:10 +0000379def CheckCallAndFilter(args, stdout=None, filter_fn=None,
380 print_stdout=None, call_filter_on_first_line=False,
szager@chromium.org12b07e72013-05-03 22:06:34 +0000381 nag_timer=None, nag_max=None, **kwargs):
maruel@chromium.org17d01792010-09-01 18:07:10 +0000382 """Runs a command and calls back a filter function if needed.
383
maruel@chromium.org57bf78d2011-09-08 18:57:33 +0000384 Accepts all subprocess2.Popen() parameters plus:
maruel@chromium.org17d01792010-09-01 18:07:10 +0000385 print_stdout: If True, the command's stdout is forwarded to stdout.
386 filter_fn: A function taking a single string argument called with each line
maruel@chromium.org57bf78d2011-09-08 18:57:33 +0000387 of the subprocess2's output. Each line has the trailing newline
maruel@chromium.org17d01792010-09-01 18:07:10 +0000388 character trimmed.
389 stdout: Can be any bufferable output.
390
391 stderr is always redirected to stdout.
392 """
393 assert print_stdout or filter_fn
394 stdout = stdout or sys.stdout
395 filter_fn = filter_fn or (lambda x: None)
maruel@chromium.orga82a8ee2011-09-08 18:41:37 +0000396 kid = subprocess2.Popen(
397 args, bufsize=0, stdout=subprocess2.PIPE, stderr=subprocess2.STDOUT,
398 **kwargs)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000399
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000400 GClientChildren.add(kid)
401
maruel@chromium.org57bf78d2011-09-08 18:57:33 +0000402 # Do a flush of stdout before we begin reading from the subprocess2's stdout
maruel@chromium.org559c3f82010-08-23 19:26:08 +0000403 stdout.flush()
chase@chromium.org8ad1cee2010-08-16 19:12:27 +0000404
szager@chromium.org12b07e72013-05-03 22:06:34 +0000405 nag = None
406 if nag_timer:
407 # Hack thread.index to force correct annotation.
408 index = getattr(threading.currentThread(), 'index', 0)
409 def _nag_cb(elapsed):
410 setattr(threading.currentThread(), 'index', index)
411 stdout.write(' No output for %.0f seconds from command:\n' % elapsed)
412 stdout.write(' %s\n' % kid.cmd_str)
413 if (nag_max and
414 int('%.0f' % (elapsed / nag_timer)) >= nag_max):
415 stdout.write(' ... killing it!\n')
416 kid.kill()
417 nag = subprocess2.NagTimer(nag_timer, _nag_cb)
418 nag.start()
419
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000420 # Also, we need to forward stdout to prevent weird re-ordering of output.
421 # This has to be done on a per byte basis to make sure it is not buffered:
422 # normally buffering is done for each line, but if svn requests input, no
423 # end-of-line character is output after the prompt and it would not show up.
maruel@chromium.org109cb9d2011-09-14 20:03:11 +0000424 try:
425 in_byte = kid.stdout.read(1)
426 if in_byte:
szager@chromium.org12b07e72013-05-03 22:06:34 +0000427 if nag:
428 nag.event()
maruel@chromium.org109cb9d2011-09-14 20:03:11 +0000429 if call_filter_on_first_line:
430 filter_fn(None)
431 in_line = ''
432 while in_byte:
433 if in_byte != '\r':
434 if print_stdout:
435 stdout.write(in_byte)
436 if in_byte != '\n':
437 in_line += in_byte
438 else:
439 filter_fn(in_line)
440 in_line = ''
szager@google.com85d3e3a2011-10-07 17:12:00 +0000441 else:
442 filter_fn(in_line)
443 in_line = ''
maruel@chromium.org109cb9d2011-09-14 20:03:11 +0000444 in_byte = kid.stdout.read(1)
szager@chromium.org12b07e72013-05-03 22:06:34 +0000445 if in_byte and nag:
446 nag.event()
maruel@chromium.org109cb9d2011-09-14 20:03:11 +0000447 # Flush the rest of buffered output. This is only an issue with
448 # stdout/stderr not ending with a \n.
449 if len(in_line):
450 filter_fn(in_line)
451 rv = kid.wait()
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000452
453 # Don't put this in a 'finally,' since the child may still run if we get an
454 # exception.
455 GClientChildren.remove(kid)
456
maruel@chromium.org109cb9d2011-09-14 20:03:11 +0000457 except KeyboardInterrupt:
458 print >> sys.stderr, 'Failed while running "%s"' % ' '.join(args)
459 raise
szager@chromium.org12b07e72013-05-03 22:06:34 +0000460 finally:
461 if nag:
462 nag.cancel()
maruel@chromium.org109cb9d2011-09-14 20:03:11 +0000463
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000464 if rv:
maruel@chromium.orga82a8ee2011-09-08 18:41:37 +0000465 raise subprocess2.CalledProcessError(
466 rv, args, kwargs.get('cwd', None), None, None)
maruel@chromium.org17d01792010-09-01 18:07:10 +0000467 return 0
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000468
469
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000470def FindGclientRoot(from_dir, filename='.gclient'):
maruel@chromium.orga9371762009-12-22 18:27:38 +0000471 """Tries to find the gclient root."""
jochen@chromium.org20760a52010-09-08 08:47:28 +0000472 real_from_dir = os.path.realpath(from_dir)
473 path = real_from_dir
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000474 while not os.path.exists(os.path.join(path, filename)):
maruel@chromium.org3a292682010-08-23 18:54:55 +0000475 split_path = os.path.split(path)
476 if not split_path[1]:
maruel@chromium.orga9371762009-12-22 18:27:38 +0000477 return None
maruel@chromium.org3a292682010-08-23 18:54:55 +0000478 path = split_path[0]
jochen@chromium.org20760a52010-09-08 08:47:28 +0000479
480 # If we did not find the file in the current directory, make sure we are in a
481 # sub directory that is controlled by this configuration.
482 if path != real_from_dir:
483 entries_filename = os.path.join(path, filename + '_entries')
484 if not os.path.exists(entries_filename):
485 # If .gclient_entries does not exist, a previous call to gclient sync
486 # might have failed. In that case, we cannot verify that the .gclient
487 # is the one we want to use. In order to not to cause too much trouble,
488 # just issue a warning and return the path anyway.
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +0000489 print >> sys.stderr, ("%s file in parent directory %s might not be the "
jochen@chromium.org20760a52010-09-08 08:47:28 +0000490 "file you want to use" % (filename, path))
491 return path
492 scope = {}
493 try:
494 exec(FileRead(entries_filename), scope)
495 except SyntaxError, e:
496 SyntaxErrorToError(filename, e)
497 all_directories = scope['entries'].keys()
498 path_to_check = real_from_dir[len(path)+1:]
499 while path_to_check:
500 if path_to_check in all_directories:
501 return path
502 path_to_check = os.path.dirname(path_to_check)
503 return None
maruel@chromium.org3742c842010-09-09 19:27:14 +0000504
maruel@chromium.orgd9141bf2009-12-23 16:13:32 +0000505 logging.info('Found gclient root at ' + path)
maruel@chromium.orga9371762009-12-22 18:27:38 +0000506 return path
maruel@chromium.org3ccbf7e2009-12-22 20:46:42 +0000507
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000508
maruel@chromium.org3ccbf7e2009-12-22 20:46:42 +0000509def PathDifference(root, subpath):
510 """Returns the difference subpath minus root."""
511 root = os.path.realpath(root)
512 subpath = os.path.realpath(subpath)
513 if not subpath.startswith(root):
514 return None
515 # If the root does not have a trailing \ or /, we add it so the returned
516 # path starts immediately after the seperator regardless of whether it is
517 # provided.
518 root = os.path.join(root, '')
519 return subpath[len(root):]
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000520
521
522def FindFileUpwards(filename, path=None):
rcui@google.com13595ff2011-10-13 01:25:07 +0000523 """Search upwards from the a directory (default: current) to find a file.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000524
rcui@google.com13595ff2011-10-13 01:25:07 +0000525 Returns nearest upper-level directory with the passed in file.
526 """
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000527 if not path:
528 path = os.getcwd()
529 path = os.path.realpath(path)
530 while True:
531 file_path = os.path.join(path, filename)
rcui@google.com13595ff2011-10-13 01:25:07 +0000532 if os.path.exists(file_path):
533 return path
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000534 (new_path, _) = os.path.split(path)
535 if new_path == path:
536 return None
537 path = new_path
538
539
540def GetGClientRootAndEntries(path=None):
541 """Returns the gclient root and the dict of entries."""
542 config_file = '.gclient_entries'
maruel@chromium.org93a9ee02011-10-18 18:23:58 +0000543 root = FindFileUpwards(config_file, path)
544 if not root:
maruel@chromium.org116704f2010-06-11 17:34:38 +0000545 print "Can't find %s" % config_file
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000546 return None
maruel@chromium.org93a9ee02011-10-18 18:23:58 +0000547 config_path = os.path.join(root, config_file)
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000548 env = {}
549 execfile(config_path, env)
550 config_dir = os.path.dirname(config_path)
551 return config_dir, env['entries']
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000552
553
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000554def lockedmethod(method):
555 """Method decorator that holds self.lock for the duration of the call."""
556 def inner(self, *args, **kwargs):
557 try:
558 try:
559 self.lock.acquire()
560 except KeyboardInterrupt:
561 print >> sys.stderr, 'Was deadlocked'
562 raise
563 return method(self, *args, **kwargs)
564 finally:
565 self.lock.release()
566 return inner
567
568
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000569class WorkItem(object):
570 """One work item."""
maruel@chromium.org4901daf2011-10-20 14:34:47 +0000571 # On cygwin, creating a lock throwing randomly when nearing ~100 locks.
572 # As a workaround, use a single lock. Yep you read it right. Single lock for
573 # all the 100 objects.
574 lock = threading.Lock()
575
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000576 def __init__(self, name):
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000577 # A unique string representing this work item.
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000578 self._name = name
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000579
maruel@chromium.org77e4eca2010-09-21 13:23:07 +0000580 def run(self, work_queue):
581 """work_queue is passed as keyword argument so it should be
maruel@chromium.org3742c842010-09-09 19:27:14 +0000582 the last parameters of the function when you override it."""
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000583 pass
584
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000585 @property
586 def name(self):
587 return self._name
588
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000589
590class ExecutionQueue(object):
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000591 """Runs a set of WorkItem that have interdependencies and were WorkItem are
592 added as they are processed.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000593
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000594 In gclient's case, Dependencies sometime needs to be run out of order due to
595 From() keyword. This class manages that all the required dependencies are run
596 before running each one.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000597
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000598 Methods of this class are thread safe.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000599 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000600 def __init__(self, jobs, progress, ignore_requirements):
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000601 """jobs specifies the number of concurrent tasks to allow. progress is a
602 Progress instance."""
603 # Set when a thread is done or a new item is enqueued.
604 self.ready_cond = threading.Condition()
605 # Maximum number of concurrent tasks.
606 self.jobs = jobs
607 # List of WorkItem, for gclient, these are Dependency instances.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000608 self.queued = []
609 # List of strings representing each Dependency.name that was run.
610 self.ran = []
611 # List of items currently running.
612 self.running = []
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000613 # Exceptions thrown if any.
maruel@chromium.org3742c842010-09-09 19:27:14 +0000614 self.exceptions = Queue.Queue()
615 # Progress status
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000616 self.progress = progress
617 if self.progress:
maruel@chromium.org3742c842010-09-09 19:27:14 +0000618 self.progress.update(0)
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000619
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000620 self.ignore_requirements = ignore_requirements
621
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000622 def enqueue(self, d):
623 """Enqueue one Dependency to be executed later once its requirements are
624 satisfied.
625 """
626 assert isinstance(d, WorkItem)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000627 self.ready_cond.acquire()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000628 try:
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000629 self.queued.append(d)
630 total = len(self.queued) + len(self.ran) + len(self.running)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000631 logging.debug('enqueued(%s)' % d.name)
632 if self.progress:
633 self.progress._total = total + 1
634 self.progress.update(0)
635 self.ready_cond.notifyAll()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000636 finally:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000637 self.ready_cond.release()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000638
639 def flush(self, *args, **kwargs):
640 """Runs all enqueued items until all are executed."""
maruel@chromium.org3742c842010-09-09 19:27:14 +0000641 kwargs['work_queue'] = self
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000642 self.ready_cond.acquire()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000643 try:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000644 while True:
645 # Check for task to run first, then wait.
646 while True:
maruel@chromium.org3742c842010-09-09 19:27:14 +0000647 if not self.exceptions.empty():
648 # Systematically flush the queue when an exception logged.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000649 self.queued = []
maruel@chromium.org3742c842010-09-09 19:27:14 +0000650 self._flush_terminated_threads()
651 if (not self.queued and not self.running or
652 self.jobs == len(self.running)):
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000653 logging.debug('No more worker threads or can\'t queue anything.')
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000654 break
maruel@chromium.org3742c842010-09-09 19:27:14 +0000655
656 # Check for new tasks to start.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000657 for i in xrange(len(self.queued)):
658 # Verify its requirements.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000659 if (self.ignore_requirements or
660 not (set(self.queued[i].requirements) - set(self.ran))):
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000661 # Start one work item: all its requirements are satisfied.
maruel@chromium.org3742c842010-09-09 19:27:14 +0000662 self._run_one_task(self.queued.pop(i), args, kwargs)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000663 break
664 else:
665 # Couldn't find an item that could run. Break out the outher loop.
666 break
maruel@chromium.org3742c842010-09-09 19:27:14 +0000667
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000668 if not self.queued and not self.running:
maruel@chromium.org3742c842010-09-09 19:27:14 +0000669 # We're done.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000670 break
671 # We need to poll here otherwise Ctrl-C isn't processed.
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000672 try:
673 self.ready_cond.wait(10)
674 except KeyboardInterrupt:
675 # Help debugging by printing some information:
676 print >> sys.stderr, (
677 ('\nAllowed parallel jobs: %d\n# queued: %d\nRan: %s\n'
678 'Running: %d') % (
679 self.jobs,
680 len(self.queued),
681 ', '.join(self.ran),
682 len(self.running)))
683 for i in self.queued:
684 print >> sys.stderr, '%s: %s' % (i.name, ', '.join(i.requirements))
685 raise
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000686 # Something happened: self.enqueue() or a thread terminated. Loop again.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000687 finally:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000688 self.ready_cond.release()
maruel@chromium.org3742c842010-09-09 19:27:14 +0000689
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000690 assert not self.running, 'Now guaranteed to be single-threaded'
maruel@chromium.org3742c842010-09-09 19:27:14 +0000691 if not self.exceptions.empty():
maruel@chromium.orgc8d064b2010-08-16 16:46:14 +0000692 # To get back the stack location correctly, the raise a, b, c form must be
693 # used, passing a tuple as the first argument doesn't work.
maruel@chromium.org3742c842010-09-09 19:27:14 +0000694 e = self.exceptions.get()
maruel@chromium.orgc8d064b2010-08-16 16:46:14 +0000695 raise e[0], e[1], e[2]
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000696 if self.progress:
697 self.progress.end()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000698
maruel@chromium.org3742c842010-09-09 19:27:14 +0000699 def _flush_terminated_threads(self):
700 """Flush threads that have terminated."""
701 running = self.running
702 self.running = []
703 for t in running:
704 if t.isAlive():
705 self.running.append(t)
706 else:
707 t.join()
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000708 sys.stdout.flush()
maruel@chromium.org3742c842010-09-09 19:27:14 +0000709 if self.progress:
maruel@chromium.org55a2eb82010-10-06 23:35:18 +0000710 self.progress.update(1, t.item.name)
maruel@chromium.orgf36c0ee2011-09-14 19:16:47 +0000711 if t.item.name in self.ran:
712 raise Error(
713 'gclient is confused, "%s" is already in "%s"' % (
714 t.item.name, ', '.join(self.ran)))
maruel@chromium.orgacc45672010-09-09 21:21:21 +0000715 if not t.item.name in self.ran:
716 self.ran.append(t.item.name)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000717
718 def _run_one_task(self, task_item, args, kwargs):
719 if self.jobs > 1:
720 # Start the thread.
721 index = len(self.ran) + len(self.running) + 1
maruel@chromium.org77e4eca2010-09-21 13:23:07 +0000722 new_thread = self._Worker(task_item, index, args, kwargs)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000723 self.running.append(new_thread)
724 new_thread.start()
725 else:
726 # Run the 'thread' inside the main thread. Don't try to catch any
727 # exception.
728 task_item.run(*args, **kwargs)
729 self.ran.append(task_item.name)
730 if self.progress:
maruel@chromium.org55a2eb82010-10-06 23:35:18 +0000731 self.progress.update(1, ', '.join(t.item.name for t in self.running))
maruel@chromium.org3742c842010-09-09 19:27:14 +0000732
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000733 class _Worker(threading.Thread):
734 """One thread to execute one WorkItem."""
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000735 def __init__(self, item, index, args, kwargs):
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000736 threading.Thread.__init__(self, name=item.name or 'Worker')
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000737 logging.info('_Worker(%s) reqs:%s' % (item.name, item.requirements))
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000738 self.item = item
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000739 self.index = index
maruel@chromium.org3742c842010-09-09 19:27:14 +0000740 self.args = args
741 self.kwargs = kwargs
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000742 self.daemon = True
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000743
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000744 def run(self):
745 """Runs in its own thread."""
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000746 logging.debug('_Worker.run(%s)' % self.item.name)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000747 work_queue = self.kwargs['work_queue']
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000748 try:
749 self.item.run(*self.args, **self.kwargs)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000750 except KeyboardInterrupt:
xusydoc@chromium.orgc144e062013-05-03 23:23:53 +0000751 logging.info('Caught KeyboardInterrupt in thread %s', self.item.name)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000752 logging.info(str(sys.exc_info()))
753 work_queue.exceptions.put(sys.exc_info())
754 raise
maruel@chromium.orgc8d064b2010-08-16 16:46:14 +0000755 except Exception:
756 # Catch exception location.
xusydoc@chromium.orgc144e062013-05-03 23:23:53 +0000757 logging.info('Caught exception in thread %s', self.item.name)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000758 logging.info(str(sys.exc_info()))
759 work_queue.exceptions.put(sys.exc_info())
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000760 finally:
xusydoc@chromium.orgc144e062013-05-03 23:23:53 +0000761 logging.info('_Worker.run(%s) done', self.item.name)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000762 work_queue.ready_cond.acquire()
763 try:
764 work_queue.ready_cond.notifyAll()
765 finally:
766 work_queue.ready_cond.release()
maruel@chromium.org0e0436a2011-10-25 13:32:41 +0000767
768
jbroman@chromium.org615a2622013-05-03 13:20:14 +0000769def GetEditor(git, git_editor=None):
770 """Returns the most plausible editor to use.
771
772 In order of preference:
773 - GIT_EDITOR/SVN_EDITOR environment variable
774 - core.editor git configuration variable (if supplied by git-cl)
775 - VISUAL environment variable
776 - EDITOR environment variable
777 - vim (non-Windows) or notepad (Windows)
778
779 In the case of git-cl, this matches git's behaviour, except that it does not
780 include dumb terminal detection.
781
782 In the case of gcl, this matches svn's behaviour, except that it does not
783 accept a command-line flag or check the editor-cmd configuration variable.
784 """
maruel@chromium.org0e0436a2011-10-25 13:32:41 +0000785 if git:
jbroman@chromium.org615a2622013-05-03 13:20:14 +0000786 editor = os.environ.get('GIT_EDITOR') or git_editor
maruel@chromium.org0e0436a2011-10-25 13:32:41 +0000787 else:
788 editor = os.environ.get('SVN_EDITOR')
789 if not editor:
jbroman@chromium.org615a2622013-05-03 13:20:14 +0000790 editor = os.environ.get('VISUAL')
791 if not editor:
maruel@chromium.org0e0436a2011-10-25 13:32:41 +0000792 editor = os.environ.get('EDITOR')
793 if not editor:
794 if sys.platform.startswith('win'):
795 editor = 'notepad'
796 else:
797 editor = 'vim'
798 return editor
799
800
jbroman@chromium.org615a2622013-05-03 13:20:14 +0000801def RunEditor(content, git, git_editor=None):
maruel@chromium.org0e0436a2011-10-25 13:32:41 +0000802 """Opens up the default editor in the system to get the CL description."""
803 file_handle, filename = tempfile.mkstemp(text=True)
804 # Make sure CRLF is handled properly by requiring none.
805 if '\r' in content:
asvitkine@chromium.org0eff22d2011-10-25 16:11:16 +0000806 print >> sys.stderr, (
807 '!! Please remove \\r from your change description !!')
maruel@chromium.org0e0436a2011-10-25 13:32:41 +0000808 fileobj = os.fdopen(file_handle, 'w')
809 # Still remove \r if present.
810 fileobj.write(re.sub('\r?\n', '\n', content))
811 fileobj.close()
812
813 try:
jbroman@chromium.org615a2622013-05-03 13:20:14 +0000814 editor = GetEditor(git, git_editor=git_editor)
815 if not editor:
816 return None
817 cmd = '%s %s' % (editor, filename)
maruel@chromium.org0e0436a2011-10-25 13:32:41 +0000818 if sys.platform == 'win32' and os.environ.get('TERM') == 'msys':
819 # Msysgit requires the usage of 'env' to be present.
820 cmd = 'env ' + cmd
821 try:
822 # shell=True to allow the shell to handle all forms of quotes in
823 # $EDITOR.
824 subprocess2.check_call(cmd, shell=True)
825 except subprocess2.CalledProcessError:
826 return None
827 return FileRead(filename)
828 finally:
829 os.remove(filename)
maruel@chromium.org99ac1c52012-01-16 14:52:12 +0000830
831
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +0000832def UpgradeToHttps(url):
833 """Upgrades random urls to https://.
834
835 Do not touch unknown urls like ssh:// or git://.
836 Do not touch http:// urls with a port number,
837 Fixes invalid GAE url.
838 """
839 if not url:
840 return url
841 if not re.match(r'[a-z\-]+\://.*', url):
842 # Make sure it is a valid uri. Otherwise, urlparse() will consider it a
843 # relative url and will use http:///foo. Note that it defaults to http://
844 # for compatibility with naked url like "localhost:8080".
845 url = 'http://%s' % url
846 parsed = list(urlparse.urlparse(url))
847 # Do not automatically upgrade http to https if a port number is provided.
848 if parsed[0] == 'http' and not re.match(r'^.+?\:\d+$', parsed[1]):
849 parsed[0] = 'https'
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +0000850 return urlparse.urlunparse(parsed)
851
852
maruel@chromium.org99ac1c52012-01-16 14:52:12 +0000853def ParseCodereviewSettingsContent(content):
854 """Process a codereview.settings file properly."""
855 lines = (l for l in content.splitlines() if not l.strip().startswith("#"))
856 try:
857 keyvals = dict([x.strip() for x in l.split(':', 1)] for l in lines if l)
858 except ValueError:
859 raise Error(
860 'Failed to process settings, please fix. Content:\n\n%s' % content)
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +0000861 def fix_url(key):
862 if keyvals.get(key):
863 keyvals[key] = UpgradeToHttps(keyvals[key])
864 fix_url('CODE_REVIEW_SERVER')
865 fix_url('VIEW_VC')
maruel@chromium.org99ac1c52012-01-16 14:52:12 +0000866 return keyvals
ilevy@chromium.org13691502012-10-16 04:26:37 +0000867
868
869def NumLocalCpus():
870 """Returns the number of processors.
871
872 Python on OSX 10.6 raises a NotImplementedError exception.
873 """
874 try:
875 import multiprocessing
876 return multiprocessing.cpu_count()
877 except: # pylint: disable=W0702
878 # Mac OS 10.6 only
879 # pylint: disable=E1101
880 return int(os.sysconf('SC_NPROCESSORS_ONLN'))