Skip to content

Commit 9e8a544

Browse files
authored
Merge pull request #9689 from uranusjr/isolated-pip
2 parents 770e5aa + bba1226 commit 9e8a544

File tree

4 files changed

+97
-6
lines changed

4 files changed

+97
-6
lines changed

news/8214.bugfix.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Prevent packages already-installed alongside with pip to be injected into an
2+
isolated build environment during build-time dependency population.

src/pip/_internal/build_env.py

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
"""Build Environment used for isolation during sdist building
22
"""
33

4+
import contextlib
45
import logging
56
import os
7+
import pathlib
68
import sys
79
import textwrap
10+
import zipfile
811
from collections import OrderedDict
912
from sysconfig import get_paths
1013
from types import TracebackType
11-
from typing import TYPE_CHECKING, Iterable, List, Optional, Set, Tuple, Type
14+
from typing import TYPE_CHECKING, Iterable, Iterator, List, Optional, Set, Tuple, Type
1215

16+
from pip._vendor.certifi import where
1317
from pip._vendor.pkg_resources import Requirement, VersionConflict, WorkingSet
1418

1519
from pip import __file__ as pip_location
@@ -37,6 +41,29 @@ def __init__(self, path):
3741
self.lib_dirs = get_prefixed_libs(path)
3842

3943

44+
@contextlib.contextmanager
45+
def _create_standalone_pip() -> Iterator[str]:
46+
"""Create a "standalone pip" zip file.
47+
48+
The zip file's content is identical to the currently-running pip.
49+
It will be used to install requirements into the build environment.
50+
"""
51+
source = pathlib.Path(pip_location).resolve().parent
52+
53+
# Return the current instance if it is already a zip file. This can happen
54+
# if a PEP 517 requirement is an sdist itself.
55+
if not source.is_dir() and source.parent.name == "__env_pip__.zip":
56+
yield str(source)
57+
return
58+
59+
with TempDirectory(kind="standalone-pip") as tmp_dir:
60+
pip_zip = os.path.join(tmp_dir.path, "__env_pip__.zip")
61+
with zipfile.ZipFile(pip_zip, "w") as zf:
62+
for child in source.rglob("*"):
63+
zf.write(child, child.relative_to(source.parent).as_posix())
64+
yield os.path.join(pip_zip, "pip")
65+
66+
4067
class BuildEnvironment:
4168
"""Creates and manages an isolated environment to install build deps
4269
"""
@@ -160,8 +187,25 @@ def install_requirements(
160187
prefix.setup = True
161188
if not requirements:
162189
return
190+
with _create_standalone_pip() as standalone_pip:
191+
self._install_requirements(
192+
standalone_pip,
193+
finder,
194+
requirements,
195+
prefix,
196+
message,
197+
)
198+
199+
@staticmethod
200+
def _install_requirements(
201+
standalone_pip: str,
202+
finder: "PackageFinder",
203+
requirements: Iterable[str],
204+
prefix: _Prefix,
205+
message: str,
206+
) -> None:
163207
args = [
164-
sys.executable, os.path.dirname(pip_location), 'install',
208+
sys.executable, standalone_pip, 'install',
165209
'--ignore-installed', '--no-user', '--prefix', prefix.path,
166210
'--no-warn-script-location',
167211
] # type: List[str]
@@ -190,8 +234,9 @@ def install_requirements(
190234
args.append('--prefer-binary')
191235
args.append('--')
192236
args.extend(requirements)
237+
extra_environ = {"_PIP_STANDALONE_CERT": where()}
193238
with open_spinner(message) as spinner:
194-
call_subprocess(args, spinner=spinner)
239+
call_subprocess(args, spinner=spinner, extra_environ=extra_environ)
195240

196241

197242
class NoOpBuildEnvironment(BuildEnvironment):

src/pip/_vendor/certifi/core.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,21 @@
88
"""
99
import os
1010

11+
12+
class _PipPatchedCertificate(Exception):
13+
pass
14+
15+
1116
try:
17+
# Return a certificate file on disk for a standalone pip zipapp running in
18+
# an isolated build environment to use. Passing --cert to the standalone
19+
# pip does not work since requests calls where() unconditionally on import.
20+
_PIP_STANDALONE_CERT = os.environ.get("_PIP_STANDALONE_CERT")
21+
if _PIP_STANDALONE_CERT:
22+
def where():
23+
return _PIP_STANDALONE_CERT
24+
raise _PipPatchedCertificate()
25+
1226
from importlib.resources import path as get_path, read_text
1327

1428
_CACERT_CTX = None
@@ -38,6 +52,8 @@ def where():
3852

3953
return _CACERT_PATH
4054

55+
except _PipPatchedCertificate:
56+
pass
4157

4258
except ImportError:
4359
# This fallback will work for Python versions prior to 3.7 that lack the

tools/vendoring/patches/certifi.patch

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,41 @@
11
diff --git a/src/pip/_vendor/certifi/core.py b/src/pip/_vendor/certifi/core.py
2-
index 5d2b8cd32..8987449f6 100644
2+
index 5d2b8cd32..b8140cf1a 100644
33
--- a/src/pip/_vendor/certifi/core.py
44
+++ b/src/pip/_vendor/certifi/core.py
5-
@@ -33,7 +33,7 @@ try:
5+
@@ -8,7 +8,21 @@ This module returns the installation location of cacert.pem or its contents.
6+
"""
7+
import os
8+
9+
+
10+
+class _PipPatchedCertificate(Exception):
11+
+ pass
12+
+
13+
+
14+
try:
15+
+ # Return a certificate file on disk for a standalone pip zipapp running in
16+
+ # an isolated build environment to use. Passing --cert to the standalone
17+
+ # pip does not work since requests calls where() unconditionally on import.
18+
+ _PIP_STANDALONE_CERT = os.environ.get("_PIP_STANDALONE_CERT")
19+
+ if _PIP_STANDALONE_CERT:
20+
+ def where():
21+
+ return _PIP_STANDALONE_CERT
22+
+ raise _PipPatchedCertificate()
23+
+
24+
from importlib.resources import path as get_path, read_text
25+
26+
_CACERT_CTX = None
27+
@@ -33,11 +47,13 @@ try:
628
# We also have to hold onto the actual context manager, because
729
# it will do the cleanup whenever it gets garbage collected, so
830
# we will also store that at the global level as well.
931
- _CACERT_CTX = get_path("certifi", "cacert.pem")
1032
+ _CACERT_CTX = get_path("pip._vendor.certifi", "cacert.pem")
1133
_CACERT_PATH = str(_CACERT_CTX.__enter__())
12-
34+
1335
return _CACERT_PATH
36+
37+
+except _PipPatchedCertificate:
38+
+ pass
39+
40+
except ImportError:
41+
# This fallback will work for Python versions prior to 3.7 that lack the

0 commit comments

Comments
 (0)