Skip to main content

A setuptools extension, for building cpython extensions with Zig.

Project description

https://sourceforge.net/p/setuptools-zig/code/ci/default/tree/_doc/_static/license.svg?format=raw https://sourceforge.net/p/setuptools-zig/code/ci/default/tree/_doc/_static/pypi.svg?format=raw https://sourceforge.net/p/oitnb/code/ci/default/tree/_doc/_static/oitnb.svg?format=raw

A setuptools extension for building cpython extensions written in Zig and/or C, with the Zig compiler.

This extension expects to find the zig command in your PATH. If it is not there, or if you need to select a specific version, you can set the environment variable PY_ZIG to% the full path of the executable. E.g.:

PY_VER=/usr/local/bin/zig

This versio of the module has been updated to Zig 0.10.0, but should work with other versions (as long as you adapt your Zig code). It has been tested with Python 3.7 - 3.11, on Ubuntu 22.4 (binary zig install) and macOS 13.0.1 (brew install).

The package setuptools-zig is available on PyPI, but doesn’t need to be installed, as it is a setup requirement. Once your setup.py has the apropriate entries, building an sdist or bdist_wheel will automatically downloaded the package (cached in the .eggs directory).

Setup.py

Your setup.py file should look like:

from setuptools import Extension
from setuptools import setup

setup(
    name=NAME,
    version='MAJ.MIN.PATCH',
    python_requires='>=3.7.15%',
    build_zig=True,
    ext_modules=[Extension(NAME, [XX1, XX2])],
    setup_requires=['setuptools-zig'],
)

with NAME replaced by the string that is your package name. MAJ, MIN, and PATCH your package’s version, and XX1, XX2 being your source files (you can have just one, or more).

With that adapted to your project:

python setup.py bdist_wheel

will result in a .whl file in your dist directory. That wheel file can be installed in a virtualenv, and the functions defined in the package imported and used. By default the compile and/or link commands executed will be shown, their output only when errors occur. Verbosity can be increased specifying -v or -vv, after bdist_wheel.

Using Zig as a C compiler

Create your setup.py:

from setuptools import Extension
from setuptools import setup

setup(
    name='c_sum',
    version='1.0.0',
    python_requires='>=3.7.15',
    build_zig=True,
    ext_modules=[Extension('c_sum', ['sum.c', ])],
    setup_requires=['setuptools-zig'],

and sum.c:

/* based on https://docs.python.org/3.9/extending/extending.html */

#define PY_SSIZE_T_CLEAN
#include <Python.h>

PyObject* sum(PyObject* self, PyObject* args) {
    long a, b;

    if (!PyArg_ParseTuple(args, "ll", &a, &b))
            return NULL;
    return PyLong_FromLong(a+b);
}


static struct PyMethodDef methods[] = {
    {"sum", (PyCFunction)sum, METH_VARARGS},
    {NULL, NULL}
};

static struct PyModuleDef zigmodule = {
    PyModuleDef_HEAD_INIT,
    "sum",
    NULL,
    -1,
    methods
};

PyMODINIT_FUNC PyInit_c_sum(void) {
    return PyModule_Create(&zigmodule);

install the resulting wheel using pip and use:

python -c "from c_sum import sum; print(sum(20, 22))"

Using Zig with .zig and .c

The Zig compiler can easily mix and match (see section macOS), here we use the C code to provide the interface and do the heavy lifting of calculating the sum in Zig.

setup.py:

from setuptools import Extension
from setuptools import setup

setup(
    name='c_zig_sum',
    version='1.0.0',
    python_requires='>=3.7.15',
    build_zig=True,
    ext_modules=[Extension('c_zig_sum', ['c_int.c', 'sum.zig', ])],
    setup_requires=['setuptools-zig'],
)

c_int.c:

/* based on https://docs.python.org/3.9/extending/extending.html */

#define PY_SSIZE_T_CLEAN
#include <Python.h>

PyObject* sum(PyObject* , PyObject*);

/*
PyObject* sum(PyObject* self, PyObject* args) {
    long a, b;

    if (!PyArg_ParseTuple(args, "ll", &a, &b))
        return NULL;
    return PyLong_FromLong(a+b);
}
*/


static struct PyMethodDef methods[] = {
    {"sum", (PyCFunction)sum, METH_VARARGS},
    {NULL, NULL}
};

static struct PyModuleDef zigmodule = {
    PyModuleDef_HEAD_INIT,
    "c_zig_sum",
    NULL,
    -1,
    methods
};

PyMODINIT_FUNC PyInit_c_zig_sum(void) {
    return PyModule_Create(&zigmodule);
}

sum.zig:

const c = @cImport({
    @cDefine("PY_SSIZE_T_CLEAN", "1");
    @cInclude("Python.h");
});

pub export fn sum(self: [*]c.PyObject, args: [*]c.PyObject) [*c]c.PyObject {
    var a: c_long = undefined;
    var b: c_long = undefined;
    _ = self;
    if (!(c._PyArg_ParseTuple_SizeT(args, "ll", &a, &b) != 0)) return null;
    return c.PyLong_FromLong((a + b));

Zig code only

The orignal converted code is rather ugly to read. There were no differences in the program specific Zig code converted from C between Python 3.7/3.8/3.9/3.10/3.11 (but there was of course in the header). This is a initial attempt to clean things up. Only the part under the comment line should need adaption for your project.

setup.py:

from setuptools import Extension
from setuptools import setup

setup(
    name='zig_sum',
    version='1.0.1',
    python_requires='>=3.7.15',
    build_zig=True,
    ext_modules=[Extension('zig_sum', ['sum.zig' ])],
    setup_requires=['setuptools-zig'],
)

sum.zig:

const c = @cImport({
    @cDefine("PY_SSIZE_T_CLEAN", "1");
    @cInclude("Python.h");
});

const PyObject = c.PyObject;

const PyModuleDef_Base = extern struct {
    ob_base: PyObject,
    // m_init: ?fn () callconv(.C) [*c]PyObject = null,
    m_init: ?*const fn () callconv(.C) [*c]PyObject = null,
    m_index: c.Py_ssize_t = 0,
    m_copy: [*c]PyObject = null,
};

const PyModuleDef_HEAD_INIT = PyModuleDef_Base {
    .ob_base = PyObject {
        .ob_refcnt = 1,
        .ob_type = null,
    }
};

const PyMethodDef = extern struct {
    ml_name: [*c]const u8 = null,
    ml_meth: c.PyCFunction = null,
    ml_flags: c_int = 0,
    ml_doc: [*c]const u8 = null,
};

const PyModuleDef = extern struct {
    // m_base: c.PyModuleDef_Base,
    m_base: PyModuleDef_Base = PyModuleDef_HEAD_INIT,
    m_name: [*c]const u8,
    m_doc: [*c]const u8 = null,
    m_size: c.Py_ssize_t = -1,
    m_methods: [*]PyMethodDef,
    m_slots: [*c]c.struct_PyModuleDef_Slot = null,
    m_traverse: c.traverseproc = null,
    m_clear: c.inquiry = null,
    m_free: c.freefunc = null,
};

/////////////////////////////////////////////////

pub export fn sum(self: [*]PyObject, args: [*]PyObject) [*c]PyObject {
    var a: c_long = undefined;
    var b: c_long = undefined;
    _ = self;
    if (!(c._PyArg_ParseTuple_SizeT(args, "ll", &a, &b) != 0)) return null;
    return c.PyLong_FromLong((a + b));
}

pub var methods = [_:PyMethodDef{}]PyMethodDef{
    PyMethodDef{
        .ml_name = "sum",
        .ml_meth = @ptrCast(c.PyCFunction, @alignCast(@import("std").meta.alignment(c.PyCFunction), &sum)),
        .ml_flags = @as(c_int, 1),
        .ml_doc = null,
    },
};

pub var zigmodule = PyModuleDef{
    .m_name = "zig_sum",
    .m_methods = &methods,
};

pub export fn PyInit_zig_sum() [*c]c.PyObject {
    return c.PyModule_Create(@ptrCast([*c]c.struct_PyModuleDef, &zigmodule));
}

macOS

Running zig build-lib on macOS will result in a .dylib file that cannot be loaded by Python. Instead setuptools-zig will run zig build-obj on the individual source files (as combining .c and .zig files results in an error) and then combines the .o files using clang -bundle generating a loadable .so file.

cleanup

Running zig build-obj sum.zig in Zig 0.10.0 generates both sum.o and sum.o.o. This extension tries to clean up those extra files.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

setuptools_zig-0.2.0.tar.gz (6.5 kB view details)

Uploaded Source

Built Distribution

setuptools_zig-0.2.0-py3-none-any.whl (6.9 kB view details)

Uploaded Python 3

File details

Details for the file setuptools_zig-0.2.0.tar.gz.

File metadata

  • Download URL: setuptools_zig-0.2.0.tar.gz
  • Upload date:
  • Size: 6.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.1 CPython/3.10.5

File hashes

Hashes for setuptools_zig-0.2.0.tar.gz
Algorithm Hash digest
SHA256 f248ba066bc66885a8035e1c1713f74f9a28735a7916d643f0da95071fa45322
MD5 853c0d147133798be7638c9a11773477
BLAKE2b-256 24837abe110441f7bb46312f94d5d1d7ee5d6ec9273052aad8bda8a369fc876a

See more details on using hashes here.

File details

Details for the file setuptools_zig-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for setuptools_zig-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 bba60f68a70474ccbd3d51d5386f26e48b165758ae972bf0117d0fe373a0ac6f
MD5 a9308a10b066e51b035ab7995c32f9ba
BLAKE2b-256 0b0afca1d1b0a480b5f5897b8dc655840c675efcc4fb919f0f1e27d16e9f3f3f

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page