| //===----------------------------------------------------------------------===// | |
| // | |
| // 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 ships Python3.framework; python.org and Homebrew ship Python.framework. | |
| constexpr llvm::StringLiteral kAppleFrameworkSuffix = | |
| "Library/Frameworks/Python3.framework/Versions/Current/Python3"; | |
| constexpr llvm::StringLiteral kPythonOrgFrameworkSuffix = | |
| "Library/Frameworks/Python.framework/Versions/Current/Python"; | |
| bool TryIfExists(llvm::function_ref<bool(const char *)> 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.c_str()); | |
| } | |
| /// Locates the Python3.framework that `xcrun -f python3` points at. Returns | |
| /// an empty string on any failure or unexpected layout. | |
| 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 {}; | |
| // xcrun is not guaranteed to return an Xcode-layout interpreter, so verify | |
| // the .../usr/bin/python3 structure before deriving a developer dir. | |
| 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); | |
| llvm::SmallString<256> framework(developer); | |
| llvm::sys::path::append(framework, kAppleFrameworkSuffix); | |
| if (llvm::sys::fs::exists(framework)) | |
| return std::string(framework); | |
| return {}; | |
| } | |
| } // namespace | |
| void ForEachPythonRuntimeCandidate( | |
| llvm::function_ref<bool(const char *)> callback) { | |
| if (const char *developer_dir = std::getenv("DEVELOPER_DIR"); | |
| developer_dir && *developer_dir) | |
| if (TryIfExists(callback, developer_dir, kAppleFrameworkSuffix)) | |
| return; | |
| if (TryIfExists(callback, "/Applications/Xcode.app/Contents/Developer", | |
| kAppleFrameworkSuffix)) | |
| return; | |
| if (TryIfExists(callback, "/Library/Developer/CommandLineTools", | |
| kAppleFrameworkSuffix)) | |
| return; | |
| if (TryIfExists(callback, "/", kPythonOrgFrameworkSuffix)) | |
| return; | |
| if (TryIfExists(callback, "/opt/homebrew", kPythonOrgFrameworkSuffix)) | |
| return; | |
| if (TryIfExists(callback, "/usr/local", kPythonOrgFrameworkSuffix)) | |
| return; | |
| // xcrun is a subprocess; only run it when the well-known paths miss. | |
| if (std::string xcrun_path = FindPythonViaXcrun(); !xcrun_path.empty()) | |
| if (callback(xcrun_path.c_str())) | |
| return; | |
| // Bare-name dlopen is the only way to hit the dyld shared cache. | |
| callback("libpython3.dylib"); | |
| } | |
| } // namespace lldb_private | |
| #endif // LLDB_ENABLE_PYTHON |