blob: bf9803e86f3552c151d788cec8cf354233dc432a [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,
378 **kwargs):
379 """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
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000402 # Also, we need to forward stdout to prevent weird re-ordering of output.
403 # This has to be done on a per byte basis to make sure it is not buffered:
404 # normally buffering is done for each line, but if svn requests input, no
405 # end-of-line character is output after the prompt and it would not show up.
maruel@chromium.org109cb9d2011-09-14 20:03:11 +0000406 try:
407 in_byte = kid.stdout.read(1)
408 if in_byte:
409 if call_filter_on_first_line:
410 filter_fn(None)
411 in_line = ''
412 while in_byte:
413 if in_byte != '\r':
414 if print_stdout:
415 stdout.write(in_byte)
416 if in_byte != '\n':
417 in_line += in_byte
418 else:
419 filter_fn(in_line)
420 in_line = ''
szager@google.com85d3e3a2011-10-07 17:12:00 +0000421 else:
422 filter_fn(in_line)
423 in_line = ''
maruel@chromium.org109cb9d2011-09-14 20:03:11 +0000424 in_byte = kid.stdout.read(1)
425 # Flush the rest of buffered output. This is only an issue with
426 # stdout/stderr not ending with a \n.
427 if len(in_line):
428 filter_fn(in_line)
429 rv = kid.wait()
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000430
431 # Don't put this in a 'finally,' since the child may still run if we get an
432 # exception.
433 GClientChildren.remove(kid)
434
maruel@chromium.org109cb9d2011-09-14 20:03:11 +0000435 except KeyboardInterrupt:
436 print >> sys.stderr, 'Failed while running "%s"' % ' '.join(args)
437 raise
438
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000439 if rv:
maruel@chromium.orga82a8ee2011-09-08 18:41:37 +0000440 raise subprocess2.CalledProcessError(
441 rv, args, kwargs.get('cwd', None), None, None)
maruel@chromium.org17d01792010-09-01 18:07:10 +0000442 return 0
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000443
444
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000445def FindGclientRoot(from_dir, filename='.gclient'):
maruel@chromium.orga9371762009-12-22 18:27:38 +0000446 """Tries to find the gclient root."""
jochen@chromium.org20760a52010-09-08 08:47:28 +0000447 real_from_dir = os.path.realpath(from_dir)
448 path = real_from_dir
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000449 while not os.path.exists(os.path.join(path, filename)):
maruel@chromium.org3a292682010-08-23 18:54:55 +0000450 split_path = os.path.split(path)
451 if not split_path[1]:
maruel@chromium.orga9371762009-12-22 18:27:38 +0000452 return None
maruel@chromium.org3a292682010-08-23 18:54:55 +0000453 path = split_path[0]
jochen@chromium.org20760a52010-09-08 08:47:28 +0000454
455 # If we did not find the file in the current directory, make sure we are in a
456 # sub directory that is controlled by this configuration.
457 if path != real_from_dir:
458 entries_filename = os.path.join(path, filename + '_entries')
459 if not os.path.exists(entries_filename):
460 # If .gclient_entries does not exist, a previous call to gclient sync
461 # might have failed. In that case, we cannot verify that the .gclient
462 # is the one we want to use. In order to not to cause too much trouble,
463 # just issue a warning and return the path anyway.
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +0000464 print >> sys.stderr, ("%s file in parent directory %s might not be the "
jochen@chromium.org20760a52010-09-08 08:47:28 +0000465 "file you want to use" % (filename, path))
466 return path
467 scope = {}
468 try:
469 exec(FileRead(entries_filename), scope)
470 except SyntaxError, e:
471 SyntaxErrorToError(filename, e)
472 all_directories = scope['entries'].keys()
473 path_to_check = real_from_dir[len(path)+1:]
474 while path_to_check:
475 if path_to_check in all_directories:
476 return path
477 path_to_check = os.path.dirname(path_to_check)
478 return None
maruel@chromium.org3742c842010-09-09 19:27:14 +0000479
maruel@chromium.orgd9141bf2009-12-23 16:13:32 +0000480 logging.info('Found gclient root at ' + path)
maruel@chromium.orga9371762009-12-22 18:27:38 +0000481 return path
maruel@chromium.org3ccbf7e2009-12-22 20:46:42 +0000482
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000483
maruel@chromium.org3ccbf7e2009-12-22 20:46:42 +0000484def PathDifference(root, subpath):
485 """Returns the difference subpath minus root."""
486 root = os.path.realpath(root)
487 subpath = os.path.realpath(subpath)
488 if not subpath.startswith(root):
489 return None
490 # If the root does not have a trailing \ or /, we add it so the returned
491 # path starts immediately after the seperator regardless of whether it is
492 # provided.
493 root = os.path.join(root, '')
494 return subpath[len(root):]
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000495
496
497def FindFileUpwards(filename, path=None):
rcui@google.com13595ff2011-10-13 01:25:07 +0000498 """Search upwards from the a directory (default: current) to find a file.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000499
rcui@google.com13595ff2011-10-13 01:25:07 +0000500 Returns nearest upper-level directory with the passed in file.
501 """
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000502 if not path:
503 path = os.getcwd()
504 path = os.path.realpath(path)
505 while True:
506 file_path = os.path.join(path, filename)
rcui@google.com13595ff2011-10-13 01:25:07 +0000507 if os.path.exists(file_path):
508 return path
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000509 (new_path, _) = os.path.split(path)
510 if new_path == path:
511 return None
512 path = new_path
513
514
515def GetGClientRootAndEntries(path=None):
516 """Returns the gclient root and the dict of entries."""
517 config_file = '.gclient_entries'
maruel@chromium.org93a9ee02011-10-18 18:23:58 +0000518 root = FindFileUpwards(config_file, path)
519 if not root:
maruel@chromium.org116704f2010-06-11 17:34:38 +0000520 print "Can't find %s" % config_file
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000521 return None
maruel@chromium.org93a9ee02011-10-18 18:23:58 +0000522 config_path = os.path.join(root, config_file)
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000523 env = {}
524 execfile(config_path, env)
525 config_dir = os.path.dirname(config_path)
526 return config_dir, env['entries']
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000527
528
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000529def lockedmethod(method):
530 """Method decorator that holds self.lock for the duration of the call."""
531 def inner(self, *args, **kwargs):
532 try:
533 try:
534 self.lock.acquire()
535 except KeyboardInterrupt:
536 print >> sys.stderr, 'Was deadlocked'
537 raise
538 return method(self, *args, **kwargs)
539 finally:
540 self.lock.release()
541 return inner
542
543
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000544class WorkItem(object):
545 """One work item."""
maruel@chromium.org4901daf2011-10-20 14:34:47 +0000546 # On cygwin, creating a lock throwing randomly when nearing ~100 locks.
547 # As a workaround, use a single lock. Yep you read it right. Single lock for
548 # all the 100 objects.
549 lock = threading.Lock()
550
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000551 def __init__(self, name):
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000552 # A unique string representing this work item.
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000553 self._name = name
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000554
maruel@chromium.org77e4eca2010-09-21 13:23:07 +0000555 def run(self, work_queue):
556 """work_queue is passed as keyword argument so it should be
maruel@chromium.org3742c842010-09-09 19:27:14 +0000557 the last parameters of the function when you override it."""
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000558 pass
559
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000560 @property
561 def name(self):
562 return self._name
563
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000564
565class ExecutionQueue(object):
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000566 """Runs a set of WorkItem that have interdependencies and were WorkItem are
567 added as they are processed.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000568
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000569 In gclient's case, Dependencies sometime needs to be run out of order due to
570 From() keyword. This class manages that all the required dependencies are run
571 before running each one.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000572
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000573 Methods of this class are thread safe.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000574 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000575 def __init__(self, jobs, progress, ignore_requirements):
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000576 """jobs specifies the number of concurrent tasks to allow. progress is a
577 Progress instance."""
578 # Set when a thread is done or a new item is enqueued.
579 self.ready_cond = threading.Condition()
580 # Maximum number of concurrent tasks.
581 self.jobs = jobs
582 # List of WorkItem, for gclient, these are Dependency instances.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000583 self.queued = []
584 # List of strings representing each Dependency.name that was run.
585 self.ran = []
586 # List of items currently running.
587 self.running = []
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000588 # Exceptions thrown if any.
maruel@chromium.org3742c842010-09-09 19:27:14 +0000589 self.exceptions = Queue.Queue()
590 # Progress status
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000591 self.progress = progress
592 if self.progress:
maruel@chromium.org3742c842010-09-09 19:27:14 +0000593 self.progress.update(0)
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000594
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000595 self.ignore_requirements = ignore_requirements
596
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000597 def enqueue(self, d):
598 """Enqueue one Dependency to be executed later once its requirements are
599 satisfied.
600 """
601 assert isinstance(d, WorkItem)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000602 self.ready_cond.acquire()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000603 try:
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000604 self.queued.append(d)
605 total = len(self.queued) + len(self.ran) + len(self.running)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000606 logging.debug('enqueued(%s)' % d.name)
607 if self.progress:
608 self.progress._total = total + 1
609 self.progress.update(0)
610 self.ready_cond.notifyAll()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000611 finally:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000612 self.ready_cond.release()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000613
614 def flush(self, *args, **kwargs):
615 """Runs all enqueued items until all are executed."""
maruel@chromium.org3742c842010-09-09 19:27:14 +0000616 kwargs['work_queue'] = self
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000617 self.ready_cond.acquire()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000618 try:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000619 while True:
620 # Check for task to run first, then wait.
621 while True:
maruel@chromium.org3742c842010-09-09 19:27:14 +0000622 if not self.exceptions.empty():
623 # Systematically flush the queue when an exception logged.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000624 self.queued = []
maruel@chromium.org3742c842010-09-09 19:27:14 +0000625 self._flush_terminated_threads()
626 if (not self.queued and not self.running or
627 self.jobs == len(self.running)):
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000628 logging.debug('No more worker threads or can\'t queue anything.')
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000629 break
maruel@chromium.org3742c842010-09-09 19:27:14 +0000630
631 # Check for new tasks to start.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000632 for i in xrange(len(self.queued)):
633 # Verify its requirements.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000634 if (self.ignore_requirements or
635 not (set(self.queued[i].requirements) - set(self.ran))):
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000636 # Start one work item: all its requirements are satisfied.
maruel@chromium.org3742c842010-09-09 19:27:14 +0000637 self._run_one_task(self.queued.pop(i), args, kwargs)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000638 break
639 else:
640 # Couldn't find an item that could run. Break out the outher loop.
641 break
maruel@chromium.org3742c842010-09-09 19:27:14 +0000642
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000643 if not self.queued and not self.running:
maruel@chromium.org3742c842010-09-09 19:27:14 +0000644 # We're done.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000645 break
646 # We need to poll here otherwise Ctrl-C isn't processed.
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000647 try:
648 self.ready_cond.wait(10)
649 except KeyboardInterrupt:
650 # Help debugging by printing some information:
651 print >> sys.stderr, (
652 ('\nAllowed parallel jobs: %d\n# queued: %d\nRan: %s\n'
653 'Running: %d') % (
654 self.jobs,
655 len(self.queued),
656 ', '.join(self.ran),
657 len(self.running)))
658 for i in self.queued:
659 print >> sys.stderr, '%s: %s' % (i.name, ', '.join(i.requirements))
660 raise
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000661 # Something happened: self.enqueue() or a thread terminated. Loop again.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000662 finally:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000663 self.ready_cond.release()
maruel@chromium.org3742c842010-09-09 19:27:14 +0000664
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000665 assert not self.running, 'Now guaranteed to be single-threaded'
maruel@chromium.org3742c842010-09-09 19:27:14 +0000666 if not self.exceptions.empty():
maruel@chromium.orgc8d064b2010-08-16 16:46:14 +0000667 # To get back the stack location correctly, the raise a, b, c form must be
668 # used, passing a tuple as the first argument doesn't work.
maruel@chromium.org3742c842010-09-09 19:27:14 +0000669 e = self.exceptions.get()
maruel@chromium.orgc8d064b2010-08-16 16:46:14 +0000670 raise e[0], e[1], e[2]
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000671 if self.progress:
672 self.progress.end()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000673
maruel@chromium.org3742c842010-09-09 19:27:14 +0000674 def _flush_terminated_threads(self):
675 """Flush threads that have terminated."""
676 running = self.running
677 self.running = []
678 for t in running:
679 if t.isAlive():
680 self.running.append(t)
681 else:
682 t.join()
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000683 sys.stdout.flush()
maruel@chromium.org3742c842010-09-09 19:27:14 +0000684 if self.progress:
maruel@chromium.org55a2eb82010-10-06 23:35:18 +0000685 self.progress.update(1, t.item.name)
maruel@chromium.orgf36c0ee2011-09-14 19:16:47 +0000686 if t.item.name in self.ran:
687 raise Error(
688 'gclient is confused, "%s" is already in "%s"' % (
689 t.item.name, ', '.join(self.ran)))
maruel@chromium.orgacc45672010-09-09 21:21:21 +0000690 if not t.item.name in self.ran:
691 self.ran.append(t.item.name)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000692
693 def _run_one_task(self, task_item, args, kwargs):
694 if self.jobs > 1:
695 # Start the thread.
696 index = len(self.ran) + len(self.running) + 1
maruel@chromium.org77e4eca2010-09-21 13:23:07 +0000697 new_thread = self._Worker(task_item, index, args, kwargs)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000698 self.running.append(new_thread)
699 new_thread.start()
700 else:
701 # Run the 'thread' inside the main thread. Don't try to catch any
702 # exception.
703 task_item.run(*args, **kwargs)
704 self.ran.append(task_item.name)
705 if self.progress:
maruel@chromium.org55a2eb82010-10-06 23:35:18 +0000706 self.progress.update(1, ', '.join(t.item.name for t in self.running))
maruel@chromium.org3742c842010-09-09 19:27:14 +0000707
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000708 class _Worker(threading.Thread):
709 """One thread to execute one WorkItem."""
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000710 def __init__(self, item, index, args, kwargs):
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000711 threading.Thread.__init__(self, name=item.name or 'Worker')
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000712 logging.info('_Worker(%s) reqs:%s' % (item.name, item.requirements))
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000713 self.item = item
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000714 self.index = index
maruel@chromium.org3742c842010-09-09 19:27:14 +0000715 self.args = args
716 self.kwargs = kwargs
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000717 self.daemon = True
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000718
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000719 def run(self):
720 """Runs in its own thread."""
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000721 logging.debug('_Worker.run(%s)' % self.item.name)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000722 work_queue = self.kwargs['work_queue']
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000723 try:
724 self.item.run(*self.args, **self.kwargs)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000725 except KeyboardInterrupt:
726 logging.info('Caught KeyboardInterrupt in thread %s' % self.item.name)
727 logging.info(str(sys.exc_info()))
728 work_queue.exceptions.put(sys.exc_info())
729 raise
maruel@chromium.orgc8d064b2010-08-16 16:46:14 +0000730 except Exception:
731 # Catch exception location.
maruel@chromium.org3742c842010-09-09 19:27:14 +0000732 logging.info('Caught exception in thread %s' % self.item.name)
733 logging.info(str(sys.exc_info()))
734 work_queue.exceptions.put(sys.exc_info())
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000735 finally:
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000736 logging.info('_Worker.run(%s) done' % self.item.name)
737 work_queue.ready_cond.acquire()
738 try:
739 work_queue.ready_cond.notifyAll()
740 finally:
741 work_queue.ready_cond.release()
maruel@chromium.org0e0436a2011-10-25 13:32:41 +0000742
743
jbroman@chromium.org615a2622013-05-03 13:20:14 +0000744def GetEditor(git, git_editor=None):
745 """Returns the most plausible editor to use.
746
747 In order of preference:
748 - GIT_EDITOR/SVN_EDITOR environment variable
749 - core.editor git configuration variable (if supplied by git-cl)
750 - VISUAL environment variable
751 - EDITOR environment variable
752 - vim (non-Windows) or notepad (Windows)
753
754 In the case of git-cl, this matches git's behaviour, except that it does not
755 include dumb terminal detection.
756
757 In the case of gcl, this matches svn's behaviour, except that it does not
758 accept a command-line flag or check the editor-cmd configuration variable.
759 """
maruel@chromium.org0e0436a2011-10-25 13:32:41 +0000760 if git:
jbroman@chromium.org615a2622013-05-03 13:20:14 +0000761 editor = os.environ.get('GIT_EDITOR') or git_editor
maruel@chromium.org0e0436a2011-10-25 13:32:41 +0000762 else:
763 editor = os.environ.get('SVN_EDITOR')
764 if not editor:
jbroman@chromium.org615a2622013-05-03 13:20:14 +0000765 editor = os.environ.get('VISUAL')
766 if not editor:
maruel@chromium.org0e0436a2011-10-25 13:32:41 +0000767 editor = os.environ.get('EDITOR')
768 if not editor:
769 if sys.platform.startswith('win'):
770 editor = 'notepad'
771 else:
772 editor = 'vim'
773 return editor
774
775
jbroman@chromium.org615a2622013-05-03 13:20:14 +0000776def RunEditor(content, git, git_editor=None):
maruel@chromium.org0e0436a2011-10-25 13:32:41 +0000777 """Opens up the default editor in the system to get the CL description."""
778 file_handle, filename = tempfile.mkstemp(text=True)
779 # Make sure CRLF is handled properly by requiring none.
780 if '\r' in content:
asvitkine@chromium.org0eff22d2011-10-25 16:11:16 +0000781 print >> sys.stderr, (
782 '!! Please remove \\r from your change description !!')
maruel@chromium.org0e0436a2011-10-25 13:32:41 +0000783 fileobj = os.fdopen(file_handle, 'w')
784 # Still remove \r if present.
785 fileobj.write(re.sub('\r?\n', '\n', content))
786 fileobj.close()
787
788 try:
jbroman@chromium.org615a2622013-05-03 13:20:14 +0000789 editor = GetEditor(git, git_editor=git_editor)
790 if not editor:
791 return None
792 cmd = '%s %s' % (editor, filename)
maruel@chromium.org0e0436a2011-10-25 13:32:41 +0000793 if sys.platform == 'win32' and os.environ.get('TERM') == 'msys':
794 # Msysgit requires the usage of 'env' to be present.
795 cmd = 'env ' + cmd
796 try:
797 # shell=True to allow the shell to handle all forms of quotes in
798 # $EDITOR.
799 subprocess2.check_call(cmd, shell=True)
800 except subprocess2.CalledProcessError:
801 return None
802 return FileRead(filename)
803 finally:
804 os.remove(filename)
maruel@chromium.org99ac1c52012-01-16 14:52:12 +0000805
806
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +0000807def UpgradeToHttps(url):
808 """Upgrades random urls to https://.
809
810 Do not touch unknown urls like ssh:// or git://.
811 Do not touch http:// urls with a port number,
812 Fixes invalid GAE url.
813 """
814 if not url:
815 return url
816 if not re.match(r'[a-z\-]+\://.*', url):
817 # Make sure it is a valid uri. Otherwise, urlparse() will consider it a
818 # relative url and will use http:///foo. Note that it defaults to http://
819 # for compatibility with naked url like "localhost:8080".
820 url = 'http://%s' % url
821 parsed = list(urlparse.urlparse(url))
822 # Do not automatically upgrade http to https if a port number is provided.
823 if parsed[0] == 'http' and not re.match(r'^.+?\:\d+$', parsed[1]):
824 parsed[0] = 'https'
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +0000825 return urlparse.urlunparse(parsed)
826
827
maruel@chromium.org99ac1c52012-01-16 14:52:12 +0000828def ParseCodereviewSettingsContent(content):
829 """Process a codereview.settings file properly."""
830 lines = (l for l in content.splitlines() if not l.strip().startswith("#"))
831 try:
832 keyvals = dict([x.strip() for x in l.split(':', 1)] for l in lines if l)
833 except ValueError:
834 raise Error(
835 'Failed to process settings, please fix. Content:\n\n%s' % content)
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +0000836 def fix_url(key):
837 if keyvals.get(key):
838 keyvals[key] = UpgradeToHttps(keyvals[key])
839 fix_url('CODE_REVIEW_SERVER')
840 fix_url('VIEW_VC')
maruel@chromium.org99ac1c52012-01-16 14:52:12 +0000841 return keyvals
ilevy@chromium.org13691502012-10-16 04:26:37 +0000842
843
844def NumLocalCpus():
845 """Returns the number of processors.
846
847 Python on OSX 10.6 raises a NotImplementedError exception.
848 """
849 try:
850 import multiprocessing
851 return multiprocessing.cpu_count()
852 except: # pylint: disable=W0702
853 # Mac OS 10.6 only
854 # pylint: disable=E1101
855 return int(os.sysconf('SC_NPROCESSORS_ONLN'))