diff --git a/Lib/test/test_getpath.py b/Lib/test/test_getpath.py index eaf4a99279..3f8250b24b 100644 --- a/Lib/test/test_getpath.py +++ b/Lib/test/test_getpath.py @@ -264,6 +264,38 @@ def test_normal_posix(self): actual = getpath(ns, expected) self.assertEqual(expected, actual) + def dont_test_normal_posix_in_opt(self): + """Test a 'standard' install layout on *nix + + This uses '/opt/python9.8' as PREFIX + """ + ns = MockPosixNamespace( + PREFIX="/opt/python9.8", + argv0="python", + ENV_PATH="/usr/bin:/opt/python9.8/bin", + ) + ns.add_known_xfile("/opt/python9.8/bin/python") + ns.add_known_file("/opt/python9.8/lib/python9.8/os.py") + ns.add_known_dir("/opt/python9.8/lib/python9.8/lib-dynload") + + # This shouldn't matter: + ns.add_known_file("/opt/lib/python98.zip") + + expected = dict( + executable="/opt/python9.8/bin/python", + base_executable="/opt/python9.8/bin/python", + prefix="/opt/python9.8", + exec_prefix="/opt/python9.8", + module_search_paths_set=1, + module_search_paths=[ + "/opt/python9.8/lib/python98.zip", + "/opt/python9.8/lib/python9.8", + "/opt/python9.8/lib/python9.8/lib-dynload", + ], + ) + actual = getpath(ns, expected) + self.assertEqual(expected, actual) + def test_buildpath_posix(self): """Test an in-build-tree layout on POSIX. @@ -446,6 +478,175 @@ 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_file("/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_file("/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( + argv0="/Library/Frameworks/DebugPython.framework/Versions/9.8/Resources/Python.app/Contents/MacOS/DebugPython", + os_name="darwin", + 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", + PYTHONPATH=None, + ENV_PYTHONHOME=None, + ENV_PYTHONEXECUTABLE=None, + executable_dir=None, + py_setpath=None, + ) + 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_alt_framework_macos(self): + """Test a venv layout on macOS using a framework build + + ``--with-framework-name=DebugPython`` + """ + venv_path = "/tmp/workdir/venv" + 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__=f"{venv_path}/bin/python", + 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_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/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") + ns.add_known_file(f"{venv_path}/pyvenv.cfg", [ + "home = /Library/Frameworks/DebugPython.framework/Versions/9.8/bin" + ]) + expected = dict( + executable=f"{venv_path}/bin/python", + prefix=venv_path, + exec_prefix=venv_path, + 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_macos(self): """Test a venv layout on macOS. @@ -787,6 +988,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/Modules/getpath.c b/Modules/getpath.c index 5c646c9c83..0e64303d87 100644 --- a/Modules/getpath.c +++ b/Modules/getpath.c @@ -528,6 +528,7 @@ getpath_warn(PyObject *Py_UNUSED(self), PyObject *args) { PyObject *msgobj; if (!PyArg_ParseTuple(args, "U", &msgobj)) { + fprintf(stderr, "warn error\n"); return NULL; } fprintf(stderr, "%s\n", PyUnicode_AsUTF8(msgobj)); @@ -538,6 +539,14 @@ getpath_warn(PyObject *Py_UNUSED(self), PyObject *args) static PyObject * getpath_nowarn(PyObject *Py_UNUSED(self), PyObject *args) { + /* XXX */ + PyObject *msgobj; + if (!PyArg_ParseTuple(args, "U", &msgobj)) { + fprintf(stderr, "warn error\n"); + return NULL; + } + fprintf(stderr, "%s\n", PyUnicode_AsUTF8(msgobj)); + /* XXX */ Py_RETURN_NONE; } @@ -766,6 +775,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 +784,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 +884,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,6 +917,22 @@ _PyConfig_InitPathConfig(PyConfig *config, int compute_path_config) _PyErr_WriteUnraisableMsg("error evaluating initial values", NULL); return PyStatus_Error("error evaluating initial values"); } +#if 0 + 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(dict, k); + o = PyObject_Repr(o); + fprintf(stderr, "%s\n", PyUnicode_AsUTF8(o)); + Py_DECREF(o); + Py_DECREF(k); + } + Py_DECREF(it); +#endif PyObject *r = PyEval_EvalCode(co, dict, dict); Py_DECREF(co); @@ -916,7 +944,6 @@ _PyConfig_InitPathConfig(PyConfig *config, int compute_path_config) } Py_DECREF(r); -#if 0 PyObject *it = PyObject_GetIter(configDict); for (PyObject *k = PyIter_Next(it); k; k = PyIter_Next(it)) { if (!strcmp("__builtins__", PyUnicode_AsUTF8(k))) { @@ -925,19 +952,47 @@ _PyConfig_InitPathConfig(PyConfig *config, int compute_path_config) } fprintf(stderr, "%s = ", PyUnicode_AsUTF8(k)); PyObject *o = PyDict_GetItem(configDict, k); + PyObject* t = PyObject_Repr((PyObject*)Py_TYPE(o)); o = PyObject_Repr(o); - fprintf(stderr, "%s\n", PyUnicode_AsUTF8(o)); + fprintf(stderr, "%s", PyUnicode_AsUTF8(o)); + fprintf(stderr, " of %s\n", PyUnicode_AsUTF8(t)); Py_DECREF(o); + Py_DECREF(t); + Py_DECREF(k); } Py_DECREF(it); -#endif + + fprintf(stderr, "before reconfig: config->prefix = %ls\n", config->prefix); + { + PyObject* obj = PySys_GetObject("prefix"); \ + PySys_WriteStderr("before reconfig: sys.prefix = "); \ + if (obj != NULL) { \ + PySys_FormatStderr("%A", obj); \ + } \ + else { \ + PySys_WriteStderr("(not set)"); \ + } \ + PySys_FormatStderr("\n"); \ + } if (_PyConfig_FromDict(config, configDict) < 0) { _PyErr_WriteUnraisableMsg("reading getpath results", NULL); Py_DECREF(dict); return PyStatus_Error("error getting getpath results"); } + fprintf(stderr, "after reconfig: config->prefix = %ls\n", config->prefix); + { + PyObject* obj = PySys_GetObject("prefix"); \ + PySys_WriteStderr("after reconfig: sys.prefix = "); \ + if (obj != NULL) { \ + PySys_FormatStderr("%A", obj); \ + } \ + else { \ + PySys_WriteStderr("(not set)"); \ + } \ + PySys_FormatStderr("\n"); \ + } Py_DECREF(dict); diff --git a/Modules/getpath.py b/Modules/getpath.py index f84e6e8afa..5bf1521d3b 100644 --- a/Modules/getpath.py +++ b/Modules/getpath.py @@ -28,11 +28,19 @@ # realpath(path) -- resolves symlinks in path # warn(message) -- print a warning (if enabled) +if 1: + warn("STARTUP") + for k, v in list(globals().items()): + warn(f" {k} = {v}") + warn("/STARTUP") + + # ** Values known at compile time ** # os_name -- [in] one of 'nt', 'posix', 'darwin' # 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 @@ -301,11 +309,27 @@ def search_up(prefix, *landmarks, test=isfile): # If set, these variables imply that we should be using them as # sys.executable and when searching for venvs. However, we should # use the argv0 path for prefix calculation - base_executable = executable - if not real_executable: - real_executable = executable - executable = ENV_PYTHONEXECUTABLE or ENV___PYVENV_LAUNCHER__ - executable_dir = dirname(executable) + + if os_name == 'darwin' and WITH_NEXT_FRAMEWORK: + # 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)) + if not real_executable: + real_executable = base_executable + real_executable_dir = dirname(real_executable) + executable = ENV_PYTHONEXECUTABLE or ENV___PYVENV_LAUNCHER__ + executable_dir = dirname(executable) + #prefix = exec_prefix = dirname(dirname(executable)) + + else: + base_executable = executable + if not real_executable: + real_executable = executable + executable = ENV_PYTHONEXECUTABLE or ENV___PYVENV_LAUNCHER__ + executable_dir = dirname(executable) # ****************************************************************************** @@ -394,6 +418,7 @@ def search_up(prefix, *landmarks, test=isfile): # previous behavior) executable_dir = realpath(library_dir) real_executable_dir = executable_dir + warn(f"found executable_dir = {executable_dir}") # If we do not have the executable's directory, we can calculate it. # This is the directory used to find prefix/exec_prefix if necessary. @@ -736,3 +761,8 @@ def search_up(prefix, *landmarks, test=isfile): config['platlibdir'] = platlibdir config['stdlib_dir'] = stdlib_dir config['platstdlib_dir'] = platstdlib_dir +warn(f"END prefix: {config['prefix']}") +warn(f"END exec_prefix: {config['exec_prefix']}") +warn(f"END base_prefix: {config['base_prefix']}") +warn(f"END base_exec_prefix: {config['base_exec_prefix']}") +warn(f"END home: {config['home']}")