Skip to content

Repository files navigation

headerkit

CI Docs PyPI Python License

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.


What are you looking to do?

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

Why HeaderKit?

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:

  1. Turnkey Packages vs. Raw Code Dumps: HeaderKit's --layout package doesn'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.
  2. 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.
  3. 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 consumers pip install wheels without needing libclang or system compilers installed.

Quick Solutions

Every example below assumes this sample C header:

// mylib.h
typedef struct { int x, y; } Point;
int distance(const Point *a, const Point *b);

1. Generate Python Bindings (ctypes, CFFI, Cython)

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.pxd

See the ctypes Reference, CFFI Guide, and Cython Reference.

2. Generate Systems & Scripting Bindings (Mojo, Nim, LuaJIT)

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.lua

See the Mojo Reference, Nim Reference, and LuaJIT Reference.

3. Wrap C++ Classes in a C-ABI Shim

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.

4. Zero-Dependency Parsing with Tree-Sitter

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.pxd

See the Tree-sitter Backend Guide.

5. Scaffold Turnkey Packages with Tests

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 tripwire

Generated 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.

6. Detect Breaking API Changes

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.

7. Compress Headers for LLM Prompts

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.

8. Ship Wheels Without Requiring libclang

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.

9. Inspect IR Programmatically

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.


Installation

pip install headerkit

Requires Python 3.10+.

To install the optional libclang parser backend (if not already present on your system):

headerkit install-libclang

Or 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.


CLI Reference

headerkit [options] HEADER_OR_GLOB [HEADER_OR_GLOB ...]

Options

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

Configuration File

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.


Test Work Orders

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.

Extensibility & Plugins

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.


Roadmap

  • 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.

Development

git clone https://github.com/axiomantic/headerkit.git
cd headerkit
pip install -e '.[dev]'
pytest

License

HeaderKit 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.

About

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.

Topics

Resources

Code of conduct

Contributing

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages