mojo-bindgen 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mojo_bindgen-0.1.0/.gitignore +15 -0
- mojo_bindgen-0.1.0/CHANGELOG.md +18 -0
- mojo_bindgen-0.1.0/LICENSE +21 -0
- mojo_bindgen-0.1.0/PKG-INFO +189 -0
- mojo_bindgen-0.1.0/README.md +154 -0
- mojo_bindgen-0.1.0/mojo_bindgen/__init__.py +20 -0
- mojo_bindgen-0.1.0/mojo_bindgen/cli.py +138 -0
- mojo_bindgen-0.1.0/mojo_bindgen/codegen/__init__.py +15 -0
- mojo_bindgen-0.1.0/mojo_bindgen/codegen/_struct_order.py +108 -0
- mojo_bindgen-0.1.0/mojo_bindgen/codegen/generator.py +48 -0
- mojo_bindgen-0.1.0/mojo_bindgen/codegen/mojo_emit_options.py +40 -0
- mojo_bindgen-0.1.0/mojo_bindgen/codegen/mojo_mapper.py +635 -0
- mojo_bindgen-0.1.0/mojo_bindgen/codegen/render.py +628 -0
- mojo_bindgen-0.1.0/mojo_bindgen/ir.py +750 -0
- mojo_bindgen-0.1.0/mojo_bindgen/parsing/__init__.py +9 -0
- mojo_bindgen-0.1.0/mojo_bindgen/parsing/diagnostics.py +65 -0
- mojo_bindgen-0.1.0/mojo_bindgen/parsing/frontend.py +233 -0
- mojo_bindgen-0.1.0/mojo_bindgen/parsing/lowering/__init__.py +34 -0
- mojo_bindgen-0.1.0/mojo_bindgen/parsing/lowering/const_expr.py +494 -0
- mojo_bindgen-0.1.0/mojo_bindgen/parsing/lowering/decl_lowering.py +200 -0
- mojo_bindgen-0.1.0/mojo_bindgen/parsing/lowering/literal_resolver.py +126 -0
- mojo_bindgen-0.1.0/mojo_bindgen/parsing/lowering/macro_env.py +92 -0
- mojo_bindgen-0.1.0/mojo_bindgen/parsing/lowering/primitive.py +118 -0
- mojo_bindgen-0.1.0/mojo_bindgen/parsing/lowering/record_lowering.py +325 -0
- mojo_bindgen-0.1.0/mojo_bindgen/parsing/lowering/type_lowering.py +301 -0
- mojo_bindgen-0.1.0/mojo_bindgen/parsing/parser.py +183 -0
- mojo_bindgen-0.1.0/mojo_bindgen/parsing/registry.py +259 -0
- mojo_bindgen-0.1.0/mojo_bindgen/passes/__init__.py +52 -0
- mojo_bindgen-0.1.0/mojo_bindgen/passes/analyze_for_mojo.py +957 -0
- mojo_bindgen-0.1.0/mojo_bindgen/passes/pipeline.py +14 -0
- mojo_bindgen-0.1.0/mojo_bindgen/passes/reachability.py +217 -0
- mojo_bindgen-0.1.0/mojo_bindgen/passes/validate_ir.py +99 -0
- mojo_bindgen-0.1.0/mojo_bindgen/py.typed +0 -0
- mojo_bindgen-0.1.0/mojo_bindgen/serde.py +213 -0
- mojo_bindgen-0.1.0/mojo_bindgen/utils.py +38 -0
- mojo_bindgen-0.1.0/pyproject.toml +86 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0] - 2026-04-19
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Initial published packaging metadata, MIT license, and PEP 561 `py.typed` marker.
|
|
15
|
+
- Development tooling: Ruff, Pyright (scoped; see `pyproject.toml`), and CI workflow.
|
|
16
|
+
|
|
17
|
+
[Unreleased]: https://github.com/MoSafi2/mojo_bindgen/compare/v0.1.0...HEAD
|
|
18
|
+
[0.1.0]: https://github.com/MoSafi2/mojo_bindgen/releases/tag/v0.1.0
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mohamed Mabrouk
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mojo-bindgen
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Generate Mojo FFI bindings from C headers using libclang
|
|
5
|
+
Project-URL: Homepage, https://github.com/MoSafi2/mojo_bindgen
|
|
6
|
+
Project-URL: Repository, https://github.com/MoSafi2/mojo_bindgen
|
|
7
|
+
Project-URL: Issues, https://github.com/MoSafi2/mojo_bindgen/issues
|
|
8
|
+
Project-URL: Documentation, https://github.com/MoSafi2/mojo_bindgen/blob/main/README.md
|
|
9
|
+
Project-URL: Changelog, https://github.com/MoSafi2/mojo_bindgen/blob/main/CHANGELOG.md
|
|
10
|
+
Author-email: Mohamed Mabrouk <mohamed.mabrouk@rwth-aachen.de>
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: bindings,c,codegen,ffi,libclang,mojo
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Environment :: Console
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Programming Language :: C
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.14
|
|
26
|
+
Requires-Dist: libclang<19,>=18.1.1
|
|
27
|
+
Requires-Dist: rich
|
|
28
|
+
Requires-Dist: typer
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: build>=1.4; extra == 'dev'
|
|
31
|
+
Requires-Dist: pyright>=1.1.400; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest>=9; extra == 'dev'
|
|
33
|
+
Requires-Dist: ruff>=0.8; extra == 'dev'
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
[](https://github.com/MoSafi2/mojo_bindgen/actions/workflows/ci.yml)
|
|
37
|
+
|
|
38
|
+
# mojo-bindgen
|
|
39
|
+
|
|
40
|
+
Generate **Mojo FFI** bindings from **C headers** using [libclang](https://pypi.org/project/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.
|
|
41
|
+
|
|
42
|
+
**Requires Python 3.14+** (aligned with current Modular/Mojo Pixi stacks).
|
|
43
|
+
|
|
44
|
+
For maintainer-facing architecture notes, see [docs/codegen-architecture.md](docs/codegen-architecture.md). For contributing, security contact, and release notes, see [CONTRIBUTING.md](CONTRIBUTING.md), [SECURITY.md](SECURITY.md), and [CHANGELOG.md](CHANGELOG.md).
|
|
45
|
+
|
|
46
|
+
## Setup
|
|
47
|
+
|
|
48
|
+
### 1) System dependencies
|
|
49
|
+
|
|
50
|
+
Install `clang` and `libclang` first (required for header parsing):
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# Ubuntu / Debian
|
|
54
|
+
sudo apt update
|
|
55
|
+
sudo apt install -y clang libclang1
|
|
56
|
+
|
|
57
|
+
# Fedora
|
|
58
|
+
sudo dnf install -y clang llvm-libs
|
|
59
|
+
|
|
60
|
+
# macOS (Homebrew)
|
|
61
|
+
brew install llvm
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
If `libclang` is installed in a non-standard location, set `LIBCLANG_PATH` to the directory containing `libclang.so` / `libclang.dylib`.
|
|
65
|
+
|
|
66
|
+
### 2) Install project dependencies
|
|
67
|
+
|
|
68
|
+
From the repository root (requires [Pixi](https://pixi.sh)):
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pixi install
|
|
72
|
+
pixi shell
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
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](CONTRIBUTING.md)).
|
|
76
|
+
|
|
77
|
+
This installs the `mojo-bindgen` package in editable mode and puts the `mojo-bindgen` CLI on your `PATH`.
|
|
78
|
+
|
|
79
|
+
You also need a **system libclang** shared library compatible with the `libclang` Python wheel.
|
|
80
|
+
|
|
81
|
+
### Library usage (Python API)
|
|
82
|
+
|
|
83
|
+
The CLI wraps the same pipeline you can call from Python: parse a header to IR, then generate Mojo source.
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
from pathlib import Path
|
|
87
|
+
|
|
88
|
+
from mojo_bindgen.codegen import MojoEmitOptions, generate_mojo
|
|
89
|
+
from mojo_bindgen.parsing.parser import ClangParser
|
|
90
|
+
|
|
91
|
+
header = Path("include/mylib.h")
|
|
92
|
+
unit = ClangParser(
|
|
93
|
+
header,
|
|
94
|
+
library="mylib",
|
|
95
|
+
link_name="mylib",
|
|
96
|
+
compile_args=["-I", "include"],
|
|
97
|
+
).run()
|
|
98
|
+
mojo_src = generate_mojo(unit, MojoEmitOptions(linking="external_call"))
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
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.
|
|
102
|
+
|
|
103
|
+
## CLI
|
|
104
|
+
|
|
105
|
+
Inspect all options and examples:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
mojo-bindgen --help
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Emit Mojo FFI (default):
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
mojo-bindgen path/to/header.h -o bindings.mojo
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Dump IR as JSON:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
mojo-bindgen path/to/header.h --json -o unit.json
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Extra clang flags (include paths, sysroot, target):
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
mojo-bindgen include/me.h --compile-arg=-I./include --compile-arg=--sysroot=/path/to/sysroot -o out.mojo
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Set a specific C language standard:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
mojo-bindgen include/me.h --compile-arg=-std=c99 -o out.mojo
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`--compile-arg` standard flags accept `-std=...`, `--std=...`, and `std=...` forms. If no standard is provided, `-std=gnu11` is used by default.
|
|
136
|
+
|
|
137
|
+
By default, `--library` and `--link-name` are the header file stem (e.g. `me` for `me.h`).
|
|
138
|
+
`--compile-arg` is repeatable.
|
|
139
|
+
Use `mojo-bindgen --help` for full option details.
|
|
140
|
+
|
|
141
|
+
## Features
|
|
142
|
+
|
|
143
|
+
- **libclang parsing:** Walks a primary C header with configurable compile flags (default language is `gnu11` if you do not pass `-std=...`). Repeatable `--compile-arg` passes include paths, sysroot, target triple, and standard selection.
|
|
144
|
+
- **IR and tooling:** Builds a structured IR, runs validation and a reachability materialization pass, and can **dump JSON** (`--json`) for debugging or downstream tools.
|
|
145
|
+
- **Generated Mojo surface:** Emits structs (with `@align` from C alignment when expressible in Mojo), unions (including `@unsafe_union` when layout analysis marks them eligible), enums, typedefs, and thin wrappers for non-variadic functions using `external_call` or `OwnedDLHandle.call` depending on `--linking`.
|
|
146
|
+
- **Linking modes:** `external_call` (default) links C symbols at Mojo build time; `owned_dl_handle` resolves calls through `OwnedDLHandle`, with optional `--library-path-hint` for dlopen-style loading.
|
|
147
|
+
- **C globals:** For globals whose types are thin-FFI-compatible, the emitter generates `GlobalVar` / `GlobalConst` helpers that load and store through `UnsafePointer`, resolving symbols with `OwnedDLHandle.get_symbol` when needed (see `examples/global_consts/`). Atomics and layouts that cannot be lowered still become comment stubs with a short reason.
|
|
148
|
+
- **Macros and constants:** Object-like macros and `const` initializers 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.
|
|
149
|
+
- **Python API extras:** `MojoEmitOptions` also exposes pointer provenance (`ffi_origin`: `external` vs `any`), module header comments, and ABI reminder comments—tunable from code even though the CLI only exposes linking and library path hint today.
|
|
150
|
+
|
|
151
|
+
## Limitations & Rough Edges
|
|
152
|
+
|
|
153
|
+
Parsing / IR:
|
|
154
|
+
|
|
155
|
+
- **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.
|
|
156
|
+
- **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.
|
|
157
|
+
- **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.
|
|
158
|
+
- **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 `UnsupportedType` or require manual review.
|
|
159
|
+
- **C storage/linkage qualifiers:** Type qualifiers on pointers are preserved, but C declaration-level linkage/storage semantics such as `inline` / `extern inline` still are not modeled precisely enough to guarantee symbol availability.
|
|
160
|
+
|
|
161
|
+
Mojo lowering / runtime:
|
|
162
|
+
|
|
163
|
+
- **Variadic functions:** No thin callable wrapper is emitted; output is a comment noting that varargs are not modeled for FFI.
|
|
164
|
+
- **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).
|
|
165
|
+
- **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).
|
|
166
|
+
- **Non-`RegisterPassable` struct-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.
|
|
167
|
+
- **`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.
|
|
168
|
+
|
|
169
|
+
## Development
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
pixi run pytest
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Build distributable artifacts through Pixi tasks:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
pixi run clean-dist
|
|
179
|
+
pixi run build
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Optional one-shot targets:
|
|
183
|
+
|
|
184
|
+
- `pixi run build-wheel`
|
|
185
|
+
- `pixi run build-sdist`
|
|
186
|
+
|
|
187
|
+
## License
|
|
188
|
+
|
|
189
|
+
Licensed under the **MIT License**. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
[](https://github.com/MoSafi2/mojo_bindgen/actions/workflows/ci.yml)
|
|
2
|
+
|
|
3
|
+
# mojo-bindgen
|
|
4
|
+
|
|
5
|
+
Generate **Mojo FFI** bindings from **C headers** using [libclang](https://pypi.org/project/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.
|
|
6
|
+
|
|
7
|
+
**Requires Python 3.14+** (aligned with current Modular/Mojo Pixi stacks).
|
|
8
|
+
|
|
9
|
+
For maintainer-facing architecture notes, see [docs/codegen-architecture.md](docs/codegen-architecture.md). For contributing, security contact, and release notes, see [CONTRIBUTING.md](CONTRIBUTING.md), [SECURITY.md](SECURITY.md), and [CHANGELOG.md](CHANGELOG.md).
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
### 1) System dependencies
|
|
14
|
+
|
|
15
|
+
Install `clang` and `libclang` first (required for header parsing):
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Ubuntu / Debian
|
|
19
|
+
sudo apt update
|
|
20
|
+
sudo apt install -y clang libclang1
|
|
21
|
+
|
|
22
|
+
# Fedora
|
|
23
|
+
sudo dnf install -y clang llvm-libs
|
|
24
|
+
|
|
25
|
+
# macOS (Homebrew)
|
|
26
|
+
brew install llvm
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
If `libclang` is installed in a non-standard location, set `LIBCLANG_PATH` to the directory containing `libclang.so` / `libclang.dylib`.
|
|
30
|
+
|
|
31
|
+
### 2) Install project dependencies
|
|
32
|
+
|
|
33
|
+
From the repository root (requires [Pixi](https://pixi.sh)):
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pixi install
|
|
37
|
+
pixi shell
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
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](CONTRIBUTING.md)).
|
|
41
|
+
|
|
42
|
+
This installs the `mojo-bindgen` package in editable mode and puts the `mojo-bindgen` CLI on your `PATH`.
|
|
43
|
+
|
|
44
|
+
You also need a **system libclang** shared library compatible with the `libclang` Python wheel.
|
|
45
|
+
|
|
46
|
+
### Library usage (Python API)
|
|
47
|
+
|
|
48
|
+
The CLI wraps the same pipeline you can call from Python: parse a header to IR, then generate Mojo source.
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from pathlib import Path
|
|
52
|
+
|
|
53
|
+
from mojo_bindgen.codegen import MojoEmitOptions, generate_mojo
|
|
54
|
+
from mojo_bindgen.parsing.parser import ClangParser
|
|
55
|
+
|
|
56
|
+
header = Path("include/mylib.h")
|
|
57
|
+
unit = ClangParser(
|
|
58
|
+
header,
|
|
59
|
+
library="mylib",
|
|
60
|
+
link_name="mylib",
|
|
61
|
+
compile_args=["-I", "include"],
|
|
62
|
+
).run()
|
|
63
|
+
mojo_src = generate_mojo(unit, MojoEmitOptions(linking="external_call"))
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
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.
|
|
67
|
+
|
|
68
|
+
## CLI
|
|
69
|
+
|
|
70
|
+
Inspect all options and examples:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
mojo-bindgen --help
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Emit Mojo FFI (default):
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
mojo-bindgen path/to/header.h -o bindings.mojo
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Dump IR as JSON:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
mojo-bindgen path/to/header.h --json -o unit.json
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Extra clang flags (include paths, sysroot, target):
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
mojo-bindgen include/me.h --compile-arg=-I./include --compile-arg=--sysroot=/path/to/sysroot -o out.mojo
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Set a specific C language standard:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
mojo-bindgen include/me.h --compile-arg=-std=c99 -o out.mojo
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`--compile-arg` standard flags accept `-std=...`, `--std=...`, and `std=...` forms. If no standard is provided, `-std=gnu11` is used by default.
|
|
101
|
+
|
|
102
|
+
By default, `--library` and `--link-name` are the header file stem (e.g. `me` for `me.h`).
|
|
103
|
+
`--compile-arg` is repeatable.
|
|
104
|
+
Use `mojo-bindgen --help` for full option details.
|
|
105
|
+
|
|
106
|
+
## Features
|
|
107
|
+
|
|
108
|
+
- **libclang parsing:** Walks a primary C header with configurable compile flags (default language is `gnu11` if you do not pass `-std=...`). Repeatable `--compile-arg` passes include paths, sysroot, target triple, and standard selection.
|
|
109
|
+
- **IR and tooling:** Builds a structured IR, runs validation and a reachability materialization pass, and can **dump JSON** (`--json`) for debugging or downstream tools.
|
|
110
|
+
- **Generated Mojo surface:** Emits structs (with `@align` from C alignment when expressible in Mojo), unions (including `@unsafe_union` when layout analysis marks them eligible), enums, typedefs, and thin wrappers for non-variadic functions using `external_call` or `OwnedDLHandle.call` depending on `--linking`.
|
|
111
|
+
- **Linking modes:** `external_call` (default) links C symbols at Mojo build time; `owned_dl_handle` resolves calls through `OwnedDLHandle`, with optional `--library-path-hint` for dlopen-style loading.
|
|
112
|
+
- **C globals:** For globals whose types are thin-FFI-compatible, the emitter generates `GlobalVar` / `GlobalConst` helpers that load and store through `UnsafePointer`, resolving symbols with `OwnedDLHandle.get_symbol` when needed (see `examples/global_consts/`). Atomics and layouts that cannot be lowered still become comment stubs with a short reason.
|
|
113
|
+
- **Macros and constants:** Object-like macros and `const` initializers 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.
|
|
114
|
+
- **Python API extras:** `MojoEmitOptions` also exposes pointer provenance (`ffi_origin`: `external` vs `any`), module header comments, and ABI reminder comments—tunable from code even though the CLI only exposes linking and library path hint today.
|
|
115
|
+
|
|
116
|
+
## Limitations & Rough Edges
|
|
117
|
+
|
|
118
|
+
Parsing / IR:
|
|
119
|
+
|
|
120
|
+
- **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.
|
|
121
|
+
- **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.
|
|
122
|
+
- **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.
|
|
123
|
+
- **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 `UnsupportedType` or require manual review.
|
|
124
|
+
- **C storage/linkage qualifiers:** Type qualifiers on pointers are preserved, but C declaration-level linkage/storage semantics such as `inline` / `extern inline` still are not modeled precisely enough to guarantee symbol availability.
|
|
125
|
+
|
|
126
|
+
Mojo lowering / runtime:
|
|
127
|
+
|
|
128
|
+
- **Variadic functions:** No thin callable wrapper is emitted; output is a comment noting that varargs are not modeled for FFI.
|
|
129
|
+
- **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).
|
|
130
|
+
- **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).
|
|
131
|
+
- **Non-`RegisterPassable` struct-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.
|
|
132
|
+
- **`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.
|
|
133
|
+
|
|
134
|
+
## Development
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
pixi run pytest
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Build distributable artifacts through Pixi tasks:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
pixi run clean-dist
|
|
144
|
+
pixi run build
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Optional one-shot targets:
|
|
148
|
+
|
|
149
|
+
- `pixi run build-wheel`
|
|
150
|
+
- `pixi run build-sdist`
|
|
151
|
+
|
|
152
|
+
## License
|
|
153
|
+
|
|
154
|
+
Licensed under the **MIT License**. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""C header → Mojo FFI bindings via libclang."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
6
|
+
|
|
7
|
+
from mojo_bindgen.codegen import FFIScalarStyle, MojoEmitOptions, MojoGenerator, generate_mojo
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
__version__ = version("mojo-bindgen")
|
|
11
|
+
except PackageNotFoundError:
|
|
12
|
+
__version__ = "0.1.0"
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"FFIScalarStyle",
|
|
16
|
+
"MojoEmitOptions",
|
|
17
|
+
"MojoGenerator",
|
|
18
|
+
"__version__",
|
|
19
|
+
"generate_mojo",
|
|
20
|
+
]
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Command-line interface for mojo-bindgen."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Annotated, Literal
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
|
|
12
|
+
from mojo_bindgen.codegen.generator import MojoGenerator
|
|
13
|
+
from mojo_bindgen.codegen.mojo_emit_options import MojoEmitOptions
|
|
14
|
+
from mojo_bindgen.parsing.parser import ClangParser, ParseError
|
|
15
|
+
|
|
16
|
+
stderr_console = Console(stderr=True)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def run(
|
|
20
|
+
header: Annotated[Path, typer.Argument(help="Path to the primary C header file")],
|
|
21
|
+
output: Annotated[
|
|
22
|
+
Path | None,
|
|
23
|
+
typer.Option(
|
|
24
|
+
"--output",
|
|
25
|
+
"-o",
|
|
26
|
+
help="Write output to this file (default: stdout).",
|
|
27
|
+
show_default=False,
|
|
28
|
+
),
|
|
29
|
+
] = None,
|
|
30
|
+
library: Annotated[
|
|
31
|
+
str | None,
|
|
32
|
+
typer.Option(
|
|
33
|
+
"--library",
|
|
34
|
+
metavar="NAME",
|
|
35
|
+
help="Logical library name in the IR (default: stem of the header path).",
|
|
36
|
+
),
|
|
37
|
+
] = None,
|
|
38
|
+
link_name: Annotated[
|
|
39
|
+
str | None,
|
|
40
|
+
typer.Option(
|
|
41
|
+
"--link-name",
|
|
42
|
+
metavar="NAME",
|
|
43
|
+
help="Shared-library link name (default: same as --library).",
|
|
44
|
+
),
|
|
45
|
+
] = None,
|
|
46
|
+
compile_arg: Annotated[
|
|
47
|
+
list[str] | None,
|
|
48
|
+
typer.Option(
|
|
49
|
+
"--compile-arg",
|
|
50
|
+
metavar="FLAG",
|
|
51
|
+
help="Extra clang flag (repeatable). If omitted, use built-in system include probing.",
|
|
52
|
+
),
|
|
53
|
+
] = None,
|
|
54
|
+
json_output: Annotated[
|
|
55
|
+
bool,
|
|
56
|
+
typer.Option(
|
|
57
|
+
"--json",
|
|
58
|
+
help="Emit IR as JSON instead of Mojo source.",
|
|
59
|
+
),
|
|
60
|
+
] = False,
|
|
61
|
+
linking: Annotated[
|
|
62
|
+
Literal["external_call", "owned_dl_handle"],
|
|
63
|
+
typer.Option(
|
|
64
|
+
"--linking",
|
|
65
|
+
help="FFI linking mode for Mojo output (ignored with --json).",
|
|
66
|
+
case_sensitive=False,
|
|
67
|
+
),
|
|
68
|
+
] = "external_call",
|
|
69
|
+
library_path_hint: Annotated[
|
|
70
|
+
str | None,
|
|
71
|
+
typer.Option(
|
|
72
|
+
"--library-path-hint",
|
|
73
|
+
metavar="PATH",
|
|
74
|
+
help="Path hint for OwnedDLHandle when --linking owned_dl_handle.",
|
|
75
|
+
),
|
|
76
|
+
] = None,
|
|
77
|
+
) -> int:
|
|
78
|
+
"""Generate Mojo FFI from a C header using libclang.
|
|
79
|
+
|
|
80
|
+
Examples:
|
|
81
|
+
mojo-bindgen path/to/header.h -o bindings.mojo
|
|
82
|
+
mojo-bindgen path/to/header.h --json -o unit.json
|
|
83
|
+
mojo-bindgen include/me.h --compile-arg=-I./include -o out.mojo
|
|
84
|
+
"""
|
|
85
|
+
stem = header.stem if header.suffix else str(header)
|
|
86
|
+
library_name = library if library is not None else stem
|
|
87
|
+
link_name_value = link_name if link_name is not None else library_name
|
|
88
|
+
compile_args = compile_arg
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
parser = ClangParser(
|
|
92
|
+
header,
|
|
93
|
+
library=library_name,
|
|
94
|
+
link_name=link_name_value,
|
|
95
|
+
compile_args=compile_args,
|
|
96
|
+
raise_on_error=True,
|
|
97
|
+
)
|
|
98
|
+
unit = parser.run()
|
|
99
|
+
except (ParseError, FileNotFoundError, OSError) as e:
|
|
100
|
+
stderr_console.print(f"[bold red]mojo-bindgen error:[/bold red] {e}")
|
|
101
|
+
raise typer.Exit(code=1) from e
|
|
102
|
+
|
|
103
|
+
if json_output:
|
|
104
|
+
text = unit.to_json()
|
|
105
|
+
else:
|
|
106
|
+
opts = MojoEmitOptions(
|
|
107
|
+
linking=linking,
|
|
108
|
+
library_path_hint=library_path_hint,
|
|
109
|
+
)
|
|
110
|
+
text = MojoGenerator(opts).generate(unit)
|
|
111
|
+
|
|
112
|
+
if output is None:
|
|
113
|
+
sys.stdout.write(text)
|
|
114
|
+
if not text.endswith("\n"):
|
|
115
|
+
sys.stdout.write("\n")
|
|
116
|
+
else:
|
|
117
|
+
output.write_text(text, encoding="utf-8")
|
|
118
|
+
return 0
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def main(argv: list[str] | None = None) -> int:
|
|
122
|
+
cli_args = argv if argv is not None else sys.argv[1:]
|
|
123
|
+
previous_argv = sys.argv[:]
|
|
124
|
+
sys.argv = ["mojo-bindgen", *cli_args]
|
|
125
|
+
try:
|
|
126
|
+
typer.run(run)
|
|
127
|
+
return 0
|
|
128
|
+
except typer.Exit as e:
|
|
129
|
+
return e.exit_code
|
|
130
|
+
except SystemExit as e:
|
|
131
|
+
code = e.code
|
|
132
|
+
return code if isinstance(code, int) else 0
|
|
133
|
+
finally:
|
|
134
|
+
sys.argv = previous_argv
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
if __name__ == "__main__":
|
|
138
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Public Mojo code generation API.
|
|
2
|
+
|
|
3
|
+
Import from this package when you want the stable, higher-level codegen
|
|
4
|
+
surface rather than the individual implementation modules.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from mojo_bindgen.codegen.generator import MojoGenerator, generate_mojo
|
|
8
|
+
from mojo_bindgen.codegen.mojo_emit_options import FFIScalarStyle, MojoEmitOptions
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"FFIScalarStyle",
|
|
12
|
+
"MojoGenerator",
|
|
13
|
+
"MojoEmitOptions",
|
|
14
|
+
"generate_mojo",
|
|
15
|
+
]
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Value-embedding struct dependency ordering for code generation.
|
|
2
|
+
|
|
3
|
+
These helpers compute an emission order for structs that embed other structs
|
|
4
|
+
by value, so referenced layouts appear before the records that depend on them.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from collections import defaultdict, deque
|
|
10
|
+
|
|
11
|
+
from mojo_bindgen.codegen.mojo_mapper import mojo_ident
|
|
12
|
+
from mojo_bindgen.ir import (
|
|
13
|
+
Array,
|
|
14
|
+
AtomicType,
|
|
15
|
+
EnumRef,
|
|
16
|
+
QualifiedType,
|
|
17
|
+
Struct,
|
|
18
|
+
StructRef,
|
|
19
|
+
Type,
|
|
20
|
+
TypeRef,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def struct_dependency_edges(s: Struct) -> list[tuple[str, str]]:
|
|
25
|
+
"""Return (successor, predecessor) pairs: `succ` depends on `pred` (emit `pred` first).
|
|
26
|
+
|
|
27
|
+
Only **by-value** struct references create ordering edges. Pointer and function-pointer
|
|
28
|
+
fields do not (C allows pointers to incomplete types); nested struct layout is still
|
|
29
|
+
captured via ``Array``/``StructRef`` for value-embedded arrays of structs.
|
|
30
|
+
"""
|
|
31
|
+
me = mojo_ident(s.name.strip() or s.c_name.strip())
|
|
32
|
+
edges: list[tuple[str, str]] = []
|
|
33
|
+
|
|
34
|
+
def walk(ty: Type) -> None:
|
|
35
|
+
"""Collect by-value struct dependencies reachable from ``ty``."""
|
|
36
|
+
if isinstance(ty, TypeRef):
|
|
37
|
+
walk(ty.canonical)
|
|
38
|
+
return
|
|
39
|
+
if isinstance(ty, QualifiedType):
|
|
40
|
+
walk(ty.unqualified)
|
|
41
|
+
return
|
|
42
|
+
if isinstance(ty, AtomicType):
|
|
43
|
+
walk(ty.value_type)
|
|
44
|
+
return
|
|
45
|
+
if isinstance(ty, EnumRef):
|
|
46
|
+
return
|
|
47
|
+
if isinstance(ty, StructRef):
|
|
48
|
+
if ty.is_union:
|
|
49
|
+
return
|
|
50
|
+
pred = mojo_ident(ty.name.strip())
|
|
51
|
+
if pred != me:
|
|
52
|
+
edges.append((me, pred))
|
|
53
|
+
return
|
|
54
|
+
if isinstance(ty, Array):
|
|
55
|
+
walk(ty.element)
|
|
56
|
+
|
|
57
|
+
for f in s.fields:
|
|
58
|
+
walk(f.type)
|
|
59
|
+
return edges
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def toposort_structs(structs: list[Struct]) -> list[Struct]:
|
|
63
|
+
"""Return structs in an order suitable for emission.
|
|
64
|
+
|
|
65
|
+
Value-embedded :class:`~mojo_bindgen.ir.StructRef` dependencies are emitted
|
|
66
|
+
before the records that use them. Cycles fall back to the original input
|
|
67
|
+
order once the acyclic portion of the graph is exhausted.
|
|
68
|
+
"""
|
|
69
|
+
if not structs:
|
|
70
|
+
return []
|
|
71
|
+
|
|
72
|
+
def name_of(s: Struct) -> str:
|
|
73
|
+
"""Return the sanitized emitted name for ``s``."""
|
|
74
|
+
return mojo_ident(s.name.strip() or s.c_name.strip())
|
|
75
|
+
|
|
76
|
+
names = [name_of(s) for s in structs]
|
|
77
|
+
known = set(names)
|
|
78
|
+
name_to_struct = {name_of(s): s for s in structs}
|
|
79
|
+
|
|
80
|
+
# graph[pred] = successors that must appear after pred
|
|
81
|
+
graph: dict[str, set[str]] = defaultdict(set)
|
|
82
|
+
indegree: dict[str, int] = {n: 0 for n in names}
|
|
83
|
+
|
|
84
|
+
for s in structs:
|
|
85
|
+
succ = name_of(s)
|
|
86
|
+
for succ2, pred in struct_dependency_edges(s):
|
|
87
|
+
if succ2 != succ:
|
|
88
|
+
continue
|
|
89
|
+
if pred in known and pred != succ:
|
|
90
|
+
if succ not in graph[pred]:
|
|
91
|
+
graph[pred].add(succ)
|
|
92
|
+
indegree[succ] += 1
|
|
93
|
+
|
|
94
|
+
q = deque(sorted([n for n in names if indegree[n] == 0], key=lambda x: names.index(x)))
|
|
95
|
+
order: list[str] = []
|
|
96
|
+
while q:
|
|
97
|
+
n = q.popleft()
|
|
98
|
+
order.append(n)
|
|
99
|
+
for succ in graph.get(n, ()):
|
|
100
|
+
indegree[succ] -= 1
|
|
101
|
+
if indegree[succ] == 0:
|
|
102
|
+
q.append(succ)
|
|
103
|
+
|
|
104
|
+
for n in names:
|
|
105
|
+
if n not in order:
|
|
106
|
+
order.append(n)
|
|
107
|
+
|
|
108
|
+
return [name_to_struct[n] for n in order]
|