66import logging
77import os
88import shutil
9+ import subprocess
910import sys
1011
1112from pip ._vendor import pkg_resources
1213from pip ._vendor .six .moves .urllib import parse as urllib_parse
1314
14- from pip ._internal .exceptions import BadCommand , InstallationError
15- from pip ._internal .utils .compat import samefile
15+ from pip ._internal .exceptions import (
16+ BadCommand ,
17+ InstallationError ,
18+ SubProcessError ,
19+ )
20+ from pip ._internal .utils .compat import console_to_str , samefile
21+ from pip ._internal .utils .logging import subprocess_logger
1622from pip ._internal .utils .misc import (
1723 ask_path_exists ,
1824 backup_dir ,
2127 hide_value ,
2228 rmtree ,
2329)
24- from pip ._internal .utils .subprocess import call_subprocess , make_command
30+ from pip ._internal .utils .subprocess import (
31+ format_command_args ,
32+ make_command ,
33+ make_subprocess_output_error ,
34+ reveal_command_args ,
35+ )
2536from pip ._internal .utils .typing import MYPY_CHECK_RUNNING
2637from pip ._internal .utils .urls import get_url_scheme
2738
2839if MYPY_CHECK_RUNNING :
2940 from typing import (
30- Any , Dict , Iterable , Iterator , List , Mapping , Optional , Text , Tuple ,
41+ Dict , Iterable , Iterator , List , Optional , Text , Tuple ,
3142 Type , Union
3243 )
33- from pip ._internal .cli .spinners import SpinnerInterface
3444 from pip ._internal .utils .misc import HiddenText
3545 from pip ._internal .utils .subprocess import CommandArgs
3646
@@ -71,6 +81,123 @@ def make_vcs_requirement_url(repo_url, rev, project_name, subdir=None):
7181 return req
7282
7383
84+ def call_subprocess (
85+ cmd , # type: Union[List[str], CommandArgs]
86+ show_stdout = False , # type: bool
87+ cwd = None , # type: Optional[str]
88+ on_returncode = 'raise' , # type: str
89+ extra_ok_returncodes = None , # type: Optional[Iterable[int]]
90+ log_failed_cmd = True # type: Optional[bool]
91+ ):
92+ # type: (...) -> Text
93+ """
94+ Args:
95+ show_stdout: if true, use INFO to log the subprocess's stderr and
96+ stdout streams. Otherwise, use DEBUG. Defaults to False.
97+ extra_ok_returncodes: an iterable of integer return codes that are
98+ acceptable, in addition to 0. Defaults to None, which means [].
99+ log_failed_cmd: if false, failed commands are not logged,
100+ only raised.
101+ """
102+ if extra_ok_returncodes is None :
103+ extra_ok_returncodes = []
104+ # Most places in pip use show_stdout=False.
105+ # What this means is--
106+ #
107+ # - We log this output of stdout and stderr at DEBUG level
108+ # as it is received.
109+ # - If DEBUG logging isn't enabled (e.g. if --verbose logging wasn't
110+ # requested), then we show a spinner so the user can still see the
111+ # subprocess is in progress.
112+ # - If the subprocess exits with an error, we log the output to stderr
113+ # at ERROR level if it hasn't already been displayed to the console
114+ # (e.g. if --verbose logging wasn't enabled). This way we don't log
115+ # the output to the console twice.
116+ #
117+ # If show_stdout=True, then the above is still done, but with DEBUG
118+ # replaced by INFO.
119+ if show_stdout :
120+ # Then log the subprocess output at INFO level.
121+ log_subprocess = subprocess_logger .info
122+ used_level = logging .INFO
123+ else :
124+ # Then log the subprocess output using DEBUG. This also ensures
125+ # it will be logged to the log file (aka user_log), if enabled.
126+ log_subprocess = subprocess_logger .debug
127+ used_level = logging .DEBUG
128+
129+ # Whether the subprocess will be visible in the console.
130+ showing_subprocess = subprocess_logger .getEffectiveLevel () <= used_level
131+
132+ command_desc = format_command_args (cmd )
133+ try :
134+ proc = subprocess .Popen (
135+ # Convert HiddenText objects to the underlying str.
136+ reveal_command_args (cmd ),
137+ stdout = subprocess .PIPE ,
138+ stderr = subprocess .PIPE ,
139+ cwd = cwd
140+ )
141+ if proc .stdin :
142+ proc .stdin .close ()
143+ except Exception as exc :
144+ if log_failed_cmd :
145+ subprocess_logger .critical (
146+ "Error %s while executing command %s" , exc , command_desc ,
147+ )
148+ raise
149+ all_output = []
150+ while True :
151+ # The "line" value is a unicode string in Python 2.
152+ line = None
153+ if proc .stdout :
154+ line = console_to_str (proc .stdout .readline ())
155+ if not line :
156+ break
157+ line = line .rstrip ()
158+ all_output .append (line + '\n ' )
159+
160+ # Show the line immediately.
161+ log_subprocess (line )
162+ try :
163+ proc .wait ()
164+ finally :
165+ if proc .stdout :
166+ proc .stdout .close ()
167+
168+ proc_had_error = (
169+ proc .returncode and proc .returncode not in extra_ok_returncodes
170+ )
171+ if proc_had_error :
172+ if on_returncode == 'raise' :
173+ if not showing_subprocess and log_failed_cmd :
174+ # Then the subprocess streams haven't been logged to the
175+ # console yet.
176+ msg = make_subprocess_output_error (
177+ cmd_args = cmd ,
178+ cwd = cwd ,
179+ lines = all_output ,
180+ exit_status = proc .returncode ,
181+ )
182+ subprocess_logger .error (msg )
183+ exc_msg = (
184+ 'Command errored out with exit status {}: {} '
185+ 'Check the logs for full command output.'
186+ ).format (proc .returncode , command_desc )
187+ raise SubProcessError (exc_msg )
188+ elif on_returncode == 'warn' :
189+ subprocess_logger .warning (
190+ 'Command "{}" had error code {} in {}' .format (
191+ command_desc , proc .returncode , cwd )
192+ )
193+ elif on_returncode == 'ignore' :
194+ pass
195+ else :
196+ raise ValueError ('Invalid value: on_returncode={!r}' .format (
197+ on_returncode ))
198+ return '' .join (all_output )
199+
200+
74201def find_path_to_setup_from_repo_root (location , repo_root ):
75202 # type: (str, str) -> Optional[str]
76203 """
@@ -663,9 +790,6 @@ def run_command(
663790 cwd = None , # type: Optional[str]
664791 on_returncode = 'raise' , # type: str
665792 extra_ok_returncodes = None , # type: Optional[Iterable[int]]
666- command_desc = None , # type: Optional[str]
667- extra_environ = None , # type: Optional[Mapping[str, Any]]
668- spinner = None , # type: Optional[SpinnerInterface]
669793 log_failed_cmd = True # type: bool
670794 ):
671795 # type: (...) -> Text
@@ -679,10 +803,6 @@ def run_command(
679803 return call_subprocess (cmd , show_stdout , cwd ,
680804 on_returncode = on_returncode ,
681805 extra_ok_returncodes = extra_ok_returncodes ,
682- command_desc = command_desc ,
683- extra_environ = extra_environ ,
684- unset_environ = cls .unset_environ ,
685- spinner = spinner ,
686806 log_failed_cmd = log_failed_cmd )
687807 except OSError as e :
688808 # errno.ENOENT = no such file or directory
0 commit comments