Skip to content

Commit 1c3de54

Browse files
authored
bpo-34977: Use venv redirector instead of original python.exe on Windows (pythonGH-11029)
1 parent b6ef6f6 commit 1c3de54

File tree

11 files changed

+452
-53
lines changed

11 files changed

+452
-53
lines changed

Doc/library/venv.rst

+10-7
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,6 @@ creation according to their needs, the :class:`EnvBuilder` class.
130130
.. versionadded:: 3.6
131131
Added the ``prompt`` parameter
132132

133-
134133
Creators of third-party virtual environment tools will be free to use the
135134
provided ``EnvBuilder`` class as a base class.
136135

@@ -177,23 +176,27 @@ creation according to their needs, the :class:`EnvBuilder` class.
177176

178177
.. method:: setup_python(context)
179178

180-
Creates a copy of the Python executable (and, under Windows, DLLs) in
181-
the environment. On a POSIX system, if a specific executable
182-
``python3.x`` was used, symlinks to ``python`` and ``python3`` will be
183-
created pointing to that executable, unless files with those names
184-
already exist.
179+
Creates a copy of the Python executable in the environment on POSIX
180+
systems. If a specific executable ``python3.x`` was used, symlinks to
181+
``python`` and ``python3`` will be created pointing to that executable,
182+
unless files with those names already exist.
185183

186184
.. method:: setup_scripts(context)
187185

188186
Installs activation scripts appropriate to the platform into the virtual
189-
environment.
187+
environment. On Windows, also installs the ``python[w].exe`` scripts.
190188

191189
.. method:: post_setup(context)
192190

193191
A placeholder method which can be overridden in third party
194192
implementations to pre-install packages in the virtual environment or
195193
perform other post-creation steps.
196194

195+
.. versionchanged:: 3.7.2
196+
Windows now uses redirector scripts for ``python[w].exe`` instead of
197+
copying the actual binaries, and so :meth:`setup_python` does nothing
198+
unless running from a build in the source tree.
199+
197200
In addition, :class:`EnvBuilder` provides this utility method that can be
198201
called from :meth:`setup_scripts` or :meth:`post_setup` in subclasses to
199202
assist in installing custom scripts into the virtual environment.

Doc/whatsnew/3.7.rst

+10
Original file line numberDiff line numberDiff line change
@@ -2512,3 +2512,13 @@ In 3.7.1 the :mod:`tokenize` module now implicitly emits a ``NEWLINE`` token
25122512
when provided with input that does not have a trailing new line. This behavior
25132513
now matches what the C tokenizer does internally.
25142514
(Contributed by Ammar Askar in :issue:`33899`.)
2515+
2516+
Notable changes in Python 3.7.2
2517+
===============================
2518+
2519+
In 3.7.2, :mod:`venv` on Windows no longer copies the original binaries, but
2520+
creates redirector scripts named ``python.exe`` and ``pythonw.exe`` instead.
2521+
This resolves a long standing issue where all virtual environments would have
2522+
to be upgraded or recreated with each Python update. However, note that this
2523+
release will still require recreation of virtual environments in order to get
2524+
the new scripts.

Lib/test/test_venv.py

+1
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ def test_isolation(self):
243243
self.assertIn('include-system-site-packages = %s\n' % s, data)
244244

245245
@unittest.skipUnless(can_symlink(), 'Needs symlinks')
246+
@unittest.skipIf(os.name == 'nt', 'Symlinks are never used on Windows')
246247
def test_symlinking(self):
247248
"""
248249
Test symlinking works as expected

Lib/venv/__init__.py

+19-30
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,11 @@ def create(self, env_dir):
6464
self.system_site_packages = False
6565
self.create_configuration(context)
6666
self.setup_python(context)
67+
if not self.upgrade:
68+
self.setup_scripts(context)
6769
if self.with_pip:
6870
self._setup_pip(context)
6971
if not self.upgrade:
70-
self.setup_scripts(context)
7172
self.post_setup(context)
7273
if true_system_site_packages:
7374
# We had set it to False before, now
@@ -158,14 +159,6 @@ def create_configuration(self, context):
158159
f.write('include-system-site-packages = %s\n' % incl)
159160
f.write('version = %d.%d.%d\n' % sys.version_info[:3])
160161

161-
if os.name == 'nt':
162-
def include_binary(self, f):
163-
if f.endswith(('.pyd', '.dll')):
164-
result = True
165-
else:
166-
result = f.startswith('python') and f.endswith('.exe')
167-
return result
168-
169162
def symlink_or_copy(self, src, dst, relative_symlinks_ok=False):
170163
"""
171164
Try symlinking a file, and if that fails, fall back to copying.
@@ -195,9 +188,9 @@ def setup_python(self, context):
195188
binpath = context.bin_path
196189
path = context.env_exe
197190
copier = self.symlink_or_copy
198-
copier(context.executable, path)
199191
dirname = context.python_dir
200192
if os.name != 'nt':
193+
copier(context.executable, path)
201194
if not os.path.islink(path):
202195
os.chmod(path, 0o755)
203196
for suffix in ('python', 'python3'):
@@ -209,26 +202,22 @@ def setup_python(self, context):
209202
if not os.path.islink(path):
210203
os.chmod(path, 0o755)
211204
else:
212-
# See bpo-34011. When using a proper install, we should only need to
213-
# copy the top-level of DLLs.
214-
include = self.include_binary
215-
files = [f for f in os.listdir(dirname) if include(f)]
216-
for f in files:
217-
src = os.path.join(dirname, f)
218-
dst = os.path.join(binpath, f)
219-
if dst != context.env_exe: # already done, above
220-
copier(src, dst)
221-
222-
# When creating from a build directory, we continue to copy all files.
205+
# For normal cases, the venvlauncher will be copied from
206+
# our scripts folder. For builds, we need to copy it
207+
# manually.
223208
if sysconfig.is_python_build(True):
224-
subdir = 'DLLs'
225-
dirname = os.path.join(dirname, subdir)
226-
if os.path.isdir(dirname):
227-
files = [f for f in os.listdir(dirname) if include(f)]
228-
for f in files:
229-
src = os.path.join(dirname, f)
230-
dst = os.path.join(binpath, f)
231-
copier(src, dst)
209+
suffix = '.exe'
210+
if context.python_exe.lower().endswith('_d.exe'):
211+
suffix = '_d.exe'
212+
213+
src = os.path.join(dirname, "venvlauncher" + suffix)
214+
dst = os.path.join(binpath, context.python_exe)
215+
copier(src, dst)
216+
217+
src = os.path.join(dirname, "venvwlauncher" + suffix)
218+
dst = os.path.join(binpath, "pythonw" + suffix)
219+
copier(src, dst)
220+
232221
# copy init.tcl over
233222
for root, dirs, files in os.walk(context.python_dir):
234223
if 'init.tcl' in files:
@@ -326,7 +315,7 @@ def install_scripts(self, context, path):
326315
dstfile = os.path.join(dstdir, f)
327316
with open(srcfile, 'rb') as f:
328317
data = f.read()
329-
if not srcfile.endswith('.exe'):
318+
if not srcfile.endswith(('.exe', '.pdb')):
330319
try:
331320
data = data.decode('utf-8')
332321
data = self.replace_variables(data, context)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
venv on Windows will now use a python.exe redirector rather than copying the
2+
actual binaries from the base environment.

PC/getpathp.c

+7-1
Original file line numberDiff line numberDiff line change
@@ -536,10 +536,16 @@ static _PyInitError
536536
get_program_full_path(const _PyCoreConfig *core_config,
537537
PyCalculatePath *calculate, _PyPathConfig *config)
538538
{
539+
const wchar_t *pyvenv_launcher;
539540
wchar_t program_full_path[MAXPATHLEN+1];
540541
memset(program_full_path, 0, sizeof(program_full_path));
541542

542-
if (!GetModuleFileNameW(NULL, program_full_path, MAXPATHLEN)) {
543+
/* The launcher may need to force the executable path to a
544+
* different environment, so override it here. */
545+
pyvenv_launcher = _wgetenv(L"__PYVENV_LAUNCHER__");
546+
if (pyvenv_launcher && pyvenv_launcher[0]) {
547+
wcscpy_s(program_full_path, MAXPATHLEN+1, pyvenv_launcher);
548+
} else if (!GetModuleFileNameW(NULL, program_full_path, MAXPATHLEN)) {
543549
/* GetModuleFileName should never fail when passed NULL */
544550
return _Py_INIT_ERR("Cannot determine program path");
545551
}

0 commit comments

Comments
 (0)