1"""Build environment related pip exceptions."""
2
3from __future__ import annotations
4
5import os
6import sys
7import traceback
8from collections.abc import Iterable
9from typing import TYPE_CHECKING
10
11from pip._vendor.rich.markup import escape
12from pip._vendor.rich.text import Text
13
14from pip._internal.exceptions._base import (
15 DiagnosticPipError,
16 InstallationError,
17 PipError,
18)
19
20if TYPE_CHECKING:
21 from pip._internal.req.req_install import InstallRequirement
22
23
24class BuildDependencyInstallError(DiagnosticPipError):
25 """Raised when build dependencies cannot be installed."""
26
27 reference = "failed-build-dependency-install"
28
29 def __init__(
30 self,
31 req: InstallRequirement | None,
32 build_reqs: Iterable[str],
33 *,
34 cause: Exception,
35 log_lines: list[str] | None,
36 ) -> None:
37 if isinstance(cause, PipError):
38 note = "This is likely not a problem with pip."
39 else:
40 note = (
41 "pip crashed unexpectedly. Please file an issue on pip's issue "
42 "tracker: https://github.com/pypa/pip/issues/new"
43 )
44
45 if log_lines is None:
46 # No logs are available, they must have been printed earlier.
47 context = Text("See above for more details.")
48 else:
49 if isinstance(cause, PipError):
50 log_lines.append(f"ERROR: {cause}")
51 else:
52 # Split rendered error into real lines without trailing newlines.
53 log_lines.extend(
54 "".join(traceback.format_exception(cause)).splitlines()
55 )
56
57 context = Text.assemble(
58 f"Installing {' '.join(build_reqs)}\n",
59 (f"[{len(log_lines)} lines of output]\n", "red"),
60 "\n".join(log_lines),
61 ("\n[end of output]", "red"),
62 )
63
64 message = Text("Cannot install build dependencies", "green")
65 if req:
66 message += Text(f" for {req}")
67 super().__init__(
68 message=message, context=context, hint_stmt=None, note_stmt=note
69 )
70
71
72class VenvImportError(DiagnosticPipError):
73 """Raised when 'venv' can't be imported."""
74
75 reference = "venv-import-error"
76
77 def __init__(self) -> None:
78 if sys.platform != "linux":
79 hint_stmt = None
80 else:
81 hint_stmt = (
82 "If this is an OS-provided Python, it's likely that your OS "
83 "package maintainers have split Python's standard library across "
84 "multiple OS packages."
85 )
86 super().__init__(
87 message="Cannot import the 'venv' module of the Python standard library",
88 context=(
89 "This is a symptom of a broken/modified Python, which cannot be used "
90 "with pip."
91 ),
92 note_stmt="This is an issue with the Python installation itself, not pip.",
93 hint_stmt=hint_stmt,
94 )
95
96
97class VenvCreationError(DiagnosticPipError):
98 """Raised when a virtual environment can't be created."""
99
100 reference = "venv-creation-error"
101
102 def __init__(self, context: str) -> None:
103 if os.name == "nt":
104 hint = "This may be caused by running antivirus software."
105 else:
106 hint = None
107 super().__init__(
108 message="Cannot create a virtual environment",
109 context=Text(context),
110 hint_stmt=hint,
111 )
112
113
114class InstallationSubprocessError(DiagnosticPipError, InstallationError):
115 """A subprocess call failed."""
116
117 reference = "subprocess-exited-with-error"
118
119 def __init__(
120 self,
121 *,
122 command_description: str,
123 exit_code: int,
124 output_lines: list[str] | None,
125 ) -> None:
126 if output_lines is None:
127 output_prompt = Text("No available output.")
128 else:
129 output_prompt = (
130 Text.from_markup(f"[red][{len(output_lines)} lines of output][/]\n")
131 + Text("".join(output_lines))
132 + Text.from_markup(R"[red]\[end of output][/]")
133 )
134
135 super().__init__(
136 message=(
137 f"[green]{escape(command_description)}[/] did not run successfully.\n"
138 f"exit code: {exit_code}"
139 ),
140 context=output_prompt,
141 hint_stmt=None,
142 note_stmt=(
143 "This error originates from a subprocess, and is likely not a "
144 "problem with pip."
145 ),
146 )
147
148 self.command_description = command_description
149 self.exit_code = exit_code
150
151 def __str__(self) -> str:
152 return f"{self.command_description} exited with {self.exit_code}"
153
154
155class BackendUnavailableError(InstallationSubprocessError):
156 """The build backend could not be loaded."""
157
158 reference = "backend-unavailable"
159
160 def __init__(
161 self, *, hook_name: str, backend_name: str, backend_error: str
162 ) -> None:
163 DiagnosticPipError.__init__(
164 self,
165 message=f"Cannot import build backend {escape(backend_name)!r}.",
166 context=backend_error,
167 hint_stmt=None,
168 note_stmt="This is likely not a problem with pip.",
169 )
170 self.command_description = f"Calling build backend hook {hook_name}"
171
172 def __str__(self) -> str:
173 return str(self.message)