Skip to content

Inconsistent platform support for taking the loaded libpython path into account in getpath#127970

@FFY00

Description

@FFY00

Bug report

Bug description:

The current getpath.py code tries determining base_prefix/base_exec_prefix by searching the location of the libpython library loaded in the current process, falling back to the location of the Python interpreter executable.

# First try to detect prefix by looking alongside our runtime library, if known
iflibraryandnotprefix:
library_dir=dirname(library)
ifZIP_LANDMARK:
ifos_name=='nt':
# QUIRK: Windows does not search up for ZIP file
ifisfile(joinpath(library_dir, ZIP_LANDMARK)):
prefix=library_dir
else:
prefix=search_up(library_dir, ZIP_LANDMARK)
ifSTDLIB_SUBDIRandSTDLIB_LANDMARKSandnotprefix:
ifany(isfile(joinpath(library_dir, f)) forfinSTDLIB_LANDMARKS):
prefix=library_dir
ifnotstdlib_dir_was_set_in_config:
stdlib_dir=joinpath(prefix, STDLIB_SUBDIR)
# Detect prefix by looking for zip file
ifZIP_LANDMARKandexecutable_dirandnotprefix:
ifos_name=='nt':
# QUIRK: Windows does not search up for ZIP file
ifisfile(joinpath(executable_dir, ZIP_LANDMARK)):
prefix=executable_dir
else:
prefix=search_up(executable_dir, ZIP_LANDMARK)
ifprefixandnotstdlib_dir_was_set_in_config:
stdlib_dir=joinpath(prefix, STDLIB_SUBDIR)
ifnotisdir(stdlib_dir):
stdlib_dir=None
# Detect prefix by searching from our executable location for the stdlib_dir
ifSTDLIB_SUBDIRandSTDLIB_LANDMARKSandexecutable_dirandnotprefix:
prefix=search_up(executable_dir, *STDLIB_LANDMARKS)
ifprefixandnotstdlib_dir:
stdlib_dir=joinpath(prefix, STDLIB_SUBDIR)

Looking at the location of the libpython library in use first makes sense, as that is more reliable than looking at interpreter location — it works when embedding, where there isn't any interpreter executable, it works when the executable is not on base_prefix, etc. However, this is only currently supported on Windows and macOS framework builds.

/* Add the runtime library's path to the dict */
staticint
library_to_dict(PyObject*dict, constchar*key)
{
#ifdefMS_WINDOWS
#ifdefPy_ENABLE_SHARED
externHMODULEPyWin_DLLhModule;
if (PyWin_DLLhModule){
returnwinmodule_to_dict(dict, key, PyWin_DLLhModule);
}
#endif
#elif defined(WITH_NEXT_FRAMEWORK)
staticcharmodPath[MAXPATHLEN+1];
staticintmodPathInitialized=-1;
if (modPathInitialized<0){
modPathInitialized=0;
/* On Mac OS X we have a special case if we're running from a framework.
This is because the python home should be set relative to the library,
which is in the framework, not relative to the executable, which may
be outside of the framework. Except when we're in the build
directory... */
Dl_infopythonInfo;
if (dladdr(&Py_Initialize, &pythonInfo)){
if (pythonInfo.dli_fname){
strncpy(modPath, pythonInfo.dli_fname, MAXPATHLEN);
modPathInitialized=1;
}
}
}
if (modPathInitialized>0){
returndecode_to_dict(dict, key, modPath);
}
#endif
returnPyDict_SetItemString(dict, key, Py_None) ==0;
}

The spotty platform support stroke me as odd, especially on macOS, as I see no apparent reason for only supporting framework builds, so I looked traced back the origin of this code.

The macOS logic goes back to Python 2.0, having been introduced in 54ecc3d. At this time, we were determining base_prefix/base_exec_prefix based on the Python interpreter location, which was problematic on OS X Frameworks, as the Python interpreter is provided via a launcher. The comment traces back to 55070f5 and is unrelated to the change made by that commit, it just highlights the special case for macOS framework builds.

In GH-29041, which introduced getpath.py, rewriting the old path initialization C code in Python, the logic changed to purposefully search the libpython location before the executable location, also adding Windows support. I imagine the existing macOS code was kept as-is as a mistake, leaving it behind the WITH_NEXT_FRAMEWORK flag, maybe under the assumption it was needed for some reason.

Considering the clear intent in the code, I am treating this a bug.

cc @zooba

CPython versions tested on:

CPython main branch

Operating systems tested on:

No response

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    extension-modulesC modules in the Modules dirinterpreter-core(Objects, Python, Grammar, and Parser dirs)type-featureA feature request or enhancement

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions