Skip to content

Commit 3437785

Browse files
committed
Fix legacy virtualenv setup in tests
1 parent d65418b commit 3437785

File tree

2 files changed

+121
-39
lines changed

2 files changed

+121
-39
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ jobs:
122122
if: matrix.os == 'MacOS'
123123
run: brew install breezy
124124

125-
- run: pip install nox 'virtualenv>=20' 'setuptools!=60.6.0'
125+
- run: pip install nox
126126

127127
# Main check
128128
- name: Run unit tests
@@ -179,7 +179,7 @@ jobs:
179179
$acl.AddAccessRule($rule)
180180
Set-Acl "R:\Temp" $acl
181181
182-
- run: pip install nox 'virtualenv>=20'
182+
- run: pip install nox
183183
env:
184184
TEMP: "R:\\Temp"
185185

@@ -261,7 +261,7 @@ jobs:
261261
- name: Install Ubuntu dependencies
262262
run: sudo apt-get install bzr
263263

264-
- run: pip install nox 'virtualenv>=20'
264+
- run: pip install nox
265265

266266
- name: Run unit tests
267267
run: >-

tests/lib/venv.py

Lines changed: 118 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import compileall
22
import os
33
import shutil
4+
import subprocess
5+
import sys
46
import sysconfig
57
import textwrap
68
import venv as _venv
@@ -18,6 +20,9 @@
1820
VirtualEnvironmentType = str
1921

2022

23+
LEGACY_VIRTUALENV = int(_virtualenv.__version__.split(".", 1)[0]) < 20
24+
25+
2126
class VirtualEnvironment:
2227
"""
2328
An abstraction around virtual environments, currently it only uses
@@ -39,13 +44,28 @@ def __init__(
3944
self._venv_type = venv_type
4045
else:
4146
self._venv_type = "virtualenv"
47+
assert self._venv_type in ("virtualenv", "venv")
4248
self._user_site_packages = False
4349
self._template = template
4450
self._sitecustomize: Optional[str] = None
4551
self._update_paths()
4652
self._create()
4753

54+
def __update_paths_legacy(self) -> None:
55+
home, lib, inc, bin = _virtualenv.path_locations(self.location)
56+
self.bin = Path(bin)
57+
self.site = Path(lib) / "site-packages"
58+
# Workaround for https://github.com/pypa/virtualenv/issues/306
59+
if hasattr(sys, "pypy_version_info"):
60+
version_dir = str(sys.version_info.major)
61+
self.lib = Path(home, "lib-python", version_dir)
62+
else:
63+
self.lib = Path(lib)
64+
4865
def _update_paths(self) -> None:
66+
if LEGACY_VIRTUALENV:
67+
self.__update_paths_legacy()
68+
return
4969
bases = {
5070
"installed_base": self.location,
5171
"installed_platbase": self.location,
@@ -64,21 +84,39 @@ def _create(self, clear: bool = False) -> None:
6484
if clear:
6585
shutil.rmtree(self.location)
6686
if self._template:
87+
# On Windows, calling `_virtualenv.path_locations(target)`
88+
# will have created the `target` directory...
89+
if LEGACY_VIRTUALENV and sys.platform == "win32" and self.location.exists():
90+
self.location.rmdir()
6791
# Clone virtual environment from template.
6892
shutil.copytree(self._template.location, self.location, symlinks=True)
6993
self._sitecustomize = self._template.sitecustomize
7094
self._user_site_packages = self._template.user_site_packages
7195
else:
7296
# Create a new virtual environment.
7397
if self._venv_type == "virtualenv":
74-
_virtualenv.cli_run(
75-
[
76-
"--no-pip",
77-
"--no-wheel",
78-
"--no-setuptools",
79-
os.fspath(self.location),
80-
],
81-
)
98+
if LEGACY_VIRTUALENV:
99+
subprocess.check_call(
100+
[
101+
sys.executable,
102+
"-m",
103+
"virtualenv",
104+
"--no-pip",
105+
"--no-wheel",
106+
"--no-setuptools",
107+
os.fspath(self.location),
108+
]
109+
)
110+
self._fix_legacy_virtualenv_site_module()
111+
else:
112+
_virtualenv.cli_run(
113+
[
114+
"--no-pip",
115+
"--no-wheel",
116+
"--no-setuptools",
117+
os.fspath(self.location),
118+
],
119+
)
82120
elif self._venv_type == "venv":
83121
builder = _venv.EnvBuilder()
84122
context = builder.ensure_directories(self.location)
@@ -88,31 +126,68 @@ def _create(self, clear: bool = False) -> None:
88126
self.sitecustomize = self._sitecustomize
89127
self.user_site_packages = self._user_site_packages
90128

129+
def _fix_legacy_virtualenv_site_module(self) -> None:
130+
# Patch `site.py` so user site work as expected.
131+
site_py = self.lib / "site.py"
132+
with open(site_py) as fp:
133+
site_contents = fp.read()
134+
for pattern, replace in (
135+
(
136+
# Ensure enabling user site does not result in adding
137+
# the real site-packages' directory to `sys.path`.
138+
("\ndef virtual_addsitepackages(known_paths):\n"),
139+
(
140+
"\ndef virtual_addsitepackages(known_paths):\n"
141+
" return known_paths\n"
142+
),
143+
),
144+
(
145+
# Fix sites ordering: user site must be added before system.
146+
(
147+
"\n paths_in_sys = addsitepackages(paths_in_sys)"
148+
"\n paths_in_sys = addusersitepackages(paths_in_sys)\n"
149+
),
150+
(
151+
"\n paths_in_sys = addusersitepackages(paths_in_sys)"
152+
"\n paths_in_sys = addsitepackages(paths_in_sys)\n"
153+
),
154+
),
155+
):
156+
assert pattern in site_contents
157+
site_contents = site_contents.replace(pattern, replace)
158+
with open(site_py, "w") as fp:
159+
fp.write(site_contents)
160+
# Make sure bytecode is up-to-date too.
161+
assert compileall.compile_file(str(site_py), quiet=1, force=True)
162+
91163
def _customize_site(self) -> None:
92-
# Enable user site (before system).
93-
contents = textwrap.dedent(
94-
f"""
95-
import os, site, sys
96-
if not os.environ.get('PYTHONNOUSERSITE', False):
97-
site.ENABLE_USER_SITE = {self._user_site_packages}
98-
# First, drop system-sites related paths.
99-
original_sys_path = sys.path[:]
100-
known_paths = set()
101-
for path in site.getsitepackages():
102-
site.addsitedir(path, known_paths=known_paths)
103-
system_paths = sys.path[len(original_sys_path):]
104-
for path in system_paths:
105-
if path in original_sys_path:
106-
original_sys_path.remove(path)
107-
sys.path = original_sys_path
108-
# Second, add user-site.
109-
if {self._user_site_packages}:
110-
site.addsitedir(site.getusersitepackages())
111-
# Third, add back system-sites related paths.
112-
for path in site.getsitepackages():
113-
site.addsitedir(path)
114-
"""
115-
).strip()
164+
if not LEGACY_VIRTUALENV or self._venv_type == "venv":
165+
# Enable user site (before system).
166+
contents = textwrap.dedent(
167+
f"""
168+
import os, site, sys
169+
if not os.environ.get('PYTHONNOUSERSITE', False):
170+
site.ENABLE_USER_SITE = {self._user_site_packages}
171+
# First, drop system-sites related paths.
172+
original_sys_path = sys.path[:]
173+
known_paths = set()
174+
for path in site.getsitepackages():
175+
site.addsitedir(path, known_paths=known_paths)
176+
system_paths = sys.path[len(original_sys_path):]
177+
for path in system_paths:
178+
if path in original_sys_path:
179+
original_sys_path.remove(path)
180+
sys.path = original_sys_path
181+
# Second, add user-site.
182+
if {self._user_site_packages}:
183+
site.addsitedir(site.getusersitepackages())
184+
# Third, add back system-sites related paths.
185+
for path in site.getsitepackages():
186+
site.addsitedir(path)
187+
"""
188+
).strip()
189+
else:
190+
contents = ""
116191
if self._sitecustomize is not None:
117192
contents += "\n" + self._sitecustomize
118193
sitecustomize = self.site / "sitecustomize.py"
@@ -159,7 +234,14 @@ def user_site_packages(self) -> bool:
159234
@user_site_packages.setter
160235
def user_site_packages(self, value: bool) -> None:
161236
self._user_site_packages = value
162-
self._rewrite_pyvenv_cfg(
163-
{"include-system-site-packages": str(bool(value)).lower()}
164-
)
165-
self._customize_site()
237+
if not LEGACY_VIRTUALENV or self._venv_type == "venv":
238+
self._rewrite_pyvenv_cfg(
239+
{"include-system-site-packages": str(bool(value)).lower()}
240+
)
241+
self._customize_site()
242+
else:
243+
marker = self.lib / "no-global-site-packages.txt"
244+
if self._user_site_packages:
245+
marker.unlink()
246+
else:
247+
marker.touch()

0 commit comments

Comments
 (0)