Need sturdy, upgradeable bindings? We got you. The universal interop & bindings toolkit: C/C++ in → Python, Mojo, Nim, LuaJIT, & C shims out. Scaffolds packages, not just files. Easily extended for any input language or output project type. For humans and LLMs.
HeaderKit parses native C and C++ headers into a normalized intermediate representation (IR). From that single IR, it generates foreign language bindings, scaffolds turnkey packages with verification tests, tracks breaking API diffs, and compresses headers for LLM prompt windows.
| Goal | Quick Command / Action | Section |
|---|---|---|
| Generate Python bindings (ctypes, CFFI, Cython) | headerkit mylib.h -w ctypes -o ctypes:bindings.py |
Python Bindings |
| Generate Mojo, Nim, or LuaJIT bindings | headerkit mylib.h -w mojo -o mojo:mylib.mojo |
Systems & Scripting |
Wrap C++ classes in a C-ABI shim (extern "C") |
headerkit mylib.hpp -w cshim -o cshim:mylib_cshim.cpp |
C Shim Wrappers |
| Parse C/C++ without LLVM/libclang | headerkit mylib.h -b tree-sitter -w ctypes |
Zero-Dependency Parsing |
| Scaffold a turnkey package with tests | headerkit mylib.h -w nim --layout package --package-name mypkg |
Package Scaffolding |
| Detect breaking API changes between versions | DiffWriter(baseline=v1, format="markdown").write(v2) |
API Diffing |
| Compress headers for LLM prompt windows | headerkit mylib.h -w prompt |
LLM Context |
| Ship wheels without requiring libclang on user machines | headerkit cache populate mylib.h -w cffi |
Build Backend & Cache |
| Inspect or transform IR programmatically | headerkit mylib.h -w json -o json:ast.json |
Programmatic IR |
Traditional binding generators (SWIG, bindgen, ctypesgen) dump raw, isolated binding code into the void. Integrating those files into a project requires handwritten Makefiles, setup scripts, package manifests, and linking glue. Keeping bindings updated as upstream libraries evolve is an error-prone chore.
HeaderKit solves this with three core design pillars:
- Turnkey Packages vs. Raw Code Dumps: HeaderKit's
--layout packagedoesn't just emit binding syntax; it scaffolds complete, idiomatic packages (e.g. Nimble packages with--mm:orc, Mojo packages with dynamic library handles, Python packages with build backends) along with automated tests that immediately verify foreign symbol resolution. - Easily Extended to Any Source or Target Language: Built on a unified hook engine (
headerkit.hooks). Write a custom backend or target writer in ~50 lines of Python using typed [SourceUnit][headerkit.ir.SourceUnit] IR, register it via standard Python entry points or config files, or customize layout scaffolding templates without touching core code. - Automated Upstream Updates & Zero-Dependency Downstream: Keep bindings in lockstep with upstream releases. HeaderKit's PEP 517 build backend and committed cache store (
.headerkit/) let downstream consumerspip installwheels without needinglibclangor system compilers installed.
Every example below assumes this sample C header:
// mylib.h
typedef struct { int x, y; } Point;
int distance(const Point *a, const Point *b);Generate zero-build ctypes modules, CFFI cdef declarations, or compiled Cython .pxd headers:
# Drop-in Python ctypes module (zero build step)
headerkit mylib.h -w ctypes -o ctypes:bindings.py
# CFFI declarations for ffibuilder.cdef()
headerkit mylib.h -w cffi -o cffi:_defs.cdef.txt
# Cython .pxd declaration file for compiled C/C++ interop
headerkit mylib.h -w cython -o cython:mylib.pxdSee the ctypes Reference, CFFI Guide, and Cython Reference.
Bridge C and C++ libraries into modern systems and scripting runtimes:
# Mojo module with DLHandle dynamic symbol loading
headerkit mylib.h -w mojo -o mojo:mylib.mojo
# Native Nim module with {.importc.} pragma bindings
headerkit mylib.h -w nim -o nim:mylib.nim
# LuaJIT FFI bindings with ffi.cdef[[ ... ]]
headerkit mylib.h -w lua -o lua:mylib_ffi.luaSee the Mojo Reference, Nim Reference, and LuaJIT Reference.
Directly binding complex C++ classes across foreign function interfaces is fragile due to mangled symbols and exception boundaries. The cshim writer automatically generates an extern "C" wrapper library with opaque handles and exception guards:
headerkit mylib.hpp -w cshim -o cshim:mylib_cshim.cpp// generated mylib_cshim.cpp
#include "mylib.hpp"
extern "C" {
typedef void* PointHandle;
PointHandle Point_create(int x, int y) { return new Point(x, y); }
void Point_destroy(PointHandle self) { delete static_cast<Point*>(self); }
}See the CShim Reference.
When system LLVM / libclang is not installed or in lightweight CI environments, HeaderKit can parse C and C++ headers directly using precompiled Tree-sitter grammars (tree-sitter-c and tree-sitter-cpp):
# Parse C header using tree-sitter backend
headerkit mylib.h -b tree-sitter -w ctypes -o ctypes:bindings.py
# Parse C++ header using tree-sitter backend
headerkit mylib.hpp -b tree-sitter -w cython -o cython:mylib.pxdSee the Tree-sitter Backend Guide.
Generate complete, buildable multi-file packages containing package manifests, compiler configs, and automated test stubs that verify foreign symbol resolution:
# Scaffold a full Nimble package with tests
headerkit mylib.h -w nim --layout package --package-name nim_vector -o nim:./nim_vector
# Scaffold with specific test stub styles (tripwire, unit, or both)
headerkit mylib.h -w nim --layout package --package-name nim_vector --test-type tripwireGenerated project structure:
nim_vector/
├── nim_vector.nimble # Package manifest with test tasks
├── nim.cfg # Compiler configuration (--mm:orc, --threads:on)
├── src/
│ ├── nim_vector.nim # Public API module
│ └── nim_vector/
│ └── bindings.nim # Generated foreign function interface
└── tests/
├── test_tripwire.nim # Symbol resolution verification tests
└── test_nim_vector.nim # High-level unit test skeleton
See the Scaffolding Guide.
Compare two versions of a C/C++ header to detect signature mutations, missing struct fields, altered enum values, and type changes:
from headerkit.backends import get_backend
from headerkit.writers.diff import DiffWriter
backend = get_backend("libclang")
old_api = backend.parse('#include "mylib_v1.h"', "v1.h")
new_api = backend.parse('#include "mylib_v2.h"', "v2.h")
print(DiffWriter(baseline=old_api, format="markdown").write(new_api))See the Diff Writer Reference.
Large C/C++ headers waste tokens and clutter context windows with preprocessor noise and implementation details. The prompt writer produces a dense, token-optimized summary designed for LLM prompts:
headerkit mylib.h -w prompt// mylib.h (headerkit compact)
STRUCT Point {x:int, y:int}
FUNC distance(a:const Point*, b:const Point*) -> int
See the Prompt Writer Reference.
HeaderKit includes a two-layer cache (.headerkit/) storing parsed IR and generated bindings. Commit the cache to version control and downstream users can install wheels without having libclang or LLVM installed:
# Populate cache across multiple architecture targets
headerkit cache populate mylib.h -w cffi --platform linux/amd64 --platform linux/arm64
git add .headerkit/ && git commit -m "cache: populate bindings"In your project's pyproject.toml, declare the PEP 517 build backend to automatically regenerate bindings during pip install:
[build-system]
requires = ["headerkit", "hatchling"]
build-backend = "headerkit.build_backend"See the Cache Guide and Build Backend Guide.
Parse headers directly into a strongly typed Python AST or serialize them to JSON for downstream code generators, linters, or analysis tools:
from headerkit import generate
from headerkit.backends import get_backend
# Serialized JSON IR
json_ir = generate("mylib.h", "json")
# Typed Python AST
backend = get_backend("libclang")
unit = backend.parse('#include "mylib.h"', "mylib.h")
for decl in unit.declarations:
print(decl.name, type(decl))See the IR Reference and JSON Reference.
pip install headerkitRequires Python 3.10+.
To install the optional libclang parser backend (if not already present on your system):
headerkit install-libclangOr install it via your system package manager:
| Platform | Command |
|---|---|
| macOS | brew install llvm or Xcode Command Line Tools |
| Ubuntu / Debian | sudo apt install libclang-dev |
| Fedora / RHEL | sudo dnf install clang-devel |
| Windows | winget install LLVM.LLVM or LLVM releases |
HeaderKit vendors LLVM bindings supporting libclang 18, 19, 20, 21, 22, and 23.
headerkit [options] HEADER_OR_GLOB [HEADER_OR_GLOB ...]
| Flag | Description |
|---|---|
-b NAME, --backend NAME |
Parser backend (default: libclang, or tree-sitter) |
-w WRITER, --writer WRITER |
Output writer to invoke (repeatable) |
-o WRITER:PATH, --output |
Output destination template (repeatable, e.g. ctypes:bindings.py) |
--layout {file,package,project} |
Output layout mode (file or package) |
--package-name NAME |
Package name when scaffolding package layouts |
--test-type {both,tripwire,unit,none} |
Test stub style to scaffold (default: both) |
-I DIR, --include-dir DIR |
Add include directory (repeatable) |
-D MACRO[=VALUE] |
Define preprocessor macro (repeatable) |
--backend-arg ARG |
Pass extra argument to the parser backend |
--writer-opt WRITER:KEY=VALUE |
Pass writer-specific options (repeatable) |
--store-dir DIR |
Cache store directory (default: .headerkit/) |
--target TRIPLE |
Target triple for cross-compilation (e.g. aarch64-apple-darwin) |
--no-cache |
Disable all cache lookups |
--config PATH |
Path to explicit .headerkit.toml config file |
--no-config |
Skip loading configuration files |
--version |
Display version and exit |
HeaderKit automatically reads configuration from .headerkit.toml or the [tool.headerkit] table in pyproject.toml:
# .headerkit.toml
backend = "libclang"
writers = ["ctypes", "cffi"]
include_dirs = ["/usr/local/include"]
[writer.ctypes]
lib_name = "mylib"
[writer.cffi]
exclude_patterns = ["^__", "^_internal"]Values support ${ENV_VAR} expansion for build-time paths injected by CMake or CI systems.
Scaffolding a project also generates its tests. A generator knows what an API is, but not what it is for, so HeaderKit splits generated tests into three tiers and is explicit about which is which.
| Tier | What HeaderKit knows | What it emits |
|---|---|---|
| 1 | The call and the expected result | A real, passing test -- struct field round-trip, unsigned bit-field bounds, enum value coverage |
| 2 | The cases, but not the expectation | One failing case per enumerator, per overload, or a NULL case |
| 3 | Only that the function exists | One failing stub |
Stubs fail loudly and carry their instruction on the failing assertion, together with the signature verbatim and a definition of done:
FAILED tests/test_workorder.py::test_ct_scale[null_s] - Failed: WORK ORDER: for the
`behaviour` case, describe what `ct_scale` is for and assert it. For the `null_s`
case, decide what it does when `s` is NULL -- an error, or undefined and therefore
untestable? Done means: call it and assert on the result. Asserting that it does not
raise is insufficient.
The test run is the progress meter -- a stub turns from red to green when it is
written. Scaffolded projects also get WORK_ORDER.md (the same list in prose),
SUGGESTIONS.md (generic wrapper design ideas), and an AGENTS.md telling the next
AI session to ask you before working through any of it.
Re-running the scaffolder never overwrites a test you have written.
See Test work orders for the full tier rules, the deliberate exclusions, and the Nim macro that makes per-case reporting work.
HeaderKit's hook architecture makes it straightforward to add new source languages, target writers, or custom package layouts without modifying the core repository.
Register third-party plugins in your own package via pyproject.toml entry points:
[project.entry-points."headerkit.backends"]
mybackend = "mypkg.backend:MyBackend"
[project.entry-points."headerkit.writers"]
mywriter = "mypkg.writer:MyWriter"Or write a custom writer in Python:
from headerkit.ir import SourceUnit
from headerkit.writers import BaseWriter, register_writer
class RubyFfiWriter(BaseWriter):
@property
def name(self) -> str:
return "ruby"
def write(self, unit: SourceUnit) -> str:
lines = ["require 'ffi'", "module MyLib", " extend FFI::Library"]
# Iterate over unit.declarations...
return "\n".join(lines)
register_writer("ruby", RubyFfiWriter)See the Architecture Guide and Custom Writers Guide.
- Origin Library Versioning & ABI Multi-Version Metadata: Track signatures across multiple upstream release versions to generate version-guarded symbols and automated migration shims.
- Bi-directional Bridges: Generate C/C++ header interfaces and C export shims from high-level Python and Mojo source definitions.
- Grammar-Based Polyglot Source Extraction: Extract C-ABI interfaces from Rust, Zig, and Nim source code into normalized IR using formal Tree-sitter grammars (
tree-sitter-rust,tree-sitter-zig,tree-sitter-nim). - Expanded Target Writers: Additional turnkey writers for Rust (
bindgen-free FFI), Go (cgo), and Swift.
git clone https://github.com/axiomantic/headerkit.git
cd headerkit
pip install -e '.[dev]'
pytestHeaderKit is open source licensed under the MIT License.
Vendored LLVM clang Python bindings in headerkit/_clang/ are licensed under the Apache License v2.0 with LLVM Exceptions.