[lldb] Add PythonRuntimeLoader for runtime libpython lookup (NFC)#200524
[lldb] Add PythonRuntimeLoader for runtime libpython lookup (NFC)#200524JDevlieghere wants to merge 1 commit into
Conversation
Generalizes the Windows-only Python lookup in lldb/Host/windows/ PythonPathSetup into a cross-platform abstraction. Adds lldb_private::PythonRuntimeLoader in lldb/Host/PythonRuntimeLoader.h, whose Load() member dlopens libpython into the current process via llvm::sys::DynamicLibrary::getPermanentLibrary so the script interpreter plugin's stable-ABI references can resolve against a runtime-supplied Python. The loader no-ops when Python is already in the process (lldb-in-python case), then walks: LLDB_PYTHON_LIBRARY env override, the build-time Python baked in via CMake, and a platform candidate list: - **Darwin**: HostInfoMacOSX::GetXcodeDeveloperDirectory() already covers DEVELOPER_DIR, the enclosing Xcode.app when LLDB ships inside one, and an `xcrun --show-sdk-path` fallback — joined against Python3.framework. Then explicit Command Line Tools, python.org, /opt/homebrew, and /usr/local prefixes. Bare libpython3.dylib as a last resort to hit the dyld cache. - **Linux**: libpython3.so plus descending stable-ABI SONAMEs. - **Windows** the LLDB_PYTHON_RUNTIME_LIBRARY_FILENAME bare name (resolved via the loader's default search list) and the exe-relative LLDB_PYTHON_DLL_RELATIVE_PATH fallback (built off HostInfo::GetProgramFileSpec). Pre-mapping python3xx.dll lets the script interpreter plugin's delay-load thunks resolve against the already-loaded module by base name on first use. This commit only introduces the abstraction. No existing call site is changed, and the script interpreter plugin still hard-links libpython.
|
@llvm/pr-subscribers-lldb Author: Jonas Devlieghere (JDevlieghere) ChangesGeneralizes the Windows-only Python lookup in lldb/Host/windows/ PythonPathSetup into a cross-platform abstraction. Adds lldb_private::PythonRuntimeLoader in lldb/Host/PythonRuntimeLoader.h, whose Load() member dlopens libpython into the current process via llvm::sys::DynamicLibrary::getPermanentLibrary so the script interpreter plugin's stable-ABI references can resolve against a runtime-supplied Python. The loader no-ops when Python is already in the process (lldb-in-python case), then walks: LLDB_PYTHON_LIBRARY env override, the build-time Python baked in via CMake, and a platform candidate list:
This commit only introduces the abstraction. No existing call site is changed, and the script interpreter plugin still hard-links libpython, which are part of two follow-up PRs. Patch is 27.04 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/200524.diff 12 Files Affected:
diff --git a/lldb/cmake/modules/LLDBConfig.cmake b/lldb/cmake/modules/LLDBConfig.cmake
index cf890fa9a9c44..4651fdd6b43b8 100644
--- a/lldb/cmake/modules/LLDBConfig.cmake
+++ b/lldb/cmake/modules/LLDBConfig.cmake
@@ -211,6 +211,17 @@ if (LLDB_ENABLE_PYTHON)
"Path to use as PYTHONHOME in lldb. If a relative path is specified, it will be resolved at runtime relative to liblldb directory.")
endif()
+ # Capture the build-time libpython path so Config.h can expose it as a
+ # runtime fallback for the dynamic Python plugin loader. Going through
+ # Config.h.cmake's R"(...)" substitution avoids the per-platform escaping
+ # hazards of stuffing the path into a target_compile_definitions string.
+ if(TARGET Python3::Python)
+ get_target_property(_Python3_LIB_PATH Python3::Python IMPORTED_LIBRARY_LOCATION)
+ if(_Python3_LIB_PATH)
+ set(LLDB_PYTHON_RUNTIME_LIBRARY_BUILD_PATH "${_Python3_LIB_PATH}")
+ endif()
+ endif()
+
# Enable targeting the Python Limited C API.
set(PYTHON_LIMITED_API_MIN_SWIG_VERSION "4.2")
if (SWIG_VERSION VERSION_EQUAL "4.4.0" AND Python3_VERSION VERSION_GREATER_EQUAL "3.13")
diff --git a/lldb/include/lldb/Host/Config.h.cmake b/lldb/include/lldb/Host/Config.h.cmake
index ae32c001ee5dc..19fee25f96bfa 100644
--- a/lldb/include/lldb/Host/Config.h.cmake
+++ b/lldb/include/lldb/Host/Config.h.cmake
@@ -57,6 +57,8 @@
#cmakedefine LLDB_PYTHON_HOME R"(${LLDB_PYTHON_HOME})"
+#cmakedefine LLDB_PYTHON_RUNTIME_LIBRARY_BUILD_PATH R"(${LLDB_PYTHON_RUNTIME_LIBRARY_BUILD_PATH})"
+
#define LLDB_INSTALL_LIBDIR_BASENAME "${LLDB_INSTALL_LIBDIR_BASENAME}"
#cmakedefine LLDB_GLOBAL_INIT_DIRECTORY R"(${LLDB_GLOBAL_INIT_DIRECTORY})"
diff --git a/lldb/include/lldb/Host/PythonRuntimeLoader.h b/lldb/include/lldb/Host/PythonRuntimeLoader.h
new file mode 100644
index 0000000000000..f046a902a2625
--- /dev/null
+++ b/lldb/include/lldb/Host/PythonRuntimeLoader.h
@@ -0,0 +1,52 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_HOST_PYTHONRUNTIMELOADER_H
+#define LLDB_HOST_PYTHONRUNTIMELOADER_H
+
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+namespace lldb_private {
+
+class PythonRuntimeLoader {
+public:
+ /// Make the Python runtime available in the current process so the
+ /// ScriptInterpreterPython plugin's undefined Python symbols can resolve.
+ ///
+ /// On POSIX this dlopens libpython into the process via
+ /// llvm::sys::DynamicLibrary::getPermanentLibrary, leaving Python's
+ /// stable-ABI symbols visible in the global namespace. On Windows this
+ /// configures the DLL search path so the plugin's delay-load thunks can
+ /// find python3xx.dll.
+ ///
+ /// No-op when Python is already loaded in the current process (i.e. when
+ /// LLDB is imported into a Python interpreter).
+ ///
+ /// Honors the LLDB_PYTHON_LIBRARY environment variable (full path to a
+ /// libpython binary or framework Python file). Otherwise walks a
+ /// platform-specific list of well-known locations (Xcode, Command Line
+ /// Tools, /Library/Frameworks, /opt/homebrew, /usr/local on Darwin; SONAME
+ /// variants on Linux).
+ ///
+ /// The first call drives the load; subsequent calls return the cached
+ /// outcome. Returns success on no-op, on first successful load, or on
+ /// builds without Python support. Returns an Error aggregating the
+ /// per-candidate failures when no Python runtime can be located.
+ static llvm::Error Load();
+
+ /// Path of the Python runtime that was loaded, for diagnostics. Empty if
+ /// Python was already in the process, if loading failed, or on builds
+ /// without Python support. Triggers the load on first call, mirroring
+ /// Load().
+ static llvm::StringRef GetLoadedPath();
+};
+
+} // namespace lldb_private
+
+#endif // LLDB_HOST_PYTHONRUNTIMELOADER_H
diff --git a/lldb/include/module.modulemap b/lldb/include/module.modulemap
index 8429ff9b331ad..02f2fd82c5a6f 100644
--- a/lldb/include/module.modulemap
+++ b/lldb/include/module.modulemap
@@ -46,6 +46,7 @@ module lldb_Host {
module ProcessLaunchInfo { header "lldb/Host/ProcessLaunchInfo.h" export * }
module ProcessRunLock { header "lldb/Host/ProcessRunLock.h" export * }
module PseudoTerminal { header "lldb/Host/PseudoTerminal.h" export * }
+ module PythonRuntimeLoader { header "lldb/Host/PythonRuntimeLoader.h" export * }
module SafeMachO { header "lldb/Host/SafeMachO.h" export * }
module SocketAddress { header "lldb/Host/SocketAddress.h" export * }
module Socket { header "lldb/Host/Socket.h" export * }
diff --git a/lldb/source/Host/CMakeLists.txt b/lldb/source/Host/CMakeLists.txt
index f7bd00457613a..ee67e83ec8c82 100644
--- a/lldb/source/Host/CMakeLists.txt
+++ b/lldb/source/Host/CMakeLists.txt
@@ -55,6 +55,12 @@ add_host_subdirectory(common
common/ZipFileResolver.cpp
)
+if (LLDB_ENABLE_PYTHON)
+ add_host_subdirectory(common
+ common/PythonRuntimeLoader.cpp
+ )
+endif()
+
if (LLDB_ENABLE_LIBEDIT)
add_host_subdirectory(common
common/Editline.cpp
@@ -84,6 +90,11 @@ if (CMAKE_SYSTEM_NAME MATCHES "Windows")
windows/ProcessRunLock.cpp
windows/WindowsFileAction.cpp
)
+ if (LLDB_ENABLE_PYTHON)
+ add_host_subdirectory(windows
+ windows/PythonRuntimeLoaderWindows.cpp
+ )
+ endif()
else()
add_host_subdirectory(posix
posix/DomainSocket.cpp
@@ -110,6 +121,11 @@ else()
macosx/cfcpp/CFCMutableSet.cpp
macosx/cfcpp/CFCString.cpp
)
+ if (LLDB_ENABLE_PYTHON)
+ add_host_subdirectory(macosx
+ macosx/PythonRuntimeLoaderDarwin.cpp
+ )
+ endif()
if(APPLE_EMBEDDED)
set_property(SOURCE macosx/Host.mm APPEND PROPERTY
COMPILE_DEFINITIONS "NO_XPC_SERVICES=1")
@@ -124,6 +140,11 @@ else()
linux/LibcGlue.cpp
linux/Support.cpp
)
+ if (LLDB_ENABLE_PYTHON)
+ add_host_subdirectory(linux
+ linux/PythonRuntimeLoaderLinux.cpp
+ )
+ endif()
if (CMAKE_SYSTEM_NAME MATCHES "Android")
add_host_subdirectory(android
android/HostInfoAndroid.cpp
@@ -201,3 +222,13 @@ add_lldb_library(lldbHost NO_PLUGIN_DEPENDENCIES
lldbHostMacOSXObjCXX
)
+if (WIN32 AND LLDB_ENABLE_PYTHON)
+ if (DEFINED LLDB_PYTHON_RUNTIME_LIBRARY_FILENAME)
+ target_compile_definitions(lldbHost PRIVATE
+ LLDB_PYTHON_RUNTIME_LIBRARY_FILENAME="${LLDB_PYTHON_RUNTIME_LIBRARY_FILENAME}")
+ endif()
+ if (DEFINED LLDB_PYTHON_DLL_RELATIVE_PATH)
+ target_compile_definitions(lldbHost PRIVATE
+ LLDB_PYTHON_DLL_RELATIVE_PATH="${LLDB_PYTHON_DLL_RELATIVE_PATH}")
+ endif()
+endif()
diff --git a/lldb/source/Host/common/PythonRuntimeLoader.cpp b/lldb/source/Host/common/PythonRuntimeLoader.cpp
new file mode 100644
index 0000000000000..1b9f64d05cf5f
--- /dev/null
+++ b/lldb/source/Host/common/PythonRuntimeLoader.cpp
@@ -0,0 +1,165 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Host/Config.h"
+
+#if LLDB_ENABLE_PYTHON
+
+#include "lldb/Host/PythonRuntimeLoader.h"
+
+#include "PythonRuntimeLoaderInternal.h"
+#include "lldb/Utility/LLDBLog.h"
+#include "lldb/Utility/Log.h"
+#include "llvm/Support/DynamicLibrary.h"
+#include "llvm/Support/ErrorExtras.h"
+#include "llvm/Support/Threading.h"
+
+#include <cstdlib>
+#include <string>
+
+#if defined(_WIN32)
+#include "lldb/Host/windows/windows.h"
+#else
+#include <dlfcn.h>
+#endif
+
+namespace lldb_private {
+
+namespace {
+
+/// Process-wide state of the Python runtime load attempt. Populated once via
+/// GetState and observed read-only thereafter.
+struct PythonRuntimeState {
+ /// Path of the Python runtime that was loaded, empty if Python was already
+ /// in the process or no load was attempted.
+ std::string path;
+ /// Aggregated error message, empty if no attempt has been made or the load
+ /// succeeded.
+ std::string error_message;
+};
+
+/// True if libpython is already in the current process. This happens when
+/// LLDB is imported into a Python interpreter, in which case we must not
+/// dlopen another libpython on top of it.
+///
+/// Probe two stable-ABI symbols. Py_IsInitialized has existed forever
+/// (including incompatible Python 2), but Py_InitializeFromConfig is 3.8+.
+/// Requiring both rejects a stale Python 2 libpython, a stub, or another
+/// tool's vendored runtime that happens to export Py_IsInitialized.
+bool IsPythonAlreadyLoaded() {
+#if defined(_WIN32)
+ HMODULE main = ::GetModuleHandleW(nullptr);
+ return ::GetProcAddress(main, "Py_IsInitialized") != nullptr &&
+ ::GetProcAddress(main, "Py_InitializeFromConfig") != nullptr;
+#else
+ return ::dlsym(RTLD_DEFAULT, "Py_IsInitialized") != nullptr &&
+ ::dlsym(RTLD_DEFAULT, "Py_InitializeFromConfig") != nullptr;
+#endif
+}
+
+/// Returns success only if the load succeeded and the resulting library
+/// exposes Py_IsInitialized (a stable-ABI symbol).
+llvm::Error TryLoad(llvm::StringRef path) {
+ std::string err_msg;
+ llvm::sys::DynamicLibrary lib =
+ llvm::sys::DynamicLibrary::getPermanentLibrary(path.str().c_str(),
+ &err_msg);
+ if (!lib.isValid())
+ return llvm::createStringErrorV("could not load '{0}': {1}", path, err_msg);
+
+ if (!lib.getAddressOfSymbol("Py_IsInitialized"))
+ return llvm::createStringErrorV(
+ "'{0}' does not export Py_IsInitialized; not a Python runtime", path);
+
+ return llvm::Error::success();
+}
+
+llvm::Error LoadPythonRuntimeImpl(std::string &out_path) {
+ if (IsPythonAlreadyLoaded())
+ return llvm::Error::success();
+
+ llvm::Error failures =
+ llvm::createStringError("could not locate the Python runtime library");
+
+ auto try_path = [&](llvm::StringRef path) -> bool {
+ if (llvm::Error err = TryLoad(path)) {
+ failures = llvm::joinErrors(std::move(failures), std::move(err));
+ return false;
+ }
+ out_path = std::string(path);
+ return true;
+ };
+
+ // Honor an explicit override via LLDB_PYTHON_LIBRARY. Empty values are
+ // ignored so an exported-but-unset variable doesn't trigger dlopen("").
+ if (const char *override_path = std::getenv("LLDB_PYTHON_LIBRARY");
+ override_path && *override_path) {
+ if (try_path(override_path)) {
+ consumeError(std::move(failures));
+ return llvm::Error::success();
+ }
+ }
+
+#ifdef LLDB_PYTHON_RUNTIME_LIBRARY_BUILD_PATH
+ // Try the Python lldb was linked against at build time. The stable-ABI
+ // guarantees most closely match what the plugin was built for, so prefer
+ // it over the platform search list when an env override hasn't been set
+ // (or has been set but didn't load).
+ if (try_path(LLDB_PYTHON_RUNTIME_LIBRARY_BUILD_PATH)) {
+ consumeError(std::move(failures));
+ return llvm::Error::success();
+ }
+#endif
+
+ // Walk platform-specific well-known locations. The first candidate that
+ // loads cleanly wins; the rest of the list is not enumerated.
+ bool found = false;
+ ForEachPythonRuntimeCandidate(
+ [&](llvm::StringRef candidate) { return found = try_path(candidate); });
+
+ if (found) {
+ consumeError(std::move(failures));
+ return llvm::Error::success();
+ }
+ return failures;
+}
+
+const PythonRuntimeState &GetState() {
+ static PythonRuntimeState g_state;
+ static llvm::once_flag g_once;
+ llvm::call_once(g_once, [] {
+ if (llvm::Error err = LoadPythonRuntimeImpl(g_state.path)) {
+ g_state.error_message = llvm::toString(std::move(err));
+ LLDB_LOG(GetLog(LLDBLog::Host), "Python runtime load failed: {0}",
+ g_state.error_message);
+ }
+ });
+ return g_state;
+}
+
+} // namespace
+
+llvm::Error PythonRuntimeLoader::Load() {
+ const PythonRuntimeState &state = GetState();
+ if (state.error_message.empty())
+ return llvm::Error::success();
+ return llvm::createStringError(state.error_message);
+}
+
+llvm::StringRef PythonRuntimeLoader::GetLoadedPath() { return GetState().path; }
+
+} // namespace lldb_private
+
+#else // !LLDB_ENABLE_PYTHON
+
+namespace lldb_private {
+llvm::Error PythonRuntimeLoader::Load() { return llvm::Error::success(); }
+llvm::StringRef PythonRuntimeLoader::GetLoadedPath() { return {}; }
+} // namespace lldb_private
+
+#endif // LLDB_ENABLE_PYTHON
diff --git a/lldb/source/Host/common/PythonRuntimeLoaderInternal.h b/lldb/source/Host/common/PythonRuntimeLoaderInternal.h
new file mode 100644
index 0000000000000..a27ed74c1eaa7
--- /dev/null
+++ b/lldb/source/Host/common/PythonRuntimeLoaderInternal.h
@@ -0,0 +1,31 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_SOURCE_HOST_COMMON_PYTHONRUNTIMELOADERINTERNAL_H
+#define LLDB_SOURCE_HOST_COMMON_PYTHONRUNTIMELOADERINTERNAL_H
+
+#include "llvm/ADT/STLFunctionalExtras.h"
+#include "llvm/ADT/StringRef.h"
+
+namespace lldb_private {
+
+/// Platform-specific candidate enumeration. Calls \p callback once per
+/// candidate path in priority order; stops at the first call that returns
+/// true. Implemented in PythonRuntimeLoaderDarwin.cpp /
+/// PythonRuntimeLoaderLinux.cpp / PythonRuntimeLoaderWindows.cpp.
+///
+/// Using a callback (rather than returning a vector) lets the caller
+/// short-circuit on the first candidate that loads cleanly, so platforms
+/// that synthesize candidates lazily (e.g. Darwin invokes `xcrun` only when
+/// hardcoded paths miss) don't pay for the more expensive ones up front.
+void ForEachPythonRuntimeCandidate(
+ llvm::function_ref<bool(llvm::StringRef)> callback);
+
+} // namespace lldb_private
+
+#endif // LLDB_SOURCE_HOST_COMMON_PYTHONRUNTIMELOADERINTERNAL_H
diff --git a/lldb/source/Host/linux/PythonRuntimeLoaderLinux.cpp b/lldb/source/Host/linux/PythonRuntimeLoaderLinux.cpp
new file mode 100644
index 0000000000000..7311756889313
--- /dev/null
+++ b/lldb/source/Host/linux/PythonRuntimeLoaderLinux.cpp
@@ -0,0 +1,42 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Host/Config.h"
+
+#if LLDB_ENABLE_PYTHON
+
+#include "../common/PythonRuntimeLoaderInternal.h"
+#include "llvm/ADT/STLFunctionalExtras.h"
+#include "llvm/ADT/StringRef.h"
+
+namespace lldb_private {
+
+void ForEachPythonRuntimeCandidate(
+ llvm::function_ref<bool(llvm::StringRef)> callback) {
+ // Bare names rely on the dynamic linker's search (LD_LIBRARY_PATH,
+ // ldconfig cache, default lib paths). Stable ABI guarantees any of these
+ // is sufficient for a stable-ABI plugin.
+ //
+ // libpython3.so is generally only present in -dev packages, so the
+ // versioned SONAMEs are tried as a fallback. The supported range is
+ // 3.8+ (the lower bound is the Python Stable ABI baseline LLDB already
+ // requires).
+ static constexpr llvm::StringLiteral kCandidates[] = {
+ "libpython3.so", "libpython3.13.so.1.0", "libpython3.12.so.1.0",
+ "libpython3.11.so.1.0", "libpython3.10.so.1.0", "libpython3.9.so.1.0",
+ "libpython3.8.so.1.0",
+ };
+ for (llvm::StringRef candidate : kCandidates) {
+ if (callback(candidate))
+ return;
+ }
+}
+
+} // namespace lldb_private
+
+#endif // LLDB_ENABLE_PYTHON
diff --git a/lldb/source/Host/macosx/PythonRuntimeLoaderDarwin.cpp b/lldb/source/Host/macosx/PythonRuntimeLoaderDarwin.cpp
new file mode 100644
index 0000000000000..5df0ee1773f7e
--- /dev/null
+++ b/lldb/source/Host/macosx/PythonRuntimeLoaderDarwin.cpp
@@ -0,0 +1,143 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Host/Config.h"
+
+#if LLDB_ENABLE_PYTHON
+
+#include "../common/PythonRuntimeLoaderInternal.h"
+#include "llvm/ADT/STLFunctionalExtras.h"
+#include "llvm/ADT/ScopeExit.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/Program.h"
+
+#include <cstdlib>
+#include <memory>
+#include <optional>
+#include <string>
+
+namespace lldb_private {
+
+namespace {
+
+// Apple Xcode and Command Line Tools ship Python3.framework, whose framework
+// binary is named "Python3" (matching the framework name).
+constexpr llvm::StringLiteral kAppleFrameworkSuffix =
+ "Library/Frameworks/Python3.framework/Versions/Current/Python3";
+// python.org and Homebrew distribute Python.framework, whose framework binary
+// is named "Python".
+constexpr llvm::StringLiteral kPythonOrgFrameworkSuffix =
+ "Library/Frameworks/Python.framework/Versions/Current/Python";
+
+/// Build an absolute path by joining \p base and \p relative; emit it via the
+/// callback only if the file exists. Returns the callback's result so the
+/// caller can short-circuit on first success.
+bool TryIfExists(llvm::function_ref<bool(llvm::StringRef)> callback,
+ llvm::StringRef base, llvm::StringRef relative) {
+ llvm::SmallString<256> path(base);
+ llvm::sys::path::append(path, relative);
+ if (!llvm::sys::fs::exists(path))
+ return false;
+ return callback(path);
+}
+
+/// Invoke `xcrun -f python3` and follow the resulting interpreter path back
+/// to its enclosing Python3.framework. Empty string on any failure or when
+/// the framework binary is not at the conventional location relative to the
+/// developer dir.
+std::string FindPythonViaXcrun() {
+ llvm::ErrorOr<std::string> xcrun = llvm::sys::findProgramByName("xcrun");
+ if (!xcrun)
+ return {};
+
+ llvm::SmallString<128> stdout_path;
+ if (llvm::sys::fs::createTemporaryFile("xcrun-python3", "txt", stdout_path))
+ return {};
+ auto remove_temp =
+ llvm::scope_exit([&] { llvm::sys::fs::remove(stdout_path.str()); });
+
+ std::optional<llvm::StringRef> redirects[3] = {
+ llvm::StringRef(""), llvm::StringRef(stdout_path), llvm::StringRef("")};
+ llvm::StringRef args[] = {*xcrun, "-f", "python3"};
+ int rc =
+ llvm::sys::ExecuteAndWait(*xcrun, args, /*env=*/std::nullopt, redirects);
+ if (rc != 0)
+ return {};
+
+ llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> buf =
+ llvm::MemoryBuffer::getFile(stdout_path.str());
+ if (!buf)
+ return {};
+
+ llvm::StringRef python = (*buf)->getBuffer().rtrim();
+ if (python.empty())
+ return {};
+
+ // python is e.g. /Applications/Xcode.app/Contents/Developer/usr/bin/python3.
+ // Validate the structure (.../usr/bin/python3) before deriving the developer
+ // dir; if xcrun returned a non-Xcode interpreter (e.g. a Homebrew shim)
+ // there's no framework at the expected location and we should bail.
+ if (llvm::sys::path::filename(python) != "python3")
+ return {};
+ llvm::StringRef parent = llvm::sys::path::parent_path(python);
+ if (llvm::sys::path::filename(parent) != "bin")
+ return {};
+ llvm::StringRef grandparent = llvm::sys::path::parent_path(parent);
+ if (llvm::sys::path::filename(grandparent) != "usr")
+ return {};
+ llvm::StringRef developer = llvm::sys::path::parent_path(grandparent);...
[truncated]
|
Generalizes the Windows-only Python lookup in lldb/Host/windows/ PythonPathSetup into a cross-platform abstraction. Adds lldb_private::PythonRuntimeLoader in lldb/Host/PythonRuntimeLoader.h, whose Load() member dlopens libpython into the current process via llvm::sys::DynamicLibrary::getPermanentLibrary so the script interpreter plugin's stable-ABI references can resolve against a runtime-supplied Python.
The loader no-ops when Python is already in the process (lldb-in-python case), then walks: LLDB_PYTHON_LIBRARY env override, the build-time Python baked in via CMake, and a platform candidate list:
xcrun --show-sdk-pathfallback, joined against Python3.framework. Then explicit Command Line Tools, python.org, /opt/homebrew, and /usr/local prefixes. Bare libpython3.dylib as a last resort to hit the dyld cache.This commit only introduces the abstraction. No existing call site is changed, and the script interpreter plugin still hard-links libpython, which are part of two follow-up PRs.