LLDB’s dependency on Python is a longstanding source of friction. Because we use Python’s private API, LLDB requires the exact version of Python it was built against. This can be confusing for users:
- They want to
import lldbfrom the Python interpreter in their path, which they reasonably expect to work. - They want to use the command line driver, or a tool linking against LLDB as a library, but we can’t load the matching Python library, because it’s available in the location we expect.
These issues are so common that we have a dedicated explanation on the website.
What We Do Today
Here’s how we handle linking Python on the various platforms we support today:
- macOS: LLDB links against a copy of Python that ships with Xcode. However, Xcode is relocatable, which means this only works when LLDB is part of Xcode and can use a relative RPATH to load Python. For the nightly Swift toolchains (which get installed in
~/Library/Toolchains), we use an absolute RPATH that only works if you have Xcode installed in/Applications/Xcode.appwith the expected Python version. - Linux and BSD: LLDB links against the default Python for that system. This works because we build releases for specific distributions.
- Windows: We link a specific Python version and require users to download the matching release from python.org.
Embedding Python
Shipping a copy of Python is the most robust solution because it guarantees that you load the exact version. However, this essentially means maintaining our own Python distribution. Our users often rely on packages and want to import Python packages in their LLDB scripts, or import LLDB from scripts that use a variety of packages. Maintaining a Python distribution for every platform is not something the LLDB community can shoulder.
Breaking the Revlock
As mentioned above, LLDB uses the Python private (unstable) API, which is why we had to load the matching Python library. We were also limited by SWIG, which generated code that wasn’t part of the limited API. This has changed:
- As of version 4.2, SWIG generates code that’s compatible with the Python 3.4 stable API and later.
- With Make LLDB compatible with the Python Limited C API (#151617), we now only use the limited API in LLDB.
By using only the Python Limited C API, we can import LLDB in a Python 3.8 or newer interpreter, and similarly, load a Python 3.8 or later shared library. While we’ve made significant progress by transitioning to the Python Limited C API, there’s still more work to be done to enable loading LLDB in a different Python interpreter. However, this opens up a path towards a hybrid solution.
The Hybrid Solution
I’m proposing we ship LLDB with a copy of the Python shared library, but without everything else that makes up a Python distribution. This means a Python shared library and standard library, but no standalone interpreter and no third-party packages. By default, users get a minimal installation of Python that guarantees the embedded interpreter (i.e., the script command) in LLDB works.
What about import lldb?
To run a Python script that uses import lldb, users will need a Python interpreter, which means they need to install a complete Python distribution. Because we use the stable API, they can use any version as long as it’s Python 3.8 or newer.
What about packages?
Users wouldn’t be able to use third-party packages with the minimal Python distribution that comes with LLDB, and there’s no pip module to install them. Similarly, they would need to install a Python distribution. The proposed hybrid approach hinges on LLDB automatically picking up and preferring the full distribution if it’s available.
- On macOS: We use an absolute RPATH to
/Library/Frameworks/Python.framework/Pythonand have a fallback to the minimal embedded library. - On Windows: Since #162509, we can use
SetDllDirectoryWto set the load path dynamically. - On Linux: TBD (?)
Next Steps
I’m sharing this RFC to outline my plan for how we can move forward with Python distribution in LLDB. It’s important to note that adoption of this hybrid approach depends on the various LLDB distributors and whether they choose to follow this model.
What’s outlined here is the path I’m planning to pursue for macOS. @charles-zablit has started working on a similar approach for LLDB on Windows on the Swift fork.
Feedback Requested
I’m looking for feedback from the community on:
- Feasibility on your platform: Does this approach make sense for Linux, Windows, or other platforms you maintain?
- Technical concerns: Are there edge cases or compatibility issues I haven’t considered?
- Implementation details: Particularly for Linux, what’s the best way to implement the dynamic library loading preferences?