diff --git a/Lib/test/test_getpath.py b/Lib/test/test_getpath.py index eaf4a99279..7dfbbdb08f 100644 --- a/Lib/test/test_getpath.py +++ b/Lib/test/test_getpath.py @@ -446,6 +446,126 @@ def test_custom_platlibdir_posix(self): actual = getpath(ns, expected) self.assertEqual(expected, actual) + def test_framework_macos(self): + """ Test framework layout on macOS """ + ns = MockPosixNamespace( + os_name="darwin", + argv0="/Library/Frameworks/Python.framework/Versions/9.8/Resources/Python.app/Contents/MacOS/Python", + WITH_NEXT_FRAMEWORK=1, + PREFIX="/Library/Frameworks/Python.framework/Versions/9.8", + EXEC_PREFIX="/Library/Frameworks/Python.framework/Versions/9.8", + ENV___PYVENV_LAUNCHER__="/Library/Frameworks/Python.framework/Versions/9.8/bin/python9.8", + real_executable="/Library/Frameworks/Python.framework/Versions/9.8/Resources/Python.app/Contents/MacOS/Python", + library="/Library/Frameworks/Python.framework/Versions/9.8/Python", + ) + ns.add_known_xfile("/Library/Frameworks/Python.framework/Versions/9.8/Resources/Python.app/Contents/MacOS/Python") + ns.add_known_xfile("/Library/Frameworks/Python.framework/Versions/9.8/bin/python9.8") + ns.add_known_dir("/Library/Frameworks/Python.framework/Versions/9.8/lib/python9.8/lib-dynload") + ns.add_known_xfile("/Library/Frameworks/Python.framework/Versions/9.8/lib/python9.8/os.py") + + # This is definitely not the stdlib (see discusion in bpo-46980) + ns.add_known_xfile("/Library/Frameworks/lib/python98.zip") + + expected = dict( + executable="/Library/Frameworks/Python.framework/Versions/9.8/bin/python9.8", + prefix="/Library/Frameworks/Python.framework/Versions/9.8", + exec_prefix="/Library/Frameworks/Python.framework/Versions/9.8", + base_executable="/Library/Frameworks/Python.framework/Versions/9.8/bin/python9.8", + base_prefix="/Library/Frameworks/Python.framework/Versions/9.8", + base_exec_prefix="/Library/Frameworks/Python.framework/Versions/9.8", + module_search_paths_set=1, + module_search_paths=[ + "/Library/Frameworks/Python.framework/Versions/9.8/lib/python98.zip", + "/Library/Frameworks/Python.framework/Versions/9.8/lib/python9.8", + "/Library/Frameworks/Python.framework/Versions/9.8/lib/python9.8/lib-dynload", + ], + ) + actual = getpath(ns, expected) + self.assertEqual(expected, actual) + + def test_alt_framework_macos(self): + """ Test framework layout on macOS with alternate framework name + + ``--with-framework-name=DebugPython`` + """ + ns = MockPosixNamespace( + os_name="darwin", + argv0="/Library/Frameworks/DebugPython.framework/Versions/9.8/Resources/Python.app/Contents/MacOS/DebugPython", + WITH_NEXT_FRAMEWORK=1, + PREFIX="/Library/Frameworks/DebugPython.framework/Versions/9.8", + EXEC_PREFIX="/Library/Frameworks/DebugPython.framework/Versions/9.8", + ENV___PYVENV_LAUNCHER__="/Library/Frameworks/DebugPython.framework/Versions/9.8/bin/python9.8", + real_executable="/Library/Frameworks/DebugPython.framework/Versions/9.8/Resources/Python.app/Contents/MacOS/DebugPython", + library="/Library/Frameworks/DebugPython.framework/Versions/9.8/DebugPython", + ) + ns.add_known_xfile("/Library/Frameworks/DebugPython.framework/Versions/9.8/Resources/Python.app/Contents/MacOS/DebugPython") + ns.add_known_xfile("/Library/Frameworks/DebugPython.framework/Versions/9.8/bin/python9.8") + ns.add_known_dir("/Library/Frameworks/DebugPython.framework/Versions/9.8/lib/python9.8/lib-dynload") + ns.add_known_xfile("/Library/Frameworks/DebugPython.framework/Versions/9.8/lib/python9.8/os.py") + + # This is definitely not the stdlib (see discusion in bpo-46980) + ns.add_known_xfile("/Library/lib/python98.zip") + expected = dict( + executable="/Library/Frameworks/DebugPython.framework/Versions/9.8/bin/python9.8", + prefix="/Library/Frameworks/DebugPython.framework/Versions/9.8", + exec_prefix="/Library/Frameworks/DebugPython.framework/Versions/9.8", + base_executable="/Library/Frameworks/DebugPython.framework/Versions/9.8/bin/python9.8", + base_prefix="/Library/Frameworks/DebugPython.framework/Versions/9.8", + base_exec_prefix="/Library/Frameworks/DebugPython.framework/Versions/9.8", + module_search_paths_set=1, + module_search_paths=[ + "/Library/Frameworks/DebugPython.framework/Versions/9.8/lib/python98.zip", + "/Library/Frameworks/DebugPython.framework/Versions/9.8/lib/python9.8", + "/Library/Frameworks/DebugPython.framework/Versions/9.8/lib/python9.8/lib-dynload", + ], + ) + actual = getpath(ns, expected) + self.assertEqual(expected, actual) + + def test_venv_framework_macos(self): + """Test a venv layout on macOS using a framework build + """ + venv_path = "/tmp/workdir/venv" + ns = MockPosixNamespace( + os_name="darwin", + argv0="/Library/Frameworks/Python.framework/Versions/9.8/Resources/Python.app/Contents/MacOS/Python", + WITH_NEXT_FRAMEWORK=1, + PREFIX="/Library/Frameworks/Python.framework/Versions/9.8", + EXEC_PREFIX="/Library/Frameworks/Python.framework/Versions/9.8", + ENV___PYVENV_LAUNCHER__=f"{venv_path}/bin/python", + real_executable="/Library/Frameworks/Python.framework/Versions/9.8/Resources/Python.app/Contents/MacOS/Python", + library="/Library/Frameworks/Python.framework/Versions/9.8/Python", + ) + ns.add_known_dir(venv_path) + ns.add_known_dir(f"{venv_path}/bin") + ns.add_known_dir(f"{venv_path}/lib") + ns.add_known_dir(f"{venv_path}/lib/python9.8") + ns.add_known_xfile(f"{venv_path}/bin/python") + ns.add_known_xfile("/Library/Frameworks/Python.framework/Versions/9.8/Resources/Python.app/Contents/MacOS/Python") + ns.add_known_xfile("/Library/Frameworks/Python.framework/Versions/9.8/bin/python9.8") + ns.add_known_dir("/Library/Frameworks/Python.framework/Versions/9.8/lib/python9.8/lib-dynload") + ns.add_known_xfile("/Library/Frameworks/Python.framework/Versions/9.8/lib/python9.8/os.py") + ns.add_known_file(f"{venv_path}/pyvenv.cfg", [ + "home = /Library/Frameworks/Python.framework/Versions/9.8/bin" + ]) + expected = dict( + executable=f"{venv_path}/bin/python", + prefix=venv_path, + exec_prefix=venv_path, + base_executable="/Library/Frameworks/Python.framework/Versions/9.8/bin/python9.8", + base_prefix="/Library/Frameworks/Python.framework/Versions/9.8", + base_exec_prefix="/Library/Frameworks/Python.framework/Versions/9.8", + module_search_paths_set=1, + module_search_paths=[ + "/Library/Frameworks/Python.framework/Versions/9.8/lib/python98.zip", + "/Library/Frameworks/Python.framework/Versions/9.8/lib/python9.8", + "/Library/Frameworks/Python.framework/Versions/9.8/lib/python9.8/lib-dynload", + ], + ) + actual = getpath(ns, expected) + self.assertEqual(expected, actual) + + def test_venv_macos(self): """Test a venv layout on macOS. @@ -787,6 +907,7 @@ def __init__(self, *a, argv0=None, config=None, **kw): self["config"] = DEFAULT_CONFIG.copy() self["os_name"] = "posix" self["PLATLIBDIR"] = "lib" + self["WITH_NEXT_FRAMEWORK"] = 0 super().__init__(*a, **kw) if argv0: self["config"]["orig_argv"] = [argv0] diff --git a/Makefile.pre.in b/Makefile.pre.in index 7b6f54a9ae..56a4c531f1 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1218,6 +1218,7 @@ Modules/getpath.o: $(srcdir)/Modules/getpath.c Python/frozen_modules/getpath.h M -DVERSION='"$(VERSION)"' \ -DVPATH='"$(VPATH)"' \ -DPLATLIBDIR='"$(PLATLIBDIR)"' \ + -DPYTHONFRAMEWORK='"$(PYTHONFRAMEWORK)"' \ -o $@ $(srcdir)/Modules/getpath.c Programs/python.o: $(srcdir)/Programs/python.c diff --git a/Modules/getpath.c b/Modules/getpath.c index 5c646c9c83..ebf825b2c9 100644 --- a/Modules/getpath.c +++ b/Modules/getpath.c @@ -766,6 +766,7 @@ library_to_dict(PyObject *dict, const char *key) be outside of the framework. Except when we're in the build directory... */ NSSymbol symbol = NSLookupAndBindSymbol("_Py_Initialize"); + fprintf(stderr, "Found _Py_Initialize at %p\n", (void*)symbol); if (symbol != NULL) { NSModule pythonModule = NSModuleForSymbol(symbol); if (pythonModule != NULL) { @@ -774,6 +775,7 @@ library_to_dict(PyObject *dict, const char *key) if (path) { strncpy(modPath, path, MAXPATHLEN); modPathInitialized = 1; + fprintf(stderr, "Found modPath %s\n", modPath); } } } @@ -873,6 +875,7 @@ _PyConfig_InitPathConfig(PyConfig *config, int compute_path_config) !decode_to_dict(dict, "os_name", "nt") || #elif defined(__APPLE__) !decode_to_dict(dict, "os_name", "darwin") || + !int_to_dict(dict, "WITH_NEXT_FRAMEWORK", WITH_NEXT_FRAMEWORK) || #else !decode_to_dict(dict, "os_name", "posix") || #endif @@ -905,26 +908,15 @@ _PyConfig_InitPathConfig(PyConfig *config, int compute_path_config) _PyErr_WriteUnraisableMsg("error evaluating initial values", NULL); return PyStatus_Error("error evaluating initial values"); } - - PyObject *r = PyEval_EvalCode(co, dict, dict); - Py_DECREF(co); - - if (!r) { - Py_DECREF(dict); - _PyErr_WriteUnraisableMsg("error evaluating path", NULL); - return PyStatus_Error("error evaluating path"); - } - Py_DECREF(r); - #if 0 - PyObject *it = PyObject_GetIter(configDict); + PyObject *it = PyObject_GetIter(dict); for (PyObject *k = PyIter_Next(it); k; k = PyIter_Next(it)) { if (!strcmp("__builtins__", PyUnicode_AsUTF8(k))) { Py_DECREF(k); continue; } fprintf(stderr, "%s = ", PyUnicode_AsUTF8(k)); - PyObject *o = PyDict_GetItem(configDict, k); + PyObject *o = PyDict_GetItem(dict, k); o = PyObject_Repr(o); fprintf(stderr, "%s\n", PyUnicode_AsUTF8(o)); Py_DECREF(o); @@ -933,6 +925,17 @@ _PyConfig_InitPathConfig(PyConfig *config, int compute_path_config) Py_DECREF(it); #endif + PyObject *r = PyEval_EvalCode(co, dict, dict); + Py_DECREF(co); + + if (!r) { + Py_DECREF(dict); + _PyErr_WriteUnraisableMsg("error evaluating path", NULL); + return PyStatus_Error("error evaluating path"); + } + Py_DECREF(r); + + if (_PyConfig_FromDict(config, configDict) < 0) { _PyErr_WriteUnraisableMsg("reading getpath results", NULL); Py_DECREF(dict); diff --git a/Modules/getpath.py b/Modules/getpath.py index f84e6e8afa..7a72253ea3 100644 --- a/Modules/getpath.py +++ b/Modules/getpath.py @@ -33,6 +33,7 @@ # PREFIX -- [in] sysconfig.get_config_var(...) # EXEC_PREFIX -- [in] sysconfig.get_config_var(...) # PYTHONPATH -- [in] sysconfig.get_config_var(...) +# WITH_NEXT_FRAMEWORK -- [in] sysconfig.get_config_var(...) (on macOS) # VPATH -- [in] sysconfig.get_config_var(...) # PLATLIBDIR -- [in] sysconfig.get_config_var(...) # PYDEBUGEXT -- [in, opt] '_d' on Windows for debug builds @@ -307,6 +308,16 @@ def search_up(prefix, *landmarks, test=isfile): executable = ENV_PYTHONEXECUTABLE or ENV___PYVENV_LAUNCHER__ executable_dir = dirname(executable) + if os_name == 'darwin' and WITH_NEXT_FRAMEWORK: + prefix = exec_prefix = dirname(executable_dir) + #print(library) + # In a framework build the binary in {sys.exec_prefix}/bin is + # a stub executable that execs the real interpreter in an + # embedded app bundle. That bundle is an implementation detail + # and should not affect base_execuble. + base_executable = f"{dirname(library)}/bin/python{VERSION_MAJOR}.{VERSION_MINOR}" + base_prefix = base_exec_prefix = dirname(dirname(base_executable)) + # ****************************************************************************** # CALCULATE (default) home