Skip to content

Commit 340d9c2

Browse files
committed
pythongh-106045: Fix venv creation from a python executable symlink
1 parent 165ed68 commit 340d9c2

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
@@ -791,6 +791,57 @@ def test_venv_same_path(self):
791791
else:
792792
self.assertFalse(same_path(path1, path2))
793793

794+
@requires_subprocess()
795+
@unittest.skipIf(os.name == 'nt', 'not relevant on Windows')
796+
@unittest.skipUnless(can_symlink(), 'Needs symlinks')
797+
def test_executable_symlink(self):
798+
"""
799+
Test creation using a symlink to python executable.
800+
"""
801+
rmtree(self.env_dir)
802+
with tempfile.TemporaryDirectory() as symlink_dir:
803+
executable_symlink = os.path.join(
804+
os.path.realpath(symlink_dir),
805+
os.path.basename(sys.executable))
806+
os.symlink(os.path.abspath(sys.executable), executable_symlink)
807+
cmd = [executable_symlink, "-m", "venv", "--without-pip",
808+
self.env_dir]
809+
subprocess.check_call(cmd)
810+
data = self.get_text_file_contents('pyvenv.cfg')
811+
executable = sys._base_executable
812+
path = os.path.dirname(executable)
813+
self.assertIn('home = %s' % path, data)
814+
self.assertIn('executable = %s' %
815+
os.path.realpath(sys.executable), data)
816+
817+
@requires_subprocess()
818+
@unittest.skipIf(os.name == 'nt', 'not relevant on Windows')
819+
@unittest.skipUnless(can_symlink(), 'Needs symlinks')
820+
@requireVenvCreate
821+
def test_tree_symlink(self):
822+
"""
823+
Test creation using a symlink to python tree.
824+
"""
825+
rmtree(self.env_dir)
826+
executable_abspath = os.path.abspath(sys._base_executable)
827+
tree_abspath = os.path.dirname(os.path.dirname(executable_abspath))
828+
with tempfile.TemporaryDirectory() as symlink_dir:
829+
tree_symlink = os.path.join(
830+
os.path.realpath(symlink_dir),
831+
os.path.basename(tree_abspath))
832+
executable_symlink = os.path.join(
833+
tree_symlink,
834+
os.path.basename(os.path.dirname(executable_abspath)),
835+
os.path.basename(sys._base_executable))
836+
os.symlink(tree_abspath, tree_symlink)
837+
cmd = [executable_symlink, "-m", "venv", "--without-pip",
838+
self.env_dir]
839+
subprocess.check_call(cmd)
840+
data = self.get_text_file_contents('pyvenv.cfg')
841+
self.assertIn('home = %s' % tree_symlink, data)
842+
self.assertIn('executable = %s' %
843+
os.path.realpath(sys._base_executable), data)
844+
794845
@requireVenvCreate
795846
class EnsurePipTest(BaseTest):
796847
"""Test venv module installation of pip."""

Lib/venv/__init__.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,16 @@ def create_if_needed(d):
164164
'Python interpreter. Provide an explicit path or '
165165
'check that your PATH environment variable is '
166166
'correctly set.')
167-
dirname, exename = os.path.split(os.path.abspath(executable))
167+
# only resolve executable symlinks, not the full chain, see gh-106045
168+
# we don't want to overwrite the executable used in context
169+
executable_ = os.path.abspath(executable)
170+
while os.path.islink(executable_):
171+
link = os.readlink(executable_)
172+
if os.path.isabs(link):
173+
executable_ = link
174+
else:
175+
executable_ = os.path.join(os.path.dirname(executable_), link)
176+
dirname, exename = os.path.split(executable_)
168177
if sys.platform == 'win32':
169178
# Always create the simplest name in the venv. It will either be a
170179
# 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
@@ -417,6 +417,7 @@ Eric Daniel
417417
Scott David Daniels
418418
Derzsi Dániel
419419
Lawrence D'Anna
420+
Matthieu Darbois
420421
Ben Darnell
421422
Kushal Das
422423
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)