OLD | NEW |
1 # Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2010 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 """Generic utils.""" | 5 """Generic utils.""" |
6 | 6 |
7 import errno | 7 import errno |
8 import logging | 8 import logging |
9 import os | 9 import os |
10 import Queue | 10 import Queue |
11 import re | 11 import re |
12 import stat | 12 import stat |
13 import subprocess | 13 import subprocess |
14 import sys | 14 import sys |
15 import threading | 15 import threading |
16 import time | 16 import time |
17 import xml.dom.minidom | 17 import xml.dom.minidom |
18 import xml.parsers.expat | 18 import xml.parsers.expat |
19 | 19 |
20 import subprocess2 | |
21 | 20 |
22 # Keep an alias for now. | 21 def hack_subprocess(): |
23 Popen = subprocess2.Popen | 22 """subprocess functions may throw exceptions when used in multiple threads. |
| 23 |
| 24 See http://bugs.python.org/issue1731717 for more information. |
| 25 """ |
| 26 subprocess._cleanup = lambda: None |
24 | 27 |
25 | 28 |
26 class Error(Exception): | 29 class Error(Exception): |
27 """gclient exception class.""" | 30 """gclient exception class.""" |
28 pass | 31 pass |
29 | 32 |
30 | 33 |
31 class CheckCallError(OSError, Error): | 34 class CheckCallError(OSError, Error): |
32 """CheckCall() returned non-0.""" | 35 """CheckCall() returned non-0.""" |
33 def __init__(self, command, cwd, returncode, stdout, stderr=None): | 36 def __init__(self, command, cwd, returncode, stdout, stderr=None): |
(...skipping 11 matching lines...) Expand all Loading... |
45 out += ' in ' + self.cwd | 48 out += ' in ' + self.cwd |
46 if self.returncode is not None: | 49 if self.returncode is not None: |
47 out += ' returned %d' % self.returncode | 50 out += ' returned %d' % self.returncode |
48 if self.stdout is not None: | 51 if self.stdout is not None: |
49 out += '\nstdout: %s\n' % self.stdout | 52 out += '\nstdout: %s\n' % self.stdout |
50 if self.stderr is not None: | 53 if self.stderr is not None: |
51 out += '\nstderr: %s\n' % self.stderr | 54 out += '\nstderr: %s\n' % self.stderr |
52 return out | 55 return out |
53 | 56 |
54 | 57 |
| 58 def Popen(args, **kwargs): |
| 59 """Calls subprocess.Popen() with hacks to work around certain behaviors. |
| 60 |
| 61 Ensure English outpout for svn and make it work reliably on Windows. |
| 62 """ |
| 63 logging.debug(u'%s, cwd=%s' % (u' '.join(args), kwargs.get('cwd', ''))) |
| 64 if not 'env' in kwargs: |
| 65 # It's easier to parse the stdout if it is always in English. |
| 66 kwargs['env'] = os.environ.copy() |
| 67 kwargs['env']['LANGUAGE'] = 'en' |
| 68 if not 'shell' in kwargs: |
| 69 # *Sigh*: Windows needs shell=True, or else it won't search %PATH% for the |
| 70 # executable, but shell=True makes subprocess on Linux fail when it's called |
| 71 # with a list because it only tries to execute the first item in the list. |
| 72 kwargs['shell'] = (sys.platform=='win32') |
| 73 try: |
| 74 return subprocess.Popen(args, **kwargs) |
| 75 except OSError, e: |
| 76 if e.errno == errno.EAGAIN and sys.platform == 'cygwin': |
| 77 raise Error( |
| 78 'Visit ' |
| 79 'http://code.google.com/p/chromium/wiki/CygwinDllRemappingFailure to ' |
| 80 'learn how to fix this error; you need to rebase your cygwin dlls') |
| 81 raise |
| 82 |
| 83 |
55 def CheckCall(command, print_error=True, **kwargs): | 84 def CheckCall(command, print_error=True, **kwargs): |
56 """Similar subprocess.check_call() but redirects stdout and | 85 """Similar subprocess.check_call() but redirects stdout and |
57 returns (stdout, stderr). | 86 returns (stdout, stderr). |
58 | 87 |
59 Works on python 2.4 | 88 Works on python 2.4 |
60 """ | 89 """ |
61 try: | 90 try: |
62 stderr = None | 91 stderr = None |
63 if not print_error: | 92 if not print_error: |
64 stderr = subprocess.PIPE | 93 stderr = subprocess.PIPE |
(...skipping 465 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
530 | 559 |
531 In gclient's case, Dependencies sometime needs to be run out of order due to | 560 In gclient's case, Dependencies sometime needs to be run out of order due to |
532 From() keyword. This class manages that all the required dependencies are run | 561 From() keyword. This class manages that all the required dependencies are run |
533 before running each one. | 562 before running each one. |
534 | 563 |
535 Methods of this class are thread safe. | 564 Methods of this class are thread safe. |
536 """ | 565 """ |
537 def __init__(self, jobs, progress): | 566 def __init__(self, jobs, progress): |
538 """jobs specifies the number of concurrent tasks to allow. progress is a | 567 """jobs specifies the number of concurrent tasks to allow. progress is a |
539 Progress instance.""" | 568 Progress instance.""" |
| 569 hack_subprocess() |
540 # Set when a thread is done or a new item is enqueued. | 570 # Set when a thread is done or a new item is enqueued. |
541 self.ready_cond = threading.Condition() | 571 self.ready_cond = threading.Condition() |
542 # Maximum number of concurrent tasks. | 572 # Maximum number of concurrent tasks. |
543 self.jobs = jobs | 573 self.jobs = jobs |
544 # List of WorkItem, for gclient, these are Dependency instances. | 574 # List of WorkItem, for gclient, these are Dependency instances. |
545 self.queued = [] | 575 self.queued = [] |
546 # List of strings representing each Dependency.name that was run. | 576 # List of strings representing each Dependency.name that was run. |
547 self.ran = [] | 577 self.ran = [] |
548 # List of items currently running. | 578 # List of items currently running. |
549 self.running = [] | 579 self.running = [] |
(...skipping 123 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
673 logging.info('Caught exception in thread %s' % self.item.name) | 703 logging.info('Caught exception in thread %s' % self.item.name) |
674 logging.info(str(sys.exc_info())) | 704 logging.info(str(sys.exc_info())) |
675 work_queue.exceptions.put(sys.exc_info()) | 705 work_queue.exceptions.put(sys.exc_info()) |
676 logging.info('Task %s done' % self.item.name) | 706 logging.info('Task %s done' % self.item.name) |
677 | 707 |
678 work_queue.ready_cond.acquire() | 708 work_queue.ready_cond.acquire() |
679 try: | 709 try: |
680 work_queue.ready_cond.notifyAll() | 710 work_queue.ready_cond.notifyAll() |
681 finally: | 711 finally: |
682 work_queue.ready_cond.release() | 712 work_queue.ready_cond.release() |
OLD | NEW |