Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hhhrrrttt222111
GitHub Repository: hhhrrrttt222111/Dorkify
Path: blob/master/venv/Lib/site-packages/pip/_internal/utils/subprocess.py
811 views
1
# The following comment should be removed at some point in the future.
2
# mypy: strict-optional=False
3
4
from __future__ import absolute_import
5
6
import logging
7
import os
8
import subprocess
9
10
from pip._vendor.six.moves import shlex_quote
11
12
from pip._internal.cli.spinners import SpinnerInterface, open_spinner
13
from pip._internal.exceptions import InstallationError
14
from pip._internal.utils.compat import console_to_str, str_to_display
15
from pip._internal.utils.logging import subprocess_logger
16
from pip._internal.utils.misc import HiddenText, path_to_display
17
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
18
19
if MYPY_CHECK_RUNNING:
20
from typing import (
21
Any, Callable, Iterable, List, Mapping, Optional, Text, Union,
22
)
23
24
CommandArgs = List[Union[str, HiddenText]]
25
26
27
LOG_DIVIDER = '----------------------------------------'
28
29
30
def make_command(*args):
31
# type: (Union[str, HiddenText, CommandArgs]) -> CommandArgs
32
"""
33
Create a CommandArgs object.
34
"""
35
command_args = [] # type: CommandArgs
36
for arg in args:
37
# Check for list instead of CommandArgs since CommandArgs is
38
# only known during type-checking.
39
if isinstance(arg, list):
40
command_args.extend(arg)
41
else:
42
# Otherwise, arg is str or HiddenText.
43
command_args.append(arg)
44
45
return command_args
46
47
48
def format_command_args(args):
49
# type: (Union[List[str], CommandArgs]) -> str
50
"""
51
Format command arguments for display.
52
"""
53
# For HiddenText arguments, display the redacted form by calling str().
54
# Also, we don't apply str() to arguments that aren't HiddenText since
55
# this can trigger a UnicodeDecodeError in Python 2 if the argument
56
# has type unicode and includes a non-ascii character. (The type
57
# checker doesn't ensure the annotations are correct in all cases.)
58
return ' '.join(
59
shlex_quote(str(arg)) if isinstance(arg, HiddenText)
60
else shlex_quote(arg) for arg in args
61
)
62
63
64
def reveal_command_args(args):
65
# type: (Union[List[str], CommandArgs]) -> List[str]
66
"""
67
Return the arguments in their raw, unredacted form.
68
"""
69
return [
70
arg.secret if isinstance(arg, HiddenText) else arg for arg in args
71
]
72
73
74
def make_subprocess_output_error(
75
cmd_args, # type: Union[List[str], CommandArgs]
76
cwd, # type: Optional[str]
77
lines, # type: List[Text]
78
exit_status, # type: int
79
):
80
# type: (...) -> Text
81
"""
82
Create and return the error message to use to log a subprocess error
83
with command output.
84
85
:param lines: A list of lines, each ending with a newline.
86
"""
87
command = format_command_args(cmd_args)
88
# Convert `command` and `cwd` to text (unicode in Python 2) so we can use
89
# them as arguments in the unicode format string below. This avoids
90
# "UnicodeDecodeError: 'ascii' codec can't decode byte ..." in Python 2
91
# if either contains a non-ascii character.
92
command_display = str_to_display(command, desc='command bytes')
93
cwd_display = path_to_display(cwd)
94
95
# We know the joined output value ends in a newline.
96
output = ''.join(lines)
97
msg = (
98
# Use a unicode string to avoid "UnicodeEncodeError: 'ascii'
99
# codec can't encode character ..." in Python 2 when a format
100
# argument (e.g. `output`) has a non-ascii character.
101
u'Command errored out with exit status {exit_status}:\n'
102
' command: {command_display}\n'
103
' cwd: {cwd_display}\n'
104
'Complete output ({line_count} lines):\n{output}{divider}'
105
).format(
106
exit_status=exit_status,
107
command_display=command_display,
108
cwd_display=cwd_display,
109
line_count=len(lines),
110
output=output,
111
divider=LOG_DIVIDER,
112
)
113
return msg
114
115
116
def call_subprocess(
117
cmd, # type: Union[List[str], CommandArgs]
118
show_stdout=False, # type: bool
119
cwd=None, # type: Optional[str]
120
on_returncode='raise', # type: str
121
extra_ok_returncodes=None, # type: Optional[Iterable[int]]
122
command_desc=None, # type: Optional[str]
123
extra_environ=None, # type: Optional[Mapping[str, Any]]
124
unset_environ=None, # type: Optional[Iterable[str]]
125
spinner=None, # type: Optional[SpinnerInterface]
126
log_failed_cmd=True # type: Optional[bool]
127
):
128
# type: (...) -> Text
129
"""
130
Args:
131
show_stdout: if true, use INFO to log the subprocess's stderr and
132
stdout streams. Otherwise, use DEBUG. Defaults to False.
133
extra_ok_returncodes: an iterable of integer return codes that are
134
acceptable, in addition to 0. Defaults to None, which means [].
135
unset_environ: an iterable of environment variable names to unset
136
prior to calling subprocess.Popen().
137
log_failed_cmd: if false, failed commands are not logged, only raised.
138
"""
139
if extra_ok_returncodes is None:
140
extra_ok_returncodes = []
141
if unset_environ is None:
142
unset_environ = []
143
# Most places in pip use show_stdout=False. What this means is--
144
#
145
# - We connect the child's output (combined stderr and stdout) to a
146
# single pipe, which we read.
147
# - We log this output to stderr at DEBUG level as it is received.
148
# - If DEBUG logging isn't enabled (e.g. if --verbose logging wasn't
149
# requested), then we show a spinner so the user can still see the
150
# subprocess is in progress.
151
# - If the subprocess exits with an error, we log the output to stderr
152
# at ERROR level if it hasn't already been displayed to the console
153
# (e.g. if --verbose logging wasn't enabled). This way we don't log
154
# the output to the console twice.
155
#
156
# If show_stdout=True, then the above is still done, but with DEBUG
157
# 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
168
# Whether the subprocess will be visible in the console.
169
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
if command_desc is None:
176
command_desc = format_command_args(cmd)
177
178
log_subprocess("Running command %s", command_desc)
179
env = os.environ.copy()
180
if extra_environ:
181
env.update(extra_environ)
182
for name in unset_environ:
183
env.pop(name, None)
184
try:
185
proc = subprocess.Popen(
186
# Convert HiddenText objects to the underlying str.
187
reveal_command_args(cmd),
188
stderr=subprocess.STDOUT, stdin=subprocess.PIPE,
189
stdout=subprocess.PIPE, cwd=cwd, env=env,
190
)
191
proc.stdin.close()
192
except Exception as exc:
193
if log_failed_cmd:
194
subprocess_logger.critical(
195
"Error %s while executing command %s", exc, command_desc,
196
)
197
raise
198
all_output = []
199
while True:
200
# The "line" value is a unicode string in Python 2.
201
line = console_to_str(proc.stdout.readline())
202
if not line:
203
break
204
line = line.rstrip()
205
all_output.append(line + '\n')
206
207
# Show the line immediately.
208
log_subprocess(line)
209
# Update the spinner.
210
if use_spinner:
211
spinner.spin()
212
try:
213
proc.wait()
214
finally:
215
if proc.stdout:
216
proc.stdout.close()
217
proc_had_error = (
218
proc.returncode and proc.returncode not in extra_ok_returncodes
219
)
220
if use_spinner:
221
if proc_had_error:
222
spinner.finish("error")
223
else:
224
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)
253
254
255
def runner_with_spinner_message(message):
256
# type: (str) -> Callable[..., None]
257
"""Provide a subprocess_runner that shows a spinner message.
258
259
Intended for use with for pep517's Pep517HookCaller. Thus, the runner has
260
an API that matches what's expected by Pep517HookCaller.subprocess_runner.
261
"""
262
263
def runner(
264
cmd, # type: List[str]
265
cwd=None, # type: Optional[str]
266
extra_environ=None # type: Optional[Mapping[str, Any]]
267
):
268
# type: (...) -> None
269
with open_spinner(message) as spinner:
270
call_subprocess(
271
cmd,
272
cwd=cwd,
273
extra_environ=extra_environ,
274
spinner=spinner,
275
)
276
277
return runner
278
279