6
6
import logging
7
7
import os
8
8
import shutil
9
+ import subprocess
9
10
import sys
10
11
11
12
from pip ._vendor import pkg_resources
12
13
from pip ._vendor .six .moves .urllib import parse as urllib_parse
13
14
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
16
22
from pip ._internal .utils .misc import (
17
23
ask_path_exists ,
18
24
backup_dir ,
21
27
hide_value ,
22
28
rmtree ,
23
29
)
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
+ )
25
36
from pip ._internal .utils .typing import MYPY_CHECK_RUNNING
26
37
from pip ._internal .utils .urls import get_url_scheme
27
38
28
39
if MYPY_CHECK_RUNNING :
29
40
from typing import (
30
- Any , Dict , Iterable , Iterator , List , Mapping , Optional , Text , Tuple ,
41
+ Dict , Iterable , Iterator , List , Optional , Text , Tuple ,
31
42
Type , Union
32
43
)
33
- from pip ._internal .cli .spinners import SpinnerInterface
34
44
from pip ._internal .utils .misc import HiddenText
35
45
from pip ._internal .utils .subprocess import CommandArgs
36
46
@@ -71,6 +81,123 @@ def make_vcs_requirement_url(repo_url, rev, project_name, subdir=None):
71
81
return req
72
82
73
83
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
+
74
201
def find_path_to_setup_from_repo_root (location , repo_root ):
75
202
# type: (str, str) -> Optional[str]
76
203
"""
@@ -663,9 +790,6 @@ def run_command(
663
790
cwd = None , # type: Optional[str]
664
791
on_returncode = 'raise' , # type: str
665
792
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]
669
793
log_failed_cmd = True # type: bool
670
794
):
671
795
# type: (...) -> Text
@@ -679,10 +803,6 @@ def run_command(
679
803
return call_subprocess (cmd , show_stdout , cwd ,
680
804
on_returncode = on_returncode ,
681
805
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 ,
686
806
log_failed_cmd = log_failed_cmd )
687
807
except OSError as e :
688
808
# errno.ENOENT = no such file or directory
0 commit comments