Examples¶
Each example below corresponds to a runnable directory in the repository.
Clone the repo and pip install the example directly to try it:
git clone https://github.com/just-buildit/just-buildit
cd just-buildit
pip install ./examples/make # zero-config
pip install ./examples/cmake
pip install ./examples/meson
pip install ./examples/mixed
pip install ./examples/nested
pip install ./examples/mingw # Windows/MSYS2 UCRT64 only
pip install ./examples/bazel # requires Bazel
Zero config¶
No build command needed — just-buildit discovers src/add/add.c and
compiles it automatically.
Requires: C compiler (cc, gcc, or clang) on $PATH
pyproject.toml
[build-system]
requires = ["just-buildit"]
build-backend = "just_buildit"
[project]
name = "add"
version = "0.1.0"
description = "just-buildit zero-config example — no Makefile, no command."
requires-python = ">=3.8"
[tool.just-buildit]
repair = false
# Zero-config means no build command — just-buildit finds src/add/add.c
# and compiles it automatically. Repair is a separate distribution concern.
src/add/add.c
/*
* add.c — zero-config just-buildit example.
* No Makefile. No command. Just source.
*
* Exports: add.add(a, b) -> int
*/
#define PY_SSIZE_T_CLEAN
#include <Python.h>
static PyObject *
add_add(PyObject *self, PyObject *args)
{
long a, b;
if (!PyArg_ParseTuple(args, "ll", &a, &b))
return NULL;
return PyLong_FromLong(a + b);
}
static PyMethodDef AddMethods[] = {
{"add", add_add, METH_VARARGS, "Add two integers."},
{NULL, NULL, 0, NULL}
};
static struct PyModuleDef addmodule = {
PyModuleDef_HEAD_INIT, "add", NULL, -1, AddMethods
};
PyMODINIT_FUNC
PyInit_add(void) { return PyModule_Create(&addmodule); }
CMake¶
Requires: cmake, make, C compiler
pyproject.toml
[build-system]
requires = ["just-buildit"]
build-backend = "just_buildit"
[project]
name = "add"
version = "0.1.0"
description = "just-buildit CMake example."
requires-python = ">=3.8"
[tool.just-buildit]
command = "make pyext"
repair = false
Makefile
pyext:
cmake -S . -B _build -DCMAKE_BUILD_TYPE=Release
cmake --build _build
find _build -name "*$(JUST_BUILDIT_EXT_SUFFIX)" \
-exec cp {} $(JUST_BUILDIT_OUTPUT_DIR)/ \;
clean:
rm -rf _build
CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(add C)
find_package(Python3 COMPONENTS Development.Module REQUIRED)
Python3_add_library(add MODULE src/add.c)
set_target_properties(add PROPERTIES
OUTPUT_NAME "$ENV{JUST_BUILDIT_NAME}"
SUFFIX "$ENV{JUST_BUILDIT_EXT_SUFFIX}"
PREFIX "")
src/add.c
/*
* add.c — just-buildit CMake example.
*
* Exports: add.add(a, b) -> int
*/
#define PY_SSIZE_T_CLEAN
#include <Python.h>
static PyObject *
add_add(PyObject *self, PyObject *args)
{
long a, b;
if (!PyArg_ParseTuple(args, "ll", &a, &b))
return NULL;
return PyLong_FromLong(a + b);
}
static PyMethodDef AddMethods[] = {
{"add", add_add, METH_VARARGS, "Add two integers."},
{NULL, NULL, 0, NULL}
};
static struct PyModuleDef addmodule = {
PyModuleDef_HEAD_INIT, "add", NULL, -1, AddMethods
};
PyMODINIT_FUNC
PyInit_add(void) { return PyModule_Create(&addmodule); }
Meson¶
Requires: meson, ninja, make, C compiler
pyproject.toml
[build-system]
requires = ["just-buildit"]
build-backend = "just_buildit"
[project]
name = "add"
version = "0.1.0"
description = "just-buildit Meson example."
requires-python = ">=3.8"
[tool.just-buildit]
command = "make pyext"
repair = false
Makefile
pyext:
meson setup _build --wipe -Dbuildtype=release
meson compile -C _build
find _build -name "*$(JUST_BUILDIT_EXT_SUFFIX)" \
-exec cp {} $(JUST_BUILDIT_OUTPUT_DIR)/ \;
clean:
rm -rf _build
meson.build
project('add', 'c')
py = import('python').find_installation()
py.extension_module('add', 'src/add.c', install: false, build_by_default: true)
src/add.c — same as the CMake example above.
Mixed pure Python + C extension¶
A package (calc) where __init__.py is pure Python and _core is a C
extension. Both ship in the same wheel from a single $JUST_BUILDIT_OUTPUT_DIR.
Requires: make, C compiler
Layout
pyproject.toml
[build-system]
requires = ["just-buildit"]
build-backend = "just_buildit"
[project]
name = "calc"
version = "0.1.0"
description = "just-buildit mixed pure/platform example."
requires-python = ">=3.8"
[tool.just-buildit]
command = "make"
repair = false
Makefile
EXT := $(JUST_BUILDIT_OUTPUT_DIR)/calc/_core$(JUST_BUILDIT_EXT_SUFFIX)
all: $(EXT)
cp src/calc/__init__.py $(JUST_BUILDIT_OUTPUT_DIR)/calc/
$(EXT):
mkdir -p $(JUST_BUILDIT_OUTPUT_DIR)/calc
$(CC) $(JUST_BUILDIT_LDFLAGS) \
-I$(JUST_BUILDIT_INCLUDE_DIR) \
src/calc/_core.c \
-o $(EXT) \
$(JUST_BUILDIT_LIBS)
clean:
rm -f src/calc/_core*.so src/calc/_core*.pyd
src/calc/__init__.py
from calc._core import add, multiply
def add_and_multiply(a: int, b: int) -> tuple[int, int]:
"""Return (a+b, a*b)."""
return add(a, b), multiply(a, b)
src/calc/_core.c
/*
* _core.c — just-buildit mixed pure/platform example.
*
* Exports: calc._core.add(a, b) -> int
* calc._core.multiply(a, b) -> int
*/
#define PY_SSIZE_T_CLEAN
#include <Python.h>
static PyObject *
core_add(PyObject *self, PyObject *args)
{
long a, b;
if (!PyArg_ParseTuple(args, "ll", &a, &b))
return NULL;
return PyLong_FromLong(a + b);
}
static PyObject *
core_multiply(PyObject *self, PyObject *args)
{
long a, b;
if (!PyArg_ParseTuple(args, "ll", &a, &b))
return NULL;
return PyLong_FromLong(a * b);
}
static PyMethodDef CoreMethods[] = {
{"add", core_add, METH_VARARGS, "Add two integers."},
{"multiply", core_multiply, METH_VARARGS, "Multiply two integers."},
{NULL, NULL, 0, NULL}
};
static struct PyModuleDef coremodule = {
PyModuleDef_HEAD_INIT, "_core", NULL, -1, CoreMethods
};
PyMODINIT_FUNC
PyInit__core(void) { return PyModule_Create(&coremodule); }
Nested packages with multiple extensions¶
A package (imagelib) with C extensions nested at multiple directory levels.
The Makefile mirrors the source tree under $JUST_BUILDIT_OUTPUT_DIR —
just-buildit packages the directory verbatim.
Requires: make, C compiler
pip install ./examples/nested
python -c "from imagelib import blur, encode; print(blur(100), encode(10))"
Layout
pyproject.toml
[build-system]
requires = ["just-buildit"]
build-backend = "just_buildit"
[project]
name = "imagelib"
version = "0.1.0"
description = "just-buildit nested recursive package example."
requires-python = ">=3.8"
[tool.just-buildit]
command = "make"
repair = false
Makefile
OUT := $(JUST_BUILDIT_OUTPUT_DIR)
EXT := $(JUST_BUILDIT_EXT_SUFFIX)
CC_EXT = $(CC) $(JUST_BUILDIT_LDFLAGS) -I$(JUST_BUILDIT_INCLUDE_DIR) $< -o $@ $(JUST_BUILDIT_LIBS)
EXTS := \
$(OUT)/imagelib/filters/_blur$(EXT) \
$(OUT)/imagelib/codec/_encode$(EXT)
all: $(EXTS)
find src/imagelib -name "*.py" | while read f; do \
dest=$(OUT)/$${f#src/}; \
mkdir -p $$(dirname $$dest); \
cp $$f $$dest; \
done
$(OUT)/imagelib/filters/_blur$(EXT): src/imagelib/filters/_blur.c | $(OUT)/imagelib/filters
$(CC_EXT)
$(OUT)/imagelib/codec/_encode$(EXT): src/imagelib/codec/_encode.c | $(OUT)/imagelib/codec
$(CC_EXT)
$(OUT)/imagelib/filters $(OUT)/imagelib/codec:
mkdir -p $@
clean:
rm -f src/imagelib/filters/_blur*.so src/imagelib/filters/_blur*.pyd
rm -f src/imagelib/codec/_encode*.so src/imagelib/codec/_encode*.pyd
MinGW UCRT64 (Windows)¶
An explicit Makefile build in the MSYS2 UCRT64 environment. The key
difference from Linux: JUST_BUILDIT_LIBS is non-empty on Windows/MinGW
(-L<libdir> -lpython3.X) and must appear after -o in the linker
invocation. On Linux and macOS it is empty and the rule is a no-op either way,
so this Makefile layout is portable.
Prerequisites (MSYS2 UCRT64 shell):
Try it (MSYS2 UCRT64 shell):
pyproject.toml
[build-system]
requires = ["just-buildit"]
build-backend = "just_buildit"
[project]
name = "add"
version = "0.1.0"
description = "just-buildit MinGW UCRT64 example."
requires-python = ">=3.8"
[tool.just-buildit]
command = "make"
repair = false
Makefile
TARGET := $(JUST_BUILDIT_OUTPUT_DIR)/add$(JUST_BUILDIT_EXT_SUFFIX)
all: $(TARGET)
# On Windows/MinGW, JUST_BUILDIT_LIBS = "-L<libdir> -lpython3.X" and must
# come after -o. On Linux/macOS it is empty and the rule is a no-op either way.
$(TARGET):
$(CC) $(JUST_BUILDIT_LDFLAGS) \
-I$(JUST_BUILDIT_INCLUDE_DIR) \
src/add/add.c \
-o $(TARGET) \
$(JUST_BUILDIT_LIBS)
clean:
rm -f src/add/add*.pyd src/add/add*.so
src/add/add.c — same as the zero-config example above.
Wheel repair on Windows
For distribution, set repair = true (or omit it) and install delvewheel:
just-buildit auto-detects uvx delvewheel repair on Windows and bundles
the required DLLs into the wheel.
Bazel¶
A build_ext.py script bridges just-buildit and Bazel, forwarding
environment variables into the build via --action_env.
Requires: Bazel, C compiler
pyproject.toml
[build-system]
requires = ["just-buildit"]
build-backend = "just_buildit"
[project]
name = "greeter"
version = "0.1.0"
description = "just-buildit Bazel example."
requires-python = ">=3.8"
[tool.just-buildit]
command = "python build_ext.py"
repair = false
build_ext.py
import os
import shutil
import subprocess
from pathlib import Path
out = Path(os.environ["JUST_BUILDIT_OUTPUT_DIR"])
name = os.environ["JUST_BUILDIT_NAME"]
ext_suffix = os.environ["JUST_BUILDIT_EXT_SUFFIX"]
cc = shutil.which("cc") or shutil.which("gcc") or shutil.which("clang") or "cc"
subprocess.run(
[
"bazel", "build", "//:greeter_so",
f"--action_env=CC={cc}",
f"--action_env=JUST_BUILDIT_INCLUDE_DIR={os.environ['JUST_BUILDIT_INCLUDE_DIR']}",
f"--action_env=JUST_BUILDIT_LDFLAGS={os.environ['JUST_BUILDIT_LDFLAGS']}",
f"--action_env=JUST_BUILDIT_LIBS={os.environ.get('JUST_BUILDIT_LIBS', '')}",
],
check=True,
)
shutil.copy2("bazel-bin/greeter.so", out / (name + ext_suffix))
BUILD.bazel
genrule(
name = "greeter_so",
srcs = ["src/greeter.c"],
outs = ["greeter.so"],
cmd = """
$$CC $$JUST_BUILDIT_LDFLAGS \
-I$$JUST_BUILDIT_INCLUDE_DIR \
$(location src/greeter.c) \
-o $@ \
$$JUST_BUILDIT_LIBS
""",
)
src/greeter.c
/*
* greeter.c — just-buildit Bazel example.
*
* Exports: greeter.greet(name) -> str
*/
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <stdio.h>
static PyObject *
greeter_greet(PyObject *self, PyObject *args)
{
const char *name;
if (!PyArg_ParseTuple(args, "s", &name))
return NULL;
char buf[256];
snprintf(buf, sizeof(buf), "Hello, %s!", name);
return PyUnicode_FromString(buf);
}
static PyMethodDef GreeterMethods[] = {
{"greet", greeter_greet, METH_VARARGS, "Greet by name."},
{NULL, NULL, 0, NULL}
};
static struct PyModuleDef greetermodule = {
PyModuleDef_HEAD_INIT, "greeter", NULL, -1, GreeterMethods
};
PyMODINIT_FUNC
PyInit_greeter(void) { return PyModule_Create(&greetermodule); }
For pre-built .so files from a Bazel C++ rule (e.g. nanobind), the same
pattern applies: build_ext.py runs the Bazel target and copies the output
into $JUST_BUILDIT_OUTPUT_DIR, renaming with $JUST_BUILDIT_EXT_SUFFIX.
Scaffolding with just-makeit¶
No build system yet? just-makeit generates a complete CMake-based C extension project that wires up just-buildit out of the box — no configuration needed.
Requires: cmake ≥ 3.16, C99 compiler, NumPy
pip install just-makeit
just-makeit new my_dsp --object gain --state gain:double:1.0
cd my_dsp
pip install .
The generated layout:
my_dsp/
just-makeit.toml # project config — single source of truth
CMakeLists.txt
Makefile
pyproject.toml # just-buildit backend, command = "make just-build"
README.md
Doxyfile # Doxygen config (pre-configured)
zensical.toml # docs scaffolding
compile_commands.json # clangd / IDE support
cmake/
my-dsp.pc.in # pkg-config template
my_dsp-config.cmake.in # CMake find_package support
docs/
index.md
api.md
native/
inc/
clib_common.h # shared C types
pyex_common.h # Python extension helpers
my_dsp.h # umbrella header
gain/
gain_core.h # C API (state struct, create/destroy/step/reset)
src/
my_dsp_lib.c # library version stub
gain/
gain_core.c # algorithm ← implement step() here
gain_ext.c # Python binding ← do not edit
CMakeLists.txt
tests/
test_gain_core.c # CTest lifecycle test
benchmarks/
bench_gain_core.c # C timing benchmark
jm_bench.h # header-only bench stats library
benchmarks/
history/ # dated JSON snapshots (git-ignored locally)
src/my_dsp/
__init__.py
gain.pyi # type stub (regenerated on every mutation)
tests/
__init__.py
test_gain.py # pytest suite
benchmarks/
__init__.py
bench_gain.py # Python throughput benchmark
The generated pyproject.toml uses just-buildit with no extra work:
[build-system]
requires = ["just-buildit", "numpy"]
build-backend = "just_buildit"
[tool.just-buildit]
command = "make just-build"
Add a new object at any time with just-makeit object <name>. Add state
variables to an existing object with just-makeit add --state
name:type[:default]. just-buildit remains agnostic to how many objects
exist — it packages whatever make just-build writes to
$JUST_BUILDIT_OUTPUT_DIR.
Use just-makeit apply to materialise a project from a TOML manifest
alone — useful for CI templates and composing projects declaratively. See
the just-makeit docs for
the full reference.