Generate Mojo FFI bindings from C headers using libclang
Reason this release was yanked:
are marked as stable (stale)
Project description
mojo-bindgen
Generate Mojo FFI bindings from C headers using libclang (Python bindings). The tool parses a header, builds an internal IR, and emits a .mojo module with external_call or owned_dl_handle linking.
Requires Python 3.14+ (aligned with current Modular/Mojo Pixi stacks).
For maintainer-facing architecture notes, see docs/codegen-architecture.md. For contributing, security contact, and release notes, see CONTRIBUTING.md, SECURITY.md, and CHANGELOG.md.
Setup
1) System dependencies
Install clang and libclang first (required for header parsing):
# 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 libclang is installed in a non-standard location, set LIBCLANG_PATH to the directory containing libclang.so / libclang.dylib.
2) Install project dependencies
From the repository root (requires Pixi):
pixi install
pixi shell
The Pixi workspace is linux-64 only at the moment; on macOS or Windows use a virtual environment and pip install -e ".[dev]" instead (see CONTRIBUTING.md).
This installs the mojo-bindgen package in editable mode and puts the mojo-bindgen CLI on your PATH.
You also need a system libclang shared library compatible with the libclang Python wheel.
Library usage (Python API)
The CLI wraps the same pipeline you can call from Python: parse a header to IR, then generate Mojo source.
from pathlib import Path
from mojo_bindgen.codegen import MojoEmitOptions, generate_mojo
from mojo_bindgen.parsing.parser import ClangParser
header = Path("include/mylib.h")
unit = ClangParser(
header,
library="mylib",
link_name="mylib",
compile_args=["-I", "include"],
).run()
mojo_src = generate_mojo(unit, MojoEmitOptions(linking="external_call"))
Stable exports from the mojo_bindgen package root are MojoGenerator, MojoEmitOptions, generate_mojo, and __version__. Parsing types (ClangParser, ParseError, Unit) live in their modules and are treated as public for programmatic use; follow semver for the workflow above when upgrading.
CLI
Inspect all options and examples:
mojo-bindgen --help
Emit Mojo FFI (default):
mojo-bindgen path/to/header.h -o bindings.mojo
Dump IR as JSON:
mojo-bindgen path/to/header.h --json -o unit.json
Extra clang flags (include paths, sysroot, target):
mojo-bindgen include/me.h --compile-arg=-I./include --compile-arg=--sysroot=/path/to/sysroot -o out.mojo
Set a specific C language standard:
mojo-bindgen include/me.h --compile-arg=-std=c99 -o out.mojo
--compile-arg standard flags accept -std=..., --std=..., and std=... forms. If no standard is provided, -std=gnu11 is used by default.
By default, --library and --link-name are the header file stem (e.g. me for me.h).
--compile-arg is repeatable.
Use mojo-bindgen --help for full option details.
Features
- libclang parsing: Walks a primary C header with configurable compile flags (default language is
gnu11if you do not pass-std=...). Repeatable--compile-argpasses include paths, sysroot, target triple, and standard selection. - IR and tooling: Builds a structured IR, runs validation and a reachability materialization pass, and can dump JSON (
--json) for debugging or downstream tools. - Generated Mojo surface: Emits structs (with
@alignfrom C alignment when expressible in Mojo), unions (including@unsafe_unionwhen layout analysis marks them eligible), enums, typedefs, and thin wrappers for non-variadic functions usingexternal_callorOwnedDLHandle.calldepending on--linking. - Linking modes:
external_call(default) links C symbols at Mojo build time;owned_dl_handleresolves calls throughOwnedDLHandle, with optional--library-path-hintfor dlopen-style loading. - C globals: For globals whose types are thin-FFI-compatible, the emitter generates
GlobalVar/GlobalConsthelpers that load and store throughUnsafePointer, resolving symbols withOwnedDLHandle.get_symbolwhen needed (seeexamples/global_consts/). Atomics and layouts that cannot be lowered still become comment stubs with a short reason. - Macros and constants: Object-like macros and
constinitializers are handled when the body fits the supported token expression grammar (literals, identifiers, parentheses, unary-/~, and binary operators); integer subexpressions are constant-folded when both operands are literals. Unsupported forms are classified and often preserved as comments rather than silent guesses. - Python API extras:
MojoEmitOptionsalso exposes pointer provenance (ffi_origin:externalvsany), module header comments, and ABI reminder comments—tunable from code even though the CLI only exposes linking and library path hint today.
Limitations & Rough Edges
Parsing / IR:
- Macros and constant expressions: Function-like macros are not expanded into expressions.
sizeof, most casts, and other constructs outside the supported token grammar are skipped or left as diagnostics; unsupported object-like macros may be emitted as comments only. - Bitfields: Bitfields are modeled with width/offset metadata and emitted with layout comments and warnings to verify ABI against your C compiler; exotic packing or mixed backing-unit edge cases can still be wrong without cross-checking.
- Hard declaration shapes: A few difficult C declarator shapes and compiler-extension-heavy signatures may still require manual review, especially when mixed with unusual attributes or unsupported macro-driven spellings.
- Anonymous and extension-heavy constructs: Anonymous enums are emitted as top-level constants, and anonymous struct/union members are preserved as synthetic storage fields; some compiler-extension or unusual anonymous record cases still degrade to
UnsupportedTypeor require manual review. - C storage/linkage qualifiers: Type qualifiers on pointers are preserved, but C declaration-level linkage/storage semantics such as
inline/extern inlinestill are not modeled precisely enough to guarantee symbol availability.
Mojo lowering / runtime:
- Variadic functions: No thin callable wrapper is emitted; output is a comment noting that varargs are not modeled for FFI.
- Function pointers: Function-pointer types are preserved in IR and lowered as opaque pointer ABI values in Mojo. Returned/parameter fnptr values can be passed through thin FFI, but callable wrappers for invoking function-pointer values are not generated yet; semantic signature comments are emitted where applicable (notably struct fields).
- Globals: Wrappers are not emitted for atomic globals or for types that cannot be lowered to a concrete Mojo surface type (see stub comments in generated output).
- Non-
RegisterPassablestruct-by-value returns: Functions that return a struct by value when that struct is not considered register-passable in the analysis pass are emitted as comment stubs rather than callable wrappers. inline/ non-standard linkage: The generated bindings may still treat declarations as normal extern symbols even when C linkage rules are more subtle, which can produce symbol mismatches at runtime.
Development
pixi run pytest
Build distributable artifacts through Pixi tasks:
pixi run clean-dist
pixi run build
Optional one-shot targets:
pixi run build-wheelpixi run build-sdist
License
Licensed under the MIT License. See LICENSE.
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.0.tar.gz.
File metadata
- Download URL: mojo_bindgen-0.1.0.tar.gz
- Upload date:
- Size: 56.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
acda3d960ad4b5d83766480f679394d4daf25bc3cd5882f103dd9de85afa87a3
|
|
| MD5 |
92adc125575ccf9a68d01e5cadc8a6ba
|
|
| BLAKE2b-256 |
a75b3675ec211a36b661ac7b1f6f7af49b44cfa17d45aea64988dc9022ebae56
|
Provenance
The following attestation bundles were made for mojo_bindgen-0.1.0.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.0.tar.gz -
Subject digest:
acda3d960ad4b5d83766480f679394d4daf25bc3cd5882f103dd9de85afa87a3 - Sigstore transparency entry: 1340778579
- Sigstore integration time:
-
Permalink:
MoSafi2/mojo_bindgen@f8600cf58f98a2ed7a9b25a40f683a358323b5c1 -
Branch / Tag:
refs/tags/v0.1rc - Owner: https://github.com/MoSafi2
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@f8600cf58f98a2ed7a9b25a40f683a358323b5c1 -
Trigger Event:
push
-
Statement type:
File details
Details for the file mojo_bindgen-0.1.0-py3-none-any.whl.
File metadata
- Download URL: mojo_bindgen-0.1.0-py3-none-any.whl
- Upload date:
- Size: 70.3 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 |
a129a9a4650850fc3dbb0821241fc82dc974bc78ceff58f49f86cf53f3e66e7c
|
|
| MD5 |
4ac89c37369a4e324d0d8b39ea19494d
|
|
| BLAKE2b-256 |
0ea72352d1f59db94f3e8d7046e0a1f193e9ee8541a1af65cbf381cf73da913b
|
Provenance
The following attestation bundles were made for mojo_bindgen-0.1.0-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.0-py3-none-any.whl -
Subject digest:
a129a9a4650850fc3dbb0821241fc82dc974bc78ceff58f49f86cf53f3e66e7c - Sigstore transparency entry: 1340778580
- Sigstore integration time:
-
Permalink:
MoSafi2/mojo_bindgen@f8600cf58f98a2ed7a9b25a40f683a358323b5c1 -
Branch / Tag:
refs/tags/v0.1rc - Owner: https://github.com/MoSafi2
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@f8600cf58f98a2ed7a9b25a40f683a358323b5c1 -
Trigger Event:
push
-
Statement type: