Generate Mojo FFI bindings from C headers using libclang
Project description
mojo-bindgen
[!WARNING] Alpha stage: this project is under heavy development and may change quickly.
C headers -> Mojo FFI. mojo-bindgen parses real C with
libclang, and emits Mojo bindings for external_call or
owned_dl_handle workflows. this mirrors the spirit of rust-bindgen which follows the same approch for Rust
The goal is simple: make binding generation easy and faithful as possible to the actual C surface, and fail conservatively when a declaration cannot be modeled correctly.
Requirements
- Python 3.14+
- a system
libclangcompatible with thelibclangPython wheel - a Mojo (nightly) toolchain if you want to build or run the generated bindings
Installation
System dependencies
Install Clang and the shared libclang library first:
# Ubuntu / Debian
sudo apt update && sudo apt install -y clang libclang1
# Fedora
sudo dnf install -y clang llvm-libs
# macOS (Homebrew)
brew install llvm
If the shared library is not on the default loader path, set LIBCLANG_PATH
to the directory containing libclang.so or libclang.dylib.
Install from PyPI
pip install mojo-bindgen
PyPI package: mojo-bindgen
Install from source
git clone https://github.com/MoSafi2/mojo_bindgen
cd mojo_bindgen
pip install -e .
For development setup, checks, and Pixi workflows, see CONTRIBUTING.md.
Quick start
Generate bindings from a primary header:
mojo-bindgen path/to/header.h -o bindings.mojo --linking external_call|owned_dl_handle
Pass include paths and other Clang flags with repeated --compile-arg:
mojo-bindgen include/mylib.h \
--compile-arg=-I./include \
--compile-arg=-DMYLIB_FEATURE=1 \
-o mylib_bindings.mojo
By default the parser uses -std=gnu11 when no C standard is provided. Pin a
standard explicitly if your header requires one:
mojo-bindgen include/mylib.h --compile-arg=-std=c99 -o mylib_bindings.mojo
Linking modes
mojo-bindgen supports two output styles:
external_callDirect FFI wrappers. Use this when the target library is linked at Mojo build time.owned_dl_handleDynamic runtime symbol lookup viaOwnedDLHandlefor loading a shared library (.so,.dylib).
Examples:
# default
mojo-bindgen include/mylib.h --linking external_call -o mylib_bindings.mojo
# runtime-loaded shared library
mojo-bindgen include/mylib.h \
--linking owned_dl_handle \
--library-path-hint /usr/lib/libmylib.so \
-o mylib_bindings_dl.mojo
What works today?
mojo-bindgen is still alpha and evolves quickly, but it already supports a
useful slice of real C headers and is practical today as a starting point for
generating bindings.
Current support includes:
- Parsing and lowering: real C parsing through
libclang, repeatable--compile-argsupport, and a structured IR pipeline rather than text-only generation. - Primitive types: scalar types, typedef chains, pointers with const-aware mutability, fixed arrays, incomplete-array decay cases, complex values, vector extension types, and representable atomics.
- Mojo-native numeric lowering: vector types lower to
SIMD[...], complex values lower toComplexSIMD[...], and representable atomics lower toAtomic[...]. - Records: structs, anonymous members, mixed layouts that combine plain fields and bitfields, synthesized padding, and custom alignment emission where Mojo can represent the layout faithfully.
- Bitfields: bitfields are emitted through explicit storage fields plus synthesized getter and setter methods.
- Unions: eligible unions lower to
UnsafeUnion[...]; unions that cannot be represented safely fall back to opaqueInlineArray[...]storage with diagnostics to preserve layout. - Opaque and difficult layouts: incomplete records, packed layouts, and alignment-sensitive record shapes are preserved conservatively as opaque byte storage when a faithful typed layout is not possible.
- Callbacks and function pointers: callback typedefs, function-pointer
fields, and function-pointer parameters and returns are preserved in Mojo via
emitted
comptimecallback declarations and synthesized aliases when needed. - Functions: thin wrappers are generated for non-variadic functions under
both
external_callandowned_dl_handlelink modes. - Globals and constants: because Mojo does not currently expose native C
globals directly, supported globals lower through generated
GlobalVar/GlobalConsthelper structs with synthesizedload()/store()methods; constants and supported object-like macros lower tocomptimedeclarations. - Macros: integer, float, string, and char literal macros, foldable macro
chains, supported casts, and
sizeof(type)expressions are emitted as Mojo code. - JSON IR output: the CLI can emit serialized parser IR for debugging, testing, or downstream tooling.
Current limitations
Known gaps you may still hit in generated code. For ABI-sensitive surfaces, verify emitted layouts and symbols against your target toolchain.
- Macros: function-like macros, predefined macros, and more complex preprocessor behavior are preserved but usually emitted as comments for end-user review.
- Variadics: variadic C functions are not wrapped as callable thin-FFI bindings yet and are emitted as comment stubs.
- Non-prototype / K&R-style functions: older C declaration styles are only partially modeled and should be treated with caution.
- Records with hostile layouts: some packed, ABI-sensitive, or otherwise difficult record layouts cannot be emitted as fully typed Mojo structs and fall back to opaque storage; layout-sensitive declarations may still require manual verification.
- Anonymous members: anonymous struct and union members are preserved structurally, but they are not automatically promoted into a flattened parent record surface.
- Atomics: atomic support is conservative. Representable atomic fields and pointer-based usage work, but atomic globals are still emitted as stubs and some surfaces require manual handling.
- Linkage and compiler edge cases:
inline, compiler-specific linkage hints, and other extension-heavy cases can still require manual review and may lead to symbol mismatches at runtime. - Primary-header model: declarations are emitted from the primary header you pass to the tool. Thin wrapper headers that only include another header can produce unexpectedly small output if the real declarations belong to the included file instead.
Real-world examples
The repository includes worked examples and smoke programs for:
- SQLite: examples/sqlite
- Cairo: examples/cairo
- libpng: examples/libpng
- zlib: examples/zlib
These examples do more than generate bindings: their generate.sh scripts also
build and run small functional tests to check the usability of the generated bindings.
The test suite also has end-to-end runtime coverage for:
- by-value records and enums
- callbacks and function-pointer returns
- globals and constants
- vectors and complex values
- atomic pointer-based APIs
- opaque forward declarations
- pointer-to-array and array-decay cases
- both
external_callandowned_dl_handlelink modes
See tests/e2e/README.md for the current runtime case matrix.
Troubleshooting
The generated module is empty or missing declarations
mojo-bindgen emits declarations from the primary header you pass in. If you
point it at a thin wrapper that only includes another header, Clang may
attribute declarations to the included header instead of the wrapper. In that
case, pass the real header directly.
Parsing fails on project headers
Most parser failures are missing include paths, target flags, or defines. Add
the same flags your C build uses via repeated --compile-arg.
Build succeeds but symbols are missing at runtime
Double-check:
--libraryand--link-name- your Mojo link flags for
external_call - your
--library-path-hintforowned_dl_handle - whether the original C declaration involved tricky
inlineor exotic layout that needs manual review.
License
Licensed under the MIT License. See LICENSE.
Contributing: CONTRIBUTING.md.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mojo_bindgen-0.1.1a0.tar.gz.
File metadata
- Download URL: mojo_bindgen-0.1.1a0.tar.gz
- Upload date:
- Size: 68.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
387e15fd7c0bda396efd7cabf9e80050af0bc425ffbb713e03ce808522411f2a
|
|
| MD5 |
b3428212413817c6e7a931b797f2c40f
|
|
| BLAKE2b-256 |
ee6fa971e4f9dae0eb43702d32d80bf3bc53cd2663101c7bb4aa35a03870ac44
|
Provenance
The following attestation bundles were made for mojo_bindgen-0.1.1a0.tar.gz:
Publisher:
release.yml on MoSafi2/mojo_bindgen
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mojo_bindgen-0.1.1a0.tar.gz -
Subject digest:
387e15fd7c0bda396efd7cabf9e80050af0bc425ffbb713e03ce808522411f2a - Sigstore transparency entry: 1368351019
- Sigstore integration time:
-
Permalink:
MoSafi2/mojo_bindgen@ccd1a789a8a4573005f124aa21431256594225fe -
Branch / Tag:
refs/tags/v0.1.1a - Owner: https://github.com/MoSafi2
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ccd1a789a8a4573005f124aa21431256594225fe -
Trigger Event:
push
-
Statement type:
File details
Details for the file mojo_bindgen-0.1.1a0-py3-none-any.whl.
File metadata
- Download URL: mojo_bindgen-0.1.1a0-py3-none-any.whl
- Upload date:
- Size: 89.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
55b046555bbade39914666987dceb90b03453942831116594cc3bd4b5fccb6e9
|
|
| MD5 |
947f9b76884c5b626b01886bb4391241
|
|
| BLAKE2b-256 |
089c8681558ff8a9892f937b75db046944c617fb78f7ed115abaef41f0d1029f
|
Provenance
The following attestation bundles were made for mojo_bindgen-0.1.1a0-py3-none-any.whl:
Publisher:
release.yml on MoSafi2/mojo_bindgen
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mojo_bindgen-0.1.1a0-py3-none-any.whl -
Subject digest:
55b046555bbade39914666987dceb90b03453942831116594cc3bd4b5fccb6e9 - Sigstore transparency entry: 1368351033
- Sigstore integration time:
-
Permalink:
MoSafi2/mojo_bindgen@ccd1a789a8a4573005f124aa21431256594225fe -
Branch / Tag:
refs/tags/v0.1.1a - Owner: https://github.com/MoSafi2
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ccd1a789a8a4573005f124aa21431256594225fe -
Trigger Event:
push
-
Statement type: