Skip to content

Commit eabca6e

Browse files
bpo-45337: Use the realpath of the new executable when creating a venv on Windows (GH-28663)
(cherry picked from commit 6811fda) Co-authored-by: Steve Dower <[email protected]>
1 parent 86bf45e commit eabca6e

File tree

3 files changed

+29
-9
lines changed

3 files changed

+29
-9
lines changed

Lib/test/test_venv.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,14 +150,20 @@ def test_prompt(self):
150150
def test_upgrade_dependencies(self):
151151
builder = venv.EnvBuilder()
152152
bin_path = 'Scripts' if sys.platform == 'win32' else 'bin'
153-
python_exe = 'python.exe' if sys.platform == 'win32' else 'python'
153+
python_exe = os.path.split(sys.executable)[1]
154154
with tempfile.TemporaryDirectory() as fake_env_dir:
155+
expect_exe = os.path.normcase(
156+
os.path.join(fake_env_dir, bin_path, python_exe)
157+
)
158+
if sys.platform == 'win32':
159+
expect_exe = os.path.normcase(os.path.realpath(expect_exe))
155160

156161
def pip_cmd_checker(cmd):
162+
cmd[0] = os.path.normcase(cmd[0])
157163
self.assertEqual(
158164
cmd,
159165
[
160-
os.path.join(fake_env_dir, bin_path, python_exe),
166+
expect_exe,
161167
'-m',
162168
'pip',
163169
'install',

Lib/venv/__init__.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,20 @@ def create_if_needed(d):
142142
context.bin_name = binname
143143
context.env_exe = os.path.join(binpath, exename)
144144
create_if_needed(binpath)
145+
# Assign and update the command to use when launching the newly created
146+
# environment, in case it isn't simply the executable script (e.g. bpo-45337)
147+
context.env_exec_cmd = context.env_exe
148+
if sys.platform == 'win32':
149+
# bpo-45337: Fix up env_exec_cmd to account for file system redirections.
150+
# Some redirects only apply to CreateFile and not CreateProcess
151+
real_env_exe = os.path.realpath(context.env_exe)
152+
if os.path.normcase(real_env_exe) != os.path.normcase(context.env_exe):
153+
logger.warning('Actual environment location may have moved due to '
154+
'redirects, links or junctions.\n'
155+
' Requested location: "%s"\n'
156+
' Actual location: "%s"',
157+
context.env_exe, real_env_exe)
158+
context.env_exec_cmd = real_env_exe
145159
return context
146160

147161
def create_configuration(self, context):
@@ -293,8 +307,8 @@ def _setup_pip(self, context):
293307
# We run ensurepip in isolated mode to avoid side effects from
294308
# environment vars, the current directory and anything else
295309
# intended for the global Python environment
296-
cmd = [context.env_exe, '-Im', 'ensurepip', '--upgrade',
297-
'--default-pip']
310+
cmd = [context.env_exec_cmd, '-Im', 'ensurepip', '--upgrade',
311+
'--default-pip']
298312
subprocess.check_output(cmd, stderr=subprocess.STDOUT)
299313

300314
def setup_scripts(self, context):
@@ -394,11 +408,7 @@ def upgrade_dependencies(self, context):
394408
logger.debug(
395409
f'Upgrading {CORE_VENV_DEPS} packages in {context.bin_path}'
396410
)
397-
if sys.platform == 'win32':
398-
python_exe = os.path.join(context.bin_path, 'python.exe')
399-
else:
400-
python_exe = os.path.join(context.bin_path, 'python')
401-
cmd = [python_exe, '-m', 'pip', 'install', '--upgrade']
411+
cmd = [context.env_exec_cmd, '-m', 'pip', 'install', '--upgrade']
402412
cmd.extend(CORE_VENV_DEPS)
403413
subprocess.check_call(cmd)
404414

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
venv now warns when the created environment may need to be accessed at a
2+
different path, due to redirections, links or junctions. It also now
3+
correctly installs or upgrades components when the alternate path is
4+
required.

0 commit comments

Comments
 (0)