]> jfr.im git - dlqueue.git/blob - venv/lib/python3.11/site-packages/pip/_internal/build_env.py
init: venv aand flask
[dlqueue.git] / venv / lib / python3.11 / site-packages / pip / _internal / build_env.py
1 """Build Environment used for isolation during sdist building
2 """
3
4 import logging
5 import os
6 import pathlib
7 import site
8 import sys
9 import textwrap
10 from collections import OrderedDict
11 from types import TracebackType
12 from typing import TYPE_CHECKING, Iterable, List, Optional, Set, Tuple, Type, Union
13
14 from pip._vendor.certifi import where
15 from pip._vendor.packaging.requirements import Requirement
16 from pip._vendor.packaging.version import Version
17
18 from pip import __file__ as pip_location
19 from pip._internal.cli.spinners import open_spinner
20 from pip._internal.locations import get_platlib, get_purelib, get_scheme
21 from pip._internal.metadata import get_default_environment, get_environment
22 from pip._internal.utils.subprocess import call_subprocess
23 from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
24
25 if TYPE_CHECKING:
26 from pip._internal.index.package_finder import PackageFinder
27
28 logger = logging.getLogger(__name__)
29
30
31 def _dedup(a: str, b: str) -> Union[Tuple[str], Tuple[str, str]]:
32 return (a, b) if a != b else (a,)
33
34
35 class _Prefix:
36 def __init__(self, path: str) -> None:
37 self.path = path
38 self.setup = False
39 scheme = get_scheme("", prefix=path)
40 self.bin_dir = scheme.scripts
41 self.lib_dirs = _dedup(scheme.purelib, scheme.platlib)
42
43
44 def get_runnable_pip() -> str:
45 """Get a file to pass to a Python executable, to run the currently-running pip.
46
47 This is used to run a pip subprocess, for installing requirements into the build
48 environment.
49 """
50 source = pathlib.Path(pip_location).resolve().parent
51
52 if not source.is_dir():
53 # This would happen if someone is using pip from inside a zip file. In that
54 # case, we can use that directly.
55 return str(source)
56
57 return os.fsdecode(source / "__pip-runner__.py")
58
59
60 def _get_system_sitepackages() -> Set[str]:
61 """Get system site packages
62
63 Usually from site.getsitepackages,
64 but fallback on `get_purelib()/get_platlib()` if unavailable
65 (e.g. in a virtualenv created by virtualenv<20)
66
67 Returns normalized set of strings.
68 """
69 if hasattr(site, "getsitepackages"):
70 system_sites = site.getsitepackages()
71 else:
72 # virtualenv < 20 overwrites site.py without getsitepackages
73 # fallback on get_purelib/get_platlib.
74 # this is known to miss things, but shouldn't in the cases
75 # where getsitepackages() has been removed (inside a virtualenv)
76 system_sites = [get_purelib(), get_platlib()]
77 return {os.path.normcase(path) for path in system_sites}
78
79
80 class BuildEnvironment:
81 """Creates and manages an isolated environment to install build deps"""
82
83 def __init__(self) -> None:
84 temp_dir = TempDirectory(kind=tempdir_kinds.BUILD_ENV, globally_managed=True)
85
86 self._prefixes = OrderedDict(
87 (name, _Prefix(os.path.join(temp_dir.path, name)))
88 for name in ("normal", "overlay")
89 )
90
91 self._bin_dirs: List[str] = []
92 self._lib_dirs: List[str] = []
93 for prefix in reversed(list(self._prefixes.values())):
94 self._bin_dirs.append(prefix.bin_dir)
95 self._lib_dirs.extend(prefix.lib_dirs)
96
97 # Customize site to:
98 # - ensure .pth files are honored
99 # - prevent access to system site packages
100 system_sites = _get_system_sitepackages()
101
102 self._site_dir = os.path.join(temp_dir.path, "site")
103 if not os.path.exists(self._site_dir):
104 os.mkdir(self._site_dir)
105 with open(
106 os.path.join(self._site_dir, "sitecustomize.py"), "w", encoding="utf-8"
107 ) as fp:
108 fp.write(
109 textwrap.dedent(
110 """
111 import os, site, sys
112
113 # First, drop system-sites related paths.
114 original_sys_path = sys.path[:]
115 known_paths = set()
116 for path in {system_sites!r}:
117 site.addsitedir(path, known_paths=known_paths)
118 system_paths = set(
119 os.path.normcase(path)
120 for path in sys.path[len(original_sys_path):]
121 )
122 original_sys_path = [
123 path for path in original_sys_path
124 if os.path.normcase(path) not in system_paths
125 ]
126 sys.path = original_sys_path
127
128 # Second, add lib directories.
129 # ensuring .pth file are processed.
130 for path in {lib_dirs!r}:
131 assert not path in sys.path
132 site.addsitedir(path)
133 """
134 ).format(system_sites=system_sites, lib_dirs=self._lib_dirs)
135 )
136
137 def __enter__(self) -> None:
138 self._save_env = {
139 name: os.environ.get(name, None)
140 for name in ("PATH", "PYTHONNOUSERSITE", "PYTHONPATH")
141 }
142
143 path = self._bin_dirs[:]
144 old_path = self._save_env["PATH"]
145 if old_path:
146 path.extend(old_path.split(os.pathsep))
147
148 pythonpath = [self._site_dir]
149
150 os.environ.update(
151 {
152 "PATH": os.pathsep.join(path),
153 "PYTHONNOUSERSITE": "1",
154 "PYTHONPATH": os.pathsep.join(pythonpath),
155 }
156 )
157
158 def __exit__(
159 self,
160 exc_type: Optional[Type[BaseException]],
161 exc_val: Optional[BaseException],
162 exc_tb: Optional[TracebackType],
163 ) -> None:
164 for varname, old_value in self._save_env.items():
165 if old_value is None:
166 os.environ.pop(varname, None)
167 else:
168 os.environ[varname] = old_value
169
170 def check_requirements(
171 self, reqs: Iterable[str]
172 ) -> Tuple[Set[Tuple[str, str]], Set[str]]:
173 """Return 2 sets:
174 - conflicting requirements: set of (installed, wanted) reqs tuples
175 - missing requirements: set of reqs
176 """
177 missing = set()
178 conflicting = set()
179 if reqs:
180 env = (
181 get_environment(self._lib_dirs)
182 if hasattr(self, "_lib_dirs")
183 else get_default_environment()
184 )
185 for req_str in reqs:
186 req = Requirement(req_str)
187 # We're explicitly evaluating with an empty extra value, since build
188 # environments are not provided any mechanism to select specific extras.
189 if req.marker is not None and not req.marker.evaluate({"extra": ""}):
190 continue
191 dist = env.get_distribution(req.name)
192 if not dist:
193 missing.add(req_str)
194 continue
195 if isinstance(dist.version, Version):
196 installed_req_str = f"{req.name}=={dist.version}"
197 else:
198 installed_req_str = f"{req.name}==={dist.version}"
199 if not req.specifier.contains(dist.version, prereleases=True):
200 conflicting.add((installed_req_str, req_str))
201 # FIXME: Consider direct URL?
202 return conflicting, missing
203
204 def install_requirements(
205 self,
206 finder: "PackageFinder",
207 requirements: Iterable[str],
208 prefix_as_string: str,
209 *,
210 kind: str,
211 ) -> None:
212 prefix = self._prefixes[prefix_as_string]
213 assert not prefix.setup
214 prefix.setup = True
215 if not requirements:
216 return
217 self._install_requirements(
218 get_runnable_pip(),
219 finder,
220 requirements,
221 prefix,
222 kind=kind,
223 )
224
225 @staticmethod
226 def _install_requirements(
227 pip_runnable: str,
228 finder: "PackageFinder",
229 requirements: Iterable[str],
230 prefix: _Prefix,
231 *,
232 kind: str,
233 ) -> None:
234 args: List[str] = [
235 sys.executable,
236 pip_runnable,
237 "install",
238 "--ignore-installed",
239 "--no-user",
240 "--prefix",
241 prefix.path,
242 "--no-warn-script-location",
243 ]
244 if logger.getEffectiveLevel() <= logging.DEBUG:
245 args.append("-v")
246 for format_control in ("no_binary", "only_binary"):
247 formats = getattr(finder.format_control, format_control)
248 args.extend(
249 (
250 "--" + format_control.replace("_", "-"),
251 ",".join(sorted(formats or {":none:"})),
252 )
253 )
254
255 index_urls = finder.index_urls
256 if index_urls:
257 args.extend(["-i", index_urls[0]])
258 for extra_index in index_urls[1:]:
259 args.extend(["--extra-index-url", extra_index])
260 else:
261 args.append("--no-index")
262 for link in finder.find_links:
263 args.extend(["--find-links", link])
264
265 for host in finder.trusted_hosts:
266 args.extend(["--trusted-host", host])
267 if finder.allow_all_prereleases:
268 args.append("--pre")
269 if finder.prefer_binary:
270 args.append("--prefer-binary")
271 args.append("--")
272 args.extend(requirements)
273 extra_environ = {"_PIP_STANDALONE_CERT": where()}
274 with open_spinner(f"Installing {kind}") as spinner:
275 call_subprocess(
276 args,
277 command_desc=f"pip subprocess to install {kind}",
278 spinner=spinner,
279 extra_environ=extra_environ,
280 )
281
282
283 class NoOpBuildEnvironment(BuildEnvironment):
284 """A no-op drop-in replacement for BuildEnvironment"""
285
286 def __init__(self) -> None:
287 pass
288
289 def __enter__(self) -> None:
290 pass
291
292 def __exit__(
293 self,
294 exc_type: Optional[Type[BaseException]],
295 exc_val: Optional[BaseException],
296 exc_tb: Optional[TracebackType],
297 ) -> None:
298 pass
299
300 def cleanup(self) -> None:
301 pass
302
303 def install_requirements(
304 self,
305 finder: "PackageFinder",
306 requirements: Iterable[str],
307 prefix_as_string: str,
308 *,
309 kind: str,
310 ) -> None:
311 raise NotImplementedError()