27
27
LOG_DIVIDER = '----------------------------------------'
28
28
29
29
30
+ class SubProcessResult (object ):
31
+ def __init__ (self , cmd , returncode , had_error , output = None ):
32
+ # type (...) -> SubProcessResult
33
+ """
34
+ Initialize a SubProcessResult
35
+ """
36
+ self .cmd = cmd
37
+ self .returncode = returncode
38
+ self .output = output
39
+ self .had_error = had_error
40
+
41
+
30
42
def make_command (* args ):
31
43
# type: (Union[str, HiddenText, CommandArgs]) -> CommandArgs
32
44
"""
@@ -113,7 +125,7 @@ def make_subprocess_output_error(
113
125
return msg
114
126
115
127
116
- def call_subprocess (
128
+ def call_subprocess_for_install (
117
129
cmd , # type: Union[List[str], CommandArgs]
118
130
show_stdout = False , # type: bool
119
131
cwd = None , # type: Optional[str]
@@ -136,10 +148,6 @@ def call_subprocess(
136
148
prior to calling subprocess.Popen().
137
149
log_failed_cmd: if false, failed commands are not logged, only raised.
138
150
"""
139
- if extra_ok_returncodes is None :
140
- extra_ok_returncodes = []
141
- if unset_environ is None :
142
- unset_environ = []
143
151
# Most places in pip use show_stdout=False. What this means is--
144
152
#
145
153
# - We connect the child's output (combined stderr and stdout) to a
@@ -155,55 +163,101 @@ def call_subprocess(
155
163
#
156
164
# If show_stdout=True, then the above is still done, but with DEBUG
157
165
# replaced by INFO.
158
- if show_stdout :
159
- # Then log the subprocess output at INFO level.
160
- log_subprocess = subprocess_logger .info
161
- used_level = logging .INFO
162
- else :
163
- # Then log the subprocess output using DEBUG. This also ensures
164
- # it will be logged to the log file (aka user_log), if enabled.
165
- log_subprocess = subprocess_logger .debug
166
- used_level = logging .DEBUG
167
-
166
+ used_level = logging .INFO if show_stdout else logging .DEBUG
168
167
# Whether the subprocess will be visible in the console.
169
168
showing_subprocess = subprocess_logger .getEffectiveLevel () <= used_level
170
-
171
- # Only use the spinner if we're not showing the subprocess output
172
- # and we have a spinner.
173
- use_spinner = not showing_subprocess and spinner is not None
174
-
175
169
if command_desc is None :
176
170
command_desc = format_command_args (cmd )
171
+ proc = _call_subprocess (
172
+ cmd = cmd , cwd = cwd , spinner = spinner , command_desc = command_desc ,
173
+ extra_environ = extra_environ , unset_environ = unset_environ ,
174
+ log_failed_cmd = log_failed_cmd , log_level = used_level ,
175
+ extra_ok_returncodes = extra_ok_returncodes )
176
+ if proc .had_error :
177
+ if on_returncode == "raise" :
178
+ if not showing_subprocess and log_failed_cmd :
179
+ # Then the subprocess streams haven't been logged to the
180
+ # console yet.
181
+ msg = make_subprocess_output_error (
182
+ cmd_args = cmd ,
183
+ cwd = cwd ,
184
+ lines = proc .output ,
185
+ exit_status = proc .returncode ,
186
+ )
187
+ subprocess_logger .error (msg )
188
+ exc_msg = (
189
+ "Command errored out with exit status {}: {} "
190
+ "Check the logs for full command output."
191
+ ).format (proc .returncode , command_desc )
192
+ raise InstallationError (exc_msg )
193
+ elif on_returncode == "warn" :
194
+ subprocess_logger .warning (
195
+ "Command '{}' had error code {} in {}" .format (
196
+ command_desc , proc .returncode , cwd ))
197
+ elif on_returncode == "ignore" :
198
+ pass
199
+ else :
200
+ raise ValueError (
201
+ "Invalid value: on_returncode={!r}" .format (on_returncode ))
202
+ return "" .join (proc .output )
177
203
178
- log_subprocess ("Running command %s" , command_desc )
204
+
205
+ def _call_subprocess (
206
+ cmd , # type: Union[List[str], CommandArgs]
207
+ spinner = None , # type: Optional[SpinnerInterface]
208
+ cwd = None , # type: Optional[str]
209
+ log_level = logging .DEBUG , # type: int
210
+ command_desc = None , # type: Optional[str]
211
+ extra_environ = None , # type: Optional[Mapping[str, Any]]
212
+ unset_environ = None , # type: Optional[Iterable[str]]
213
+ extra_ok_returncodes = None , # type: Optional[Iterable[int]]
214
+ log_failed_cmd = True # type: Optional[bool]
215
+ ):
216
+ """
217
+ type: (...) -> SubProcessResult
218
+ This function calls the subprocess and returns a SubProcessResult
219
+ object representing the stdout lines, returncode, and command that
220
+ was executed. If subprocess logging is enabled, then display it
221
+ on stdout with the INFO logging level
222
+ """
223
+ if unset_environ is None :
224
+ unset_environ = []
225
+ if extra_ok_returncodes is None :
226
+ extra_ok_returncodes = []
227
+ if log_level == logging .DEBUG :
228
+ log_subprocess = subprocess_logger .debug
229
+ else :
230
+ log_subprocess = subprocess_logger .info
231
+ showing_subprocess = subprocess_logger .getEffectiveLevel () <= log_level
232
+ use_spinner = not showing_subprocess and spinner is not None
233
+ log_subprocess ("Running command {}" .format (command_desc ))
179
234
env = os .environ .copy ()
180
235
if extra_environ :
181
236
env .update (extra_environ )
182
237
for name in unset_environ :
183
238
env .pop (name , None )
239
+
240
+ all_output = []
184
241
try :
242
+ # Convert HiddenText objects to the underlying str.
185
243
proc = subprocess .Popen (
186
- # Convert HiddenText objects to the underlying str.
187
244
reveal_command_args (cmd ),
188
245
stderr = subprocess .STDOUT , stdin = subprocess .PIPE ,
189
- stdout = subprocess .PIPE , cwd = cwd , env = env ,
246
+ stdout = subprocess .PIPE , cwd = cwd , env = env
190
247
)
191
- proc .stdin .close ()
192
248
except Exception as exc :
193
249
if log_failed_cmd :
194
250
subprocess_logger .critical (
195
- "Error %s while executing command %s" , exc , command_desc ,
251
+ "Error %s while executing command %s" , exc , command_desc
196
252
)
197
253
raise
198
- all_output = []
199
254
while True :
200
255
# The "line" value is a unicode string in Python 2.
201
256
line = console_to_str (proc .stdout .readline ())
202
257
if not line :
203
258
break
204
259
line = line .rstrip ()
205
- all_output .append (line + '\n ' )
206
-
260
+ all_output .append ("{}\n " .format (line ))
207
261
# Show the line immediately.
208
262
log_subprocess (line )
209
263
# Update the spinner.
@@ -214,42 +268,16 @@ def call_subprocess(
214
268
finally :
215
269
if proc .stdout :
216
270
proc .stdout .close ()
217
- proc_had_error = (
218
- proc .returncode and proc .returncode not in extra_ok_returncodes
219
- )
271
+ had_error = proc .returncode and proc .returncode not in extra_ok_returncodes
220
272
if use_spinner :
221
- if proc_had_error :
273
+ if had_error :
222
274
spinner .finish ("error" )
223
275
else :
224
276
spinner .finish ("done" )
225
- if proc_had_error :
226
- if on_returncode == 'raise' :
227
- if not showing_subprocess and log_failed_cmd :
228
- # Then the subprocess streams haven't been logged to the
229
- # console yet.
230
- msg = make_subprocess_output_error (
231
- cmd_args = cmd ,
232
- cwd = cwd ,
233
- lines = all_output ,
234
- exit_status = proc .returncode ,
235
- )
236
- subprocess_logger .error (msg )
237
- exc_msg = (
238
- 'Command errored out with exit status {}: {} '
239
- 'Check the logs for full command output.'
240
- ).format (proc .returncode , command_desc )
241
- raise InstallationError (exc_msg )
242
- elif on_returncode == 'warn' :
243
- subprocess_logger .warning (
244
- 'Command "{}" had error code {} in {}' .format (
245
- command_desc , proc .returncode , cwd )
246
- )
247
- elif on_returncode == 'ignore' :
248
- pass
249
- else :
250
- raise ValueError ('Invalid value: on_returncode={!r}' .format (
251
- on_returncode ))
252
- return '' .join (all_output )
277
+ return SubProcessResult (
278
+ cmd = cmd , returncode = proc .returncode ,
279
+ had_error = had_error , output = all_output
280
+ )
253
281
254
282
255
283
def runner_with_spinner_message (message ):
@@ -267,7 +295,7 @@ def runner(
267
295
):
268
296
# type: (...) -> None
269
297
with open_spinner (message ) as spinner :
270
- call_subprocess (
298
+ call_subprocess_for_install (
271
299
cmd ,
272
300
cwd = cwd ,
273
301
extra_environ = extra_environ ,
0 commit comments