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