blob: 38341337a24984de2a3e5349a656c46e8d818501 [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
Raul Tambreb946b232019-03-26 14:48:46 +00007from __future__ import print_function
8
maruel@chromium.orgdae209f2012-07-03 16:08:15 +00009import codecs
Paweł Hajdan, Jr7e502612017-06-12 16:58:38 +020010import collections
Andrii Shyshkalov351c61d2017-01-21 20:40:16 +000011import contextlib
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000012import datetime
Raul Tambreb946b232019-03-26 14:48:46 +000013import functools
14import io
maruel@chromium.orgd9141bf2009-12-23 16:13:32 +000015import logging
Paweł Hajdan, Jr7e502612017-06-12 16:58:38 +020016import operator
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000017import os
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +000018import pipes
szager@chromium.orgfc616382014-03-18 20:32:04 +000019import platform
Raul Tambreb946b232019-03-26 14:48:46 +000020
21try:
22 import Queue as queue
23except ImportError: # For Py3 compatibility
24 import queue
25
msb@chromium.orgac915bb2009-11-13 17:03:01 +000026import re
bradnelson@google.com8f9c69f2009-09-17 00:48:28 +000027import stat
borenet@google.com6b4a2ab2013-04-18 15:50:27 +000028import subprocess
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000029import sys
maruel@chromium.org0e0436a2011-10-25 13:32:41 +000030import tempfile
maruel@chromium.org9e5317a2010-08-13 20:35:11 +000031import threading
maruel@chromium.org167b9e62009-09-17 17:41:02 +000032import time
Raul Tambreb946b232019-03-26 14:48:46 +000033
34try:
35 import urlparse
36except ImportError: # For Py3 compatibility
37 import urllib.parse as urlparse
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000038
maruel@chromium.orgca0f8392011-09-08 17:15:15 +000039import subprocess2
40
Raul Tambreb946b232019-03-26 14:48:46 +000041if sys.version_info.major == 2:
42 from cStringIO import StringIO
43else:
44 from io import StringIO
45
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000046
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +000047RETRY_MAX = 3
48RETRY_INITIAL_SLEEP = 0.5
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000049START = datetime.datetime.now()
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +000050
51
borenet@google.com6a9b1682014-03-24 18:35:23 +000052_WARNINGS = []
53
54
szager@chromium.orgff113292014-03-25 06:02:08 +000055# These repos are known to cause OOM errors on 32-bit platforms, due the the
56# very large objects they contain. It is not safe to use threaded index-pack
57# when cloning/fetching them.
58THREADED_INDEX_PACK_BLACKLIST = [
59 'https://chromium.googlesource.com/chromium/reference_builds/chrome_win.git'
60]
61
Raul Tambreb946b232019-03-26 14:48:46 +000062"""To support rethrowing exceptions with tracebacks on both Py2 and 3."""
63if sys.version_info.major == 2:
64 # We have to use exec to avoid a SyntaxError in Python 3.
65 exec("def reraise(typ, value, tb=None):\n raise typ, value, tb\n")
66else:
67 def reraise(typ, value, tb=None):
68 if value is None:
69 value = typ()
70 if value.__traceback__ is not tb:
71 raise value.with_traceback(tb)
72 raise value
73
szager@chromium.orgff113292014-03-25 06:02:08 +000074
maruel@chromium.org66c83e62010-09-07 14:18:45 +000075class Error(Exception):
76 """gclient exception class."""
szager@chromium.org4a3c17e2013-05-24 23:59:29 +000077 def __init__(self, msg, *args, **kwargs):
78 index = getattr(threading.currentThread(), 'index', 0)
79 if index:
80 msg = '\n'.join('%d> %s' % (index, l) for l in msg.splitlines())
81 super(Error, self).__init__(msg, *args, **kwargs)
maruel@chromium.org66c83e62010-09-07 14:18:45 +000082
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +000083
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000084def Elapsed(until=None):
85 if until is None:
86 until = datetime.datetime.now()
87 return str(until - START).partition('.')[0]
88
89
borenet@google.com6a9b1682014-03-24 18:35:23 +000090def PrintWarnings():
91 """Prints any accumulated warnings."""
92 if _WARNINGS:
Raul Tambreb946b232019-03-26 14:48:46 +000093 print('\n\nWarnings:', file=sys.stderr)
borenet@google.com6a9b1682014-03-24 18:35:23 +000094 for warning in _WARNINGS:
Raul Tambreb946b232019-03-26 14:48:46 +000095 print(warning, file=sys.stderr)
borenet@google.com6a9b1682014-03-24 18:35:23 +000096
97
98def AddWarning(msg):
99 """Adds the given warning message to the list of accumulated warnings."""
100 _WARNINGS.append(msg)
101
102
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000103def SplitUrlRevision(url):
104 """Splits url and returns a two-tuple: url, rev"""
105 if url.startswith('ssh:'):
maruel@chromium.org78b8cd12010-10-26 12:47:07 +0000106 # Make sure ssh://user-name@example.com/~/test.git@stable works
kangil.han@samsung.com71b13572013-10-16 17:28:11 +0000107 regex = r'(ssh://(?:[-.\w]+@)?[-\w:\.]+/[-~\w\./]+)(?:@(.+))?'
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000108 components = re.search(regex, url).groups()
109 else:
scr@chromium.orgf1eccaf2014-04-11 15:51:33 +0000110 components = url.rsplit('@', 1)
111 if re.match(r'^\w+\@', url) and '@' not in components[0]:
112 components = [url]
113
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000114 if len(components) == 1:
115 components += [None]
116 return tuple(components)
117
118
primiano@chromium.org5439ea52014-08-06 17:18:18 +0000119def IsGitSha(revision):
120 """Returns true if the given string is a valid hex-encoded sha"""
121 return re.match('^[a-fA-F0-9]{6,40}$', revision) is not None
122
123
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +0200124def IsFullGitSha(revision):
125 """Returns true if the given string is a valid hex-encoded full sha"""
126 return re.match('^[a-fA-F0-9]{40}$', revision) is not None
127
128
floitsch@google.comeaab7842011-04-28 09:07:58 +0000129def IsDateRevision(revision):
130 """Returns true if the given revision is of the form "{ ... }"."""
131 return bool(revision and re.match(r'^\{.+\}$', str(revision)))
132
133
134def MakeDateRevision(date):
135 """Returns a revision representing the latest revision before the given
136 date."""
137 return "{" + date + "}"
138
139
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000140def SyntaxErrorToError(filename, e):
141 """Raises a gclient_utils.Error exception with the human readable message"""
142 try:
143 # Try to construct a human readable error message
144 if filename:
145 error_message = 'There is a syntax error in %s\n' % filename
146 else:
147 error_message = 'There is a syntax error\n'
148 error_message += 'Line #%s, character %s: "%s"' % (
149 e.lineno, e.offset, re.sub(r'[\r\n]*$', '', e.text))
150 except:
151 # Something went wrong, re-raise the original exception
152 raise e
153 else:
154 raise Error(error_message)
155
156
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000157class PrintableObject(object):
158 def __str__(self):
159 output = ''
160 for i in dir(self):
161 if i.startswith('__'):
162 continue
163 output += '%s = %s\n' % (i, str(getattr(self, i, '')))
164 return output
165
166
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000167def FileRead(filename, mode='rU'):
Edward Lemur26a8b9f2019-08-15 20:46:44 +0000168 # On Python 3 newlines are converted to '\n' by default and 'U' is deprecated.
169 if mode == 'rU' and sys.version_info.major == 3:
170 mode = 'r'
maruel@chromium.org51e84fb2012-07-03 23:06:21 +0000171 with open(filename, mode=mode) as f:
maruel@chromium.orgc3cd5372012-07-11 17:39:24 +0000172 # codecs.open() has different behavior than open() on python 2.6 so use
173 # open() and decode manually.
chrisha@chromium.org2b99d432012-07-12 18:10:28 +0000174 s = f.read()
175 try:
176 return s.decode('utf-8')
Raul Tambreb946b232019-03-26 14:48:46 +0000177 # AttributeError is for Py3 compatibility
178 except (UnicodeDecodeError, AttributeError):
chrisha@chromium.org2b99d432012-07-12 18:10:28 +0000179 return s
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000180
181
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000182def FileWrite(filename, content, mode='w'):
maruel@chromium.orgdae209f2012-07-03 16:08:15 +0000183 with codecs.open(filename, mode=mode, encoding='utf-8') as f:
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000184 f.write(content)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000185
186
Andrii Shyshkalov351c61d2017-01-21 20:40:16 +0000187@contextlib.contextmanager
188def temporary_directory(**kwargs):
189 tdir = tempfile.mkdtemp(**kwargs)
190 try:
191 yield tdir
192 finally:
193 if tdir:
194 rmtree(tdir)
195
196
cyrille@nnamrak.orgef509e42013-09-20 13:19:08 +0000197def safe_rename(old, new):
198 """Renames a file reliably.
199
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +0000200 Sometimes os.rename does not work because a dying git process keeps a handle
201 on it for a few seconds. An exception is then thrown, which make the program
cyrille@nnamrak.orgef509e42013-09-20 13:19:08 +0000202 give up what it was doing and remove what was deleted.
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +0000203 The only solution is to catch the exception and try again until it works.
cyrille@nnamrak.orgef509e42013-09-20 13:19:08 +0000204 """
205 # roughly 10s
206 retries = 100
207 for i in range(retries):
208 try:
209 os.rename(old, new)
210 break
211 except OSError:
212 if i == (retries - 1):
213 # Give up.
214 raise
215 # retry
216 logging.debug("Renaming failed from %s to %s. Retrying ..." % (old, new))
217 time.sleep(0.1)
218
219
loislo@chromium.org67b59e92014-12-25 13:48:37 +0000220def rm_file_or_tree(path):
221 if os.path.isfile(path):
222 os.remove(path)
223 else:
224 rmtree(path)
225
226
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000227def rmtree(path):
228 """shutil.rmtree() on steroids.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000229
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000230 Recursively removes a directory, even if it's marked read-only.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000231
232 shutil.rmtree() doesn't work on Windows if any of the files or directories
agable41e3a6c2016-10-20 11:36:56 -0700233 are read-only. We need to be able to force the files to be writable (i.e.,
234 deletable) as we traverse the tree.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000235
236 Even with all this, Windows still sometimes fails to delete a file, citing
237 a permission error (maybe something to do with antivirus scans or disk
238 indexing). The best suggestion any of the user forums had was to wait a
239 bit and try again, so we do that too. It's hand-waving, but sometimes it
240 works. :/
241
242 On POSIX systems, things are a little bit simpler. The modes of the files
243 to be deleted doesn't matter, only the modes of the directories containing
244 them are significant. As the directory tree is traversed, each directory
245 has its mode set appropriately before descending into it. This should
246 result in the entire tree being removed, with the possible exception of
247 *path itself, because nothing attempts to change the mode of its parent.
248 Doing so would be hazardous, as it's not a directory slated for removal.
249 In the ordinary case, this is not a problem: for our purposes, the user
250 will never lack write permission on *path's parent.
251 """
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000252 if not os.path.exists(path):
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000253 return
254
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000255 if os.path.islink(path) or not os.path.isdir(path):
256 raise Error('Called rmtree(%s) in non-directory' % path)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000257
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000258 if sys.platform == 'win32':
borenet@google.com6b4a2ab2013-04-18 15:50:27 +0000259 # Give up and use cmd.exe's rd command.
260 path = os.path.normcase(path)
Raul Tambrec2f74c12019-03-19 05:55:53 +0000261 for _ in range(3):
borenet@google.com6b4a2ab2013-04-18 15:50:27 +0000262 exitcode = subprocess.call(['cmd.exe', '/c', 'rd', '/q', '/s', path])
263 if exitcode == 0:
264 return
265 else:
Raul Tambreb946b232019-03-26 14:48:46 +0000266 print('rd exited with code %d' % exitcode, file=sys.stderr)
borenet@google.com6b4a2ab2013-04-18 15:50:27 +0000267 time.sleep(3)
268 raise Exception('Failed to remove path %s' % path)
269
270 # On POSIX systems, we need the x-bit set on the directory to access it,
271 # the r-bit to see its contents, and the w-bit to remove files from it.
272 # The actual modes of the files within the directory is irrelevant.
273 os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000274
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000275 def remove(func, subpath):
borenet@google.com6b4a2ab2013-04-18 15:50:27 +0000276 func(subpath)
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000277
278 for fn in os.listdir(path):
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000279 # If fullpath is a symbolic link that points to a directory, isdir will
280 # be True, but we don't want to descend into that as a directory, we just
281 # want to remove the link. Check islink and treat links as ordinary files
282 # would be treated regardless of what they reference.
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000283 fullpath = os.path.join(path, fn)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000284 if os.path.islink(fullpath) or not os.path.isdir(fullpath):
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000285 remove(os.remove, fullpath)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000286 else:
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000287 # Recurse.
288 rmtree(fullpath)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000289
maruel@chromium.orgf9040722011-03-09 14:47:51 +0000290 remove(os.rmdir, path)
291
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000292
maruel@chromium.org6c48a302011-10-20 23:44:20 +0000293def safe_makedirs(tree):
294 """Creates the directory in a safe manner.
295
296 Because multiple threads can create these directories concurently, trap the
297 exception and pass on.
298 """
299 count = 0
300 while not os.path.exists(tree):
301 count += 1
302 try:
303 os.makedirs(tree)
Raul Tambreb946b232019-03-26 14:48:46 +0000304 except OSError as e:
maruel@chromium.org6c48a302011-10-20 23:44:20 +0000305 # 17 POSIX, 183 Windows
306 if e.errno not in (17, 183):
307 raise
308 if count > 40:
309 # Give up.
310 raise
311
312
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000313def CommandToStr(args):
314 """Converts an arg list into a shell escaped string."""
315 return ' '.join(pipes.quote(arg) for arg in args)
316
317
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000318class Wrapper(object):
319 """Wraps an object, acting as a transparent proxy for all properties by
320 default.
321 """
322 def __init__(self, wrapped):
323 self._wrapped = wrapped
324
325 def __getattr__(self, name):
326 return getattr(self._wrapped, name)
maruel@chromium.orgdb111f72010-09-08 13:36:53 +0000327
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +0000328
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000329class AutoFlush(Wrapper):
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +0000330 """Creates a file object clone to automatically flush after N seconds."""
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000331 def __init__(self, wrapped, delay):
332 super(AutoFlush, self).__init__(wrapped)
333 if not hasattr(self, 'lock'):
334 self.lock = threading.Lock()
335 self.__last_flushed_at = time.time()
336 self.delay = delay
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +0000337
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000338 @property
339 def autoflush(self):
340 return self
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +0000341
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000342 def write(self, out, *args, **kwargs):
343 self._wrapped.write(out, *args, **kwargs)
maruel@chromium.orgdb111f72010-09-08 13:36:53 +0000344 should_flush = False
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000345 self.lock.acquire()
maruel@chromium.org9c531262010-09-08 13:41:13 +0000346 try:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000347 if self.delay and (time.time() - self.__last_flushed_at) > self.delay:
maruel@chromium.orgdb111f72010-09-08 13:36:53 +0000348 should_flush = True
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000349 self.__last_flushed_at = time.time()
maruel@chromium.org9c531262010-09-08 13:41:13 +0000350 finally:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000351 self.lock.release()
maruel@chromium.orgdb111f72010-09-08 13:36:53 +0000352 if should_flush:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000353 self.flush()
maruel@chromium.orgdb111f72010-09-08 13:36:53 +0000354
355
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000356class Annotated(Wrapper):
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000357 """Creates a file object clone to automatically prepends every line in worker
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000358 threads with a NN> prefix.
359 """
360 def __init__(self, wrapped, include_zero=False):
361 super(Annotated, self).__init__(wrapped)
362 if not hasattr(self, 'lock'):
363 self.lock = threading.Lock()
364 self.__output_buffers = {}
365 self.__include_zero = include_zero
maruel@chromium.orgcb1e97a2010-09-09 20:09:20 +0000366
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000367 @property
368 def annotated(self):
369 return self
maruel@chromium.orgcb1e97a2010-09-09 20:09:20 +0000370
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000371 def write(self, out):
372 index = getattr(threading.currentThread(), 'index', 0)
373 if not index and not self.__include_zero:
374 # Unindexed threads aren't buffered.
375 return self._wrapped.write(out)
maruel@chromium.orgcb1e97a2010-09-09 20:09:20 +0000376
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000377 self.lock.acquire()
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000378 try:
379 # Use a dummy array to hold the string so the code can be lockless.
380 # Strings are immutable, requiring to keep a lock for the whole dictionary
381 # otherwise. Using an array is faster than using a dummy object.
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000382 if not index in self.__output_buffers:
383 obj = self.__output_buffers[index] = ['']
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000384 else:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000385 obj = self.__output_buffers[index]
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000386 finally:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000387 self.lock.release()
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000388
389 # Continue lockless.
390 obj[0] += out
Raul Tambre25eb8e42019-05-14 16:39:45 +0000391 while True:
392 # TODO(agable): find both of these with a single pass.
393 cr_loc = obj[0].find('\r')
394 lf_loc = obj[0].find('\n')
395 if cr_loc == lf_loc == -1:
396 break
397 elif cr_loc == -1 or (lf_loc >= 0 and lf_loc < cr_loc):
398 line, remaining = obj[0].split('\n', 1)
399 if line:
400 self._wrapped.write('%d>%s\n' % (index, line))
401 elif lf_loc == -1 or (cr_loc >= 0 and cr_loc < lf_loc):
402 line, remaining = obj[0].split('\r', 1)
403 if line:
404 self._wrapped.write('%d>%s\r' % (index, line))
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000405 obj[0] = remaining
406
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000407 def flush(self):
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000408 """Flush buffered output."""
409 orphans = []
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000410 self.lock.acquire()
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000411 try:
412 # Detect threads no longer existing.
413 indexes = (getattr(t, 'index', None) for t in threading.enumerate())
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +0000414 indexes = filter(None, indexes)
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000415 for index in self.__output_buffers:
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000416 if not index in indexes:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000417 orphans.append((index, self.__output_buffers[index][0]))
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000418 for orphan in orphans:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000419 del self.__output_buffers[orphan[0]]
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000420 finally:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000421 self.lock.release()
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000422
423 # Don't keep the lock while writting. Will append \n when it shouldn't.
424 for orphan in orphans:
nsylvain@google.come939bb52011-06-01 22:59:15 +0000425 if orphan[1]:
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000426 self._wrapped.write('%d>%s\n' % (orphan[0], orphan[1]))
427 return self._wrapped.flush()
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000428
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000429
430def MakeFileAutoFlush(fileobj, delay=10):
431 autoflush = getattr(fileobj, 'autoflush', None)
432 if autoflush:
433 autoflush.delay = delay
434 return fileobj
435 return AutoFlush(fileobj, delay)
436
437
438def MakeFileAnnotated(fileobj, include_zero=False):
439 if getattr(fileobj, 'annotated', None):
440 return fileobj
441 return Annotated(fileobj)
maruel@chromium.orgcb1e97a2010-09-09 20:09:20 +0000442
443
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000444GCLIENT_CHILDREN = []
445GCLIENT_CHILDREN_LOCK = threading.Lock()
446
447
448class GClientChildren(object):
449 @staticmethod
450 def add(popen_obj):
451 with GCLIENT_CHILDREN_LOCK:
452 GCLIENT_CHILDREN.append(popen_obj)
453
454 @staticmethod
455 def remove(popen_obj):
456 with GCLIENT_CHILDREN_LOCK:
457 GCLIENT_CHILDREN.remove(popen_obj)
458
459 @staticmethod
460 def _attemptToKillChildren():
461 global GCLIENT_CHILDREN
462 with GCLIENT_CHILDREN_LOCK:
463 zombies = [c for c in GCLIENT_CHILDREN if c.poll() is None]
464
465 for zombie in zombies:
466 try:
467 zombie.kill()
468 except OSError:
469 pass
470
471 with GCLIENT_CHILDREN_LOCK:
472 GCLIENT_CHILDREN = [k for k in GCLIENT_CHILDREN if k.poll() is not None]
473
474 @staticmethod
475 def _areZombies():
476 with GCLIENT_CHILDREN_LOCK:
477 return bool(GCLIENT_CHILDREN)
478
479 @staticmethod
480 def KillAllRemainingChildren():
481 GClientChildren._attemptToKillChildren()
482
483 if GClientChildren._areZombies():
484 time.sleep(0.5)
485 GClientChildren._attemptToKillChildren()
486
487 with GCLIENT_CHILDREN_LOCK:
488 if GCLIENT_CHILDREN:
Raul Tambreb946b232019-03-26 14:48:46 +0000489 print('Could not kill the following subprocesses:', file=sys.stderr)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000490 for zombie in GCLIENT_CHILDREN:
Raul Tambreb946b232019-03-26 14:48:46 +0000491 print(' ', zombie.pid, file=sys.stderr)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000492
493
Edward Lemur24146be2019-08-01 21:44:52 +0000494def CheckCallAndFilter(args, print_stdout=False, filter_fn=None,
495 show_header=False, always_show_header=False, retry=False,
496 **kwargs):
maruel@chromium.org17d01792010-09-01 18:07:10 +0000497 """Runs a command and calls back a filter function if needed.
498
maruel@chromium.org57bf78d2011-09-08 18:57:33 +0000499 Accepts all subprocess2.Popen() parameters plus:
maruel@chromium.org17d01792010-09-01 18:07:10 +0000500 print_stdout: If True, the command's stdout is forwarded to stdout.
501 filter_fn: A function taking a single string argument called with each line
maruel@chromium.org57bf78d2011-09-08 18:57:33 +0000502 of the subprocess2's output. Each line has the trailing newline
maruel@chromium.org17d01792010-09-01 18:07:10 +0000503 character trimmed.
Edward Lemur24146be2019-08-01 21:44:52 +0000504 show_header: Whether to display a header before the command output.
505 always_show_header: Show header even when the command produced no output.
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000506 retry: If the process exits non-zero, sleep for a brief interval and try
507 again, up to RETRY_MAX times.
maruel@chromium.org17d01792010-09-01 18:07:10 +0000508
509 stderr is always redirected to stdout.
Edward Lemur24146be2019-08-01 21:44:52 +0000510
511 Returns the output of the command as a binary string.
maruel@chromium.org17d01792010-09-01 18:07:10 +0000512 """
Edward Lemur24146be2019-08-01 21:44:52 +0000513 def show_header_if_necessary(needs_header, attempt):
514 """Show the header at most once."""
515 if not needs_header[0]:
516 return
517
518 needs_header[0] = False
519 # Automatically generated header. We only prepend a newline if
520 # always_show_header is false, since it usually indicates there's an
521 # external progress display, and it's better not to clobber it in that case.
522 header = '' if always_show_header else '\n'
523 header += '________ running \'%s\' in \'%s\'' % (
524 ' '.join(args), kwargs.get('cwd', '.'))
525 if attempt:
526 header += ' attempt %s / %s' % (attempt + 1, RETRY_MAX + 1)
527 header += '\n'
528
529 if print_stdout:
Edward Lemurefce0d12019-09-07 03:36:37 +0000530 stdout_write = getattr(sys.stdout, 'buffer', sys.stdout).write
531 stdout_write(header.encode())
Edward Lemur24146be2019-08-01 21:44:52 +0000532 if filter_fn:
533 filter_fn(header)
534
535 def filter_line(command_output, line_start):
536 """Extract the last line from command output and filter it."""
537 if not filter_fn or line_start is None:
538 return
539 command_output.seek(line_start)
540 filter_fn(command_output.read().decode('utf-8'))
541
542 # Initialize stdout writer if needed. On Python 3, sys.stdout does not accept
543 # byte inputs and sys.stdout.buffer must be used instead.
544 if print_stdout:
545 sys.stdout.flush()
546 stdout_write = getattr(sys.stdout, 'buffer', sys.stdout).write
547 else:
548 stdout_write = lambda _: None
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000549
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000550 sleep_interval = RETRY_INITIAL_SLEEP
551 run_cwd = kwargs.get('cwd', os.getcwd())
Edward Lemur24146be2019-08-01 21:44:52 +0000552 for attempt in range(RETRY_MAX + 1):
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000553 kid = subprocess2.Popen(
554 args, bufsize=0, stdout=subprocess2.PIPE, stderr=subprocess2.STDOUT,
555 **kwargs)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000556
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000557 GClientChildren.add(kid)
chase@chromium.org8ad1cee2010-08-16 19:12:27 +0000558
Edward Lemur24146be2019-08-01 21:44:52 +0000559 # Store the output of the command regardless of the value of print_stdout or
560 # filter_fn.
561 command_output = io.BytesIO()
562
563 # Passed as a list for "by ref" semantics.
564 needs_header = [show_header]
565 if always_show_header:
566 show_header_if_necessary(needs_header, attempt)
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000567
568 # Also, we need to forward stdout to prevent weird re-ordering of output.
569 # This has to be done on a per byte basis to make sure it is not buffered:
agable41e3a6c2016-10-20 11:36:56 -0700570 # normally buffering is done for each line, but if the process requests
571 # input, no end-of-line character is output after the prompt and it would
572 # not show up.
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000573 try:
Edward Lemur24146be2019-08-01 21:44:52 +0000574 line_start = None
575 while True:
576 in_byte = kid.stdout.read(1)
577 is_newline = in_byte in (b'\n', b'\r')
578 if not in_byte:
579 break
580
581 show_header_if_necessary(needs_header, attempt)
582
583 if is_newline:
584 filter_line(command_output, line_start)
585 line_start = None
586 elif line_start is None:
587 line_start = command_output.tell()
588
589 stdout_write(in_byte)
590 command_output.write(in_byte)
591
592 # Flush the rest of buffered output.
593 sys.stdout.flush()
594 if line_start is not None:
595 filter_line(command_output, line_start)
596
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000597 rv = kid.wait()
Edward Lemurdf746d02019-07-27 00:42:46 +0000598 kid.stdout.close()
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000599
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000600 # Don't put this in a 'finally,' since the child may still run if we get
601 # an exception.
602 GClientChildren.remove(kid)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000603
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000604 except KeyboardInterrupt:
Raul Tambreb946b232019-03-26 14:48:46 +0000605 print('Failed while running "%s"' % ' '.join(args), file=sys.stderr)
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000606 raise
maruel@chromium.org109cb9d2011-09-14 20:03:11 +0000607
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000608 if rv == 0:
Edward Lemur24146be2019-08-01 21:44:52 +0000609 return command_output.getvalue()
610
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000611 if not retry:
612 break
Edward Lemur24146be2019-08-01 21:44:52 +0000613
Raul Tambreb946b232019-03-26 14:48:46 +0000614 print("WARNING: subprocess '%s' in %s failed; will retry after a short "
615 'nap...' % (' '.join('"%s"' % x for x in args), run_cwd))
raphael.kubo.da.costa@intel.com91507f72013-10-22 12:18:25 +0000616 time.sleep(sleep_interval)
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000617 sleep_interval *= 2
Edward Lemur24146be2019-08-01 21:44:52 +0000618
szager@chromium.orgf2d7d6b2013-10-17 20:41:43 +0000619 raise subprocess2.CalledProcessError(
620 rv, args, kwargs.get('cwd', None), None, None)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000621
622
agable@chromium.org5a306a22014-02-24 22:13:59 +0000623class GitFilter(object):
624 """A filter_fn implementation for quieting down git output messages.
625
626 Allows a custom function to skip certain lines (predicate), and will throttle
627 the output of percentage completed lines to only output every X seconds.
628 """
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000629 PERCENT_RE = re.compile('(.*) ([0-9]{1,3})% .*')
agable@chromium.org5a306a22014-02-24 22:13:59 +0000630
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000631 def __init__(self, time_throttle=0, predicate=None, out_fh=None):
agable@chromium.org5a306a22014-02-24 22:13:59 +0000632 """
633 Args:
634 time_throttle (int): GitFilter will throttle 'noisy' output (such as the
635 XX% complete messages) to only be printed at least |time_throttle|
636 seconds apart.
637 predicate (f(line)): An optional function which is invoked for every line.
638 The line will be skipped if predicate(line) returns False.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000639 out_fh: File handle to write output to.
agable@chromium.org5a306a22014-02-24 22:13:59 +0000640 """
Edward Lemur24146be2019-08-01 21:44:52 +0000641 self.first_line = True
agable@chromium.org5a306a22014-02-24 22:13:59 +0000642 self.last_time = 0
643 self.time_throttle = time_throttle
644 self.predicate = predicate
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000645 self.out_fh = out_fh or sys.stdout
646 self.progress_prefix = None
agable@chromium.org5a306a22014-02-24 22:13:59 +0000647
648 def __call__(self, line):
649 # git uses an escape sequence to clear the line; elide it.
Christian Biesinger1b4c7e92019-08-08 19:33:16 +0000650 esc = line.find(chr(0o33))
agable@chromium.org5a306a22014-02-24 22:13:59 +0000651 if esc > -1:
652 line = line[:esc]
653 if self.predicate and not self.predicate(line):
654 return
655 now = time.time()
Christian Biesinger1b4c7e92019-08-08 19:33:16 +0000656 match = self.PERCENT_RE.match(line)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000657 if match:
658 if match.group(1) != self.progress_prefix:
659 self.progress_prefix = match.group(1)
660 elif now - self.last_time < self.time_throttle:
661 return
662 self.last_time = now
Edward Lemur24146be2019-08-01 21:44:52 +0000663 if not self.first_line:
664 self.out_fh.write('[%s] ' % Elapsed())
665 self.first_line = False
Raul Tambreb946b232019-03-26 14:48:46 +0000666 print(line, file=self.out_fh)
agable@chromium.org5a306a22014-02-24 22:13:59 +0000667
668
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000669def FindFileUpwards(filename, path=None):
rcui@google.com13595ff2011-10-13 01:25:07 +0000670 """Search upwards from the a directory (default: current) to find a file.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000671
rcui@google.com13595ff2011-10-13 01:25:07 +0000672 Returns nearest upper-level directory with the passed in file.
673 """
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000674 if not path:
675 path = os.getcwd()
676 path = os.path.realpath(path)
677 while True:
678 file_path = os.path.join(path, filename)
rcui@google.com13595ff2011-10-13 01:25:07 +0000679 if os.path.exists(file_path):
680 return path
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000681 (new_path, _) = os.path.split(path)
682 if new_path == path:
683 return None
684 path = new_path
685
686
nick@chromium.org3ac1c4e2014-01-16 02:44:42 +0000687def GetMacWinOrLinux():
688 """Returns 'mac', 'win', or 'linux', matching the current platform."""
689 if sys.platform.startswith(('cygwin', 'win')):
690 return 'win'
691 elif sys.platform.startswith('linux'):
692 return 'linux'
693 elif sys.platform == 'darwin':
694 return 'mac'
695 raise Error('Unknown platform: ' + sys.platform)
696
697
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000698def GetGClientRootAndEntries(path=None):
699 """Returns the gclient root and the dict of entries."""
700 config_file = '.gclient_entries'
maruel@chromium.org93a9ee02011-10-18 18:23:58 +0000701 root = FindFileUpwards(config_file, path)
702 if not root:
Raul Tambreb946b232019-03-26 14:48:46 +0000703 print("Can't find %s" % config_file)
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000704 return None
maruel@chromium.org93a9ee02011-10-18 18:23:58 +0000705 config_path = os.path.join(root, config_file)
piman@chromium.orgf43d0192010-04-15 02:36:04 +0000706 env = {}
707 execfile(config_path, env)
708 config_dir = os.path.dirname(config_path)
709 return config_dir, env['entries']
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000710
711
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000712def lockedmethod(method):
713 """Method decorator that holds self.lock for the duration of the call."""
714 def inner(self, *args, **kwargs):
715 try:
716 try:
717 self.lock.acquire()
718 except KeyboardInterrupt:
Raul Tambreb946b232019-03-26 14:48:46 +0000719 print('Was deadlocked', file=sys.stderr)
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000720 raise
721 return method(self, *args, **kwargs)
722 finally:
723 self.lock.release()
724 return inner
725
726
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000727class WorkItem(object):
728 """One work item."""
maruel@chromium.org4901daf2011-10-20 14:34:47 +0000729 # On cygwin, creating a lock throwing randomly when nearing ~100 locks.
730 # As a workaround, use a single lock. Yep you read it right. Single lock for
731 # all the 100 objects.
732 lock = threading.Lock()
733
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000734 def __init__(self, name):
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000735 # A unique string representing this work item.
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000736 self._name = name
Raul Tambreb946b232019-03-26 14:48:46 +0000737 self.outbuf = StringIO()
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000738 self.start = self.finish = None
hinoka885e5b12016-06-08 14:40:09 -0700739 self.resources = [] # List of resources this work item requires.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000740
maruel@chromium.org77e4eca2010-09-21 13:23:07 +0000741 def run(self, work_queue):
742 """work_queue is passed as keyword argument so it should be
maruel@chromium.org3742c842010-09-09 19:27:14 +0000743 the last parameters of the function when you override it."""
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000744 pass
745
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000746 @property
747 def name(self):
748 return self._name
749
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000750
751class ExecutionQueue(object):
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000752 """Runs a set of WorkItem that have interdependencies and were WorkItem are
753 added as they are processed.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000754
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200755 This class manages that all the required dependencies are run
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000756 before running each one.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000757
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000758 Methods of this class are thread safe.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000759 """
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000760 def __init__(self, jobs, progress, ignore_requirements, verbose=False):
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000761 """jobs specifies the number of concurrent tasks to allow. progress is a
762 Progress instance."""
763 # Set when a thread is done or a new item is enqueued.
764 self.ready_cond = threading.Condition()
765 # Maximum number of concurrent tasks.
766 self.jobs = jobs
767 # List of WorkItem, for gclient, these are Dependency instances.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000768 self.queued = []
769 # List of strings representing each Dependency.name that was run.
770 self.ran = []
771 # List of items currently running.
772 self.running = []
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000773 # Exceptions thrown if any.
Raul Tambreb946b232019-03-26 14:48:46 +0000774 self.exceptions = queue.Queue()
maruel@chromium.org3742c842010-09-09 19:27:14 +0000775 # Progress status
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000776 self.progress = progress
777 if self.progress:
maruel@chromium.org3742c842010-09-09 19:27:14 +0000778 self.progress.update(0)
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000779
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000780 self.ignore_requirements = ignore_requirements
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000781 self.verbose = verbose
782 self.last_join = None
783 self.last_subproc_output = None
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000784
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000785 def enqueue(self, d):
786 """Enqueue one Dependency to be executed later once its requirements are
787 satisfied.
788 """
789 assert isinstance(d, WorkItem)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000790 self.ready_cond.acquire()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000791 try:
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000792 self.queued.append(d)
793 total = len(self.queued) + len(self.ran) + len(self.running)
szager@chromium.orge98e04c2014-07-25 00:28:06 +0000794 if self.jobs == 1:
795 total += 1
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000796 logging.debug('enqueued(%s)' % d.name)
797 if self.progress:
szager@chromium.orge98e04c2014-07-25 00:28:06 +0000798 self.progress._total = total
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000799 self.progress.update(0)
800 self.ready_cond.notifyAll()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000801 finally:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000802 self.ready_cond.release()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000803
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000804 def out_cb(self, _):
805 self.last_subproc_output = datetime.datetime.now()
806 return True
807
808 @staticmethod
809 def format_task_output(task, comment=''):
810 if comment:
811 comment = ' (%s)' % comment
812 if task.start and task.finish:
813 elapsed = ' (Elapsed: %s)' % (
814 str(task.finish - task.start).partition('.')[0])
815 else:
816 elapsed = ''
817 return """
818%s%s%s
819----------------------------------------
820%s
821----------------------------------------""" % (
szager@chromium.org1f4e71b2014-04-09 19:45:40 +0000822 task.name, comment, elapsed, task.outbuf.getvalue().strip())
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000823
hinoka885e5b12016-06-08 14:40:09 -0700824 def _is_conflict(self, job):
825 """Checks to see if a job will conflict with another running job."""
826 for running_job in self.running:
827 for used_resource in running_job.item.resources:
828 logging.debug('Checking resource %s' % used_resource)
829 if used_resource in job.resources:
830 return True
831 return False
832
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000833 def flush(self, *args, **kwargs):
834 """Runs all enqueued items until all are executed."""
maruel@chromium.org3742c842010-09-09 19:27:14 +0000835 kwargs['work_queue'] = self
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000836 self.last_subproc_output = self.last_join = datetime.datetime.now()
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000837 self.ready_cond.acquire()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000838 try:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000839 while True:
840 # Check for task to run first, then wait.
841 while True:
maruel@chromium.org3742c842010-09-09 19:27:14 +0000842 if not self.exceptions.empty():
843 # Systematically flush the queue when an exception logged.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000844 self.queued = []
maruel@chromium.org3742c842010-09-09 19:27:14 +0000845 self._flush_terminated_threads()
846 if (not self.queued and not self.running or
847 self.jobs == len(self.running)):
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000848 logging.debug('No more worker threads or can\'t queue anything.')
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000849 break
maruel@chromium.org3742c842010-09-09 19:27:14 +0000850
851 # Check for new tasks to start.
Raul Tambreb946b232019-03-26 14:48:46 +0000852 for i in range(len(self.queued)):
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000853 # Verify its requirements.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000854 if (self.ignore_requirements or
855 not (set(self.queued[i].requirements) - set(self.ran))):
hinoka885e5b12016-06-08 14:40:09 -0700856 if not self._is_conflict(self.queued[i]):
857 # Start one work item: all its requirements are satisfied.
858 self._run_one_task(self.queued.pop(i), args, kwargs)
859 break
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000860 else:
861 # Couldn't find an item that could run. Break out the outher loop.
862 break
maruel@chromium.org3742c842010-09-09 19:27:14 +0000863
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000864 if not self.queued and not self.running:
maruel@chromium.org3742c842010-09-09 19:27:14 +0000865 # We're done.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000866 break
867 # We need to poll here otherwise Ctrl-C isn't processed.
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000868 try:
869 self.ready_cond.wait(10)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000870 # If we haven't printed to terminal for a while, but we have received
871 # spew from a suprocess, let the user know we're still progressing.
872 now = datetime.datetime.now()
873 if (now - self.last_join > datetime.timedelta(seconds=60) and
874 self.last_subproc_output > self.last_join):
875 if self.progress:
Raul Tambreb946b232019-03-26 14:48:46 +0000876 print('')
hinoka@google.com4dfb8662014-04-25 22:21:24 +0000877 sys.stdout.flush()
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000878 elapsed = Elapsed()
Raul Tambreb946b232019-03-26 14:48:46 +0000879 print('[%s] Still working on:' % elapsed)
hinoka@google.com4dfb8662014-04-25 22:21:24 +0000880 sys.stdout.flush()
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000881 for task in self.running:
Raul Tambreb946b232019-03-26 14:48:46 +0000882 print('[%s] %s' % (elapsed, task.item.name))
hinoka@google.com4dfb8662014-04-25 22:21:24 +0000883 sys.stdout.flush()
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000884 except KeyboardInterrupt:
885 # Help debugging by printing some information:
Raul Tambreb946b232019-03-26 14:48:46 +0000886 print(
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000887 ('\nAllowed parallel jobs: %d\n# queued: %d\nRan: %s\n'
Raul Tambreb946b232019-03-26 14:48:46 +0000888 'Running: %d') % (self.jobs, len(self.queued), ', '.join(
889 self.ran), len(self.running)),
890 file=sys.stderr)
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000891 for i in self.queued:
Raul Tambreb946b232019-03-26 14:48:46 +0000892 print(
893 '%s (not started): %s' % (i.name, ', '.join(i.requirements)),
894 file=sys.stderr)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000895 for i in self.running:
Raul Tambreb946b232019-03-26 14:48:46 +0000896 print(
897 self.format_task_output(i.item, 'interrupted'), file=sys.stderr)
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000898 raise
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000899 # Something happened: self.enqueue() or a thread terminated. Loop again.
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000900 finally:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000901 self.ready_cond.release()
maruel@chromium.org3742c842010-09-09 19:27:14 +0000902
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000903 assert not self.running, 'Now guaranteed to be single-threaded'
maruel@chromium.org3742c842010-09-09 19:27:14 +0000904 if not self.exceptions.empty():
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000905 if self.progress:
Raul Tambreb946b232019-03-26 14:48:46 +0000906 print('')
maruel@chromium.orgc8d064b2010-08-16 16:46:14 +0000907 # To get back the stack location correctly, the raise a, b, c form must be
908 # used, passing a tuple as the first argument doesn't work.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000909 e, task = self.exceptions.get()
Raul Tambreb946b232019-03-26 14:48:46 +0000910 print(self.format_task_output(task.item, 'ERROR'), file=sys.stderr)
911 reraise(e[0], e[1], e[2])
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000912 elif self.progress:
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000913 self.progress.end()
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000914
maruel@chromium.org3742c842010-09-09 19:27:14 +0000915 def _flush_terminated_threads(self):
916 """Flush threads that have terminated."""
917 running = self.running
918 self.running = []
919 for t in running:
Edward Lemura877ee62019-09-03 20:23:17 +0000920 if t.is_alive():
maruel@chromium.org3742c842010-09-09 19:27:14 +0000921 self.running.append(t)
922 else:
923 t.join()
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000924 self.last_join = datetime.datetime.now()
maruel@chromium.org042f0e72011-10-23 00:04:35 +0000925 sys.stdout.flush()
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000926 if self.verbose:
Raul Tambreb946b232019-03-26 14:48:46 +0000927 print(self.format_task_output(t.item))
maruel@chromium.org3742c842010-09-09 19:27:14 +0000928 if self.progress:
maruel@chromium.org55a2eb82010-10-06 23:35:18 +0000929 self.progress.update(1, t.item.name)
maruel@chromium.orgf36c0ee2011-09-14 19:16:47 +0000930 if t.item.name in self.ran:
931 raise Error(
932 'gclient is confused, "%s" is already in "%s"' % (
933 t.item.name, ', '.join(self.ran)))
maruel@chromium.orgacc45672010-09-09 21:21:21 +0000934 if not t.item.name in self.ran:
935 self.ran.append(t.item.name)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000936
937 def _run_one_task(self, task_item, args, kwargs):
938 if self.jobs > 1:
939 # Start the thread.
940 index = len(self.ran) + len(self.running) + 1
maruel@chromium.org77e4eca2010-09-21 13:23:07 +0000941 new_thread = self._Worker(task_item, index, args, kwargs)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000942 self.running.append(new_thread)
943 new_thread.start()
944 else:
945 # Run the 'thread' inside the main thread. Don't try to catch any
946 # exception.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000947 try:
948 task_item.start = datetime.datetime.now()
Raul Tambreb946b232019-03-26 14:48:46 +0000949 print('[%s] Started.' % Elapsed(task_item.start), file=task_item.outbuf)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000950 task_item.run(*args, **kwargs)
951 task_item.finish = datetime.datetime.now()
Raul Tambreb946b232019-03-26 14:48:46 +0000952 print(
953 '[%s] Finished.' % Elapsed(task_item.finish), file=task_item.outbuf)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000954 self.ran.append(task_item.name)
955 if self.verbose:
956 if self.progress:
Raul Tambreb946b232019-03-26 14:48:46 +0000957 print('')
958 print(self.format_task_output(task_item))
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000959 if self.progress:
960 self.progress.update(1, ', '.join(t.item.name for t in self.running))
961 except KeyboardInterrupt:
Raul Tambreb946b232019-03-26 14:48:46 +0000962 print(
963 self.format_task_output(task_item, 'interrupted'), file=sys.stderr)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000964 raise
965 except Exception:
Raul Tambreb946b232019-03-26 14:48:46 +0000966 print(self.format_task_output(task_item, 'ERROR'), file=sys.stderr)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000967 raise
968
maruel@chromium.org3742c842010-09-09 19:27:14 +0000969
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000970 class _Worker(threading.Thread):
971 """One thread to execute one WorkItem."""
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000972 def __init__(self, item, index, args, kwargs):
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000973 threading.Thread.__init__(self, name=item.name or 'Worker')
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000974 logging.info('_Worker(%s) reqs:%s' % (item.name, item.requirements))
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000975 self.item = item
maruel@chromium.org4ed34182010-09-17 15:57:47 +0000976 self.index = index
maruel@chromium.org3742c842010-09-09 19:27:14 +0000977 self.args = args
978 self.kwargs = kwargs
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000979 self.daemon = True
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000980
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000981 def run(self):
982 """Runs in its own thread."""
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000983 logging.debug('_Worker.run(%s)' % self.item.name)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000984 work_queue = self.kwargs['work_queue']
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000985 try:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000986 self.item.start = datetime.datetime.now()
Raul Tambreb946b232019-03-26 14:48:46 +0000987 print('[%s] Started.' % Elapsed(self.item.start), file=self.item.outbuf)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000988 self.item.run(*self.args, **self.kwargs)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000989 self.item.finish = datetime.datetime.now()
Raul Tambreb946b232019-03-26 14:48:46 +0000990 print(
991 '[%s] Finished.' % Elapsed(self.item.finish), file=self.item.outbuf)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000992 except KeyboardInterrupt:
xusydoc@chromium.orgc144e062013-05-03 23:23:53 +0000993 logging.info('Caught KeyboardInterrupt in thread %s', self.item.name)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000994 logging.info(str(sys.exc_info()))
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000995 work_queue.exceptions.put((sys.exc_info(), self))
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +0000996 raise
maruel@chromium.orgc8d064b2010-08-16 16:46:14 +0000997 except Exception:
998 # Catch exception location.
xusydoc@chromium.orgc144e062013-05-03 23:23:53 +0000999 logging.info('Caught exception in thread %s', self.item.name)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001000 logging.info(str(sys.exc_info()))
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001001 work_queue.exceptions.put((sys.exc_info(), self))
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001002 finally:
xusydoc@chromium.orgc144e062013-05-03 23:23:53 +00001003 logging.info('_Worker.run(%s) done', self.item.name)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00001004 work_queue.ready_cond.acquire()
1005 try:
1006 work_queue.ready_cond.notifyAll()
1007 finally:
1008 work_queue.ready_cond.release()
maruel@chromium.org0e0436a2011-10-25 13:32:41 +00001009
1010
agable92bec4f2016-08-24 09:27:27 -07001011def GetEditor(git_editor=None):
jbroman@chromium.org615a2622013-05-03 13:20:14 +00001012 """Returns the most plausible editor to use.
1013
1014 In order of preference:
agable41e3a6c2016-10-20 11:36:56 -07001015 - GIT_EDITOR environment variable
jbroman@chromium.org615a2622013-05-03 13:20:14 +00001016 - core.editor git configuration variable (if supplied by git-cl)
1017 - VISUAL environment variable
1018 - EDITOR environment variable
bratell@opera.com65621c72013-12-09 15:05:32 +00001019 - vi (non-Windows) or notepad (Windows)
jbroman@chromium.org615a2622013-05-03 13:20:14 +00001020
1021 In the case of git-cl, this matches git's behaviour, except that it does not
1022 include dumb terminal detection.
jbroman@chromium.org615a2622013-05-03 13:20:14 +00001023 """
agable92bec4f2016-08-24 09:27:27 -07001024 editor = os.environ.get('GIT_EDITOR') or git_editor
maruel@chromium.org0e0436a2011-10-25 13:32:41 +00001025 if not editor:
jbroman@chromium.org615a2622013-05-03 13:20:14 +00001026 editor = os.environ.get('VISUAL')
1027 if not editor:
maruel@chromium.org0e0436a2011-10-25 13:32:41 +00001028 editor = os.environ.get('EDITOR')
1029 if not editor:
1030 if sys.platform.startswith('win'):
1031 editor = 'notepad'
1032 else:
bratell@opera.com65621c72013-12-09 15:05:32 +00001033 editor = 'vi'
maruel@chromium.org0e0436a2011-10-25 13:32:41 +00001034 return editor
1035
1036
jbroman@chromium.org615a2622013-05-03 13:20:14 +00001037def RunEditor(content, git, git_editor=None):
maruel@chromium.org0e0436a2011-10-25 13:32:41 +00001038 """Opens up the default editor in the system to get the CL description."""
maruel@chromium.orgcbd760f2013-07-23 13:02:48 +00001039 file_handle, filename = tempfile.mkstemp(text=True, prefix='cl_description')
maruel@chromium.org0e0436a2011-10-25 13:32:41 +00001040 # Make sure CRLF is handled properly by requiring none.
1041 if '\r' in content:
Raul Tambreb946b232019-03-26 14:48:46 +00001042 print(
1043 '!! Please remove \\r from your change description !!', file=sys.stderr)
maruel@chromium.org0e0436a2011-10-25 13:32:41 +00001044 fileobj = os.fdopen(file_handle, 'w')
1045 # Still remove \r if present.
gab@chromium.orga3fe2902016-04-20 18:31:37 +00001046 content = re.sub('\r?\n', '\n', content)
1047 # Some editors complain when the file doesn't end in \n.
1048 if not content.endswith('\n'):
1049 content += '\n'
1050 fileobj.write(content)
maruel@chromium.org0e0436a2011-10-25 13:32:41 +00001051 fileobj.close()
1052
1053 try:
agable92bec4f2016-08-24 09:27:27 -07001054 editor = GetEditor(git_editor=git_editor)
jbroman@chromium.org615a2622013-05-03 13:20:14 +00001055 if not editor:
1056 return None
1057 cmd = '%s %s' % (editor, filename)
maruel@chromium.org0e0436a2011-10-25 13:32:41 +00001058 if sys.platform == 'win32' and os.environ.get('TERM') == 'msys':
1059 # Msysgit requires the usage of 'env' to be present.
1060 cmd = 'env ' + cmd
1061 try:
1062 # shell=True to allow the shell to handle all forms of quotes in
1063 # $EDITOR.
1064 subprocess2.check_call(cmd, shell=True)
1065 except subprocess2.CalledProcessError:
1066 return None
1067 return FileRead(filename)
1068 finally:
1069 os.remove(filename)
maruel@chromium.org99ac1c52012-01-16 14:52:12 +00001070
1071
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +00001072def UpgradeToHttps(url):
1073 """Upgrades random urls to https://.
1074
1075 Do not touch unknown urls like ssh:// or git://.
1076 Do not touch http:// urls with a port number,
1077 Fixes invalid GAE url.
1078 """
1079 if not url:
1080 return url
1081 if not re.match(r'[a-z\-]+\://.*', url):
1082 # Make sure it is a valid uri. Otherwise, urlparse() will consider it a
1083 # relative url and will use http:///foo. Note that it defaults to http://
1084 # for compatibility with naked url like "localhost:8080".
1085 url = 'http://%s' % url
1086 parsed = list(urlparse.urlparse(url))
1087 # Do not automatically upgrade http to https if a port number is provided.
1088 if parsed[0] == 'http' and not re.match(r'^.+?\:\d+$', parsed[1]):
1089 parsed[0] = 'https'
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +00001090 return urlparse.urlunparse(parsed)
1091
1092
maruel@chromium.org99ac1c52012-01-16 14:52:12 +00001093def ParseCodereviewSettingsContent(content):
1094 """Process a codereview.settings file properly."""
1095 lines = (l for l in content.splitlines() if not l.strip().startswith("#"))
1096 try:
1097 keyvals = dict([x.strip() for x in l.split(':', 1)] for l in lines if l)
1098 except ValueError:
1099 raise Error(
1100 'Failed to process settings, please fix. Content:\n\n%s' % content)
maruel@chromium.orgeb5edbc2012-01-16 17:03:28 +00001101 def fix_url(key):
1102 if keyvals.get(key):
1103 keyvals[key] = UpgradeToHttps(keyvals[key])
1104 fix_url('CODE_REVIEW_SERVER')
1105 fix_url('VIEW_VC')
maruel@chromium.org99ac1c52012-01-16 14:52:12 +00001106 return keyvals
ilevy@chromium.org13691502012-10-16 04:26:37 +00001107
1108
1109def NumLocalCpus():
1110 """Returns the number of processors.
1111
dnj@chromium.org530523b2015-01-07 19:54:57 +00001112 multiprocessing.cpu_count() is permitted to raise NotImplementedError, and
1113 is known to do this on some Windows systems and OSX 10.6. If we can't get the
1114 CPU count, we will fall back to '1'.
ilevy@chromium.org13691502012-10-16 04:26:37 +00001115 """
dnj@chromium.org530523b2015-01-07 19:54:57 +00001116 # Surround the entire thing in try/except; no failure here should stop gclient
1117 # from working.
ilevy@chromium.org13691502012-10-16 04:26:37 +00001118 try:
dnj@chromium.org530523b2015-01-07 19:54:57 +00001119 # Use multiprocessing to get CPU count. This may raise
1120 # NotImplementedError.
1121 try:
1122 import multiprocessing
1123 return multiprocessing.cpu_count()
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -08001124 except NotImplementedError: # pylint: disable=bare-except
dnj@chromium.org530523b2015-01-07 19:54:57 +00001125 # (UNIX) Query 'os.sysconf'.
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -08001126 # pylint: disable=no-member
dnj@chromium.org530523b2015-01-07 19:54:57 +00001127 if hasattr(os, 'sysconf') and 'SC_NPROCESSORS_ONLN' in os.sysconf_names:
1128 return int(os.sysconf('SC_NPROCESSORS_ONLN'))
1129
1130 # (Windows) Query 'NUMBER_OF_PROCESSORS' environment variable.
1131 if 'NUMBER_OF_PROCESSORS' in os.environ:
1132 return int(os.environ['NUMBER_OF_PROCESSORS'])
1133 except Exception as e:
1134 logging.exception("Exception raised while probing CPU count: %s", e)
1135
1136 logging.debug('Failed to get CPU count. Defaulting to 1.')
1137 return 1
szager@chromium.orgfc616382014-03-18 20:32:04 +00001138
sbc@chromium.org9d0644d2015-06-05 23:16:54 +00001139
szager@chromium.orgfc616382014-03-18 20:32:04 +00001140def DefaultDeltaBaseCacheLimit():
1141 """Return a reasonable default for the git config core.deltaBaseCacheLimit.
1142
1143 The primary constraint is the address space of virtual memory. The cache
1144 size limit is per-thread, and 32-bit systems can hit OOM errors if this
1145 parameter is set too high.
1146 """
1147 if platform.architecture()[0].startswith('64'):
1148 return '2g'
1149 else:
1150 return '512m'
1151
sbc@chromium.org9d0644d2015-06-05 23:16:54 +00001152
szager@chromium.orgff113292014-03-25 06:02:08 +00001153def DefaultIndexPackConfig(url=''):
szager@chromium.orgfc616382014-03-18 20:32:04 +00001154 """Return reasonable default values for configuring git-index-pack.
1155
1156 Experiments suggest that higher values for pack.threads don't improve
1157 performance."""
szager@chromium.orgff113292014-03-25 06:02:08 +00001158 cache_limit = DefaultDeltaBaseCacheLimit()
1159 result = ['-c', 'core.deltaBaseCacheLimit=%s' % cache_limit]
1160 if url in THREADED_INDEX_PACK_BLACKLIST:
1161 result.extend(['-c', 'pack.threads=1'])
1162 return result
sbc@chromium.org9d0644d2015-06-05 23:16:54 +00001163
1164
1165def FindExecutable(executable):
1166 """This mimics the "which" utility."""
1167 path_folders = os.environ.get('PATH').split(os.pathsep)
1168
1169 for path_folder in path_folders:
1170 target = os.path.join(path_folder, executable)
1171 # Just incase we have some ~/blah paths.
1172 target = os.path.abspath(os.path.expanduser(target))
1173 if os.path.isfile(target) and os.access(target, os.X_OK):
1174 return target
1175 if sys.platform.startswith('win'):
1176 for suffix in ('.bat', '.cmd', '.exe'):
1177 alt_target = target + suffix
1178 if os.path.isfile(alt_target) and os.access(alt_target, os.X_OK):
1179 return alt_target
1180 return None
Paweł Hajdan, Jr7e502612017-06-12 16:58:38 +02001181
1182
1183def freeze(obj):
1184 """Takes a generic object ``obj``, and returns an immutable version of it.
1185
1186 Supported types:
1187 * dict / OrderedDict -> FrozenDict
1188 * list -> tuple
1189 * set -> frozenset
1190 * any object with a working __hash__ implementation (assumes that hashable
1191 means immutable)
1192
1193 Will raise TypeError if you pass an object which is not hashable.
1194 """
Edward Lesmes6f64a052018-03-20 17:35:49 -04001195 if isinstance(obj, collections.Mapping):
Raul Tambreb946b232019-03-26 14:48:46 +00001196 return FrozenDict((freeze(k), freeze(v)) for k, v in obj.items())
Paweł Hajdan, Jr7e502612017-06-12 16:58:38 +02001197 elif isinstance(obj, (list, tuple)):
1198 return tuple(freeze(i) for i in obj)
1199 elif isinstance(obj, set):
1200 return frozenset(freeze(i) for i in obj)
1201 else:
1202 hash(obj)
1203 return obj
1204
1205
1206class FrozenDict(collections.Mapping):
1207 """An immutable OrderedDict.
1208
1209 Modified From: http://stackoverflow.com/a/2704866
1210 """
1211 def __init__(self, *args, **kwargs):
1212 self._d = collections.OrderedDict(*args, **kwargs)
1213
1214 # Calculate the hash immediately so that we know all the items are
1215 # hashable too.
Raul Tambreb946b232019-03-26 14:48:46 +00001216 self._hash = functools.reduce(
1217 operator.xor, (hash(i) for i in enumerate(self._d.items())), 0)
Paweł Hajdan, Jr7e502612017-06-12 16:58:38 +02001218
1219 def __eq__(self, other):
1220 if not isinstance(other, collections.Mapping):
1221 return NotImplemented
1222 if self is other:
1223 return True
1224 if len(self) != len(other):
1225 return False
1226 for k, v in self.iteritems():
1227 if k not in other or other[k] != v:
1228 return False
1229 return True
1230
1231 def __iter__(self):
1232 return iter(self._d)
1233
1234 def __len__(self):
1235 return len(self._d)
1236
1237 def __getitem__(self, key):
1238 return self._d[key]
1239
1240 def __hash__(self):
1241 return self._hash
1242
1243 def __repr__(self):
1244 return 'FrozenDict(%r)' % (self._d.items(),)