Skip to content

bpo-45337: Use the realpath of the new executable when creating a venv on Windows #28663

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Oct 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions Lib/test/test_venv.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,14 +150,20 @@ def test_prompt(self):
def test_upgrade_dependencies(self):
builder = venv.EnvBuilder()
bin_path = 'Scripts' if sys.platform == 'win32' else 'bin'
python_exe = 'python.exe' if sys.platform == 'win32' else 'python'
python_exe = os.path.split(sys.executable)[1]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible that sys.executable will be called something other than python.exe? Perhaps python3.exe? AFAIK only python.exe and pythonw.exe will be added to the venv being created.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can be. python_d.exe is the only one we support upstream, but I'd like it to be viable with other names (I have some private builds with alternate executable names).

But right now it doesn't work anyway. The venv launcher executable has hardcoded names in it, so it'll only work as python.exe, python_d.exe, pythonw.exe and pythonw_d.exe. Which means that this test is basically correct, unless we one day start running our test suite with a different executable name.

with tempfile.TemporaryDirectory() as fake_env_dir:
expect_exe = os.path.normcase(
os.path.join(fake_env_dir, bin_path, python_exe)
)
if sys.platform == 'win32':
expect_exe = os.path.normcase(os.path.realpath(expect_exe))

def pip_cmd_checker(cmd):
cmd[0] = os.path.normcase(cmd[0])
self.assertEqual(
cmd,
[
os.path.join(fake_env_dir, bin_path, python_exe),
expect_exe,
'-m',
'pip',
'install',
Expand Down
24 changes: 17 additions & 7 deletions Lib/venv/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,20 @@ def create_if_needed(d):
context.bin_name = binname
context.env_exe = os.path.join(binpath, exename)
create_if_needed(binpath)
# Assign and update the command to use when launching the newly created
# environment, in case it isn't simply the executable script (e.g. bpo-45337)
context.env_exec_cmd = context.env_exe
if sys.platform == 'win32':
# bpo-45337: Fix up env_exec_cmd to account for file system redirections.
# Some redirects only apply to CreateFile and not CreateProcess
real_env_exe = os.path.realpath(context.env_exe)
if os.path.normcase(real_env_exe) != os.path.normcase(context.env_exe):
logger.warning('Actual environment location may have moved due to '
'redirects, links or junctions.\n'
' Requested location: "%s"\n'
' Actual location: "%s"',
context.env_exe, real_env_exe)
context.env_exec_cmd = real_env_exe
return context

def create_configuration(self, context):
Expand Down Expand Up @@ -294,8 +308,8 @@ def _setup_pip(self, context):
# We run ensurepip in isolated mode to avoid side effects from
# environment vars, the current directory and anything else
# intended for the global Python environment
cmd = [context.env_exe, '-Im', 'ensurepip', '--upgrade',
'--default-pip']
cmd = [context.env_exec_cmd, '-Im', 'ensurepip', '--upgrade',
'--default-pip']
subprocess.check_output(cmd, stderr=subprocess.STDOUT)

def setup_scripts(self, context):
Expand Down Expand Up @@ -395,11 +409,7 @@ def upgrade_dependencies(self, context):
logger.debug(
f'Upgrading {CORE_VENV_DEPS} packages in {context.bin_path}'
)
if sys.platform == 'win32':
python_exe = os.path.join(context.bin_path, 'python.exe')
else:
python_exe = os.path.join(context.bin_path, 'python')
cmd = [python_exe, '-m', 'pip', 'install', '--upgrade']
cmd = [context.env_exec_cmd, '-m', 'pip', 'install', '--upgrade']
cmd.extend(CORE_VENV_DEPS)
subprocess.check_call(cmd)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
venv now warns when the created environment may need to be accessed at a
different path, due to redirections, links or junctions. It also now
correctly installs or upgrades components when the alternate path is
required.