subprocess.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. from __future__ import absolute_import
  2. import logging
  3. import os
  4. import subprocess
  5. from pip._vendor.six.moves import shlex_quote
  6. from pip._internal.cli.spinners import SpinnerInterface, open_spinner
  7. from pip._internal.exceptions import InstallationSubprocessError
  8. from pip._internal.utils.compat import console_to_str, str_to_display
  9. from pip._internal.utils.logging import subprocess_logger
  10. from pip._internal.utils.misc import HiddenText, path_to_display
  11. from pip._internal.utils.typing import MYPY_CHECK_RUNNING
  12. if MYPY_CHECK_RUNNING:
  13. from typing import Any, Callable, Iterable, List, Mapping, Optional, Text, Union
  14. CommandArgs = List[Union[str, HiddenText]]
  15. LOG_DIVIDER = '----------------------------------------'
  16. def make_command(*args):
  17. # type: (Union[str, HiddenText, CommandArgs]) -> CommandArgs
  18. """
  19. Create a CommandArgs object.
  20. """
  21. command_args = [] # type: CommandArgs
  22. for arg in args:
  23. # Check for list instead of CommandArgs since CommandArgs is
  24. # only known during type-checking.
  25. if isinstance(arg, list):
  26. command_args.extend(arg)
  27. else:
  28. # Otherwise, arg is str or HiddenText.
  29. command_args.append(arg)
  30. return command_args
  31. def format_command_args(args):
  32. # type: (Union[List[str], CommandArgs]) -> str
  33. """
  34. Format command arguments for display.
  35. """
  36. # For HiddenText arguments, display the redacted form by calling str().
  37. # Also, we don't apply str() to arguments that aren't HiddenText since
  38. # this can trigger a UnicodeDecodeError in Python 2 if the argument
  39. # has type unicode and includes a non-ascii character. (The type
  40. # checker doesn't ensure the annotations are correct in all cases.)
  41. return ' '.join(
  42. shlex_quote(str(arg)) if isinstance(arg, HiddenText)
  43. else shlex_quote(arg) for arg in args
  44. )
  45. def reveal_command_args(args):
  46. # type: (Union[List[str], CommandArgs]) -> List[str]
  47. """
  48. Return the arguments in their raw, unredacted form.
  49. """
  50. return [
  51. arg.secret if isinstance(arg, HiddenText) else arg for arg in args
  52. ]
  53. def make_subprocess_output_error(
  54. cmd_args, # type: Union[List[str], CommandArgs]
  55. cwd, # type: Optional[str]
  56. lines, # type: List[Text]
  57. exit_status, # type: int
  58. ):
  59. # type: (...) -> Text
  60. """
  61. Create and return the error message to use to log a subprocess error
  62. with command output.
  63. :param lines: A list of lines, each ending with a newline.
  64. """
  65. command = format_command_args(cmd_args)
  66. # Convert `command` and `cwd` to text (unicode in Python 2) so we can use
  67. # them as arguments in the unicode format string below. This avoids
  68. # "UnicodeDecodeError: 'ascii' codec can't decode byte ..." in Python 2
  69. # if either contains a non-ascii character.
  70. command_display = str_to_display(command, desc='command bytes')
  71. cwd_display = path_to_display(cwd)
  72. # We know the joined output value ends in a newline.
  73. output = ''.join(lines)
  74. msg = (
  75. # Use a unicode string to avoid "UnicodeEncodeError: 'ascii'
  76. # codec can't encode character ..." in Python 2 when a format
  77. # argument (e.g. `output`) has a non-ascii character.
  78. u'Command errored out with exit status {exit_status}:\n'
  79. ' command: {command_display}\n'
  80. ' cwd: {cwd_display}\n'
  81. 'Complete output ({line_count} lines):\n{output}{divider}'
  82. ).format(
  83. exit_status=exit_status,
  84. command_display=command_display,
  85. cwd_display=cwd_display,
  86. line_count=len(lines),
  87. output=output,
  88. divider=LOG_DIVIDER,
  89. )
  90. return msg
  91. def call_subprocess(
  92. cmd, # type: Union[List[str], CommandArgs]
  93. show_stdout=False, # type: bool
  94. cwd=None, # type: Optional[str]
  95. on_returncode='raise', # type: str
  96. extra_ok_returncodes=None, # type: Optional[Iterable[int]]
  97. command_desc=None, # type: Optional[str]
  98. extra_environ=None, # type: Optional[Mapping[str, Any]]
  99. unset_environ=None, # type: Optional[Iterable[str]]
  100. spinner=None, # type: Optional[SpinnerInterface]
  101. log_failed_cmd=True, # type: Optional[bool]
  102. stdout_only=False, # type: Optional[bool]
  103. ):
  104. # type: (...) -> Text
  105. """
  106. Args:
  107. show_stdout: if true, use INFO to log the subprocess's stderr and
  108. stdout streams. Otherwise, use DEBUG. Defaults to False.
  109. extra_ok_returncodes: an iterable of integer return codes that are
  110. acceptable, in addition to 0. Defaults to None, which means [].
  111. unset_environ: an iterable of environment variable names to unset
  112. prior to calling subprocess.Popen().
  113. log_failed_cmd: if false, failed commands are not logged, only raised.
  114. stdout_only: if true, return only stdout, else return both. When true,
  115. logging of both stdout and stderr occurs when the subprocess has
  116. terminated, else logging occurs as subprocess output is produced.
  117. """
  118. if extra_ok_returncodes is None:
  119. extra_ok_returncodes = []
  120. if unset_environ is None:
  121. unset_environ = []
  122. # Most places in pip use show_stdout=False. What this means is--
  123. #
  124. # - We connect the child's output (combined stderr and stdout) to a
  125. # single pipe, which we read.
  126. # - We log this output to stderr at DEBUG level as it is received.
  127. # - If DEBUG logging isn't enabled (e.g. if --verbose logging wasn't
  128. # requested), then we show a spinner so the user can still see the
  129. # subprocess is in progress.
  130. # - If the subprocess exits with an error, we log the output to stderr
  131. # at ERROR level if it hasn't already been displayed to the console
  132. # (e.g. if --verbose logging wasn't enabled). This way we don't log
  133. # the output to the console twice.
  134. #
  135. # If show_stdout=True, then the above is still done, but with DEBUG
  136. # replaced by INFO.
  137. if show_stdout:
  138. # Then log the subprocess output at INFO level.
  139. log_subprocess = subprocess_logger.info
  140. used_level = logging.INFO
  141. else:
  142. # Then log the subprocess output using DEBUG. This also ensures
  143. # it will be logged to the log file (aka user_log), if enabled.
  144. log_subprocess = subprocess_logger.debug
  145. used_level = logging.DEBUG
  146. # Whether the subprocess will be visible in the console.
  147. showing_subprocess = subprocess_logger.getEffectiveLevel() <= used_level
  148. # Only use the spinner if we're not showing the subprocess output
  149. # and we have a spinner.
  150. use_spinner = not showing_subprocess and spinner is not None
  151. if command_desc is None:
  152. command_desc = format_command_args(cmd)
  153. log_subprocess("Running command %s", command_desc)
  154. env = os.environ.copy()
  155. if extra_environ:
  156. env.update(extra_environ)
  157. for name in unset_environ:
  158. env.pop(name, None)
  159. try:
  160. proc = subprocess.Popen(
  161. # Convert HiddenText objects to the underlying str.
  162. reveal_command_args(cmd),
  163. stdin=subprocess.PIPE,
  164. stdout=subprocess.PIPE,
  165. stderr=subprocess.STDOUT if not stdout_only else subprocess.PIPE,
  166. cwd=cwd,
  167. env=env,
  168. )
  169. except Exception as exc:
  170. if log_failed_cmd:
  171. subprocess_logger.critical(
  172. "Error %s while executing command %s", exc, command_desc,
  173. )
  174. raise
  175. all_output = []
  176. if not stdout_only:
  177. assert proc.stdout
  178. assert proc.stdin
  179. proc.stdin.close()
  180. # In this mode, stdout and stderr are in the same pipe.
  181. while True:
  182. # The "line" value is a unicode string in Python 2.
  183. line = console_to_str(proc.stdout.readline())
  184. if not line:
  185. break
  186. line = line.rstrip()
  187. all_output.append(line + '\n')
  188. # Show the line immediately.
  189. log_subprocess(line)
  190. # Update the spinner.
  191. if use_spinner:
  192. assert spinner
  193. spinner.spin()
  194. try:
  195. proc.wait()
  196. finally:
  197. if proc.stdout:
  198. proc.stdout.close()
  199. output = ''.join(all_output)
  200. else:
  201. # In this mode, stdout and stderr are in different pipes.
  202. # We must use communicate() which is the only safe way to read both.
  203. out_bytes, err_bytes = proc.communicate()
  204. # log line by line to preserve pip log indenting
  205. out = console_to_str(out_bytes)
  206. for out_line in out.splitlines():
  207. log_subprocess(out_line)
  208. all_output.append(out)
  209. err = console_to_str(err_bytes)
  210. for err_line in err.splitlines():
  211. log_subprocess(err_line)
  212. all_output.append(err)
  213. output = out
  214. proc_had_error = (
  215. proc.returncode and proc.returncode not in extra_ok_returncodes
  216. )
  217. if use_spinner:
  218. assert spinner
  219. if proc_had_error:
  220. spinner.finish("error")
  221. else:
  222. spinner.finish("done")
  223. if proc_had_error:
  224. if on_returncode == 'raise':
  225. if not showing_subprocess and log_failed_cmd:
  226. # Then the subprocess streams haven't been logged to the
  227. # console yet.
  228. msg = make_subprocess_output_error(
  229. cmd_args=cmd,
  230. cwd=cwd,
  231. lines=all_output,
  232. exit_status=proc.returncode,
  233. )
  234. subprocess_logger.error(msg)
  235. raise InstallationSubprocessError(proc.returncode, command_desc)
  236. elif on_returncode == 'warn':
  237. subprocess_logger.warning(
  238. 'Command "%s" had error code %s in %s',
  239. command_desc,
  240. proc.returncode,
  241. cwd,
  242. )
  243. elif on_returncode == 'ignore':
  244. pass
  245. else:
  246. raise ValueError('Invalid value: on_returncode={!r}'.format(
  247. on_returncode))
  248. return output
  249. def runner_with_spinner_message(message):
  250. # type: (str) -> Callable[..., None]
  251. """Provide a subprocess_runner that shows a spinner message.
  252. Intended for use with for pep517's Pep517HookCaller. Thus, the runner has
  253. an API that matches what's expected by Pep517HookCaller.subprocess_runner.
  254. """
  255. def runner(
  256. cmd, # type: List[str]
  257. cwd=None, # type: Optional[str]
  258. extra_environ=None # type: Optional[Mapping[str, Any]]
  259. ):
  260. # type: (...) -> None
  261. with open_spinner(message) as spinner:
  262. call_subprocess(
  263. cmd,
  264. cwd=cwd,
  265. extra_environ=extra_environ,
  266. spinner=spinner,
  267. )
  268. return runner