Skip to content

Commit 6aaf4cd

Browse files
bpo-46890: Fix setting of sys._base_executable with framework builds on macOS (GH-31958)
The side effect of this bug was that venv environments directly used the main interpreter instead of the intermediate stub executable, which can cause problems when a script uses system APIs that require the use of an application bundle.
1 parent a0c7004 commit 6aaf4cd

File tree

5 files changed

+200
-2
lines changed

5 files changed

+200
-2
lines changed

Lib/test/test_getpath.py

+177
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,182 @@ def test_custom_platlibdir_posix(self):
446446
actual = getpath(ns, expected)
447447
self.assertEqual(expected, actual)
448448

449+
def test_framework_macos(self):
450+
""" Test framework layout on macOS
451+
452+
This layout is primarily detected using a compile-time option
453+
(WITH_NEXT_FRAMEWORK).
454+
"""
455+
ns = MockPosixNamespace(
456+
os_name="darwin",
457+
argv0="/Library/Frameworks/Python.framework/Versions/9.8/Resources/Python.app/Contents/MacOS/Python",
458+
WITH_NEXT_FRAMEWORK=1,
459+
PREFIX="/Library/Frameworks/Python.framework/Versions/9.8",
460+
EXEC_PREFIX="/Library/Frameworks/Python.framework/Versions/9.8",
461+
ENV___PYVENV_LAUNCHER__="/Library/Frameworks/Python.framework/Versions/9.8/bin/python9.8",
462+
real_executable="/Library/Frameworks/Python.framework/Versions/9.8/Resources/Python.app/Contents/MacOS/Python",
463+
library="/Library/Frameworks/Python.framework/Versions/9.8/Python",
464+
)
465+
ns.add_known_xfile("/Library/Frameworks/Python.framework/Versions/9.8/Resources/Python.app/Contents/MacOS/Python")
466+
ns.add_known_xfile("/Library/Frameworks/Python.framework/Versions/9.8/bin/python9.8")
467+
ns.add_known_dir("/Library/Frameworks/Python.framework/Versions/9.8/lib/python9.8/lib-dynload")
468+
ns.add_known_file("/Library/Frameworks/Python.framework/Versions/9.8/lib/python9.8/os.py")
469+
470+
# This is definitely not the stdlib (see discusion in bpo-46890)
471+
#ns.add_known_file("/Library/Frameworks/lib/python98.zip")
472+
473+
expected = dict(
474+
executable="/Library/Frameworks/Python.framework/Versions/9.8/bin/python9.8",
475+
prefix="/Library/Frameworks/Python.framework/Versions/9.8",
476+
exec_prefix="/Library/Frameworks/Python.framework/Versions/9.8",
477+
base_executable="/Library/Frameworks/Python.framework/Versions/9.8/bin/python9.8",
478+
base_prefix="/Library/Frameworks/Python.framework/Versions/9.8",
479+
base_exec_prefix="/Library/Frameworks/Python.framework/Versions/9.8",
480+
module_search_paths_set=1,
481+
module_search_paths=[
482+
"/Library/Frameworks/Python.framework/Versions/9.8/lib/python98.zip",
483+
"/Library/Frameworks/Python.framework/Versions/9.8/lib/python9.8",
484+
"/Library/Frameworks/Python.framework/Versions/9.8/lib/python9.8/lib-dynload",
485+
],
486+
)
487+
actual = getpath(ns, expected)
488+
self.assertEqual(expected, actual)
489+
490+
def test_alt_framework_macos(self):
491+
""" Test framework layout on macOS with alternate framework name
492+
493+
``--with-framework-name=DebugPython``
494+
495+
This layout is primarily detected using a compile-time option
496+
(WITH_NEXT_FRAMEWORK).
497+
"""
498+
ns = MockPosixNamespace(
499+
argv0="/Library/Frameworks/DebugPython.framework/Versions/9.8/Resources/Python.app/Contents/MacOS/DebugPython",
500+
os_name="darwin",
501+
WITH_NEXT_FRAMEWORK=1,
502+
PREFIX="/Library/Frameworks/DebugPython.framework/Versions/9.8",
503+
EXEC_PREFIX="/Library/Frameworks/DebugPython.framework/Versions/9.8",
504+
ENV___PYVENV_LAUNCHER__="/Library/Frameworks/DebugPython.framework/Versions/9.8/bin/python9.8",
505+
real_executable="/Library/Frameworks/DebugPython.framework/Versions/9.8/Resources/Python.app/Contents/MacOS/DebugPython",
506+
library="/Library/Frameworks/DebugPython.framework/Versions/9.8/DebugPython",
507+
PYTHONPATH=None,
508+
ENV_PYTHONHOME=None,
509+
ENV_PYTHONEXECUTABLE=None,
510+
executable_dir=None,
511+
py_setpath=None,
512+
)
513+
ns.add_known_xfile("/Library/Frameworks/DebugPython.framework/Versions/9.8/Resources/Python.app/Contents/MacOS/DebugPython")
514+
ns.add_known_xfile("/Library/Frameworks/DebugPython.framework/Versions/9.8/bin/python9.8")
515+
ns.add_known_dir("/Library/Frameworks/DebugPython.framework/Versions/9.8/lib/python9.8/lib-dynload")
516+
ns.add_known_xfile("/Library/Frameworks/DebugPython.framework/Versions/9.8/lib/python9.8/os.py")
517+
518+
# This is definitely not the stdlib (see discusion in bpo-46890)
519+
#ns.add_known_xfile("/Library/lib/python98.zip")
520+
expected = dict(
521+
executable="/Library/Frameworks/DebugPython.framework/Versions/9.8/bin/python9.8",
522+
prefix="/Library/Frameworks/DebugPython.framework/Versions/9.8",
523+
exec_prefix="/Library/Frameworks/DebugPython.framework/Versions/9.8",
524+
base_executable="/Library/Frameworks/DebugPython.framework/Versions/9.8/bin/python9.8",
525+
base_prefix="/Library/Frameworks/DebugPython.framework/Versions/9.8",
526+
base_exec_prefix="/Library/Frameworks/DebugPython.framework/Versions/9.8",
527+
module_search_paths_set=1,
528+
module_search_paths=[
529+
"/Library/Frameworks/DebugPython.framework/Versions/9.8/lib/python98.zip",
530+
"/Library/Frameworks/DebugPython.framework/Versions/9.8/lib/python9.8",
531+
"/Library/Frameworks/DebugPython.framework/Versions/9.8/lib/python9.8/lib-dynload",
532+
],
533+
)
534+
actual = getpath(ns, expected)
535+
self.assertEqual(expected, actual)
536+
537+
def test_venv_framework_macos(self):
538+
"""Test a venv layout on macOS using a framework build
539+
"""
540+
venv_path = "/tmp/workdir/venv"
541+
ns = MockPosixNamespace(
542+
os_name="darwin",
543+
argv0="/Library/Frameworks/Python.framework/Versions/9.8/Resources/Python.app/Contents/MacOS/Python",
544+
WITH_NEXT_FRAMEWORK=1,
545+
PREFIX="/Library/Frameworks/Python.framework/Versions/9.8",
546+
EXEC_PREFIX="/Library/Frameworks/Python.framework/Versions/9.8",
547+
ENV___PYVENV_LAUNCHER__=f"{venv_path}/bin/python",
548+
real_executable="/Library/Frameworks/Python.framework/Versions/9.8/Resources/Python.app/Contents/MacOS/Python",
549+
library="/Library/Frameworks/Python.framework/Versions/9.8/Python",
550+
)
551+
ns.add_known_dir(venv_path)
552+
ns.add_known_dir(f"{venv_path}/bin")
553+
ns.add_known_dir(f"{venv_path}/lib")
554+
ns.add_known_dir(f"{venv_path}/lib/python9.8")
555+
ns.add_known_xfile(f"{venv_path}/bin/python")
556+
ns.add_known_xfile("/Library/Frameworks/Python.framework/Versions/9.8/Resources/Python.app/Contents/MacOS/Python")
557+
ns.add_known_xfile("/Library/Frameworks/Python.framework/Versions/9.8/bin/python9.8")
558+
ns.add_known_dir("/Library/Frameworks/Python.framework/Versions/9.8/lib/python9.8/lib-dynload")
559+
ns.add_known_xfile("/Library/Frameworks/Python.framework/Versions/9.8/lib/python9.8/os.py")
560+
ns.add_known_file(f"{venv_path}/pyvenv.cfg", [
561+
"home = /Library/Frameworks/Python.framework/Versions/9.8/bin"
562+
])
563+
expected = dict(
564+
executable=f"{venv_path}/bin/python",
565+
prefix="/Library/Frameworks/Python.framework/Versions/9.8",
566+
exec_prefix="/Library/Frameworks/Python.framework/Versions/9.8",
567+
base_executable="/Library/Frameworks/Python.framework/Versions/9.8/bin/python9.8",
568+
base_prefix="/Library/Frameworks/Python.framework/Versions/9.8",
569+
base_exec_prefix="/Library/Frameworks/Python.framework/Versions/9.8",
570+
module_search_paths_set=1,
571+
module_search_paths=[
572+
"/Library/Frameworks/Python.framework/Versions/9.8/lib/python98.zip",
573+
"/Library/Frameworks/Python.framework/Versions/9.8/lib/python9.8",
574+
"/Library/Frameworks/Python.framework/Versions/9.8/lib/python9.8/lib-dynload",
575+
],
576+
)
577+
actual = getpath(ns, expected)
578+
self.assertEqual(expected, actual)
579+
580+
def test_venv_alt_framework_macos(self):
581+
"""Test a venv layout on macOS using a framework build
582+
583+
``--with-framework-name=DebugPython``
584+
"""
585+
venv_path = "/tmp/workdir/venv"
586+
ns = MockPosixNamespace(
587+
os_name="darwin",
588+
argv0="/Library/Frameworks/DebugPython.framework/Versions/9.8/Resources/Python.app/Contents/MacOS/DebugPython",
589+
WITH_NEXT_FRAMEWORK=1,
590+
PREFIX="/Library/Frameworks/DebugPython.framework/Versions/9.8",
591+
EXEC_PREFIX="/Library/Frameworks/DebugPython.framework/Versions/9.8",
592+
ENV___PYVENV_LAUNCHER__=f"{venv_path}/bin/python",
593+
real_executable="/Library/Frameworks/DebugPython.framework/Versions/9.8/Resources/Python.app/Contents/MacOS/DebugPython",
594+
library="/Library/Frameworks/DebugPython.framework/Versions/9.8/DebugPython",
595+
)
596+
ns.add_known_dir(venv_path)
597+
ns.add_known_dir(f"{venv_path}/bin")
598+
ns.add_known_dir(f"{venv_path}/lib")
599+
ns.add_known_dir(f"{venv_path}/lib/python9.8")
600+
ns.add_known_xfile(f"{venv_path}/bin/python")
601+
ns.add_known_xfile("/Library/Frameworks/DebugPython.framework/Versions/9.8/Resources/Python.app/Contents/MacOS/DebugPython")
602+
ns.add_known_xfile("/Library/Frameworks/DebugPython.framework/Versions/9.8/bin/python9.8")
603+
ns.add_known_dir("/Library/Frameworks/DebugPython.framework/Versions/9.8/lib/python9.8/lib-dynload")
604+
ns.add_known_xfile("/Library/Frameworks/DebugPython.framework/Versions/9.8/lib/python9.8/os.py")
605+
ns.add_known_file(f"{venv_path}/pyvenv.cfg", [
606+
"home = /Library/Frameworks/DebugPython.framework/Versions/9.8/bin"
607+
])
608+
expected = dict(
609+
executable=f"{venv_path}/bin/python",
610+
prefix="/Library/Frameworks/DebugPython.framework/Versions/9.8",
611+
exec_prefix="/Library/Frameworks/DebugPython.framework/Versions/9.8",
612+
base_executable="/Library/Frameworks/DebugPython.framework/Versions/9.8/bin/python9.8",
613+
base_prefix="/Library/Frameworks/DebugPython.framework/Versions/9.8",
614+
base_exec_prefix="/Library/Frameworks/DebugPython.framework/Versions/9.8",
615+
module_search_paths_set=1,
616+
module_search_paths=[
617+
"/Library/Frameworks/DebugPython.framework/Versions/9.8/lib/python98.zip",
618+
"/Library/Frameworks/DebugPython.framework/Versions/9.8/lib/python9.8",
619+
"/Library/Frameworks/DebugPython.framework/Versions/9.8/lib/python9.8/lib-dynload",
620+
],
621+
)
622+
actual = getpath(ns, expected)
623+
self.assertEqual(expected, actual)
624+
449625
def test_venv_macos(self):
450626
"""Test a venv layout on macOS.
451627
@@ -787,6 +963,7 @@ def __init__(self, *a, argv0=None, config=None, **kw):
787963
self["config"] = DEFAULT_CONFIG.copy()
788964
self["os_name"] = "posix"
789965
self["PLATLIBDIR"] = "lib"
966+
self["WITH_NEXT_FRAMEWORK"] = 0
790967
super().__init__(*a, **kw)
791968
if argv0:
792969
self["config"]["orig_argv"] = [argv0]

Makefile.pre.in

+1
Original file line numberDiff line numberDiff line change
@@ -1218,6 +1218,7 @@ Modules/getpath.o: $(srcdir)/Modules/getpath.c Python/frozen_modules/getpath.h M
12181218
-DVERSION='"$(VERSION)"' \
12191219
-DVPATH='"$(VPATH)"' \
12201220
-DPLATLIBDIR='"$(PLATLIBDIR)"' \
1221+
-DPYTHONFRAMEWORK='"$(PYTHONFRAMEWORK)"' \
12211222
-o $@ $(srcdir)/Modules/getpath.c
12221223

12231224
Programs/python.o: $(srcdir)/Programs/python.c
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix a regression in the setting of ``sys._base_executable`` in framework
2+
builds, and thereby fix a regression in :mod:`venv` virtual environments
3+
with such builds.

Modules/getpath.c

+6
Original file line numberDiff line numberDiff line change
@@ -875,6 +875,11 @@ _PyConfig_InitPathConfig(PyConfig *config, int compute_path_config)
875875
!decode_to_dict(dict, "os_name", "darwin") ||
876876
#else
877877
!decode_to_dict(dict, "os_name", "posix") ||
878+
#endif
879+
#ifdef WITH_NEXT_FRAMEWORK
880+
!int_to_dict(dict, "WITH_NEXT_FRAMEWORK", 1) ||
881+
#else
882+
!int_to_dict(dict, "WITH_NEXT_FRAMEWORK", 0) ||
878883
#endif
879884
!decode_to_dict(dict, "PREFIX", PREFIX) ||
880885
!decode_to_dict(dict, "EXEC_PREFIX", EXEC_PREFIX) ||
@@ -943,3 +948,4 @@ _PyConfig_InitPathConfig(PyConfig *config, int compute_path_config)
943948

944949
return _PyStatus_OK();
945950
}
951+

Modules/getpath.py

+13-2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
# PREFIX -- [in] sysconfig.get_config_var(...)
3434
# EXEC_PREFIX -- [in] sysconfig.get_config_var(...)
3535
# PYTHONPATH -- [in] sysconfig.get_config_var(...)
36+
# WITH_NEXT_FRAMEWORK -- [in] sysconfig.get_config_var(...)
3637
# VPATH -- [in] sysconfig.get_config_var(...)
3738
# PLATLIBDIR -- [in] sysconfig.get_config_var(...)
3839
# PYDEBUGEXT -- [in, opt] '_d' on Windows for debug builds
@@ -301,9 +302,19 @@ def search_up(prefix, *landmarks, test=isfile):
301302
# If set, these variables imply that we should be using them as
302303
# sys.executable and when searching for venvs. However, we should
303304
# use the argv0 path for prefix calculation
304-
base_executable = executable
305+
306+
if os_name == 'darwin' and WITH_NEXT_FRAMEWORK:
307+
# In a framework build the binary in {sys.exec_prefix}/bin is
308+
# a stub executable that execs the real interpreter in an
309+
# embedded app bundle. That bundle is an implementation detail
310+
# and should not affect base_executable.
311+
base_executable = f"{dirname(library)}/bin/python{VERSION_MAJOR}.{VERSION_MINOR}"
312+
else:
313+
base_executable = executable
314+
305315
if not real_executable:
306-
real_executable = executable
316+
real_executable = base_executable
317+
#real_executable_dir = dirname(real_executable)
307318
executable = ENV_PYTHONEXECUTABLE or ENV___PYVENV_LAUNCHER__
308319
executable_dir = dirname(executable)
309320

0 commit comments

Comments
 (0)