1
1
import compileall
2
2
import os
3
3
import shutil
4
+ import subprocess
5
+ import sys
4
6
import sysconfig
5
7
import textwrap
6
8
import venv as _venv
18
20
VirtualEnvironmentType = str
19
21
20
22
23
+ LEGACY_VIRTUALENV = int (_virtualenv .__version__ .split ("." , 1 )[0 ]) < 20
24
+
25
+
21
26
class VirtualEnvironment :
22
27
"""
23
28
An abstraction around virtual environments, currently it only uses
@@ -39,13 +44,28 @@ def __init__(
39
44
self ._venv_type = venv_type
40
45
else :
41
46
self ._venv_type = "virtualenv"
47
+ assert self ._venv_type in ("virtualenv" , "venv" )
42
48
self ._user_site_packages = False
43
49
self ._template = template
44
50
self ._sitecustomize : Optional [str ] = None
45
51
self ._update_paths ()
46
52
self ._create ()
47
53
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
+
48
65
def _update_paths (self ) -> None :
66
+ if LEGACY_VIRTUALENV :
67
+ self .__update_paths_legacy ()
68
+ return
49
69
bases = {
50
70
"installed_base" : self .location ,
51
71
"installed_platbase" : self .location ,
@@ -64,21 +84,39 @@ def _create(self, clear: bool = False) -> None:
64
84
if clear :
65
85
shutil .rmtree (self .location )
66
86
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 ()
67
91
# Clone virtual environment from template.
68
92
shutil .copytree (self ._template .location , self .location , symlinks = True )
69
93
self ._sitecustomize = self ._template .sitecustomize
70
94
self ._user_site_packages = self ._template .user_site_packages
71
95
else :
72
96
# Create a new virtual environment.
73
97
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
+ )
82
120
elif self ._venv_type == "venv" :
83
121
builder = _venv .EnvBuilder ()
84
122
context = builder .ensure_directories (self .location )
@@ -88,31 +126,68 @@ def _create(self, clear: bool = False) -> None:
88
126
self .sitecustomize = self ._sitecustomize
89
127
self .user_site_packages = self ._user_site_packages
90
128
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
+ ("\n def virtual_addsitepackages(known_paths):\n " ),
139
+ (
140
+ "\n def 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
+
91
163
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 = ""
116
191
if self ._sitecustomize is not None :
117
192
contents += "\n " + self ._sitecustomize
118
193
sitecustomize = self .site / "sitecustomize.py"
@@ -159,7 +234,14 @@ def user_site_packages(self) -> bool:
159
234
@user_site_packages .setter
160
235
def user_site_packages (self , value : bool ) -> None :
161
236
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