zig-python is a lightweight Zig build plugin that links the Python
library and wires up CPython extension builds in Zig. It discovers the
required include paths (Python.h), library paths, and link flags
automatically by probing python‑config, pkg‑config, Python's
sysconfig module, and (on Windows) the filesystem — in that order.
Tested against Zig 0.14.0.
Important
The plugin handles linking but not installing. Out of the box, Zig
names a shared-object artifact libmodule.so on Linux and
module.dll on Windows, but CPython expects module.so and
module.pyd respectively. Adjust the install-step artifact name in
your build.zig to match the platform convention.
Note
A complete working extension is available at python‑zig‑extension‑example.
This walk-through adds the plugin, links it to a module, and imports
Python.h in Zig code.
$ zig fetch --save 'git+https://github.com/BratishkaErik/zig-python#main'const main_mod = b.createModule(.{
.root_source_file = b.path("src/root.zig"),
.target = target,
.optimize = optimize,
});
// If you don't care about the minor version, pass just "3".
@import("zig_python").link_everything(main_mod, "3.11") catch {
// Handle error if needed
};const c = @cImport({
@cDefine("PY_SSIZE_T_CLEAN", {});
@cInclude("Python.h");
});$ zig buildThat is all the plugin needs. The binary will link against libpython
but will use the default shared-object name — adjust the install-step
artifact name in your build.zig to match the platform convention
(module.so on Linux/macOS, module.pyd on Windows).
The plugin tries four strategies in order, stopping as soon as one returns usable paths.
python-config ──► pkg-config ──► import sysconfig ──► Windows path fallback
python3.11-config --embed --includesandpython3.11-config --embed --ldflags— used when thepython{version}-configexecutable exists.pkg-config python-3.11-embed --cflags-only-Iandpkg-config python-3.11-embed --libs— used whenpython-configis absent.python3.11 -c "import sysconfig; print(…)"— queriessysconfig.get_path("include"),sysconfig.get_config_var("LIBDIR"), andsysconfig.get_config_var("BLDLIBRARY"). Used when the first two strategies fail.- Windows path heuristic — if on Windows and the first three
strategies returned nothing, the plugin walks upward from the
python.exelocation to findInclude/andlibs/sub-directories.
Each strategy independently gathers include directories, library
directories, and system link libraries (dl, m, …). The plugin
combines everything it collected into a single call to the module
functions addIncludePath, addLibraryPath, and
linkSystemLibrary.
- Adds all discovered include paths (so
@cInclude("Python.h")resolves). - Adds all discovered library paths.
- Links
libpython{version}and any required system libraries (dl,m). - Sets
mod.pic = true(position-independent code is mandatory for shared libraries). - Sets
mod.link_libc = true(Python's C API depends on libc).
pub fn link_everything(
mod: *std.Build.Module,
python_version: []const u8,
) error{PythonNotFound}!void| Parameter | Type | Description |
|---|---|---|
mod |
*std.Build.Module |
The Zig module to configure. Must have a resolved target. |
python_version |
[]const u8 |
Python version string, e.g. "3.11". Pass "3" to let the system choose the latest Python 3.x. |
| Result | Meaning |
|---|---|
void |
Success — include/library paths and link flags were applied. |
error.PythonNotFound |
None of the four discovery strategies produced any usable paths. |
| Property | Value | Reason |
|---|---|---|
addIncludePath |
One or more paths | Locate Python.h |
addLibraryPath |
One or more paths | Locate libpython |
linkSystemLibrary |
python{version}, dl, m, … |
Link Python and its dependencies |
pic |
true |
Position-independent code (required for shared libs) |
link_libc |
true |
Python C API depends on libc |
This project is REUSE‑compliant. License texts are in the
LICENSES/ directory.
| Scope | License |
|---|---|
Source code (build.zig, build.zig.zon) |
0BSD |
Documentation (README.md, CI files) |
CC0‑1.0 |