├── .gitignore ├── README.md ├── build.zig ├── libsum.so ├── src ├── callpy.zig ├── ffi.zig ├── pysum.zig ├── python3.8.zig └── sum.zig └── test_sum.py /.gitignore: -------------------------------------------------------------------------------- 1 | zig-* 2 | __pycache__/ 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sample Python Extension in Zig 2 | 3 | This is tested on Ubuntu with `python3.8` installed. 4 | 5 | ``` 6 | # build the extension 7 | $ zig build 8 | # run zig tests 9 | $ zig build test 10 | # run python tests 11 | $ pytest 12 | ``` 13 | 14 | ## logical division of the code 15 | - Pure Zig part is in `src/sum.zig` 16 | - Python ffi is used in `src/ffi.zig` and translated headers are in `src/python3.8.zig` 17 | - The extension is in `src/pysum.zig` 18 | - Sample app to call python code is in `src/callpy.zig` 19 | 20 | ## python ffi 21 | 22 | `python3.8.zig` is generated by running 23 | ``` 24 | $ zig translate-c /usr/include/python3.8/Python.h -I/usr/include/python3.8 -I /usr/include -I /usr/include/x86_64-linux-gnu/ -D__sched_priority=0 -DNDEBUG > src/python3.8.zig 25 | ``` 26 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const pkgs = struct { 4 | const python = std.build.Pkg{ 5 | .name = "python3.8", 6 | .path = .{ .path = "./src/python3.8.zig" }, 7 | }; 8 | }; 9 | 10 | pub fn build(b: *std.build.Builder) void { 11 | const mode = b.standardReleaseOptions(); 12 | 13 | const lib = b.addSharedLibrary("sum", "src/pysum.zig", .unversioned); 14 | lib.setBuildMode(mode); 15 | lib.addPackage(pkgs.python); 16 | lib.linkLibC(); 17 | lib.install(); 18 | 19 | const exe = b.addExecutable("callpy", "src/callpy.zig"); 20 | exe.addPackage(pkgs.python); 21 | exe.addIncludeDir("/usr/include/python3.8"); 22 | exe.linkSystemLibrary("python3.8"); 23 | exe.linkLibC(); 24 | exe.install(); 25 | 26 | const main_tests = b.addTest("src/sum.zig"); 27 | main_tests.setBuildMode(mode); 28 | 29 | const test_step = b.step("test", "Run library tests"); 30 | test_step.dependOn(&main_tests.step); 31 | } 32 | -------------------------------------------------------------------------------- /libsum.so: -------------------------------------------------------------------------------- 1 | zig-out/lib/libsum.so -------------------------------------------------------------------------------- /src/callpy.zig: -------------------------------------------------------------------------------- 1 | const ffi = @import("ffi.zig"); 2 | const std = @import("std"); 3 | 4 | const py = ffi.py; 5 | 6 | pub fn main() void { 7 | const code = 8 | \\from time import time,ctime 9 | \\print('Today is', ctime(time())) 10 | ; 11 | py.Py_Initialize(); 12 | if (py.PyRun_SimpleString(code) != 0) { 13 | std.os.exit(120); 14 | } 15 | if (py.Py_FinalizeEx() != 0) { 16 | std.os.exit(120); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/ffi.zig: -------------------------------------------------------------------------------- 1 | pub const py = @import("python3.8"); 2 | 3 | pub const PyObject = py.PyObject; 4 | 5 | pub const PyMethodDef = extern struct { 6 | ml_name: [*c]const u8 = null, 7 | ml_meth: py.PyCFunction = null, 8 | ml_flags: c_int = 0, 9 | ml_doc: [*c]const u8 = null, 10 | }; 11 | 12 | pub const PyModuleDef_Base = extern struct { 13 | ob_base: PyObject = PyObject, 14 | m_init: ?fn () callconv(.C) [*c]PyObject = null, 15 | m_index: py.Py_ssize_t = 0, 16 | m_copy: [*c]PyObject = null, 17 | }; 18 | 19 | pub const PyModuleDef_HEAD_INIT = PyModuleDef_Base{ .ob_base = PyObject{ 20 | .ob_refcnt = 1, 21 | .ob_type = null, 22 | } }; 23 | 24 | pub const PyModuleDef = extern struct { 25 | // m_base: py.PyModuleDef_Base, 26 | m_base: PyModuleDef_Base = PyModuleDef_HEAD_INIT, 27 | m_name: [*c]const u8, 28 | m_doc: [*c]const u8 = null, 29 | m_size: py.Py_ssize_t = -1, 30 | m_methods: [*]PyMethodDef, 31 | m_slots: [*c]py.struct_PyModuleDef_Slot = null, 32 | m_traverse: py.traverseproc = null, 33 | m_clear: py.inquiry = null, 34 | m_free: py.freefunc = null, 35 | }; 36 | -------------------------------------------------------------------------------- /src/pysum.zig: -------------------------------------------------------------------------------- 1 | const zsum = @import("sum.zig"); 2 | const ffi = @import("ffi.zig"); 3 | 4 | const py = ffi.py; 5 | const PyObject = ffi.PyObject; 6 | const PyMethodDef = ffi.PyMethodDef; 7 | const PyModuleDef = ffi.PyModuleDef; 8 | 9 | fn _sum(_: [*c]PyObject, args: [*c]PyObject) callconv(.C) [*c]PyObject { 10 | var a: c_long = undefined; 11 | var b: c_long = undefined; 12 | if (py.PyArg_ParseTuple(args, "ll", &a, &b) == 0) return null; 13 | return py.PyLong_FromLong(zsum.sum(@intCast(i32, a), @intCast(i32, b))); 14 | } 15 | 16 | var methods = [_:PyMethodDef{}]PyMethodDef{ 17 | PyMethodDef{ 18 | .ml_name = "sum", 19 | .ml_meth = _sum, 20 | .ml_flags = py.METH_VARARGS, 21 | .ml_doc = "Add two numbers.", 22 | }, 23 | }; 24 | 25 | var module = PyModuleDef{ 26 | .m_name = "sum", 27 | .m_methods = &methods, 28 | }; 29 | 30 | export fn PyInit_libsum() *PyObject { 31 | return py.PyModule_Create(@ptrCast([*c]py.struct_PyModuleDef, &module)); 32 | } 33 | -------------------------------------------------------------------------------- /src/sum.zig: -------------------------------------------------------------------------------- 1 | const expect = @import("std").testing.expect; 2 | 3 | pub fn sum(a: i32, b: i32) i32 { 4 | return a + b; 5 | } 6 | 7 | test "sum" { 8 | try expect(sum(1, 2) == 3); 9 | } 10 | -------------------------------------------------------------------------------- /test_sum.py: -------------------------------------------------------------------------------- 1 | import libsum 2 | 3 | def test_sum(): 4 | assert libsum.sum(2,4) == 6 5 | --------------------------------------------------------------------------------