Skip to content

Commit df10571

Browse files
authored
gh-100320: Fix path calculations on Windows when python.exe is moved outside of the normal location (GH-100947)
1 parent 7b14c2e commit df10571

File tree

4 files changed

+54
-33
lines changed

4 files changed

+54
-33
lines changed

Lib/test/test_embed.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -714,8 +714,10 @@ def check_config(self, configs, expected):
714714
if MS_WINDOWS:
715715
value = config.get(key := 'program_name')
716716
if value and isinstance(value, str):
717-
ext = '_d.exe' if debug_build(sys.executable) else '.exe'
718-
config[key] = value[:len(value.lower().removesuffix(ext))]
717+
value = value[:len(value.lower().removesuffix('.exe'))]
718+
if debug_build(sys.executable):
719+
value = value[:len(value.lower().removesuffix('_d'))]
720+
config[key] = value
719721
for key, value in list(expected.items()):
720722
if value is self.IGNORE_CONFIG:
721723
config.pop(key, None)
@@ -1292,7 +1294,7 @@ def test_init_setpythonhome(self):
12921294
stdlib = os.path.join(home, "Lib")
12931295
# Because we are specifying 'home', module search paths
12941296
# are fairly static
1295-
expected_paths = [paths[0], stdlib, os.path.join(home, 'DLLs')]
1297+
expected_paths = [paths[0], os.path.join(home, 'DLLs'), stdlib]
12961298
else:
12971299
version = f'{sys.version_info.major}.{sys.version_info.minor}'
12981300
stdlib = os.path.join(home, sys.platlibdir, f'python{version}')
@@ -1333,7 +1335,7 @@ def test_init_is_python_build_with_home(self):
13331335
stdlib = os.path.join(home, "Lib")
13341336
# Because we are specifying 'home', module search paths
13351337
# are fairly static
1336-
expected_paths = [paths[0], stdlib, os.path.join(home, 'DLLs')]
1338+
expected_paths = [paths[0], os.path.join(home, 'DLLs'), stdlib]
13371339
else:
13381340
version = f'{sys.version_info.major}.{sys.version_info.minor}'
13391341
stdlib = os.path.join(home, sys.platlibdir, f'python{version}')
@@ -1361,7 +1363,7 @@ def test_init_is_python_build_with_home(self):
13611363
config['_is_python_build'] = 1
13621364
exedir = os.path.dirname(sys.executable)
13631365
with open(os.path.join(exedir, 'pybuilddir.txt'), encoding='utf8') as f:
1364-
expected_paths[2] = os.path.normpath(
1366+
expected_paths[1 if MS_WINDOWS else 2] = os.path.normpath(
13651367
os.path.join(exedir, f'{f.read()}\n$'.splitlines()[0]))
13661368
if not MS_WINDOWS:
13671369
# PREFIX (default) is set when running in build directory
@@ -1438,8 +1440,8 @@ def test_init_pybuilddir_win32(self):
14381440

14391441
module_search_paths = self.module_search_paths()
14401442
module_search_paths[-3] = os.path.join(tmpdir, os.path.basename(module_search_paths[-3]))
1441-
module_search_paths[-2] = stdlibdir
1442-
module_search_paths[-1] = tmpdir
1443+
module_search_paths[-2] = tmpdir
1444+
module_search_paths[-1] = stdlibdir
14431445

14441446
executable = self.test_exe
14451447
config = {

Lib/test/test_getpath.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,9 @@ def test_normal_win32(self):
3737
module_search_paths_set=1,
3838
module_search_paths=[
3939
r"C:\Python\python98.zip",
40-
r"C:\Python\Lib",
4140
r"C:\Python\DLLs",
41+
r"C:\Python\Lib",
42+
r"C:\Python",
4243
],
4344
)
4445
actual = getpath(ns, expected)
@@ -63,8 +64,8 @@ def test_buildtree_win32(self):
6364
module_search_paths_set=1,
6465
module_search_paths=[
6566
r"C:\CPython\PCbuild\amd64\python98.zip",
66-
r"C:\CPython\Lib",
6767
r"C:\CPython\PCbuild\amd64",
68+
r"C:\CPython\Lib",
6869
],
6970
)
7071
actual = getpath(ns, expected)
@@ -133,8 +134,9 @@ def test_registry_win32(self):
133134
r"C:\Python\python98.zip",
134135
"path1-dir",
135136
# should not contain not-subdirs
136-
r"C:\Python\Lib",
137137
r"C:\Python\DLLs",
138+
r"C:\Python\Lib",
139+
r"C:\Python",
138140
],
139141
)
140142
actual = getpath(ns, expected)
@@ -147,8 +149,9 @@ def test_registry_win32(self):
147149
module_search_paths_set=1,
148150
module_search_paths=[
149151
r"C:\Python\python98.zip",
150-
r"C:\Python\Lib",
151152
r"C:\Python\DLLs",
153+
r"C:\Python\Lib",
154+
r"C:\Python",
152155
],
153156
)
154157
actual = getpath(ns, expected)
@@ -173,8 +176,9 @@ def test_symlink_normal_win32(self):
173176
module_search_paths_set=1,
174177
module_search_paths=[
175178
r"C:\Python\python98.zip",
176-
r"C:\Python\Lib",
177179
r"C:\Python\DLLs",
180+
r"C:\Python\Lib",
181+
r"C:\Python",
178182
],
179183
)
180184
actual = getpath(ns, expected)
@@ -201,8 +205,8 @@ def test_symlink_buildtree_win32(self):
201205
module_search_paths_set=1,
202206
module_search_paths=[
203207
r"C:\CPython\PCbuild\amd64\python98.zip",
204-
r"C:\CPython\Lib",
205208
r"C:\CPython\PCbuild\amd64",
209+
r"C:\CPython\Lib",
206210
],
207211
)
208212
actual = getpath(ns, expected)
@@ -231,8 +235,8 @@ def test_buildtree_pythonhome_win32(self):
231235
module_search_paths_set=1,
232236
module_search_paths=[
233237
r"C:\Out\python98.zip",
234-
r"C:\CPython\Lib",
235238
r"C:\Out",
239+
r"C:\CPython\Lib",
236240
],
237241
)
238242
actual = getpath(ns, expected)
@@ -254,8 +258,8 @@ def test_no_dlls_win32(self):
254258
module_search_paths_set=1,
255259
module_search_paths=[
256260
r"C:\Python\python98.zip",
257-
r"C:\Python\Lib",
258261
r"C:\Python",
262+
r"C:\Python\Lib",
259263
],
260264
)
261265
actual = getpath(ns, expected)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Ensures the ``PythonPath`` registry key from an install is used when
2+
launching from a different copy of Python that relies on an existing install
3+
to provide a copy of its modules and standard library.

Modules/getpath.py

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -597,25 +597,27 @@ def search_up(prefix, *landmarks, test=isfile):
597597

598598
# Detect exec_prefix by searching from executable for the platstdlib_dir
599599
if PLATSTDLIB_LANDMARK and not exec_prefix:
600-
if executable_dir:
601-
if os_name == 'nt':
602-
# QUIRK: For compatibility and security, do not search for DLLs
603-
# directory. The fallback below will cover it
604-
exec_prefix = executable_dir
605-
else:
606-
exec_prefix = search_up(executable_dir, PLATSTDLIB_LANDMARK, test=isdir)
600+
if os_name == 'nt':
601+
# QUIRK: Windows always assumed these were the same
602+
# gh-100320: Our PYDs are assumed to be relative to the Lib directory
603+
# (that is, prefix) rather than the executable (that is, executable_dir)
604+
exec_prefix = prefix
605+
if not exec_prefix and executable_dir:
606+
exec_prefix = search_up(executable_dir, PLATSTDLIB_LANDMARK, test=isdir)
607607
if not exec_prefix and EXEC_PREFIX:
608608
exec_prefix = EXEC_PREFIX
609609
if not exec_prefix or not isdir(joinpath(exec_prefix, PLATSTDLIB_LANDMARK)):
610610
if os_name == 'nt':
611611
# QUIRK: If DLLs is missing on Windows, don't warn, just assume
612-
# that it's all the same as prefix.
613-
# gh-98790: We set platstdlib_dir here to avoid adding "DLLs" into
614-
# sys.path when it doesn't exist, which would give site-packages
615-
# precedence over executable_dir, which is *probably* where our PYDs
616-
# live. Ideally, whoever changes our layout will tell us what the
617-
# layout is, but in the past this worked, so it should keep working.
618-
platstdlib_dir = exec_prefix = prefix
612+
# that they're in exec_prefix
613+
if not platstdlib_dir:
614+
# gh-98790: We set platstdlib_dir here to avoid adding "DLLs" into
615+
# sys.path when it doesn't exist in the platstdlib place, which
616+
# would give Lib packages precedence over executable_dir where our
617+
# PYDs *probably* live. Ideally, whoever changes our layout will tell
618+
# us what the layout is, but in the past this worked, so it should
619+
# keep working.
620+
platstdlib_dir = exec_prefix
619621
else:
620622
warn('Could not find platform dependent libraries <exec_prefix>')
621623

@@ -701,8 +703,14 @@ def search_up(prefix, *landmarks, test=isfile):
701703
except OSError:
702704
break
703705
if isinstance(v, str):
704-
pythonpath.append(v)
706+
pythonpath.extend(v.split(DELIM))
705707
i += 1
708+
# Paths from the core key get appended last, but only
709+
# when home was not set and we aren't in a build dir
710+
if not home_was_set and not venv_prefix and not build_prefix:
711+
v = winreg.QueryValue(key, None)
712+
if isinstance(v, str):
713+
pythonpath.extend(v.split(DELIM))
706714
finally:
707715
winreg.CloseKey(key)
708716
except OSError:
@@ -714,13 +722,17 @@ def search_up(prefix, *landmarks, test=isfile):
714722
pythonpath.append(joinpath(prefix, p))
715723

716724
# Then add stdlib_dir and platstdlib_dir
717-
if os_name == 'nt' and venv_prefix:
718-
# QUIRK: Windows generates paths differently in a venv
725+
if os_name == 'nt':
726+
# QUIRK: Windows generates paths differently
719727
if platstdlib_dir:
720728
pythonpath.append(platstdlib_dir)
721729
if stdlib_dir:
722730
pythonpath.append(stdlib_dir)
723-
if executable_dir not in pythonpath:
731+
if executable_dir and executable_dir not in pythonpath:
732+
# QUIRK: the executable directory is on sys.path
733+
# We keep it low priority, so that properly installed modules are
734+
# found first. It may be earlier in the order if we found some
735+
# reason to put it there.
724736
pythonpath.append(executable_dir)
725737
else:
726738
if stdlib_dir:

0 commit comments

Comments
 (0)