Skip to content

Commit 044d900

Browse files
committed
fixup! Try switching to venv for isolation
1 parent d887a85 commit 044d900

File tree

3 files changed

+91
-91
lines changed

3 files changed

+91
-91
lines changed

src/pip/_internal/build_env.py

Lines changed: 52 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,25 @@
88
import sys
99
import venv
1010
import zipfile
11-
from sysconfig import get_paths
12-
from types import SimpleNamespace, TracebackType
13-
from typing import TYPE_CHECKING, Iterable, Iterator, List, Optional, Set, Tuple, Type
11+
from types import TracebackType
12+
from typing import (
13+
TYPE_CHECKING,
14+
Dict,
15+
Iterable,
16+
Iterator,
17+
List,
18+
Optional,
19+
Set,
20+
Tuple,
21+
Type,
22+
)
1423

1524
from pip._vendor.certifi import where
1625
from pip._vendor.packaging.requirements import Requirement
1726
from pip._vendor.packaging.version import Version
1827

1928
from pip import __file__ as pip_location
2029
from pip._internal.cli.spinners import open_spinner
21-
from pip._internal.locations import get_platlib, get_prefixed_libs, get_purelib
2230
from pip._internal.metadata import get_environment
2331
from pip._internal.utils.subprocess import call_subprocess
2432
from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
@@ -29,17 +37,6 @@
2937
logger = logging.getLogger(__name__)
3038

3139

32-
class _Prefix:
33-
def __init__(self, path: str) -> None:
34-
self.path = path
35-
self.setup = False
36-
self.bin_dir = get_paths(
37-
"nt" if os.name == "nt" else "posix_prefix",
38-
vars={"base": path, "platbase": path},
39-
)["scripts"]
40-
self.lib_dirs = get_prefixed_libs(path)
41-
42-
4340
@contextlib.contextmanager
4441
def _create_standalone_pip() -> Iterator[str]:
4542
"""Create a "standalone pip" zip file.
@@ -70,38 +67,54 @@ class BuildEnvironment:
7067
"""Creates and manages an isolated environment to install build deps"""
7168

7269
def __init__(self) -> None:
73-
self._venv = venv.EnvBuilder(
74-
system_site_packages=False,
75-
clear=False,
76-
symlinks=False,
77-
upgrade=False,
78-
with_pip=False,
79-
prompt=None,
80-
upgrade_deps=False,
70+
self._temp_dir = TempDirectory(
71+
kind=tempdir_kinds.BUILD_ENV, globally_managed=True
8172
)
82-
self._temp_dir = None
83-
self._env_executable_path = None
73+
self._venv = venv.EnvBuilder()
74+
context = self._venv.ensure_directories(self._temp_dir.path)
75+
self._venv.create(self._temp_dir.path)
76+
77+
# Copy-pasted from venv/__init__.py
78+
if sys.platform == "win32":
79+
libpath = os.path.join(
80+
self._temp_dir.path,
81+
"Lib",
82+
"site-packages",
83+
)
84+
else:
85+
libpath = os.path.join(
86+
self._temp_dir.path,
87+
"lib",
88+
f"python{sys.version_info.major}.{sys.version_info.minor}",
89+
"site-packages",
90+
)
91+
92+
self._lib_path = libpath
93+
self._bin_path = context.bin_path
94+
self._env_executable = context.env_exe
95+
self._save_env: Dict[str, str] = {}
8496

8597
def __enter__(self) -> None:
86-
self._temp_dir = TempDirectory(
87-
kind=tempdir_kinds.BUILD_ENV,
88-
globally_managed=False,
89-
)
98+
self._save_env = {
99+
name: os.environ.get(name, "") for name in ("PATH", "PYTHONPATH")
100+
}
90101

91-
context = self._venv.ensure_directories(self._temp_dir)
92-
self._env_executable_path = context.exe_name
102+
old_path = self._save_env["PATH"]
103+
new_path = os.pathsep.join(filter(None, [self._bin_path, old_path]))
93104

94-
self._venv.create(self._temp_dir)
105+
os.environ.update({"PATH": new_path, "PYTHONPATH": self._lib_path})
95106

96107
def __exit__(
97108
self,
98109
exc_type: Optional[Type[BaseException]],
99110
exc_val: Optional[BaseException],
100111
exc_tb: Optional[TracebackType],
101112
) -> None:
102-
self._temp_dir.cleanup()
103-
self._temp_dir = None
104-
self._env_executable_path = None
113+
for varname, old_value in self._save_env.items():
114+
if old_value is None:
115+
os.environ.pop(varname, None)
116+
else:
117+
os.environ[varname] = old_value
105118

106119
def check_requirements(
107120
self, reqs: Iterable[str]
@@ -113,7 +126,7 @@ def check_requirements(
113126
missing = set()
114127
conflicting = set()
115128
if reqs:
116-
env = get_environment(self._lib_dirs)
129+
env = get_environment([self._lib_path])
117130
for req_str in reqs:
118131
req = Requirement(req_str)
119132
dist = env.get_distribution(req.name)
@@ -136,11 +149,12 @@ def install_requirements(
136149
requirements: Iterable[str],
137150
message: str,
138151
) -> None:
152+
assert self._env_executable
139153
if not requirements:
140154
return
141155
with _create_standalone_pip() as pip_runnable:
142156
self._install_requirements(
143-
self._env_executable_path,
157+
self._env_executable,
144158
pip_runnable,
145159
finder,
146160
requirements,
@@ -153,7 +167,6 @@ def _install_requirements(
153167
pip_runnable: str,
154168
finder: "PackageFinder",
155169
requirements: Iterable[str],
156-
prefix: _Prefix,
157170
message: str,
158171
) -> None:
159172
args: List[str] = [
@@ -162,8 +175,6 @@ def _install_requirements(
162175
"install",
163176
"--ignore-installed",
164177
"--no-user",
165-
"--prefix",
166-
prefix.path,
167178
"--no-warn-script-location",
168179
]
169180
if logger.getEffectiveLevel() <= logging.DEBUG:
@@ -225,8 +236,8 @@ def check_requirements(
225236
def install_requirements(
226237
self,
227238
finder: "PackageFinder",
239+
*,
228240
requirements: Iterable[str],
229-
prefix_as_string: str,
230241
message: str,
231242
) -> None:
232243
raise NotImplementedError()

tests/functional/test_build_env.py

Lines changed: 33 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -79,29 +79,9 @@ def run_with_build_env(
7979
def test_build_env_allow_empty_requirements_install() -> None:
8080
finder = make_test_finder()
8181
build_env = BuildEnvironment()
82-
for prefix in ("normal", "overlay"):
83-
build_env.install_requirements(
84-
finder, [], prefix, "Installing build dependencies"
85-
)
86-
87-
88-
def test_build_env_allow_only_one_install(script: PipTestEnvironment) -> None:
89-
create_basic_wheel_for_package(script, "foo", "1.0")
90-
create_basic_wheel_for_package(script, "bar", "1.0")
91-
finder = make_test_finder(find_links=[script.scratch_path])
92-
build_env = BuildEnvironment()
93-
for prefix in ("normal", "overlay"):
94-
build_env.install_requirements(
95-
finder, ["foo"], prefix, f"installing foo in {prefix}"
96-
)
97-
with pytest.raises(AssertionError):
98-
build_env.install_requirements(
99-
finder, ["bar"], prefix, f"installing bar in {prefix}"
100-
)
101-
with pytest.raises(AssertionError):
102-
build_env.install_requirements(
103-
finder, [], prefix, f"installing in {prefix}"
104-
)
82+
build_env.install_requirements(
83+
finder, requirements=[], message="Installing build dependencies"
84+
)
10585

10686

10787
def test_build_env_requirements_check(script: PipTestEnvironment) -> None:
@@ -130,54 +110,59 @@ def test_build_env_requirements_check(script: PipTestEnvironment) -> None:
130110
run_with_build_env(
131111
script,
132112
"""
133-
build_env.install_requirements(finder, ['foo', 'bar==3.0'], 'normal',
134-
'installing foo in normal')
113+
build_env.install_requirements(
114+
finder, requirements=["foo", "bar==3.0"], message="installing foo"
115+
)
135116
136-
r = build_env.check_requirements(['foo', 'bar', 'other'])
137-
assert r == (set(), {'other'}), repr(r)
117+
r = build_env.check_requirements(["foo", "bar", "other"])
118+
assert r == (set(), {"other"}), repr(r)
138119
139-
r = build_env.check_requirements(['foo>1.0', 'bar==3.0'])
120+
r = build_env.check_requirements(["foo>1.0", "bar==3.0"])
140121
assert r == (set(), set()), repr(r)
141122
142-
r = build_env.check_requirements(['foo>3.0', 'bar>=2.5'])
143-
assert r == ({('foo==2.0', 'foo>3.0')}, set()), repr(r)
123+
r = build_env.check_requirements(["foo>3.0", "bar>=2.5"])
124+
assert r == ({("foo==2.0", "foo>3.0")}, set()), repr(r)
144125
""",
145126
)
146127

147128
run_with_build_env(
148129
script,
149130
"""
150-
build_env.install_requirements(finder, ['foo', 'bar==3.0'], 'normal',
151-
'installing foo in normal')
152-
build_env.install_requirements(finder, ['bar==1.0'], 'overlay',
153-
'installing foo in overlay')
131+
build_env.install_requirements(
132+
finder, requirements=["foo", "bar==3.0"], message="installing foo"
133+
)
134+
build_env.install_requirements(
135+
finder, requirements=["1.0"], message="installing bar"
136+
)
154137
155-
r = build_env.check_requirements(['foo', 'bar', 'other'])
156-
assert r == (set(), {'other'}), repr(r)
138+
r = build_env.check_requirements(["foo", "bar", "other"])
139+
assert r == (set(), {"other"}), repr(r)
157140
158-
r = build_env.check_requirements(['foo>1.0', 'bar==3.0'])
159-
assert r == ({('bar==1.0', 'bar==3.0')}, set()), repr(r)
141+
r = build_env.check_requirements(["foo>1.0", "bar==3.0"])
142+
assert r == ({("bar==1.0", "bar==3.0")}, set()), repr(r)
160143
161-
r = build_env.check_requirements(['foo>3.0', 'bar>=2.5'])
162-
assert r == ({('bar==1.0', 'bar>=2.5'), ('foo==2.0', 'foo>3.0')}, \
163-
set()), repr(r)
144+
r = build_env.check_requirements(["foo>3.0", "bar>=2.5"])
145+
assert r == ({("bar==1.0", "bar>=2.5"), ("foo==2.0", "foo>3.0")}, set()), \
146+
repr(r)
164147
""",
165148
)
166149

167150

168-
def test_build_env_overlay_prefix_has_priority(script: PipTestEnvironment) -> None:
151+
def test_build_env_latest_install_has_priority(script: PipTestEnvironment) -> None:
169152
create_basic_wheel_for_package(script, "pkg", "2.0")
170153
create_basic_wheel_for_package(script, "pkg", "4.3")
171154
result = run_with_build_env(
172155
script,
173156
"""
174-
build_env.install_requirements(finder, ['pkg==2.0'], 'overlay',
175-
'installing pkg==2.0 in overlay')
176-
build_env.install_requirements(finder, ['pkg==4.3'], 'normal',
177-
'installing pkg==4.3 in normal')
157+
build_env.install_requirements(
158+
finder, requirements=["pkg==4.3"], message="installing pkg==4.3"
159+
)
160+
build_env.install_requirements(
161+
finder, requirements=["pkg==2.0"], message="installing pkg==2.0"
162+
)
178163
""",
179164
"""
180-
print(__import__('pkg').__version__)
165+
print(__import__("pkg").__version__)
181166
""",
182167
)
183168
assert result.stdout.strip() == "2.0", str(result)
@@ -208,7 +193,7 @@ def test_build_env_isolation(script: PipTestEnvironment) -> None:
208193
run_with_build_env(
209194
script,
210195
"",
211-
r"""
196+
R"""
212197
from distutils.sysconfig import get_python_lib
213198
import sys
214199

tests/functional/test_pep517.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ def test_backend(tmpdir: Path, data: TestData) -> None:
3636
req.load_pyproject_toml()
3737
env = BuildEnvironment()
3838
finder = make_test_finder(find_links=[data.backends])
39-
env.install_requirements(finder, ["dummy_backend"], "normal", "Installing")
39+
env.install_requirements(
40+
finder, requirements=["dummy_backend"], message="Installing"
41+
)
4042
conflicting, missing = env.check_requirements(["dummy_backend"])
4143
assert not conflicting and not missing
4244
assert hasattr(req.pep517_backend, "build_wheel")
@@ -83,7 +85,9 @@ def test_backend_path_and_dep(tmpdir: Path, data: TestData) -> None:
8385
req.load_pyproject_toml()
8486
env = BuildEnvironment()
8587
finder = make_test_finder(find_links=[data.backends])
86-
env.install_requirements(finder, ["dummy_backend"], "normal", "Installing")
88+
env.install_requirements(
89+
finder, requirements=["dummy_backend"], message="Installing"
90+
)
8791

8892
assert hasattr(req.pep517_backend, "build_wheel")
8993
with env:

0 commit comments

Comments
 (0)