OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 """Enables directory-specific presubmit checks to run at upload and/or commit. | 6 """Enables directory-specific presubmit checks to run at upload and/or commit. |
7 """ | 7 """ |
8 | 8 |
9 __version__ = '1.8.0' | 9 __version__ = '1.8.0' |
10 | 10 |
(...skipping 179 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
190 | 190 |
191 | 191 |
192 # Top level object so multiprocessing can pickle | 192 # Top level object so multiprocessing can pickle |
193 # Public access through OutputApi object. | 193 # Public access through OutputApi object. |
194 class _MailTextResult(_PresubmitResult): | 194 class _MailTextResult(_PresubmitResult): |
195 """A warning that should be included in the review request email.""" | 195 """A warning that should be included in the review request email.""" |
196 def __init__(self, *args, **kwargs): | 196 def __init__(self, *args, **kwargs): |
197 super(_MailTextResult, self).__init__() | 197 super(_MailTextResult, self).__init__() |
198 raise NotImplementedError() | 198 raise NotImplementedError() |
199 | 199 |
| 200 class GerritAccessor(object): |
| 201 """Limited Gerrit functionality for canned presubmit checks to work. |
| 202 |
| 203 To avoid excessive Gerrit calls, caches the results. |
| 204 """ |
| 205 |
| 206 def __init__(self, host): |
| 207 self.host = host |
| 208 self.cache = {} |
| 209 |
| 210 def _FetchChangeDetail(self, issue): |
| 211 # Separate function to be easily mocked in tests. |
| 212 return gerrit_util.GetChangeDetail( |
| 213 self.host, str(issue), |
| 214 ['ALL_REVISIONS', 'DETAILED_LABELS']) |
| 215 |
| 216 def GetChangeInfo(self, issue): |
| 217 """Returns labels and all revisions (patchsets) for this issue. |
| 218 |
| 219 The result is a dictionary according to Gerrit REST Api. |
| 220 https://gerrit-review.googlesource.com/Documentation/rest-api.html |
| 221 |
| 222 However, API isn't very clear what's inside, so see tests for example. |
| 223 """ |
| 224 assert issue |
| 225 cache_key = int(issue) |
| 226 if cache_key not in self.cache: |
| 227 self.cache[cache_key] = self._FetchChangeDetail(issue) |
| 228 return self.cache[cache_key] |
| 229 |
| 230 def GetChangeDescription(self, issue, patchset=None): |
| 231 """If patchset is none, fetches current patchset.""" |
| 232 info = self.GetChangeInfo(issue) |
| 233 # info is a reference to cache. We'll modify it here adding description to |
| 234 # it to the right patchset, if it is not yet there. |
| 235 |
| 236 # Find revision info for the patchset we want. |
| 237 if patchset is not None: |
| 238 for rev, rev_info in info['revisions'].iteritems(): |
| 239 if str(rev_info['_number']) == str(patchset): |
| 240 break |
| 241 else: |
| 242 raise Exception('patchset %s doesn\'t exist in issue %s' % ( |
| 243 patchset, issue)) |
| 244 else: |
| 245 rev = info['current_revision'] |
| 246 rev_info = info['revisions'][rev] |
| 247 |
| 248 # Updates revision info, which is part of cached issue info. |
| 249 if 'real_description' not in rev_info: |
| 250 rev_info['real_description'] = ( |
| 251 gerrit_util.GetChangeDescriptionFromGitiles( |
| 252 rev_info['fetch']['http']['url'], rev)) |
| 253 return rev_info['real_description'] |
| 254 |
| 255 def GetChangeOwner(self, issue): |
| 256 return self.GetChangeInfo(issue)['owner']['email'] |
| 257 |
| 258 def GetChangeReviewers(self, issue, approving_only=True): |
| 259 # Gerrit has 'approved' sub-section, but it only lists 1 approver. |
| 260 # So, if we look only for approvers, we have to look at all anyway. |
| 261 # Also, assume LGTM means Code-Review label == 2. Other configurations |
| 262 # aren't supported. |
| 263 return [r['email'] |
| 264 for r in self.GetChangeInfo(issue)['labels']['Code-Review']['all'] |
| 265 if not approving_only or '2' == str(r.get('value', 0))] |
| 266 |
200 | 267 |
201 class OutputApi(object): | 268 class OutputApi(object): |
202 """An instance of OutputApi gets passed to presubmit scripts so that they | 269 """An instance of OutputApi gets passed to presubmit scripts so that they |
203 can output various types of results. | 270 can output various types of results. |
204 """ | 271 """ |
205 PresubmitResult = _PresubmitResult | 272 PresubmitResult = _PresubmitResult |
206 PresubmitAddReviewers = _PresubmitAddReviewers | 273 PresubmitAddReviewers = _PresubmitAddReviewers |
207 PresubmitError = _PresubmitError | 274 PresubmitError = _PresubmitError |
208 PresubmitPromptWarning = _PresubmitPromptWarning | 275 PresubmitPromptWarning = _PresubmitPromptWarning |
209 PresubmitNotifyResult = _PresubmitNotifyResult | 276 PresubmitNotifyResult = _PresubmitNotifyResult |
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
258 r".*\b[A-Z0-9_]{2,}$", | 325 r".*\b[A-Z0-9_]{2,}$", |
259 # SCM (can happen in dual SCM configuration). (Slightly over aggressive) | 326 # SCM (can happen in dual SCM configuration). (Slightly over aggressive) |
260 r"(|.*[\\\/])\.git[\\\/].*", | 327 r"(|.*[\\\/])\.git[\\\/].*", |
261 r"(|.*[\\\/])\.svn[\\\/].*", | 328 r"(|.*[\\\/])\.svn[\\\/].*", |
262 # There is no point in processing a patch file. | 329 # There is no point in processing a patch file. |
263 r".+\.diff$", | 330 r".+\.diff$", |
264 r".+\.patch$", | 331 r".+\.patch$", |
265 ) | 332 ) |
266 | 333 |
267 def __init__(self, change, presubmit_path, is_committing, | 334 def __init__(self, change, presubmit_path, is_committing, |
268 rietveld_obj, verbose, dry_run=None): | 335 rietveld_obj, verbose, gerrit_obj=None, dry_run=None): |
269 """Builds an InputApi object. | 336 """Builds an InputApi object. |
270 | 337 |
271 Args: | 338 Args: |
272 change: A presubmit.Change object. | 339 change: A presubmit.Change object. |
273 presubmit_path: The path to the presubmit script being processed. | 340 presubmit_path: The path to the presubmit script being processed. |
274 is_committing: True if the change is about to be committed. | 341 is_committing: True if the change is about to be committed. |
275 rietveld_obj: rietveld.Rietveld client object | 342 rietveld_obj: rietveld.Rietveld client object |
| 343 gerrit_obj: provides basic Gerrit codereview functionality. |
| 344 dry_run: if true, some Checks will be skipped. |
276 """ | 345 """ |
277 # Version number of the presubmit_support script. | 346 # Version number of the presubmit_support script. |
278 self.version = [int(x) for x in __version__.split('.')] | 347 self.version = [int(x) for x in __version__.split('.')] |
279 self.change = change | 348 self.change = change |
280 self.is_committing = is_committing | 349 self.is_committing = is_committing |
281 self.rietveld = rietveld_obj | 350 self.rietveld = rietveld_obj |
| 351 self.gerrit = gerrit_obj |
282 self.dry_run = dry_run | 352 self.dry_run = dry_run |
283 # TBD | 353 # TBD |
284 self.host_url = 'http://codereview.chromium.org' | 354 self.host_url = 'http://codereview.chromium.org' |
285 if self.rietveld: | 355 if self.rietveld: |
286 self.host_url = self.rietveld.url | 356 self.host_url = self.rietveld.url |
287 | 357 |
288 # We expose various modules and functions as attributes of the input_api | 358 # We expose various modules and functions as attributes of the input_api |
289 # so that presubmit scripts don't have to import them. | 359 # so that presubmit scripts don't have to import them. |
290 self.basename = os.path.basename | 360 self.basename = os.path.basename |
291 self.cPickle = cPickle | 361 self.cPickle = cPickle |
(...skipping 1050 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1342 output_stream.write('** Post Upload Hook Messages **\n') | 1412 output_stream.write('** Post Upload Hook Messages **\n') |
1343 for result in results: | 1413 for result in results: |
1344 result.handle(output_stream) | 1414 result.handle(output_stream) |
1345 output_stream.write('\n') | 1415 output_stream.write('\n') |
1346 | 1416 |
1347 return results | 1417 return results |
1348 | 1418 |
1349 | 1419 |
1350 class PresubmitExecuter(object): | 1420 class PresubmitExecuter(object): |
1351 def __init__(self, change, committing, rietveld_obj, verbose, | 1421 def __init__(self, change, committing, rietveld_obj, verbose, |
1352 dry_run=None): | 1422 gerrit_obj=None, dry_run=None): |
1353 """ | 1423 """ |
1354 Args: | 1424 Args: |
1355 change: The Change object. | 1425 change: The Change object. |
1356 committing: True if 'gcl commit' is running, False if 'gcl upload' is. | 1426 committing: True if 'gcl commit' is running, False if 'gcl upload' is. |
1357 rietveld_obj: rietveld.Rietveld client object. | 1427 rietveld_obj: rietveld.Rietveld client object. |
| 1428 gerrit_obj: provides basic Gerrit codereview functionality. |
| 1429 dry_run: if true, some Checks will be skipped. |
1358 """ | 1430 """ |
1359 self.change = change | 1431 self.change = change |
1360 self.committing = committing | 1432 self.committing = committing |
1361 self.rietveld = rietveld_obj | 1433 self.rietveld = rietveld_obj |
| 1434 self.gerrit = gerrit_obj |
1362 self.verbose = verbose | 1435 self.verbose = verbose |
1363 self.dry_run = dry_run | 1436 self.dry_run = dry_run |
1364 | 1437 |
1365 def ExecPresubmitScript(self, script_text, presubmit_path): | 1438 def ExecPresubmitScript(self, script_text, presubmit_path): |
1366 """Executes a single presubmit script. | 1439 """Executes a single presubmit script. |
1367 | 1440 |
1368 Args: | 1441 Args: |
1369 script_text: The text of the presubmit script. | 1442 script_text: The text of the presubmit script. |
1370 presubmit_path: The path to the presubmit file (this will be reported via | 1443 presubmit_path: The path to the presubmit file (this will be reported via |
1371 input_api.PresubmitLocalPath()). | 1444 input_api.PresubmitLocalPath()). |
1372 | 1445 |
1373 Return: | 1446 Return: |
1374 A list of result objects, empty if no problems. | 1447 A list of result objects, empty if no problems. |
1375 """ | 1448 """ |
1376 | 1449 |
1377 # Change to the presubmit file's directory to support local imports. | 1450 # Change to the presubmit file's directory to support local imports. |
1378 main_path = os.getcwd() | 1451 main_path = os.getcwd() |
1379 os.chdir(os.path.dirname(presubmit_path)) | 1452 os.chdir(os.path.dirname(presubmit_path)) |
1380 | 1453 |
1381 # Load the presubmit script into context. | 1454 # Load the presubmit script into context. |
1382 input_api = InputApi(self.change, presubmit_path, self.committing, | 1455 input_api = InputApi(self.change, presubmit_path, self.committing, |
1383 self.rietveld, self.verbose, | 1456 self.rietveld, self.verbose, |
1384 dry_run=self.dry_run) | 1457 gerrit_obj=self.gerrit, dry_run=self.dry_run) |
1385 context = {} | 1458 context = {} |
1386 try: | 1459 try: |
1387 exec script_text in context | 1460 exec script_text in context |
1388 except Exception, e: | 1461 except Exception, e: |
1389 raise PresubmitFailure('"%s" had an exception.\n%s' % (presubmit_path, e)) | 1462 raise PresubmitFailure('"%s" had an exception.\n%s' % (presubmit_path, e)) |
1390 | 1463 |
1391 # These function names must change if we make substantial changes to | 1464 # These function names must change if we make substantial changes to |
1392 # the presubmit API that are not backwards compatible. | 1465 # the presubmit API that are not backwards compatible. |
1393 if self.committing: | 1466 if self.committing: |
1394 function_name = 'CheckChangeOnCommit' | 1467 function_name = 'CheckChangeOnCommit' |
(...skipping 22 matching lines...) Expand all Loading... |
1417 | 1490 |
1418 | 1491 |
1419 def DoPresubmitChecks(change, | 1492 def DoPresubmitChecks(change, |
1420 committing, | 1493 committing, |
1421 verbose, | 1494 verbose, |
1422 output_stream, | 1495 output_stream, |
1423 input_stream, | 1496 input_stream, |
1424 default_presubmit, | 1497 default_presubmit, |
1425 may_prompt, | 1498 may_prompt, |
1426 rietveld_obj, | 1499 rietveld_obj, |
| 1500 gerrit_obj=None, |
1427 dry_run=None): | 1501 dry_run=None): |
1428 """Runs all presubmit checks that apply to the files in the change. | 1502 """Runs all presubmit checks that apply to the files in the change. |
1429 | 1503 |
1430 This finds all PRESUBMIT.py files in directories enclosing the files in the | 1504 This finds all PRESUBMIT.py files in directories enclosing the files in the |
1431 change (up to the repository root) and calls the relevant entrypoint function | 1505 change (up to the repository root) and calls the relevant entrypoint function |
1432 depending on whether the change is being committed or uploaded. | 1506 depending on whether the change is being committed or uploaded. |
1433 | 1507 |
1434 Prints errors, warnings and notifications. Prompts the user for warnings | 1508 Prints errors, warnings and notifications. Prompts the user for warnings |
1435 when needed. | 1509 when needed. |
1436 | 1510 |
1437 Args: | 1511 Args: |
1438 change: The Change object. | 1512 change: The Change object. |
1439 committing: True if 'gcl commit' is running, False if 'gcl upload' is. | 1513 committing: True if 'gcl commit' is running, False if 'gcl upload' is. |
1440 verbose: Prints debug info. | 1514 verbose: Prints debug info. |
1441 output_stream: A stream to write output from presubmit tests to. | 1515 output_stream: A stream to write output from presubmit tests to. |
1442 input_stream: A stream to read input from the user. | 1516 input_stream: A stream to read input from the user. |
1443 default_presubmit: A default presubmit script to execute in any case. | 1517 default_presubmit: A default presubmit script to execute in any case. |
1444 may_prompt: Enable (y/n) questions on warning or error. | 1518 may_prompt: Enable (y/n) questions on warning or error. |
1445 rietveld_obj: rietveld.Rietveld object. | 1519 rietveld_obj: rietveld.Rietveld object. |
| 1520 gerrit_obj: provides basic Gerrit codereview functionality. |
1446 dry_run: if true, some Checks will be skipped. | 1521 dry_run: if true, some Checks will be skipped. |
1447 | 1522 |
1448 Warning: | 1523 Warning: |
1449 If may_prompt is true, output_stream SHOULD be sys.stdout and input_stream | 1524 If may_prompt is true, output_stream SHOULD be sys.stdout and input_stream |
1450 SHOULD be sys.stdin. | 1525 SHOULD be sys.stdin. |
1451 | 1526 |
1452 Return: | 1527 Return: |
1453 A PresubmitOutput object. Use output.should_continue() to figure out | 1528 A PresubmitOutput object. Use output.should_continue() to figure out |
1454 if there were errors or warnings and the caller should abort. | 1529 if there were errors or warnings and the caller should abort. |
1455 """ | 1530 """ |
1456 old_environ = os.environ | 1531 old_environ = os.environ |
1457 try: | 1532 try: |
1458 # Make sure python subprocesses won't generate .pyc files. | 1533 # Make sure python subprocesses won't generate .pyc files. |
1459 os.environ = os.environ.copy() | 1534 os.environ = os.environ.copy() |
1460 os.environ['PYTHONDONTWRITEBYTECODE'] = '1' | 1535 os.environ['PYTHONDONTWRITEBYTECODE'] = '1' |
1461 | 1536 |
1462 output = PresubmitOutput(input_stream, output_stream) | 1537 output = PresubmitOutput(input_stream, output_stream) |
1463 if committing: | 1538 if committing: |
1464 output.write("Running presubmit commit checks ...\n") | 1539 output.write("Running presubmit commit checks ...\n") |
1465 else: | 1540 else: |
1466 output.write("Running presubmit upload checks ...\n") | 1541 output.write("Running presubmit upload checks ...\n") |
1467 start_time = time.time() | 1542 start_time = time.time() |
1468 presubmit_files = ListRelevantPresubmitFiles( | 1543 presubmit_files = ListRelevantPresubmitFiles( |
1469 change.AbsoluteLocalPaths(True), change.RepositoryRoot()) | 1544 change.AbsoluteLocalPaths(True), change.RepositoryRoot()) |
1470 if not presubmit_files and verbose: | 1545 if not presubmit_files and verbose: |
1471 output.write("Warning, no PRESUBMIT.py found.\n") | 1546 output.write("Warning, no PRESUBMIT.py found.\n") |
1472 results = [] | 1547 results = [] |
1473 executer = PresubmitExecuter(change, committing, rietveld_obj, verbose, | 1548 executer = PresubmitExecuter(change, committing, rietveld_obj, verbose, |
1474 dry_run=dry_run) | 1549 gerrit_obj, dry_run) |
1475 if default_presubmit: | 1550 if default_presubmit: |
1476 if verbose: | 1551 if verbose: |
1477 output.write("Running default presubmit script.\n") | 1552 output.write("Running default presubmit script.\n") |
1478 fake_path = os.path.join(change.RepositoryRoot(), 'PRESUBMIT.py') | 1553 fake_path = os.path.join(change.RepositoryRoot(), 'PRESUBMIT.py') |
1479 results += executer.ExecPresubmitScript(default_presubmit, fake_path) | 1554 results += executer.ExecPresubmitScript(default_presubmit, fake_path) |
1480 for filename in presubmit_files: | 1555 for filename in presubmit_files: |
1481 filename = os.path.abspath(filename) | 1556 filename = os.path.abspath(filename) |
1482 if verbose: | 1557 if verbose: |
1483 output.write("Running %s\n" % filename) | 1558 output.write("Running %s\n" % filename) |
1484 # Accept CRLF presubmit script. | 1559 # Accept CRLF presubmit script. |
(...skipping 204 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1689 "can be passed to this program.") | 1764 "can be passed to this program.") |
1690 if options.rietveld_email_file: | 1765 if options.rietveld_email_file: |
1691 with open(options.rietveld_email_file, "rb") as f: | 1766 with open(options.rietveld_email_file, "rb") as f: |
1692 options.rietveld_email = f.read().strip() | 1767 options.rietveld_email = f.read().strip() |
1693 | 1768 |
1694 change_class, files = load_files(options, args) | 1769 change_class, files = load_files(options, args) |
1695 if not change_class: | 1770 if not change_class: |
1696 parser.error('For unversioned directory, <files> is not optional.') | 1771 parser.error('For unversioned directory, <files> is not optional.') |
1697 logging.info('Found %d file(s).' % len(files)) | 1772 logging.info('Found %d file(s).' % len(files)) |
1698 | 1773 |
1699 rietveld_obj = None | 1774 rietveld_obj, gerrit_obj = None, None |
| 1775 |
1700 if options.rietveld_url: | 1776 if options.rietveld_url: |
1701 # The empty password is permitted: '' is not None. | 1777 # The empty password is permitted: '' is not None. |
1702 if options.rietveld_private_key_file: | 1778 if options.rietveld_private_key_file: |
1703 rietveld_obj = rietveld.JwtOAuth2Rietveld( | 1779 rietveld_obj = rietveld.JwtOAuth2Rietveld( |
1704 options.rietveld_url, | 1780 options.rietveld_url, |
1705 options.rietveld_email, | 1781 options.rietveld_email, |
1706 options.rietveld_private_key_file) | 1782 options.rietveld_private_key_file) |
1707 else: | 1783 else: |
1708 rietveld_obj = rietveld.CachingRietveld( | 1784 rietveld_obj = rietveld.CachingRietveld( |
1709 options.rietveld_url, | 1785 options.rietveld_url, |
1710 auth_config, | 1786 auth_config, |
1711 options.rietveld_email) | 1787 options.rietveld_email) |
1712 if options.rietveld_fetch: | 1788 if options.rietveld_fetch: |
1713 assert options.issue | 1789 assert options.issue |
1714 props = rietveld_obj.get_issue_properties(options.issue, False) | 1790 props = rietveld_obj.get_issue_properties(options.issue, False) |
1715 options.author = props['owner_email'] | 1791 options.author = props['owner_email'] |
1716 options.description = props['description'] | 1792 options.description = props['description'] |
1717 logging.info('Got author: "%s"', options.author) | 1793 logging.info('Got author: "%s"', options.author) |
1718 logging.info('Got description: """\n%s\n"""', options.description) | 1794 logging.info('Got description: """\n%s\n"""', options.description) |
1719 | 1795 |
1720 if options.gerrit_url and options.gerrit_fetch: | 1796 if options.gerrit_url and options.gerrit_fetch: |
| 1797 assert options.issue and options.patchset |
1721 rietveld_obj = None | 1798 rietveld_obj = None |
1722 assert options.issue and options.patchset | 1799 gerrit_obj = GerritAccessor(urlparse.urlparse(options.gerrit_url).netloc) |
1723 props = gerrit_util.GetChangeDetail( | 1800 options.author = gerrit_obj.GetChangeOwner(options.issue) |
1724 urlparse.urlparse(options.gerrit_url).netloc, str(options.issue), | 1801 options.description = gerrit_obj.GetChangeDescription(options.issue, |
1725 ['ALL_REVISIONS']) | 1802 options.patchset) |
1726 options.author = props['owner']['email'] | |
1727 for rev, rev_info in props['revisions'].iteritems(): | |
1728 if str(rev_info['_number']) == str(options.patchset): | |
1729 options.description = gerrit_util.GetChangeDescriptionFromGitiles( | |
1730 rev_info['fetch']['http']['url'], rev) | |
1731 break | |
1732 else: | |
1733 print >> sys.stderr, ('Patchset %d was not found in Gerrit issue %d' % | |
1734 options.patchset, options.issue) | |
1735 return 2 | |
1736 logging.info('Got author: "%s"', options.author) | 1803 logging.info('Got author: "%s"', options.author) |
1737 logging.info('Got description: """\n%s\n"""', options.description) | 1804 logging.info('Got description: """\n%s\n"""', options.description) |
1738 | 1805 |
1739 try: | 1806 try: |
1740 with canned_check_filter(options.skip_canned): | 1807 with canned_check_filter(options.skip_canned): |
1741 results = DoPresubmitChecks( | 1808 results = DoPresubmitChecks( |
1742 change_class(options.name, | 1809 change_class(options.name, |
1743 options.description, | 1810 options.description, |
1744 options.root, | 1811 options.root, |
1745 files, | 1812 files, |
1746 options.issue, | 1813 options.issue, |
1747 options.patchset, | 1814 options.patchset, |
1748 options.author, | 1815 options.author, |
1749 upstream=options.upstream), | 1816 upstream=options.upstream), |
1750 options.commit, | 1817 options.commit, |
1751 options.verbose, | 1818 options.verbose, |
1752 sys.stdout, | 1819 sys.stdout, |
1753 sys.stdin, | 1820 sys.stdin, |
1754 options.default_presubmit, | 1821 options.default_presubmit, |
1755 options.may_prompt, | 1822 options.may_prompt, |
1756 rietveld_obj, | 1823 rietveld_obj, |
| 1824 gerrit_obj, |
1757 options.dry_run) | 1825 options.dry_run) |
1758 return not results.should_continue() | 1826 return not results.should_continue() |
1759 except NonexistantCannedCheckFilter, e: | 1827 except NonexistantCannedCheckFilter, e: |
1760 print >> sys.stderr, ( | 1828 print >> sys.stderr, ( |
1761 'Attempted to skip nonexistent canned presubmit check: %s' % e.message) | 1829 'Attempted to skip nonexistent canned presubmit check: %s' % e.message) |
1762 return 2 | 1830 return 2 |
1763 except PresubmitFailure, e: | 1831 except PresubmitFailure, e: |
1764 print >> sys.stderr, e | 1832 print >> sys.stderr, e |
1765 print >> sys.stderr, 'Maybe your depot_tools is out of date?' | 1833 print >> sys.stderr, 'Maybe your depot_tools is out of date?' |
1766 print >> sys.stderr, 'If all fails, contact maruel@' | 1834 print >> sys.stderr, 'If all fails, contact maruel@' |
1767 return 2 | 1835 return 2 |
1768 | 1836 |
1769 | 1837 |
1770 if __name__ == '__main__': | 1838 if __name__ == '__main__': |
1771 fix_encoding.fix_encoding() | 1839 fix_encoding.fix_encoding() |
1772 try: | 1840 try: |
1773 sys.exit(main()) | 1841 sys.exit(main()) |
1774 except KeyboardInterrupt: | 1842 except KeyboardInterrupt: |
1775 sys.stderr.write('interrupted\n') | 1843 sys.stderr.write('interrupted\n') |
1776 sys.exit(1) | 1844 sys.exit(1) |
OLD | NEW |