subprocess2.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. # coding=utf-8
  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
  4. # found in the LICENSE file.
  5. """Collection of subprocess wrapper functions.
  6. In theory you shouldn't need anything else in subprocess, or this module failed.
  7. """
  8. import errno
  9. import logging
  10. import os
  11. import subprocess
  12. import sys
  13. import threading
  14. # Constants forwarded from subprocess.
  15. PIPE = subprocess.PIPE
  16. STDOUT = subprocess.STDOUT
  17. DEVNULL = subprocess.DEVNULL
  18. class CalledProcessError(subprocess.CalledProcessError):
  19. """Augment the standard exception with more data."""
  20. def __init__(self, returncode, cmd, cwd, stdout, stderr):
  21. super(CalledProcessError, self).__init__(returncode, cmd, output=stdout)
  22. self.stdout = self.output # for backward compatibility.
  23. self.stderr = stderr
  24. self.cwd = cwd
  25. def __str__(self):
  26. out = 'Command %r returned non-zero exit status %s' % (' '.join(
  27. self.cmd), self.returncode)
  28. if self.cwd:
  29. out += ' in ' + self.cwd
  30. if self.stdout:
  31. out += '\n' + self.stdout.decode('utf-8', 'ignore')
  32. if self.stderr:
  33. out += '\n' + self.stderr.decode('utf-8', 'ignore')
  34. return out
  35. class CygwinRebaseError(CalledProcessError):
  36. """Occurs when cygwin's fork() emulation fails due to rebased dll."""
  37. ## Utility functions
  38. def kill_pid(pid):
  39. """Kills a process by its process id."""
  40. try:
  41. # Unable to import 'module'
  42. # pylint: disable=no-member,F0401
  43. import signal
  44. return os.kill(pid, signal.SIGTERM)
  45. except ImportError:
  46. pass
  47. def get_english_env(env):
  48. """Forces LANG and/or LANGUAGE to be English.
  49. Forces encoding to utf-8 for subprocesses.
  50. Returns None if it is unnecessary.
  51. """
  52. if sys.platform == 'win32':
  53. return None
  54. env = env or os.environ
  55. # Test if it is necessary at all.
  56. is_english = lambda name: env.get(name, 'en').startswith('en')
  57. if is_english('LANG') and is_english('LANGUAGE'):
  58. return None
  59. # Requires modifications.
  60. env = env.copy()
  61. def fix_lang(name):
  62. if not is_english(name):
  63. env[name] = 'en_US.UTF-8'
  64. fix_lang('LANG')
  65. fix_lang('LANGUAGE')
  66. return env
  67. class Popen(subprocess.Popen):
  68. """Wraps subprocess.Popen() with various workarounds.
  69. - Forces English output since it's easier to parse the stdout if it is
  70. always in English.
  71. - Sets shell=True on windows by default. You can override this by forcing
  72. shell parameter to a value.
  73. - Adds support for DEVNULL to not buffer when not needed.
  74. - Adds self.start property.
  75. Note: Popen() can throw OSError when cwd or args[0] doesn't exist.
  76. Translate exceptions generated by cygwin when it fails trying to emulate
  77. fork().
  78. """
  79. # subprocess.Popen.__init__() is not threadsafe; there is a race between
  80. # creating the exec-error pipe for the child and setting it to CLOEXEC
  81. # during which another thread can fork and cause the pipe to be inherited by
  82. # its descendents, which will cause the current Popen to hang until all
  83. # those descendents exit. Protect this with a lock so that only one
  84. # fork/exec can happen at a time.
  85. popen_lock = threading.Lock()
  86. def __init__(self, args, **kwargs):
  87. env = get_english_env(kwargs.get('env'))
  88. if env:
  89. kwargs['env'] = env
  90. if kwargs.get('env') is not None:
  91. # Subprocess expects environment variables to be strings in Python
  92. # 3.
  93. def ensure_str(value):
  94. if isinstance(value, bytes):
  95. return value.decode()
  96. return value
  97. kwargs['env'] = {
  98. ensure_str(k): ensure_str(v)
  99. for k, v in kwargs['env'].items()
  100. }
  101. if kwargs.get('shell') is None:
  102. # *Sigh*: Windows needs shell=True, or else it won't search %PATH%
  103. # for the executable, but shell=True makes subprocess on Linux fail
  104. # when it's called with a list because it only tries to execute the
  105. # first item in the list.
  106. kwargs['shell'] = bool(sys.platform == 'win32')
  107. if isinstance(args, (str, bytes)):
  108. tmp_str = args
  109. elif isinstance(args, (list, tuple)):
  110. tmp_str = ' '.join(args)
  111. else:
  112. raise CalledProcessError(None, args, kwargs.get('cwd'), None, None)
  113. if kwargs.get('cwd', None):
  114. tmp_str += '; cwd=%s' % kwargs['cwd']
  115. logging.debug(tmp_str)
  116. try:
  117. with self.popen_lock:
  118. super(Popen, self).__init__(args, **kwargs)
  119. except OSError as e:
  120. if e.errno == errno.EAGAIN and sys.platform == 'cygwin':
  121. # Convert fork() emulation failure into a CygwinRebaseError().
  122. raise CygwinRebaseError(
  123. e.errno, args, kwargs.get('cwd'), None, 'Visit '
  124. 'http://code.google.com/p/chromium/wiki/'
  125. 'CygwinDllRemappingFailure '
  126. 'to learn how to fix this error; you need to rebase your '
  127. 'cygwin dlls')
  128. # Popen() can throw OSError when cwd or args[0] doesn't exist.
  129. raise OSError(
  130. 'Execution failed with error: %s.\n'
  131. 'Check that %s or %s exist and have execution permission.' %
  132. (str(e), kwargs.get('cwd'), args[0]))
  133. def communicate(args, **kwargs):
  134. """Wraps subprocess.Popen().communicate().
  135. Returns ((stdout, stderr), returncode).
  136. - If the subprocess runs for |nag_timer| seconds without producing terminal
  137. output, print a warning to stderr.
  138. - Automatically passes stdin content as input so do not specify stdin=PIPE.
  139. """
  140. stdin = None
  141. # When stdin is passed as an argument, use it as the actual input data and
  142. # set the Popen() parameter accordingly.
  143. if 'stdin' in kwargs and isinstance(kwargs['stdin'], (str, bytes)):
  144. stdin = kwargs['stdin']
  145. kwargs['stdin'] = PIPE
  146. proc = Popen(args, **kwargs)
  147. return proc.communicate(stdin), proc.returncode
  148. def call(args, **kwargs):
  149. """Emulates subprocess.call().
  150. Automatically convert stdout=PIPE or stderr=PIPE to DEVNULL.
  151. In no case they can be returned since no code path raises
  152. subprocess2.CalledProcessError.
  153. Returns exit code.
  154. """
  155. if kwargs.get('stdout') == PIPE:
  156. kwargs['stdout'] = DEVNULL
  157. if kwargs.get('stderr') == PIPE:
  158. kwargs['stderr'] = DEVNULL
  159. return communicate(args, **kwargs)[1]
  160. def check_call_out(args, **kwargs):
  161. """Improved version of subprocess.check_call().
  162. Returns (stdout, stderr), unlike subprocess.check_call().
  163. """
  164. out, returncode = communicate(args, **kwargs)
  165. if returncode:
  166. raise CalledProcessError(returncode, args, kwargs.get('cwd'), out[0],
  167. out[1])
  168. return out
  169. def check_call(args, **kwargs):
  170. """Emulate subprocess.check_call()."""
  171. check_call_out(args, **kwargs)
  172. return 0
  173. def capture(args, **kwargs):
  174. """Captures stdout of a process call and returns it.
  175. Returns stdout.
  176. - Discards returncode.
  177. - Blocks stdin by default if not specified since no output will be visible.
  178. """
  179. kwargs.setdefault('stdin', DEVNULL)
  180. # Like check_output, deny the caller from using stdout arg.
  181. return communicate(args, stdout=PIPE, **kwargs)[0][0]
  182. def check_output(args, **kwargs):
  183. """Emulates subprocess.check_output().
  184. Captures stdout of a process call and returns stdout only.
  185. - Throws if return code is not 0.
  186. - Blocks stdin by default if not specified since no output will be visible.
  187. - As per doc, "The stdout argument is not allowed as it is used internally."
  188. """
  189. kwargs.setdefault('stdin', DEVNULL)
  190. if 'stdout' in kwargs:
  191. raise ValueError('stdout argument not allowed, it would be overridden.')
  192. return check_call_out(args, stdout=PIPE, **kwargs)[0]