LLDB mainline
PythonRuntimeLoaderDarwin.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "lldb/Host/Config.h"
10
11#if LLDB_ENABLE_PYTHON
12
14#include "llvm/ADT/STLFunctionalExtras.h"
15#include "llvm/ADT/ScopeExit.h"
16#include "llvm/ADT/SmallString.h"
17#include "llvm/ADT/StringRef.h"
18#include "llvm/Support/FileSystem.h"
19#include "llvm/Support/MemoryBuffer.h"
20#include "llvm/Support/Path.h"
21#include "llvm/Support/Program.h"
22
23#include <cstdlib>
24#include <memory>
25#include <optional>
26#include <string>
27
28namespace lldb_private {
29
30namespace {
31
32// Apple ships Python3.framework; python.org and Homebrew ship Python.framework.
33constexpr llvm::StringLiteral kAppleFrameworkSuffix =
34 "Library/Frameworks/Python3.framework/Versions/Current/Python3";
35constexpr llvm::StringLiteral kPythonOrgFrameworkSuffix =
36 "Library/Frameworks/Python.framework/Versions/Current/Python";
37
38bool TryIfExists(llvm::function_ref<bool(const char *)> callback,
39 llvm::StringRef base, llvm::StringRef relative) {
40 llvm::SmallString<256> path(base);
41 llvm::sys::path::append(path, relative);
42 if (!llvm::sys::fs::exists(path))
43 return false;
44 return callback(path.c_str());
45}
46
47/// Locates the Python3.framework that `xcrun -f python3` points at. Returns
48/// an empty string on any failure or unexpected layout.
49std::string FindPythonViaXcrun() {
50 llvm::ErrorOr<std::string> xcrun = llvm::sys::findProgramByName("xcrun");
51 if (!xcrun)
52 return {};
53
54 llvm::SmallString<128> stdout_path;
55 if (llvm::sys::fs::createTemporaryFile("xcrun-python3", "txt", stdout_path))
56 return {};
57 auto remove_temp =
58 llvm::scope_exit([&] { llvm::sys::fs::remove(stdout_path.str()); });
59
60 std::optional<llvm::StringRef> redirects[3] = {
61 llvm::StringRef(""), llvm::StringRef(stdout_path), llvm::StringRef("")};
62 llvm::StringRef args[] = {*xcrun, "-f", "python3"};
63 int rc =
64 llvm::sys::ExecuteAndWait(*xcrun, args, /*env=*/std::nullopt, redirects);
65 if (rc != 0)
66 return {};
67
68 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> buf =
69 llvm::MemoryBuffer::getFile(stdout_path.str());
70 if (!buf)
71 return {};
72
73 llvm::StringRef python = (*buf)->getBuffer().rtrim();
74 if (python.empty())
75 return {};
76
77 // xcrun is not guaranteed to return an Xcode-layout interpreter, so verify
78 // the .../usr/bin/python3 structure before deriving a developer dir.
79 if (llvm::sys::path::filename(python) != "python3")
80 return {};
81 llvm::StringRef parent = llvm::sys::path::parent_path(python);
82 if (llvm::sys::path::filename(parent) != "bin")
83 return {};
84 llvm::StringRef grandparent = llvm::sys::path::parent_path(parent);
85 if (llvm::sys::path::filename(grandparent) != "usr")
86 return {};
87 llvm::StringRef developer = llvm::sys::path::parent_path(grandparent);
88
89 llvm::SmallString<256> framework(developer);
90 llvm::sys::path::append(framework, kAppleFrameworkSuffix);
91 if (llvm::sys::fs::exists(framework))
92 return std::string(framework);
93
94 return {};
95}
96
97} // namespace
98
100 llvm::function_ref<bool(const char *)> callback) {
101 if (const char *developer_dir = std::getenv("DEVELOPER_DIR");
102 developer_dir && *developer_dir)
103 if (TryIfExists(callback, developer_dir, kAppleFrameworkSuffix))
104 return;
105
106 if (TryIfExists(callback, "/Applications/Xcode.app/Contents/Developer",
107 kAppleFrameworkSuffix))
108 return;
109 if (TryIfExists(callback, "/Library/Developer/CommandLineTools",
110 kAppleFrameworkSuffix))
111 return;
112 if (TryIfExists(callback, "/", kPythonOrgFrameworkSuffix))
113 return;
114 if (TryIfExists(callback, "/opt/homebrew", kPythonOrgFrameworkSuffix))
115 return;
116 if (TryIfExists(callback, "/usr/local", kPythonOrgFrameworkSuffix))
117 return;
118
119 // xcrun is a subprocess; only run it when the well-known paths miss.
120 if (std::string xcrun_path = FindPythonViaXcrun(); !xcrun_path.empty())
121 if (callback(xcrun_path.c_str()))
122 return;
123
124 // Bare-name dlopen is the only way to hit the dyld shared cache.
125 callback("libpython3.dylib");
126}
127
128} // namespace lldb_private
129
130#endif // LLDB_ENABLE_PYTHON
static llvm::Expected< std::string > xcrun(const std::string &sdk, llvm::ArrayRef< llvm::StringRef > arguments, llvm::StringRef developer_dir="")
A class that represents a running process on the host machine.
void ForEachPythonRuntimeCandidate(llvm::function_ref< bool(const char *)> callback)
Visits candidate Python runtime paths in priority order, stopping at the first call that returns true...