Skip to content

Commit a4f000b

Browse files
committed
pythongh-106045: Fix venv creation from a python executable symlink
1 parent 553cdc6 commit a4f000b

File tree

4 files changed

+64
-1
lines changed

4 files changed

+64
-1
lines changed

Lib/test/test_venv.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -892,6 +892,57 @@ def test_venvwlauncher(self):
892892
except subprocess.CalledProcessError:
893893
self.fail("venvwlauncher.exe did not run %s" % exename)
894894

895+
@requires_subprocess()
896+
@unittest.skipIf(os.name == 'nt', 'not relevant on Windows')
897+
@unittest.skipUnless(can_symlink(), 'Needs symlinks')
898+
def test_executable_symlink(self):
899+
"""
900+
Test creation using a symlink to python executable.
901+
"""
902+
rmtree(self.env_dir)
903+
with tempfile.TemporaryDirectory() as symlink_dir:
904+
executable_symlink = os.path.join(
905+
os.path.realpath(symlink_dir),
906+
os.path.basename(sys.executable))
907+
os.symlink(os.path.abspath(sys.executable), executable_symlink)
908+
cmd = [executable_symlink, "-m", "venv", "--without-pip",
909+
self.env_dir]
910+
subprocess.check_call(cmd)
911+
data = self.get_text_file_contents('pyvenv.cfg')
912+
executable = sys._base_executable
913+
path = os.path.dirname(executable)
914+
self.assertIn('home = %s' % path, data)
915+
self.assertIn('executable = %s' %
916+
os.path.realpath(sys.executable), data)
917+
918+
@requires_subprocess()
919+
@unittest.skipIf(os.name == 'nt', 'not relevant on Windows')
920+
@unittest.skipUnless(can_symlink(), 'Needs symlinks')
921+
@requireVenvCreate
922+
def test_tree_symlink(self):
923+
"""
924+
Test creation using a symlink to python tree.
925+
"""
926+
rmtree(self.env_dir)
927+
executable_abspath = os.path.abspath(sys._base_executable)
928+
tree_abspath = os.path.dirname(os.path.dirname(executable_abspath))
929+
with tempfile.TemporaryDirectory() as symlink_dir:
930+
tree_symlink = os.path.join(
931+
os.path.realpath(symlink_dir),
932+
os.path.basename(tree_abspath))
933+
executable_symlink = os.path.join(
934+
tree_symlink,
935+
os.path.basename(os.path.dirname(executable_abspath)),
936+
os.path.basename(sys._base_executable))
937+
os.symlink(tree_abspath, tree_symlink)
938+
cmd = [executable_symlink, "-m", "venv", "--without-pip",
939+
self.env_dir]
940+
subprocess.check_call(cmd)
941+
data = self.get_text_file_contents('pyvenv.cfg')
942+
self.assertIn('home = %s' % tree_symlink, data)
943+
self.assertIn('executable = %s' %
944+
os.path.realpath(sys._base_executable), data)
945+
895946

896947
@requireVenvCreate
897948
class EnsurePipTest(BaseTest):

Lib/venv/__init__.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,16 @@ def create_if_needed(d):
163163
'Python interpreter. Provide an explicit path or '
164164
'check that your PATH environment variable is '
165165
'correctly set.')
166-
dirname, exename = os.path.split(os.path.abspath(executable))
166+
# only resolve executable symlinks, not the full chain, see gh-106045
167+
# we don't want to overwrite the executable used in context
168+
executable_ = os.path.abspath(executable)
169+
while os.path.islink(executable_):
170+
link = os.readlink(executable_)
171+
if os.path.isabs(link):
172+
executable_ = link
173+
else:
174+
executable_ = os.path.join(os.path.dirname(executable_), link)
175+
dirname, exename = os.path.split(executable_)
167176
if sys.platform == 'win32':
168177
# Always create the simplest name in the venv. It will either be a
169178
# link back to executable, or a copy of the appropriate launcher

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,7 @@ Eric Daniel
419419
Scott David Daniels
420420
Derzsi Dániel
421421
Lawrence D'Anna
422+
Matthieu Darbois
422423
Ben Darnell
423424
Kushal Das
424425
Jonathan Dasteel
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix ``venv`` creation from a python executable symlink. Patch by Matthieu
2+
Darbois.

0 commit comments

Comments
 (0)