blob: 54d4e0ecf5ce374556006a502b0f05a3fd471361 [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."""
25 pass
26
27
msb@chromium.orgac915bb2009-11-13 17:03:01 +000028def SplitUrlRevision(url):
29 """Splits url and returns a two-tuple: url, rev"""
30 if url.startswith('ssh:'):
maruel@chromium.org78b8cd12010-10-26 12:47:07 +000031 # Make sure ssh://user-name@example.com/~/test.git@stable works
32 regex = r'(ssh://(?:[-\w]+@)?[-\w:\.]+/[-~\w\./]+)(?:@(.+))?'
msb@chromium.orgac915bb2009-11-13 17:03:01 +000033 components = re.search(regex, url).groups()
34 else:
maruel@chromium.org116704f2010-06-11 17:34:38 +000035 components = url.split('@', 1)
msb@chromium.orgac915bb2009-11-13 17:03:01 +000036 if len(components) == 1:
37 components += [None]
38 return tuple(components)
39
40
floitsch@google.comeaab7842011-04-28 09:07:58 +000041def IsDateRevision(revision):
42 """Returns true if the given revision is of the form "{ ... }"."""
43 return bool(revision and re.match(r'^\{.+\}$', str(revision)))
44
45
46def MakeDateRevision(date):
47 """Returns a revision representing the latest revision before the given
48 date."""
49 return "{" + date + "}"
50
51
maruel@chromium.org5990f9d2010-07-07 18:02:58 +000052def SyntaxErrorToError(filename, e):
53 """Raises a gclient_utils.Error exception with the human readable message"""
54 try:
55 # Try to construct a human readable error message
56 if filename:
57 error_message = 'There is a syntax error in %s\n' % filename
58 else:
59 error_message = 'There is a syntax error\n'
60 error_message += 'Line #%s, character %s: "%s"' % (
61 e.lineno, e.offset, re.sub(r'[\r\n]*$', '', e.text))
62 except:
63 # Something went wrong, re-raise the original exception
64 raise e
65 else:
66 raise Error(error_message)
67
68
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000069class PrintableObject(object):
70 def __str__(self):
71 output = ''
72 for i in dir(self):
73 if i.startswith('__'):
74 continue
75 output += '%s = %s\n' % (i, str(getattr(self, i, '')))
76 return output
77
78
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +000079def FileRead(filename, mode='rU'):
maruel@chromium.org51e84fb2012-07-03 23:06:21 +000080 with open(filename, mode=mode) as f:
maruel@chromium.orgc3cd5372012-07-11 17:39:24 +000081 # codecs.open() has different behavior than open() on python 2.6 so use
82 # open() and decode manually.
chrisha@chromium.org2b99d432012-07-12 18:10:28 +000083 s = f.read()
84 try:
85 return s.decode('utf-8')
86 except UnicodeDecodeError:
87 return s
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000088
89
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +000090def FileWrite(filename, content, mode='w'):
maruel@chromium.orgdae209f2012-07-03 16:08:15 +000091 with codecs.open(filename, mode=mode, encoding='utf-8') as f:
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000092 f.write(content)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000093
94
maruel@chromium.orgf9040722011-03-09 14:47:51 +000095def rmtree(path):
96 """shutil.rmtree() on steroids.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000097
maruel@chromium.orgf9040722011-03-09 14:47:51 +000098 Recursively removes a directory, even if it's marked read-only.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000099
100 shutil.rmtree() doesn't work on Windows if any of the files or directories
101 are read-only, which svn repositories and some .svn files are. We need to
102 be able to force the files to be writable (i.e., deletable) as we traverse
103 the tree.
104
105 Even with all this, Windows still sometimes fails to delete a file, citing
106 a permission error (maybe something to do with antivirus scans or disk
107 indexing). The best suggestion any of the user forums had was to wait a
108 bit and try again, so we do that too. It's hand-waving, but sometimes it
109 works. :/
110
111 On POSIX systems, things are a little bit simpler. The modes of the files
112 to be deleted doesn't matter, only the modes of the directories containing
113 them are significant. As the directory tree is traversed, each directory
114 has its mode set appropriately before descending into it. This should
115 result in the entire tree being removed, with the possible exception of
116 *path itself, because nothing attempts to change the mode of its parent.
117 Doing so would be hazardous, as it's not a directory slated for removal.
118 In the ordinary case, this is not a problem: for our purposes, the user
119 will never lack write permission on *path's parent.
120 """
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000121 if not os.path.exists(path):
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000122 return
123
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000124 if os.path.islink(path) or not os.path.isdir(path):
125 raise Error('Called rmtree(%s) in non-directory' % path)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000126
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000127 if sys.platform == 'win32':
borenet@google.com6b4a2ab2013-04-18 15:50:27 +0000128 # Give up and use cmd.exe's rd command.
129 path = os.path.normcase(path)
130 for _ in xrange(3):
131 exitcode = subprocess.call(['cmd.exe', '/c', 'rd', '/q', '/s', path])
132 if exitcode == 0:
133 return
134 else:
135 print >> sys.stderr, 'rd exited with code %d' % exitcode
136 time.sleep(3)
137 raise Exception('Failed to remove path %s' % path)
138
139 # On POSIX systems, we need the x-bit set on the directory to access it,
140 # the r-bit to see its contents, and the w-bit to remove files from it.
141 # The actual modes of the files within the directory is irrelevant.
142 os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000143
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000144 def remove(func, subpath):
borenet@google.com6b4a2ab2013-04-18 15:50:27 +0000145 func(subpath)
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000146
147 for fn in os.listdir(path):
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000148 # If fullpath is a symbolic link that points to a directory, isdir will
149 # be True, but we don't want to descend into that as a directory, we just
150 # want to remove the link. Check islink and treat links as ordinary files
151 # would be treated regardless of what they reference.
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000152 fullpath = os.path.join(path, fn)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000153 if os.path.islink(fullpath) or not os.path.isdir(fullpath):
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000154 remove(os.remove, fullpath)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000155 else:
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000156 # Recurse.
157 rmtree(fullpath)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000158
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000159 remove(os.rmdir, path)
160
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000161
maruel@chromium.org6c48a302011-10-20 23:44:20 +0000162def safe_makedirs(tree):
163 """Creates the directory in a safe manner.
164
165 Because multiple threads can create these directories concurently, trap the
166 exception and pass on.
167 """
168 count = 0
169 while not os.path.exists(tree):
170 count += 1
171 try:
172 os.makedirs(tree)
173 except OSError, e:
174 # 17 POSIX, 183 Windows
175 if e.errno not in (17, 183):
176 raise
177 if count > 40:
178 # Give up.
179 raise
180
181
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000182def CheckCallAndFilterAndHeader(args, always=False, header=None, **kwargs):
maruel@chromium.org17d01792010-09-01 18:07:10 +0000183 """Adds 'header' support to CheckCallAndFilter.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000184
maruel@chromium.org17d01792010-09-01 18:07:10 +0000185 If |always| is True, a message indicating what is being done
186 is printed to stdout all the time even if not output is generated. Otherwise
187 the message header is printed only if the call generated any ouput.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000188 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000189 stdout = kwargs.setdefault('stdout', sys.stdout)
190 if header is None:
191 header = "\n________ running '%s' in '%s'\n" % (
192 ' '.join(args), kwargs.get('cwd', '.'))
193
maruel@chromium.org17d01792010-09-01 18:07:10 +0000194 if always:
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000195 stdout.write(header)
maruel@chromium.org17d01792010-09-01 18:07:10 +0000196 else:
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000197 filter_fn = kwargs.get('filter_fn')
maruel@chromium.org17d01792010-09-01 18:07:10 +0000198 def filter_msg(line):
199 if line is None:
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000200 stdout.write(header)
maruel@chromium.org17d01792010-09-01 18:07:10 +0000201 elif filter_fn:
202 filter_fn(line)
203 kwargs['filter_fn'] = filter_msg
204 kwargs['call_filter_on_first_line'] = True
205 # Obviously.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000206 kwargs.setdefault('print_stdout', True)
maruel@chromium.org17d01792010-09-01 18:07:10 +0000207 return CheckCallAndFilter(args, **kwargs)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000208
maruel@chromium.org17d01792010-09-01 18:07:10 +0000209
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000210class Wrapper(object):
211 """Wraps an object, acting as a transparent proxy for all properties by
212 default.
213 """
214 def __init__(self, wrapped):
215 self._wrapped = wrapped
216
217 def __getattr__(self, name):
218 return getattr(self._wrapped, name)
maruel@chromium.orgdb111f72010-09-08 13:36:53 +0000219
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +0000220
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000221class AutoFlush(Wrapper):
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +0000222 """Creates a file object clone to automatically flush after N seconds."""
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000223 def __init__(self, wrapped, delay):
224 super(AutoFlush, self).__init__(wrapped)
225 if not hasattr(self, 'lock'):
226 self.lock = threading.Lock()
227 self.__last_flushed_at = time.time()
228 self.delay = delay
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +0000229
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000230 @property
231 def autoflush(self):
232 return self
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +0000233
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000234 def write(self, out, *args, **kwargs):
235 self._wrapped.write(out, *args, **kwargs)
maruel@chromium.orgdb111f72010-09-08 13:36:53 +0000236 should_flush = False
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000237 self.lock.acquire()
maruel@chromium.org9c531262010-09-08 13:41:13 +0000238 try:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000239 if self.delay and (time.time() - self.__last_flushed_at) > self.delay:
maruel@chromium.orgdb111f72010-09-08 13:36:53 +0000240 should_flush = True
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000241 self.__last_flushed_at = time.time()
maruel@chromium.org9c531262010-09-08 13:41:13 +0000242 finally:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000243 self.lock.release()
maruel@chromium.orgdb111f72010-09-08 13:36:53 +0000244 if should_flush:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000245 self.flush()
maruel@chromium.orgdb111f72010-09-08 13:36:53 +0000246
247
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000248class Annotated(Wrapper):
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000249 """Creates a file object clone to automatically prepends every line in worker
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000250 threads with a NN> prefix.
251 """
252 def __init__(self, wrapped, include_zero=False):
253 super(Annotated, self).__init__(wrapped)
254 if not hasattr(self, 'lock'):
255 self.lock = threading.Lock()
256 self.__output_buffers = {}
257 self.__include_zero = include_zero
maruel@chromium.orgcb1e97a2010-09-09 20:09:20 +0000258
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000259 @property
260 def annotated(self):
261 return self
maruel@chromium.orgcb1e97a2010-09-09 20:09:20 +0000262
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000263 def write(self, out):
264 index = getattr(threading.currentThread(), 'index', 0)
265 if not index and not self.__include_zero:
266 # Unindexed threads aren't buffered.
267 return self._wrapped.write(out)
maruel@chromium.orgcb1e97a2010-09-09 20:09:20 +0000268
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000269 self.lock.acquire()
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000270 try:
271 # Use a dummy array to hold the string so the code can be lockless.
272 # Strings are immutable, requiring to keep a lock for the whole dictionary
273 # otherwise. Using an array is faster than using a dummy object.
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000274 if not index in self.__output_buffers:
275 obj = self.__output_buffers[index] = ['']
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000276 else:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000277 obj = self.__output_buffers[index]
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000278 finally:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000279 self.lock.release()
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000280
281 # Continue lockless.
282 obj[0] += out
283 while '\n' in obj[0]:
284 line, remaining = obj[0].split('\n', 1)
nsylvain@google.come939bb52011-06-01 22:59:15 +0000285 if line:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000286 self._wrapped.write('%d>%s\n' % (index, line))
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000287 obj[0] = remaining
288
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000289 def flush(self):
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000290 """Flush buffered output."""
291 orphans = []
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000292 self.lock.acquire()
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000293 try:
294 # Detect threads no longer existing.
295 indexes = (getattr(t, 'index', None) for t in threading.enumerate())
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +0000296 indexes = filter(None, indexes)
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000297 for index in self.__output_buffers:
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000298 if not index in indexes:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000299 orphans.append((index, self.__output_buffers[index][0]))
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000300 for orphan in orphans:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000301 del self.__output_buffers[orphan[0]]
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000302 finally:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000303 self.lock.release()
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000304
305 # Don't keep the lock while writting. Will append \n when it shouldn't.
306 for orphan in orphans:
nsylvain@google.come939bb52011-06-01 22:59:15 +0000307 if orphan[1]:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000308 self._wrapped.write('%d>%s\n' % (orphan[0], orphan[1]))
309 return self._wrapped.flush()
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000310
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000311
312def MakeFileAutoFlush(fileobj, delay=10):
313 autoflush = getattr(fileobj, 'autoflush', None)
314 if autoflush:
315 autoflush.delay = delay
316 return fileobj
317 return AutoFlush(fileobj, delay)
318
319
320def MakeFileAnnotated(fileobj, include_zero=False):
321 if getattr(fileobj, 'annotated', None):
322 return fileobj
323 return Annotated(fileobj)
maruel@chromium.orgcb1e97a2010-09-09 20:09:20 +0000324
325
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000326GCLIENT_CHILDREN = []
327GCLIENT_CHILDREN_LOCK = threading.Lock()
328
329
330class GClientChildren(object):
331 @staticmethod
332 def add(popen_obj):
333 with GCLIENT_CHILDREN_LOCK:
334 GCLIENT_CHILDREN.append(popen_obj)
335
336 @staticmethod
337 def remove(popen_obj):
338 with GCLIENT_CHILDREN_LOCK:
339 GCLIENT_CHILDREN.remove(popen_obj)
340
341 @staticmethod
342 def _attemptToKillChildren():
343 global GCLIENT_CHILDREN
344 with GCLIENT_CHILDREN_LOCK:
345 zombies = [c for c in GCLIENT_CHILDREN if c.poll() is None]
346
347 for zombie in zombies:
348 try:
349 zombie.kill()
350 except OSError:
351 pass
352
353 with GCLIENT_CHILDREN_LOCK:
354 GCLIENT_CHILDREN = [k for k in GCLIENT_CHILDREN if k.poll() is not None]
355
356 @staticmethod
357 def _areZombies():
358 with GCLIENT_CHILDREN_LOCK:
359 return bool(GCLIENT_CHILDREN)
360
361 @staticmethod
362 def KillAllRemainingChildren():
363 GClientChildren._attemptToKillChildren()
364
365 if GClientChildren._areZombies():
366 time.sleep(0.5)
367 GClientChildren._attemptToKillChildren()
368
369 with GCLIENT_CHILDREN_LOCK:
370 if GCLIENT_CHILDREN:
371 print >> sys.stderr, 'Could not kill the following subprocesses:'
372 for zombie in GCLIENT_CHILDREN:
373 print >> sys.stderr, ' ', zombie.pid
374
375
maruel@chromium.org17d01792010-09-01 18:07:10 +0000376def CheckCallAndFilter(args, stdout=None, filter_fn=None,
377 print_stdout=None, call_filter_on_first_line=False,
szager@chromium.org12b07e72013-05-03 22:06:34 +0000378 nag_timer=None, nag_max=None, **kwargs):
maruel@chromium.org17d01792010-09-01 18:07:10 +0000379 """Runs a command and calls back a filter function if needed.
380
maruel@chromium.org57bf78d2011-09-08 18:57:33 +0000381 Accepts all subprocess2.Popen() parameters plus:
maruel@chromium.org17d01792010-09-01 18:07:10 +0000382 print_stdout: If True, the command's stdout is forwarded to stdout.
383 filter_fn: A function taking a single string argument called with each line
maruel@chromium.org57bf78d2011-09-08 18:57:33 +0000384 of the subprocess2's output. Each line has the trailing newline
maruel@chromium.org17d01792010-09-01 18:07:10 +0000385 character trimmed.
386 stdout: Can be any bufferable output.
387
388 stderr is always redirected to stdout.
389 """
390 assert print_stdout or filter_fn
391 stdout = stdout or sys.stdout
392 filter_fn = filter_fn or (lambda x: None)
maruel@chromium.orga82a8ee2011-09-08 18:41:37 +0000393 kid = subprocess2.Popen(
394 args, bufsize=0, stdout=subprocess2.PIPE, stderr=subprocess2.STDOUT,
395 **kwargs)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000396
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000397 GClientChildren.add(kid)
398
maruel@chromium.org57bf78d2011-09-08 18:57:33 +0000399 # Do a flush of stdout before we begin reading from the subprocess2's stdout
maruel@chromium.org559c3f82010-08-23 19:26:08 +0000400 stdout.flush()
chase@chromium.org8ad1cee2010-08-16 19:12:27 +0000401
szager@chromium.org12b07e72013-05-03 22:06:34 +0000402 nag = None
403 if nag_timer:
404 # Hack thread.index to force correct annotation.
405 index = getattr(threading.currentThread(), 'index', 0)
406 def _nag_cb(elapsed):
407 setattr(threading.currentThread(), 'index', index)
408 stdout.write(' No output for %.0f seconds from command:\n' % elapsed)
409 stdout.write(' %s\n' % kid.cmd_str)
410 if (nag_max and
411 int('%.0f' % (elapsed / nag_timer)) >= nag_max):
412 stdout.write(' ... killing it!\n')
413 kid.kill()
414 nag = subprocess2.NagTimer(nag_timer, _nag_cb)
415 nag.start()
416
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000417 # Also, we need to forward stdout to prevent weird re-ordering of output.
418 # This has to be done on a per byte basis to make sure it is not buffered:
419 # normally buffering is done for each line, but if svn requests input, no
420 # end-of-line character is output after the prompt and it would not show up.
maruel@chromium.org109cb9d2011-09-14 20:03:11 +0000421 try:
422 in_byte = kid.stdout.read(1)
423 if in_byte:
szager@chromium.org12b07e72013-05-03 22:06:34 +0000424 if nag:
425 nag.event()
maruel@chromium.org109cb9d2011-09-14 20:03:11 +0000426 if call_filter_on_first_line:
427 filter_fn(None)
428 in_line = ''
429 while in_byte:
430 if in_byte != '\r':
431 if print_stdout:
432 stdout.write(in_byte)
433 if in_byte != '\n':
434 in_line += in_byte
435 else:
436 filter_fn(in_line)
437 in_line = ''
szager@google.com85d3e3a2011-10-07 17:12:00 +0000438 else:
439 filter_fn(in_line)
440 in_line = ''
maruel@chromium.org109cb9d2011-09-14 20:03:11 +0000441 in_byte = kid.stdout.read(1)
szager@chromium.org12b07e72013-05-03 22:06:34 +0000442 if in_byte and nag:
443 nag.event()
maruel@chromium.org109cb9d2011-09-14 20:03:11 +0000444 # Flush the rest of buffered output. This is only an issue with
445 # stdout/stderr not ending with a \n.
446 if len(in_line):
447 filter_fn(in_line)
448 rv = kid.wait()
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000449
450 # Don't put this in a 'finally,' since the child may still run if we get an
451 # exception.
452 GClientChildren.remove(kid)
453
maruel@chromium.org109cb9d2011-09-14 20:03:11 +0000454 except KeyboardInterrupt:
455 print >> sys.stderr, 'Failed while running "%s"' % ' '.join(args)
456 raise
szager@chromium.org12b07e72013-05-03 22:06:34 +0000457 finally:
458 if nag:
459 nag.cancel()
maruel@chromium.org109cb9d2011-09-14 20:03:11 +0000460
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000461 if rv:
maruel@chromium.orga82a8ee2011-09-08 18:41:37 +0000462 raise subprocess2.CalledProcessError(
463 rv, args, kwargs.get('cwd', None), None, None)
maruel@chromium.org17d01792010-09-01 18:07:10 +0000464 return 0
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000465
466
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000467def FindGclientRoot(from_dir, filename='.gclient'):
maruel@chromium.orga9371762009-12-22 18:27:38 +0000468 """Tries to find the gclient root."""
jochen@chromium.org20760a52010-09-08 08:47:28 +0000469 real_from_dir = os.path.realpath(from_dir)
470 path = real_from_dir
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000471 while not os.path.exists(os.path.join(path, filename)):
maruel@chromium.org3a292682010-08-23 18:54:55 +0000472 split_path = os.path.split(path)
473 if not split_path[1]:
maruel@chromium.orga9371762009-12-22 18:27:38 +0000474 return None
maruel@chromium.org3a292682010-08-23 18:54:55 +0000475 path = split_path[0]
jochen@chromium.org20760a52010-09-08 08:47:28 +0000476
477 # If we did not find the file in the current directory, make sure we are in a
478 # sub directory that is controlled by this configuration.
479 if path != real_from_dir:
480 entries_filename = os.path.join(path, filename + '_entries')
481 if not os.path.exists(entries_filename):
482 # If .gclient_entries does not exist, a previous call to gclient sync
483 # might have failed. In that case, we cannot verify that the .gclient
484 # is the one we want to use. In order to not to cause too much trouble,
485 # just issue a warning and return the path anyway.
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +0000486 print >> sys.stderr, ("%s file in parent directory %s might not be the "
jochen@chromium.org20760a52010-09-08 08:47:28 +0000487 "file you want to use" % (filename, path))
488 return path
489 scope = {}
490 try:
491 exec(FileRead(entries_filename), scope)
492 except SyntaxError, e:
493 SyntaxErrorToError(filename, e)
494 all_directories = scope['entries'].keys()
495 path_to_check = real_from_dir[len(path)+1:]
496 while path_to_check:
497 if path_to_check in all_directories:
498 return path
499 path_to_check = os.path.dirname(path_to_check)
500 return None
maruel@chromium.org3742c842010-09-09 19:27:14 +0000501
maruel@chromium.orgd9141bf2009-12-23 16:13:32 +0000502 logging.info('Found gclient root at ' + path)
maruel@chromium.orga9371762009-12-22 18:27:38 +0000503 return path
maruel@chromium.org3ccbf7e2009-12-22 20:46:42 +0000504
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000505
maruel@chromium.org3ccbf7e2009-12-22 20:46:42 +0000506def PathDifference(root, subpath):
507 """Returns the difference subpath minus root."""
508 root = os.path.realpath(root)
509 subpath = os.path.realpath(subpath)
510 if not subpath.startswith(root):
511 return None
512 # If the root does not have a trailing \ or /, we add it so the returned
513 # path starts immediately after the seperator regardless of whether it is
514 # provided.
515 root = os.path.join(root, '')
516 return subpath[len(root):]
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000517
518
519def FindFileUpwards(filename, path=None):
rcui@google.com13595ff2011-10-13 01:25:07 +0000520 """Search upwards from the a directory (default: current) to find a file.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000521
rcui@google.com13595ff2011-10-13 01:25:07 +0000522 Returns nearest upper-level directory with the passed in file.
523 """
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000524 if not path:
525 path = os.getcwd()
526 path = os.path.realpath(path)
527 while True:
528 file_path = os.path.join(path, filename)
rcui@google.com13595ff2011-10-13 01:25:07 +0000529 if os.path.exists(file_path):
530 return path
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000531 (new_path, _) = os.path.split(path)
532 if new_path == path:
533 return None
534 path = new_path
535
536
537def GetGClientRootAndEntries(path=None):
538 """Returns the gclient root and the dict of entries."""
539 config_file = '.gclient_entries'
maruel@chromium.org93a9ee02011-10-18 18:23:58 +0000540 root = FindFileUpwards(config_file, path)
541 if not root:
maruel@chromium.org116704f2010-06-11 17:34:38 +0000542 print "Can't find %s" % config_file
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000543 return None
maruel@chromium.org93a9ee02011-10-18 18:23:58 +0000544 config_path = os.path.join(root, config_file)
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000545 env = {}
546 execfile(config_path, env)
547 config_dir = os.path.dirname(config_path)
548 return config_dir, env['entries']
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000549
550
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000551def lockedmethod(method):
552 """Method decorator that holds self.lock for the duration of the call."""
553 def inner(self, *args, **kwargs):
554 try:
555 try:
556 self.lock.acquire()
557 except KeyboardInterrupt:
558 print >> sys.stderr, 'Was deadlocked'
559 raise
560 return method(self, *args, **kwargs)
561 finally:
562 self.lock.release()
563 return inner
564
565
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000566class WorkItem(object):
567 """One work item."""
maruel@chromium.org4901daf2011-10-20 14:34:47 +0000568 # On cygwin, creating a lock throwing randomly when nearing ~100 locks.
569 # As a workaround, use a single lock. Yep you read it right. Single lock for
570 # all the 100 objects.
571 lock = threading.Lock()
572
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000573 def __init__(self, name):
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000574 # A unique string representing this work item.
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000575 self._name = name
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000576
maruel@chromium.org77e4eca2010-09-21 13:23:07 +0000577 def run(self, work_queue):
578 """work_queue is passed as keyword argument so it should be
maruel@chromium.org3742c842010-09-09 19:27:14 +0000579 the last parameters of the function when you override it."""
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000580 pass
581
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000582 @property
583 def name(self):
584 return self._name
585
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000586
587class ExecutionQueue(object):
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000588 """Runs a set of WorkItem that have interdependencies and were WorkItem are
589 added as they are processed.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000590
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000591 In gclient's case, Dependencies sometime needs to be run out of order due to
592 From() keyword. This class manages that all the required dependencies are run
593 before running each one.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000594
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000595 Methods of this class are thread safe.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000596 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000597 def __init__(self, jobs, progress, ignore_requirements):
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000598 """jobs specifies the number of concurrent tasks to allow. progress is a
599 Progress instance."""
600 # Set when a thread is done or a new item is enqueued.
601 self.ready_cond = threading.Condition()
602 # Maximum number of concurrent tasks.
603 self.jobs = jobs
604 # List of WorkItem, for gclient, these are Dependency instances.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000605 self.queued = []
606 # List of strings representing each Dependency.name that was run.
607 self.ran = []
608 # List of items currently running.
609 self.running = []
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000610 # Exceptions thrown if any.
maruel@chromium.org3742c842010-09-09 19:27:14 +0000611 self.exceptions = Queue.Queue()
612 # Progress status
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000613 self.progress = progress
614 if self.progress:
maruel@chromium.org3742c842010-09-09 19:27:14 +0000615 self.progress.update(0)
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000616
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000617 self.ignore_requirements = ignore_requirements
618
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000619 def enqueue(self, d):
620 """Enqueue one Dependency to be executed later once its requirements are
621 satisfied.
622 """
623 assert isinstance(d, WorkItem)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000624 self.ready_cond.acquire()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000625 try:
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000626 self.queued.append(d)
627 total = len(self.queued) + len(self.ran) + len(self.running)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000628 logging.debug('enqueued(%s)' % d.name)
629 if self.progress:
630 self.progress._total = total + 1
631 self.progress.update(0)
632 self.ready_cond.notifyAll()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000633 finally:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000634 self.ready_cond.release()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000635
636 def flush(self, *args, **kwargs):
637 """Runs all enqueued items until all are executed."""
maruel@chromium.org3742c842010-09-09 19:27:14 +0000638 kwargs['work_queue'] = self
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000639 self.ready_cond.acquire()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000640 try:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000641 while True:
642 # Check for task to run first, then wait.
643 while True:
maruel@chromium.org3742c842010-09-09 19:27:14 +0000644 if not self.exceptions.empty():
645 # Systematically flush the queue when an exception logged.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000646 self.queued = []
maruel@chromium.org3742c842010-09-09 19:27:14 +0000647 self._flush_terminated_threads()
648 if (not self.queued and not self.running or
649 self.jobs == len(self.running)):
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000650 logging.debug('No more worker threads or can\'t queue anything.')
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000651 break
maruel@chromium.org3742c842010-09-09 19:27:14 +0000652
653 # Check for new tasks to start.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000654 for i in xrange(len(self.queued)):
655 # Verify its requirements.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000656 if (self.ignore_requirements or
657 not (set(self.queued[i].requirements) - set(self.ran))):
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000658 # Start one work item: all its requirements are satisfied.
maruel@chromium.org3742c842010-09-09 19:27:14 +0000659 self._run_one_task(self.queued.pop(i), args, kwargs)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000660 break
661 else:
662 # Couldn't find an item that could run. Break out the outher loop.
663 break
maruel@chromium.org3742c842010-09-09 19:27:14 +0000664
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000665 if not self.queued and not self.running:
maruel@chromium.org3742c842010-09-09 19:27:14 +0000666 # We're done.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000667 break
668 # We need to poll here otherwise Ctrl-C isn't processed.
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000669 try:
670 self.ready_cond.wait(10)
671 except KeyboardInterrupt:
672 # Help debugging by printing some information:
673 print >> sys.stderr, (
674 ('\nAllowed parallel jobs: %d\n# queued: %d\nRan: %s\n'
675 'Running: %d') % (
676 self.jobs,
677 len(self.queued),
678 ', '.join(self.ran),
679 len(self.running)))
680 for i in self.queued:
681 print >> sys.stderr, '%s: %s' % (i.name, ', '.join(i.requirements))
682 raise
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000683 # Something happened: self.enqueue() or a thread terminated. Loop again.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000684 finally:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000685 self.ready_cond.release()
maruel@chromium.org3742c842010-09-09 19:27:14 +0000686
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000687 assert not self.running, 'Now guaranteed to be single-threaded'
maruel@chromium.org3742c842010-09-09 19:27:14 +0000688 if not self.exceptions.empty():
maruel@chromium.orgc8d064b2010-08-16 16:46:14 +0000689 # To get back the stack location correctly, the raise a, b, c form must be
690 # used, passing a tuple as the first argument doesn't work.
maruel@chromium.org3742c842010-09-09 19:27:14 +0000691 e = self.exceptions.get()
maruel@chromium.orgc8d064b2010-08-16 16:46:14 +0000692 raise e[0], e[1], e[2]
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000693 if self.progress:
694 self.progress.end()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000695
maruel@chromium.org3742c842010-09-09 19:27:14 +0000696 def _flush_terminated_threads(self):
697 """Flush threads that have terminated."""
698 running = self.running
699 self.running = []
700 for t in running:
701 if t.isAlive():
702 self.running.append(t)
703 else:
704 t.join()
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000705 sys.stdout.flush()
maruel@chromium.org3742c842010-09-09 19:27:14 +0000706 if self.progress:
maruel@chromium.org55a2eb82010-10-06 23:35:18 +0000707 self.progress.update(1, t.item.name)
maruel@chromium.orgf36c0ee2011-09-14 19:16:47 +0000708 if t.item.name in self.ran:
709 raise Error(
710 'gclient is confused, "%s" is already in "%s"' % (
711 t.item.name, ', '.join(self.ran)))
maruel@chromium.orgacc45672010-09-09 21:21:21 +0000712 if not t.item.name in self.ran:
713 self.ran.append(t.item.name)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000714
715 def _run_one_task(self, task_item, args, kwargs):
716 if self.jobs > 1:
717 # Start the thread.
718 index = len(self.ran) + len(self.running) + 1
maruel@chromium.org77e4eca2010-09-21 13:23:07 +0000719 new_thread = self._Worker(task_item, index, args, kwargs)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000720 self.running.append(new_thread)
721 new_thread.start()
722 else:
723 # Run the 'thread' inside the main thread. Don't try to catch any
724 # exception.
725 task_item.run(*args, **kwargs)
726 self.ran.append(task_item.name)
727 if self.progress:
maruel@chromium.org55a2eb82010-10-06 23:35:18 +0000728 self.progress.update(1, ', '.join(t.item.name for t in self.running))
maruel@chromium.org3742c842010-09-09 19:27:14 +0000729
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000730 class _Worker(threading.Thread):
731 """One thread to execute one WorkItem."""
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000732 def __init__(self, item, index, args, kwargs):
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000733 threading.Thread.__init__(self, name=item.name or 'Worker')
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000734 logging.info('_Worker(%s) reqs:%s' % (item.name, item.requirements))
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000735 self.item = item
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000736 self.index = index
maruel@chromium.org3742c842010-09-09 19:27:14 +0000737 self.args = args
738 self.kwargs = kwargs
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000739 self.daemon = True
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000740
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000741 def run(self):
742 """Runs in its own thread."""
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000743 logging.debug('_Worker.run(%s)' % self.item.name)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000744 work_queue = self.kwargs['work_queue']
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000745 try:
746 self.item.run(*self.args, **self.kwargs)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000747 except KeyboardInterrupt:
xusydoc@chromium.orgc144e062013-05-03 23:23:53 +0000748 logging.info('Caught KeyboardInterrupt in thread %s', self.item.name)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000749 logging.info(str(sys.exc_info()))
750 work_queue.exceptions.put(sys.exc_info())
751 raise
maruel@chromium.orgc8d064b2010-08-16 16:46:14 +0000752 except Exception:
753 # Catch exception location.
xusydoc@chromium.orgc144e062013-05-03 23:23:53 +0000754 logging.info('Caught exception in thread %s', self.item.name)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000755 logging.info(str(sys.exc_info()))
756 work_queue.exceptions.put(sys.exc_info())
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000757 finally:
xusydoc@chromium.orgc144e062013-05-03 23:23:53 +0000758 logging.info('_Worker.run(%s) done', self.item.name)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000759 work_queue.ready_cond.acquire()
760 try:
761 work_queue.ready_cond.notifyAll()
762 finally:
763 work_queue.ready_cond.release()
maruel@chromium.org0e0436a2011-10-25 13:32:41 +0000764
765
jbroman@chromium.org615a2622013-05-03 13:20:14 +0000766def GetEditor(git, git_editor=None):
767 """Returns the most plausible editor to use.
768
769 In order of preference:
770 - GIT_EDITOR/SVN_EDITOR environment variable
771 - core.editor git configuration variable (if supplied by git-cl)
772 - VISUAL environment variable
773 - EDITOR environment variable
774 - vim (non-Windows) or notepad (Windows)
775
776 In the case of git-cl, this matches git's behaviour, except that it does not
777 include dumb terminal detection.
778
779 In the case of gcl, this matches svn's behaviour, except that it does not
780 accept a command-line flag or check the editor-cmd configuration variable.
781 """
maruel@chromium.org0e0436a2011-10-25 13:32:41 +0000782 if git:
jbroman@chromium.org615a2622013-05-03 13:20:14 +0000783 editor = os.environ.get('GIT_EDITOR') or git_editor
maruel@chromium.org0e0436a2011-10-25 13:32:41 +0000784 else:
785 editor = os.environ.get('SVN_EDITOR')
786 if not editor:
jbroman@chromium.org615a2622013-05-03 13:20:14 +0000787 editor = os.environ.get('VISUAL')
788 if not editor:
maruel@chromium.org0e0436a2011-10-25 13:32:41 +0000789 editor = os.environ.get('EDITOR')
790 if not editor:
791 if sys.platform.startswith('win'):
792 editor = 'notepad'
793 else:
794 editor = 'vim'
795 return editor
796
797
jbroman@chromium.org615a2622013-05-03 13:20:14 +0000798def RunEditor(content, git, git_editor=None):
maruel@chromium.org0e0436a2011-10-25 13:32:41 +0000799 """Opens up the default editor in the system to get the CL description."""
800 file_handle, filename = tempfile.mkstemp(text=True)
801 # Make sure CRLF is handled properly by requiring none.
802 if '\r' in content:
asvitkine@chromium.org0eff22d2011-10-25 16:11:16 +0000803 print >> sys.stderr, (
804 '!! Please remove \\r from your change description !!')
maruel@chromium.org0e0436a2011-10-25 13:32:41 +0000805 fileobj = os.fdopen(file_handle, 'w')
806 # Still remove \r if present.
807 fileobj.write(re.sub('\r?\n', '\n', content))
808 fileobj.close()
809
810 try:
jbroman@chromium.org615a2622013-05-03 13:20:14 +0000811 editor = GetEditor(git, git_editor=git_editor)
812 if not editor:
813 return None
814 cmd = '%s %s' % (editor, filename)
maruel@chromium.org0e0436a2011-10-25 13:32:41 +0000815 if sys.platform == 'win32' and os.environ.get('TERM') == 'msys':
816 # Msysgit requires the usage of 'env' to be present.
817 cmd = 'env ' + cmd
818 try:
819 # shell=True to allow the shell to handle all forms of quotes in
820 # $EDITOR.
821 subprocess2.check_call(cmd, shell=True)
822 except subprocess2.CalledProcessError:
823 return None
824 return FileRead(filename)
825 finally:
826 os.remove(filename)
maruel@chromium.org99ac1c52012-01-16 14:52:12 +0000827
828
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +0000829def UpgradeToHttps(url):
830 """Upgrades random urls to https://.
831
832 Do not touch unknown urls like ssh:// or git://.
833 Do not touch http:// urls with a port number,
834 Fixes invalid GAE url.
835 """
836 if not url:
837 return url
838 if not re.match(r'[a-z\-]+\://.*', url):
839 # Make sure it is a valid uri. Otherwise, urlparse() will consider it a
840 # relative url and will use http:///foo. Note that it defaults to http://
841 # for compatibility with naked url like "localhost:8080".
842 url = 'http://%s' % url
843 parsed = list(urlparse.urlparse(url))
844 # Do not automatically upgrade http to https if a port number is provided.
845 if parsed[0] == 'http' and not re.match(r'^.+?\:\d+$', parsed[1]):
846 parsed[0] = 'https'
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +0000847 return urlparse.urlunparse(parsed)
848
849
maruel@chromium.org99ac1c52012-01-16 14:52:12 +0000850def ParseCodereviewSettingsContent(content):
851 """Process a codereview.settings file properly."""
852 lines = (l for l in content.splitlines() if not l.strip().startswith("#"))
853 try:
854 keyvals = dict([x.strip() for x in l.split(':', 1)] for l in lines if l)
855 except ValueError:
856 raise Error(
857 'Failed to process settings, please fix. Content:\n\n%s' % content)
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +0000858 def fix_url(key):
859 if keyvals.get(key):
860 keyvals[key] = UpgradeToHttps(keyvals[key])
861 fix_url('CODE_REVIEW_SERVER')
862 fix_url('VIEW_VC')
maruel@chromium.org99ac1c52012-01-16 14:52:12 +0000863 return keyvals
ilevy@chromium.org13691502012-10-16 04:26:37 +0000864
865
866def NumLocalCpus():
867 """Returns the number of processors.
868
869 Python on OSX 10.6 raises a NotImplementedError exception.
870 """
871 try:
872 import multiprocessing
873 return multiprocessing.cpu_count()
874 except: # pylint: disable=W0702
875 # Mac OS 10.6 only
876 # pylint: disable=E1101
877 return int(os.sysconf('SC_NPROCESSORS_ONLN'))