Path: blob/master/venv/Lib/site-packages/pip/_internal/utils/subprocess.py
811 views
# The following comment should be removed at some point in the future.1# mypy: strict-optional=False23from __future__ import absolute_import45import logging6import os7import subprocess89from pip._vendor.six.moves import shlex_quote1011from pip._internal.cli.spinners import SpinnerInterface, open_spinner12from pip._internal.exceptions import InstallationError13from pip._internal.utils.compat import console_to_str, str_to_display14from pip._internal.utils.logging import subprocess_logger15from pip._internal.utils.misc import HiddenText, path_to_display16from pip._internal.utils.typing import MYPY_CHECK_RUNNING1718if MYPY_CHECK_RUNNING:19from typing import (20Any, Callable, Iterable, List, Mapping, Optional, Text, Union,21)2223CommandArgs = List[Union[str, HiddenText]]242526LOG_DIVIDER = '----------------------------------------'272829def make_command(*args):30# type: (Union[str, HiddenText, CommandArgs]) -> CommandArgs31"""32Create a CommandArgs object.33"""34command_args = [] # type: CommandArgs35for arg in args:36# Check for list instead of CommandArgs since CommandArgs is37# only known during type-checking.38if isinstance(arg, list):39command_args.extend(arg)40else:41# Otherwise, arg is str or HiddenText.42command_args.append(arg)4344return command_args454647def format_command_args(args):48# type: (Union[List[str], CommandArgs]) -> str49"""50Format command arguments for display.51"""52# For HiddenText arguments, display the redacted form by calling str().53# Also, we don't apply str() to arguments that aren't HiddenText since54# this can trigger a UnicodeDecodeError in Python 2 if the argument55# has type unicode and includes a non-ascii character. (The type56# checker doesn't ensure the annotations are correct in all cases.)57return ' '.join(58shlex_quote(str(arg)) if isinstance(arg, HiddenText)59else shlex_quote(arg) for arg in args60)616263def reveal_command_args(args):64# type: (Union[List[str], CommandArgs]) -> List[str]65"""66Return the arguments in their raw, unredacted form.67"""68return [69arg.secret if isinstance(arg, HiddenText) else arg for arg in args70]717273def make_subprocess_output_error(74cmd_args, # type: Union[List[str], CommandArgs]75cwd, # type: Optional[str]76lines, # type: List[Text]77exit_status, # type: int78):79# type: (...) -> Text80"""81Create and return the error message to use to log a subprocess error82with command output.8384:param lines: A list of lines, each ending with a newline.85"""86command = format_command_args(cmd_args)87# Convert `command` and `cwd` to text (unicode in Python 2) so we can use88# them as arguments in the unicode format string below. This avoids89# "UnicodeDecodeError: 'ascii' codec can't decode byte ..." in Python 290# if either contains a non-ascii character.91command_display = str_to_display(command, desc='command bytes')92cwd_display = path_to_display(cwd)9394# We know the joined output value ends in a newline.95output = ''.join(lines)96msg = (97# Use a unicode string to avoid "UnicodeEncodeError: 'ascii'98# codec can't encode character ..." in Python 2 when a format99# argument (e.g. `output`) has a non-ascii character.100u'Command errored out with exit status {exit_status}:\n'101' command: {command_display}\n'102' cwd: {cwd_display}\n'103'Complete output ({line_count} lines):\n{output}{divider}'104).format(105exit_status=exit_status,106command_display=command_display,107cwd_display=cwd_display,108line_count=len(lines),109output=output,110divider=LOG_DIVIDER,111)112return msg113114115def call_subprocess(116cmd, # type: Union[List[str], CommandArgs]117show_stdout=False, # type: bool118cwd=None, # type: Optional[str]119on_returncode='raise', # type: str120extra_ok_returncodes=None, # type: Optional[Iterable[int]]121command_desc=None, # type: Optional[str]122extra_environ=None, # type: Optional[Mapping[str, Any]]123unset_environ=None, # type: Optional[Iterable[str]]124spinner=None, # type: Optional[SpinnerInterface]125log_failed_cmd=True # type: Optional[bool]126):127# type: (...) -> Text128"""129Args:130show_stdout: if true, use INFO to log the subprocess's stderr and131stdout streams. Otherwise, use DEBUG. Defaults to False.132extra_ok_returncodes: an iterable of integer return codes that are133acceptable, in addition to 0. Defaults to None, which means [].134unset_environ: an iterable of environment variable names to unset135prior to calling subprocess.Popen().136log_failed_cmd: if false, failed commands are not logged, only raised.137"""138if extra_ok_returncodes is None:139extra_ok_returncodes = []140if unset_environ is None:141unset_environ = []142# Most places in pip use show_stdout=False. What this means is--143#144# - We connect the child's output (combined stderr and stdout) to a145# single pipe, which we read.146# - We log this output to stderr at DEBUG level as it is received.147# - If DEBUG logging isn't enabled (e.g. if --verbose logging wasn't148# requested), then we show a spinner so the user can still see the149# subprocess is in progress.150# - If the subprocess exits with an error, we log the output to stderr151# at ERROR level if it hasn't already been displayed to the console152# (e.g. if --verbose logging wasn't enabled). This way we don't log153# the output to the console twice.154#155# If show_stdout=True, then the above is still done, but with DEBUG156# replaced by INFO.157if show_stdout:158# Then log the subprocess output at INFO level.159log_subprocess = subprocess_logger.info160used_level = logging.INFO161else:162# Then log the subprocess output using DEBUG. This also ensures163# it will be logged to the log file (aka user_log), if enabled.164log_subprocess = subprocess_logger.debug165used_level = logging.DEBUG166167# Whether the subprocess will be visible in the console.168showing_subprocess = subprocess_logger.getEffectiveLevel() <= used_level169170# Only use the spinner if we're not showing the subprocess output171# and we have a spinner.172use_spinner = not showing_subprocess and spinner is not None173174if command_desc is None:175command_desc = format_command_args(cmd)176177log_subprocess("Running command %s", command_desc)178env = os.environ.copy()179if extra_environ:180env.update(extra_environ)181for name in unset_environ:182env.pop(name, None)183try:184proc = subprocess.Popen(185# Convert HiddenText objects to the underlying str.186reveal_command_args(cmd),187stderr=subprocess.STDOUT, stdin=subprocess.PIPE,188stdout=subprocess.PIPE, cwd=cwd, env=env,189)190proc.stdin.close()191except Exception as exc:192if log_failed_cmd:193subprocess_logger.critical(194"Error %s while executing command %s", exc, command_desc,195)196raise197all_output = []198while True:199# The "line" value is a unicode string in Python 2.200line = console_to_str(proc.stdout.readline())201if not line:202break203line = line.rstrip()204all_output.append(line + '\n')205206# Show the line immediately.207log_subprocess(line)208# Update the spinner.209if use_spinner:210spinner.spin()211try:212proc.wait()213finally:214if proc.stdout:215proc.stdout.close()216proc_had_error = (217proc.returncode and proc.returncode not in extra_ok_returncodes218)219if use_spinner:220if proc_had_error:221spinner.finish("error")222else:223spinner.finish("done")224if proc_had_error:225if on_returncode == 'raise':226if not showing_subprocess and log_failed_cmd:227# Then the subprocess streams haven't been logged to the228# console yet.229msg = make_subprocess_output_error(230cmd_args=cmd,231cwd=cwd,232lines=all_output,233exit_status=proc.returncode,234)235subprocess_logger.error(msg)236exc_msg = (237'Command errored out with exit status {}: {} '238'Check the logs for full command output.'239).format(proc.returncode, command_desc)240raise InstallationError(exc_msg)241elif on_returncode == 'warn':242subprocess_logger.warning(243'Command "{}" had error code {} in {}'.format(244command_desc, proc.returncode, cwd)245)246elif on_returncode == 'ignore':247pass248else:249raise ValueError('Invalid value: on_returncode={!r}'.format(250on_returncode))251return ''.join(all_output)252253254def runner_with_spinner_message(message):255# type: (str) -> Callable[..., None]256"""Provide a subprocess_runner that shows a spinner message.257258Intended for use with for pep517's Pep517HookCaller. Thus, the runner has259an API that matches what's expected by Pep517HookCaller.subprocess_runner.260"""261262def runner(263cmd, # type: List[str]264cwd=None, # type: Optional[str]265extra_environ=None # type: Optional[Mapping[str, Any]]266):267# type: (...) -> None268with open_spinner(message) as spinner:269call_subprocess(270cmd,271cwd=cwd,272extra_environ=extra_environ,273spinner=spinner,274)275276return runner277278279