Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
keewenaw
GitHub Repository: keewenaw/ethereum-wallet-cracker
Path: blob/main/test/lib/python3.9/site-packages/pip/_internal/utils/subprocess.py
4804 views
1
import logging
2
import os
3
import shlex
4
import subprocess
5
from typing import (
6
TYPE_CHECKING,
7
Any,
8
Callable,
9
Iterable,
10
List,
11
Mapping,
12
Optional,
13
Union,
14
)
15
16
from pip._vendor.rich.markup import escape
17
18
from pip._internal.cli.spinners import SpinnerInterface, open_spinner
19
from pip._internal.exceptions import InstallationSubprocessError
20
from pip._internal.utils.logging import VERBOSE, subprocess_logger
21
from pip._internal.utils.misc import HiddenText
22
23
if TYPE_CHECKING:
24
# Literal was introduced in Python 3.8.
25
#
26
# TODO: Remove `if TYPE_CHECKING` when dropping support for Python 3.7.
27
from typing import Literal
28
29
CommandArgs = List[Union[str, HiddenText]]
30
31
32
def make_command(*args: Union[str, HiddenText, CommandArgs]) -> CommandArgs:
33
"""
34
Create a CommandArgs object.
35
"""
36
command_args: CommandArgs = []
37
for arg in args:
38
# Check for list instead of CommandArgs since CommandArgs is
39
# only known during type-checking.
40
if isinstance(arg, list):
41
command_args.extend(arg)
42
else:
43
# Otherwise, arg is str or HiddenText.
44
command_args.append(arg)
45
46
return command_args
47
48
49
def format_command_args(args: 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) else shlex.quote(arg)
60
for arg in args
61
)
62
63
64
def reveal_command_args(args: Union[List[str], CommandArgs]) -> List[str]:
65
"""
66
Return the arguments in their raw, unredacted form.
67
"""
68
return [arg.secret if isinstance(arg, HiddenText) else arg for arg in args]
69
70
71
def call_subprocess(
72
cmd: Union[List[str], CommandArgs],
73
show_stdout: bool = False,
74
cwd: Optional[str] = None,
75
on_returncode: 'Literal["raise", "warn", "ignore"]' = "raise",
76
extra_ok_returncodes: Optional[Iterable[int]] = None,
77
extra_environ: Optional[Mapping[str, Any]] = None,
78
unset_environ: Optional[Iterable[str]] = None,
79
spinner: Optional[SpinnerInterface] = None,
80
log_failed_cmd: Optional[bool] = True,
81
stdout_only: Optional[bool] = False,
82
*,
83
command_desc: str,
84
) -> str:
85
"""
86
Args:
87
show_stdout: if true, use INFO to log the subprocess's stderr and
88
stdout streams. Otherwise, use DEBUG. Defaults to False.
89
extra_ok_returncodes: an iterable of integer return codes that are
90
acceptable, in addition to 0. Defaults to None, which means [].
91
unset_environ: an iterable of environment variable names to unset
92
prior to calling subprocess.Popen().
93
log_failed_cmd: if false, failed commands are not logged, only raised.
94
stdout_only: if true, return only stdout, else return both. When true,
95
logging of both stdout and stderr occurs when the subprocess has
96
terminated, else logging occurs as subprocess output is produced.
97
"""
98
if extra_ok_returncodes is None:
99
extra_ok_returncodes = []
100
if unset_environ is None:
101
unset_environ = []
102
# Most places in pip use show_stdout=False. What this means is--
103
#
104
# - We connect the child's output (combined stderr and stdout) to a
105
# single pipe, which we read.
106
# - We log this output to stderr at DEBUG level as it is received.
107
# - If DEBUG logging isn't enabled (e.g. if --verbose logging wasn't
108
# requested), then we show a spinner so the user can still see the
109
# subprocess is in progress.
110
# - If the subprocess exits with an error, we log the output to stderr
111
# at ERROR level if it hasn't already been displayed to the console
112
# (e.g. if --verbose logging wasn't enabled). This way we don't log
113
# the output to the console twice.
114
#
115
# If show_stdout=True, then the above is still done, but with DEBUG
116
# replaced by INFO.
117
if show_stdout:
118
# Then log the subprocess output at INFO level.
119
log_subprocess: Callable[..., None] = subprocess_logger.info
120
used_level = logging.INFO
121
else:
122
# Then log the subprocess output using VERBOSE. This also ensures
123
# it will be logged to the log file (aka user_log), if enabled.
124
log_subprocess = subprocess_logger.verbose
125
used_level = VERBOSE
126
127
# Whether the subprocess will be visible in the console.
128
showing_subprocess = subprocess_logger.getEffectiveLevel() <= used_level
129
130
# Only use the spinner if we're not showing the subprocess output
131
# and we have a spinner.
132
use_spinner = not showing_subprocess and spinner is not None
133
134
log_subprocess("Running command %s", command_desc)
135
env = os.environ.copy()
136
if extra_environ:
137
env.update(extra_environ)
138
for name in unset_environ:
139
env.pop(name, None)
140
try:
141
proc = subprocess.Popen(
142
# Convert HiddenText objects to the underlying str.
143
reveal_command_args(cmd),
144
stdin=subprocess.PIPE,
145
stdout=subprocess.PIPE,
146
stderr=subprocess.STDOUT if not stdout_only else subprocess.PIPE,
147
cwd=cwd,
148
env=env,
149
errors="backslashreplace",
150
)
151
except Exception as exc:
152
if log_failed_cmd:
153
subprocess_logger.critical(
154
"Error %s while executing command %s",
155
exc,
156
command_desc,
157
)
158
raise
159
all_output = []
160
if not stdout_only:
161
assert proc.stdout
162
assert proc.stdin
163
proc.stdin.close()
164
# In this mode, stdout and stderr are in the same pipe.
165
while True:
166
line: str = proc.stdout.readline()
167
if not line:
168
break
169
line = line.rstrip()
170
all_output.append(line + "\n")
171
172
# Show the line immediately.
173
log_subprocess(line)
174
# Update the spinner.
175
if use_spinner:
176
assert spinner
177
spinner.spin()
178
try:
179
proc.wait()
180
finally:
181
if proc.stdout:
182
proc.stdout.close()
183
output = "".join(all_output)
184
else:
185
# In this mode, stdout and stderr are in different pipes.
186
# We must use communicate() which is the only safe way to read both.
187
out, err = proc.communicate()
188
# log line by line to preserve pip log indenting
189
for out_line in out.splitlines():
190
log_subprocess(out_line)
191
all_output.append(out)
192
for err_line in err.splitlines():
193
log_subprocess(err_line)
194
all_output.append(err)
195
output = out
196
197
proc_had_error = proc.returncode and proc.returncode not in extra_ok_returncodes
198
if use_spinner:
199
assert spinner
200
if proc_had_error:
201
spinner.finish("error")
202
else:
203
spinner.finish("done")
204
if proc_had_error:
205
if on_returncode == "raise":
206
error = InstallationSubprocessError(
207
command_description=command_desc,
208
exit_code=proc.returncode,
209
output_lines=all_output if not showing_subprocess else None,
210
)
211
if log_failed_cmd:
212
subprocess_logger.error("[present-rich] %s", error)
213
subprocess_logger.verbose(
214
"[bold magenta]full command[/]: [blue]%s[/]",
215
escape(format_command_args(cmd)),
216
extra={"markup": True},
217
)
218
subprocess_logger.verbose(
219
"[bold magenta]cwd[/]: %s",
220
escape(cwd or "[inherit]"),
221
extra={"markup": True},
222
)
223
224
raise error
225
elif on_returncode == "warn":
226
subprocess_logger.warning(
227
'Command "%s" had error code %s in %s',
228
command_desc,
229
proc.returncode,
230
cwd,
231
)
232
elif on_returncode == "ignore":
233
pass
234
else:
235
raise ValueError(f"Invalid value: on_returncode={on_returncode!r}")
236
return output
237
238
239
def runner_with_spinner_message(message: str) -> Callable[..., None]:
240
"""Provide a subprocess_runner that shows a spinner message.
241
242
Intended for use with for pep517's Pep517HookCaller. Thus, the runner has
243
an API that matches what's expected by Pep517HookCaller.subprocess_runner.
244
"""
245
246
def runner(
247
cmd: List[str],
248
cwd: Optional[str] = None,
249
extra_environ: Optional[Mapping[str, Any]] = None,
250
) -> None:
251
with open_spinner(message) as spinner:
252
call_subprocess(
253
cmd,
254
command_desc=message,
255
cwd=cwd,
256
extra_environ=extra_environ,
257
spinner=spinner,
258
)
259
260
return runner
261
262