Skip to content

Commit 199c65e

Browse files
committed
rework _abspath_resolve_leaf to _getpath_realpath
1 parent f9ef949 commit 199c65e

File tree

2 files changed

+33
-18
lines changed

2 files changed

+33
-18
lines changed

Lib/test/test_venv.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -895,6 +895,7 @@ def test_venvwlauncher(self):
895895
@requires_subprocess()
896896
@unittest.skipIf(os.name == 'nt', 'not relevant on Windows')
897897
@unittest.skipUnless(can_symlink(), 'Needs symlinks')
898+
@unittest.skipUnless(sysconfig.get_config_var('HAVE_READLINK'), "Requires HAVE_READLINK support")
898899
def test_executable_symlink(self):
899900
"""
900901
Test creation using a symlink to python executable.
@@ -908,7 +909,7 @@ def test_executable_symlink(self):
908909
cmd = [exe_symlink, "-m", "venv", "--without-pip", self.env_dir]
909910
subprocess.check_call(cmd)
910911
data = self.get_text_file_contents('pyvenv.cfg')
911-
path = os.path.dirname(sys._base_executable)
912+
path = os.path.dirname(os.path.abspath(sys._base_executable))
912913
self.assertIn('home = %s' % path, data)
913914
self.assertIn('executable = %s' % exe.resolve(), data)
914915

Lib/venv/__init__.py

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -134,23 +134,37 @@ def _same_path(cls, path1, path2):
134134
return path1 == path2
135135

136136
@classmethod
137-
def _abspath_resolve_leaf(cls, path):
138-
"""Returns the absolute path, resolving links to the last component.
139-
140-
If there's a cycle, os.path.abspath(path) is returned
137+
def _getpath_realpath(cls, path):
138+
"""Mimics getpath.realpath
139+
140+
It only mimics it for HAVE_READLINK.
141+
There are a few differences listed here:
142+
- we ensure that we have a resolvable abspath first
143+
(i.e. exists and no symlink loop)
144+
- we stop if a candidate does not resolve to the same file
145+
(this can happen with normpath)
141146
"""
142-
path = os.path.abspath(path)
143-
result = path
144-
while os.path.islink(result):
145-
link = os.readlink(result)
146-
if os.path.isabs(link):
147-
result = link
148-
else:
149-
result = os.path.join(os.path.dirname(result), link)
150-
result = os.path.abspath(result)
151-
if result == path:
152-
# circular links
153-
break
147+
result = os.path.abspath(path)
148+
try:
149+
real_path = os.path.realpath(result, strict=True)
150+
except OSError:
151+
logger.warning('Unable to resolve %r real path', result)
152+
return result
153+
if sysconfig.get_config_var('HAVE_READLINK'):
154+
while os.path.islink(result):
155+
link = os.readlink(result)
156+
if os.path.isabs(link):
157+
candidate = link
158+
else:
159+
candidate = os.path.join(os.path.dirname(result), link)
160+
candidate = os.path.normpath(candidate)
161+
# shall exists and be the same file as the original one
162+
valid = os.path.exists(candidate) and os.path.samefile(real_path, candidate)
163+
if not valid:
164+
logger.warning('Stopped resolving %r because %r is not the same file',
165+
result, candidate)
166+
break
167+
result = candidate
154168
return result
155169

156170
def ensure_directories(self, env_dir):
@@ -184,7 +198,7 @@ def create_if_needed(d):
184198
'check that your PATH environment variable is '
185199
'correctly set.')
186200
# only resolve executable symlinks, not the full chain, see gh-106045
187-
dirname, exename = os.path.split(self._abspath_resolve_leaf(executable))
201+
dirname, exename = os.path.split(self._getpath_realpath(executable))
188202
if sys.platform == 'win32':
189203
# Always create the simplest name in the venv. It will either be a
190204
# link back to executable, or a copy of the appropriate launcher

0 commit comments

Comments
 (0)