[Python-checkins] bpo-35854: Fix EnvBuilder and --symlinks in venv on Windows (GH-11700)
Steve Dower
webhook-mailer at python.org
Wed Jan 30 16:49:17 EST 2019
https://github.com/python/cpython/commit/a1f9a3332bd4767e47013ea787022f06b6dbcbbd
commit: a1f9a3332bd4767e47013ea787022f06b6dbcbbd
branch: master
author: Steve Dower <steve.dower at microsoft.com>
committer: GitHub <noreply at github.com>
date: 2019-01-30T13:49:14-08:00
summary:
bpo-35854: Fix EnvBuilder and --symlinks in venv on Windows (GH-11700)
files:
A Misc/NEWS.d/next/Windows/2019-01-29-15-44-46.bpo-35854.Ww3z19.rst
M Doc/library/venv.rst
M Doc/using/venv-create.inc
M Lib/test/test_venv.py
M Lib/venv/__init__.py
diff --git a/Doc/library/venv.rst b/Doc/library/venv.rst
index efa51e231c82..412808ad4486 100644
--- a/Doc/library/venv.rst
+++ b/Doc/library/venv.rst
@@ -109,8 +109,7 @@ creation according to their needs, the :class:`EnvBuilder` class.
any existing target directory, before creating the environment.
* ``symlinks`` -- a Boolean value indicating whether to attempt to symlink the
- Python binary (and any necessary DLLs or other binaries,
- e.g. ``pythonw.exe``), rather than copying.
+ Python binary rather than copying.
* ``upgrade`` -- a Boolean value which, if true, will upgrade an existing
environment with the running Python - for use when that Python has been
@@ -176,15 +175,15 @@ creation according to their needs, the :class:`EnvBuilder` class.
.. method:: setup_python(context)
- Creates a copy of the Python executable in the environment on POSIX
- systems. If a specific executable ``python3.x`` was used, symlinks to
- ``python`` and ``python3`` will be created pointing to that executable,
- unless files with those names already exist.
+ Creates a copy or symlink to the Python executable in the environment.
+ On POSIX systems, if a specific executable ``python3.x`` was used,
+ symlinks to ``python`` and ``python3`` will be created pointing to that
+ executable, unless files with those names already exist.
.. method:: setup_scripts(context)
Installs activation scripts appropriate to the platform into the virtual
- environment. On Windows, also installs the ``python[w].exe`` scripts.
+ environment.
.. method:: post_setup(context)
@@ -194,8 +193,13 @@ creation according to their needs, the :class:`EnvBuilder` class.
.. versionchanged:: 3.7.2
Windows now uses redirector scripts for ``python[w].exe`` instead of
- copying the actual binaries, and so :meth:`setup_python` does nothing
- unless running from a build in the source tree.
+ copying the actual binaries. In 3.7.2 only :meth:`setup_python` does
+ nothing unless running from a build in the source tree.
+
+ .. versionchanged:: 3.7.3
+ Windows copies the redirector scripts as part of :meth:`setup_python`
+ instead of :meth:`setup_scripts`. This was not the case in 3.7.2.
+ When using symlinks, the original executables will be linked.
In addition, :class:`EnvBuilder` provides this utility method that can be
called from :meth:`setup_scripts` or :meth:`post_setup` in subclasses to
diff --git a/Doc/using/venv-create.inc b/Doc/using/venv-create.inc
index ba5096abd370..1ba538bec48b 100644
--- a/Doc/using/venv-create.inc
+++ b/Doc/using/venv-create.inc
@@ -70,6 +70,11 @@ The command, if run with ``-h``, will show the available options::
In earlier versions, if the target directory already existed, an error was
raised, unless the ``--clear`` or ``--upgrade`` option was provided.
+.. note::
+ While symlinks are supported on Windows, they are not recommended. Of
+ particular note is that double-clicking ``python.exe`` in File Explorer
+ will resolve the symlink eagerly and ignore the virtual environment.
+
The created ``pyvenv.cfg`` file also includes the
``include-system-site-packages`` key, set to ``true`` if ``venv`` is
run with the ``--system-site-packages`` option, ``false`` otherwise.
diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py
index 34c2234493bc..6096b9df45bf 100644
--- a/Lib/test/test_venv.py
+++ b/Lib/test/test_venv.py
@@ -243,7 +243,6 @@ def test_isolation(self):
self.assertIn('include-system-site-packages = %s\n' % s, data)
@unittest.skipUnless(can_symlink(), 'Needs symlinks')
- @unittest.skipIf(os.name == 'nt', 'Symlinks are never used on Windows')
def test_symlinking(self):
"""
Test symlinking works as expected
diff --git a/Lib/venv/__init__.py b/Lib/venv/__init__.py
index 5438b0d4e508..8f9e3138474a 100644
--- a/Lib/venv/__init__.py
+++ b/Lib/venv/__init__.py
@@ -64,11 +64,10 @@ def create(self, env_dir):
self.system_site_packages = False
self.create_configuration(context)
self.setup_python(context)
- if not self.upgrade:
- self.setup_scripts(context)
if self.with_pip:
self._setup_pip(context)
if not self.upgrade:
+ self.setup_scripts(context)
self.post_setup(context)
if true_system_site_packages:
# We had set it to False before, now
@@ -176,6 +175,23 @@ def symlink_or_copy(self, src, dst, relative_symlinks_ok=False):
logger.warning('Unable to symlink %r to %r', src, dst)
force_copy = True
if force_copy:
+ if os.name == 'nt':
+ # On Windows, we rewrite symlinks to our base python.exe into
+ # copies of venvlauncher.exe
+ basename, ext = os.path.splitext(os.path.basename(src))
+ if basename.endswith('_d'):
+ ext = '_d' + ext
+ basename = basename[:-2]
+ if sysconfig.is_python_build(True):
+ if basename == 'python':
+ basename = 'venvlauncher'
+ elif basename == 'pythonw':
+ basename = 'venvwlauncher'
+ scripts = os.path.dirname(src)
+ else:
+ scripts = os.path.join(os.path.dirname(__file__), "scripts", "nt")
+ src = os.path.join(scripts, basename + ext)
+
shutil.copyfile(src, dst)
def setup_python(self, context):
@@ -202,23 +218,31 @@ def setup_python(self, context):
if not os.path.islink(path):
os.chmod(path, 0o755)
else:
- # For normal cases, the venvlauncher will be copied from
- # our scripts folder. For builds, we need to copy it
- # manually.
- if sysconfig.is_python_build(True):
- suffix = '.exe'
- if context.python_exe.lower().endswith('_d.exe'):
- suffix = '_d.exe'
-
- src = os.path.join(dirname, "venvlauncher" + suffix)
- dst = os.path.join(binpath, context.python_exe)
- copier(src, dst)
+ if self.symlinks:
+ # For symlinking, we need a complete copy of the root directory
+ # If symlinks fail, you'll get unnecessary copies of files, but
+ # we assume that if you've opted into symlinks on Windows then
+ # you know what you're doing.
+ suffixes = [
+ f for f in os.listdir(dirname) if
+ os.path.normcase(os.path.splitext(f)[1]) in ('.exe', '.dll')
+ ]
+ if sysconfig.is_python_build(True):
+ suffixes = [
+ f for f in suffixes if
+ os.path.normcase(f).startswith(('python', 'vcruntime'))
+ ]
+ else:
+ suffixes = ['python.exe', 'python_d.exe', 'pythonw.exe',
+ 'pythonw_d.exe']
- src = os.path.join(dirname, "venvwlauncher" + suffix)
- dst = os.path.join(binpath, "pythonw" + suffix)
- copier(src, dst)
+ for suffix in suffixes:
+ src = os.path.join(dirname, suffix)
+ if os.path.exists(src):
+ copier(src, os.path.join(binpath, suffix))
- # copy init.tcl over
+ if sysconfig.is_python_build(True):
+ # copy init.tcl
for root, dirs, files in os.walk(context.python_dir):
if 'init.tcl' in files:
tcldir = os.path.basename(root)
@@ -304,6 +328,9 @@ def install_scripts(self, context, path):
dirs.remove(d)
continue # ignore files in top level
for f in files:
+ if (os.name == 'nt' and f.startswith('python')
+ and f.endswith(('.exe', '.pdb'))):
+ continue
srcfile = os.path.join(root, f)
suffix = root[plen:].split(os.sep)[2:]
if not suffix:
diff --git a/Misc/NEWS.d/next/Windows/2019-01-29-15-44-46.bpo-35854.Ww3z19.rst b/Misc/NEWS.d/next/Windows/2019-01-29-15-44-46.bpo-35854.Ww3z19.rst
new file mode 100644
index 000000000000..a1c761458d35
--- /dev/null
+++ b/Misc/NEWS.d/next/Windows/2019-01-29-15-44-46.bpo-35854.Ww3z19.rst
@@ -0,0 +1 @@
+Fix EnvBuilder and --symlinks in venv on Windows
More information about the Python-checkins
mailing list