abi-forge 1.0.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.
- abi_forge-1.0.0/PKG-INFO +166 -0
- abi_forge-1.0.0/README.md +144 -0
- abi_forge-1.0.0/abi_codegen_core/src/abi_codegen_core/__init__.py +13 -0
- abi_forge-1.0.0/abi_codegen_core/src/abi_codegen_core/common.py +33 -0
- abi_forge-1.0.0/abi_codegen_core/src/abi_codegen_core/native_exports.py +127 -0
- abi_forge-1.0.0/abi_codegen_core/src/abi_codegen_core/required_functions.py +58 -0
- abi_forge-1.0.0/abi_forge.egg-info/PKG-INFO +166 -0
- abi_forge-1.0.0/abi_forge.egg-info/SOURCES.txt +47 -0
- abi_forge-1.0.0/abi_forge.egg-info/dependency_links.txt +1 -0
- abi_forge-1.0.0/abi_forge.egg-info/entry_points.txt +5 -0
- abi_forge-1.0.0/abi_forge.egg-info/top_level.txt +3 -0
- abi_forge-1.0.0/generator_sdk/__init__.py +5 -0
- abi_forge-1.0.0/generator_sdk/external_generator_stub.py +33 -0
- abi_forge-1.0.0/generator_sdk/go_bindings_generator.py +634 -0
- abi_forge-1.0.0/generator_sdk/managed_api_codegen.py +386 -0
- abi_forge-1.0.0/generator_sdk/managed_api_metadata_generator.py +202 -0
- abi_forge-1.0.0/generator_sdk/managed_api_scaffold_generator.py +682 -0
- abi_forge-1.0.0/generator_sdk/managed_bindings_scaffold_generator.py +121 -0
- abi_forge-1.0.0/generator_sdk/native_exports_generator.py +80 -0
- abi_forge-1.0.0/generator_sdk/plugin.manifest.json +376 -0
- abi_forge-1.0.0/generator_sdk/python_bindings_generator.py +726 -0
- abi_forge-1.0.0/generator_sdk/rust_ffi_generator.py +488 -0
- abi_forge-1.0.0/generator_sdk/symbol_contract_generator.py +346 -0
- abi_forge-1.0.0/generator_sdk/typescript_bindings_generator.py +634 -0
- abi_forge-1.0.0/pyproject.toml +49 -0
- abi_forge-1.0.0/setup.cfg +4 -0
- abi_forge-1.0.0/src/abi_framework_core/__init__.py +3 -0
- abi_forge-1.0.0/src/abi_framework_core/_core_base.py +1491 -0
- abi_forge-1.0.0/src/abi_framework_core/_core_codegen.py +1403 -0
- abi_forge-1.0.0/src/abi_framework_core/_core_compare.py +1076 -0
- abi_forge-1.0.0/src/abi_framework_core/_core_orchestration.py +318 -0
- abi_forge-1.0.0/src/abi_framework_core/_core_plugins.py +467 -0
- abi_forge-1.0.0/src/abi_framework_core/_core_policy.py +541 -0
- abi_forge-1.0.0/src/abi_framework_core/_core_snapshot.py +382 -0
- abi_forge-1.0.0/src/abi_framework_core/cli.py +411 -0
- abi_forge-1.0.0/src/abi_framework_core/commands/__init__.py +50 -0
- abi_forge-1.0.0/src/abi_framework_core/commands/bootstrap.py +187 -0
- abi_forge-1.0.0/src/abi_framework_core/commands/common.py +42 -0
- abi_forge-1.0.0/src/abi_framework_core/commands/generation.py +276 -0
- abi_forge-1.0.0/src/abi_framework_core/commands/governance.py +356 -0
- abi_forge-1.0.0/src/abi_framework_core/commands/performance.py +210 -0
- abi_forge-1.0.0/src/abi_framework_core/commands/plugins.py +288 -0
- abi_forge-1.0.0/src/abi_framework_core/commands/release.py +351 -0
- abi_forge-1.0.0/src/abi_framework_core/commands/status.py +204 -0
- abi_forge-1.0.0/src/abi_framework_core/commands/targets.py +391 -0
- abi_forge-1.0.0/src/abi_framework_core/commands/verification.py +250 -0
- abi_forge-1.0.0/src/abi_framework_core/core.py +10 -0
- abi_forge-1.0.0/tests/test_abi_framework.py +2775 -0
- abi_forge-1.0.0/tests/test_plugin_manifest.py +211 -0
abi_forge-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: abi-forge
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: ABI governance and polyglot binding generator for C shared libraries
|
|
5
|
+
Author-email: Vitaly Kuzyaev <vitkuz573@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/vitkuz573/abi-forge
|
|
8
|
+
Project-URL: Repository, https://github.com/vitkuz573/abi-forge
|
|
9
|
+
Project-URL: Issues, https://github.com/vitkuz573/abi-forge/issues
|
|
10
|
+
Keywords: abi,codegen,bindings,ffi,interop
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
19
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# abi-forge
|
|
24
|
+
|
|
25
|
+
**Reusable ABI governance and polyglot binding generator for C shared libraries.**
|
|
26
|
+
|
|
27
|
+
From a single C header, abi-forge produces a versioned IDL snapshot and generates bindings for every language you need — with built-in breakage detection and governance.
|
|
28
|
+
|
|
29
|
+
## What it does
|
|
30
|
+
|
|
31
|
+
| Stage | What happens |
|
|
32
|
+
|-------|-------------|
|
|
33
|
+
| **Parse** | Clang-preprocesses your C header into a stable IDL JSON snapshot |
|
|
34
|
+
| **Govern** | Diffs every new snapshot against the baseline; flags removals, signature changes, enum shifts as typed errors (ABI001–ABI007, SARIF-compatible) |
|
|
35
|
+
| **Generate** | Runs your chosen language generators from the IDL: C#, Python, Rust, TypeScript, Go |
|
|
36
|
+
| **Enforce** | CI fails on ABI regressions; waivers require explicit approval with TTL |
|
|
37
|
+
|
|
38
|
+
## Language targets
|
|
39
|
+
|
|
40
|
+
| Language | Output | Mechanism |
|
|
41
|
+
|----------|--------|-----------|
|
|
42
|
+
| **C# (.NET)** | P/Invoke + SafeHandle wrappers + async layer | Roslyn source generator (`generator_sdk/csharp/`) |
|
|
43
|
+
| **Python** | ctypes module with IntEnum, OOP handles, CFUNCTYPE | `generator_sdk/python_bindings_generator.py` |
|
|
44
|
+
| **Rust** | `#[repr(C)]` enums + `extern "C"` block | `generator_sdk/rust_ffi_generator.py` |
|
|
45
|
+
| **TypeScript** | ffi-napi + OOP wrappers with `[Symbol.dispose]` | `generator_sdk/typescript_bindings_generator.py` |
|
|
46
|
+
| **Go** | cgo package with struct wrappers and `runtime.SetFinalizer` | `generator_sdk/go_bindings_generator.py` |
|
|
47
|
+
|
|
48
|
+
## Real-world example
|
|
49
|
+
|
|
50
|
+
[LumenRTC](https://github.com/vitkuz573/LumenRTC) — a .NET wrapper over `libwebrtc` — uses abi-forge to govern its C ABI and generate all language bindings from a single IDL snapshot. See `abi/config.json` there for a complete production configuration.
|
|
51
|
+
|
|
52
|
+
## Quick start
|
|
53
|
+
|
|
54
|
+
### Requirements
|
|
55
|
+
|
|
56
|
+
- Python 3.10+
|
|
57
|
+
- clang (for header parsing)
|
|
58
|
+
- .NET SDK 10+ (optional, only for C# bindings)
|
|
59
|
+
|
|
60
|
+
### Bootstrap a new target
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# Add abi-forge as a submodule
|
|
64
|
+
git submodule add https://github.com/vitkuz573/abi-forge.git tools/abi_framework
|
|
65
|
+
|
|
66
|
+
# Bootstrap: scaffolds abi/config.json + initial metadata
|
|
67
|
+
python3 tools/abi_framework/abi_framework.py bootstrap \
|
|
68
|
+
--target mylib \
|
|
69
|
+
--header path/to/mylib.h \
|
|
70
|
+
--namespace MyLib \
|
|
71
|
+
--generate-python \
|
|
72
|
+
--generate-rust
|
|
73
|
+
|
|
74
|
+
# Parse header → IDL snapshot
|
|
75
|
+
python3 tools/abi_framework/abi_framework.py generate \
|
|
76
|
+
--config abi/config.json --skip-binary
|
|
77
|
+
|
|
78
|
+
# Generate all bindings from IDL
|
|
79
|
+
python3 tools/abi_framework/abi_framework.py codegen \
|
|
80
|
+
--config abi/config.json --skip-binary
|
|
81
|
+
|
|
82
|
+
# Lock current IDL as baseline (first time)
|
|
83
|
+
python3 tools/abi_framework/abi_framework.py generate-baseline --target mylib
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### ABI governance in CI
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
# Fail on regressions
|
|
90
|
+
python3 tools/abi_framework/abi_framework.py check \
|
|
91
|
+
--config abi/config.json --skip-binary
|
|
92
|
+
|
|
93
|
+
# Full SARIF report for GitHub code scanning
|
|
94
|
+
python3 tools/abi_framework/abi_framework.py check-all \
|
|
95
|
+
--config abi/config.json --skip-binary --sarif-out abi-report.sarif
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Health dashboard
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
python3 tools/abi_framework/abi_framework.py status --config abi/config.json
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Repository structure
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
abi-forge/
|
|
108
|
+
├── abi_framework.py # CLI entry point
|
|
109
|
+
├── src/abi_framework_core/ # core: parse, diff, snapshot, policy, orchestration
|
|
110
|
+
│ └── commands/ # subcommands: generate, codegen, check, release, status, …
|
|
111
|
+
├── generator_sdk/ # language generators (Python, Rust, TypeScript, Go, C#-scaffold)
|
|
112
|
+
│ └── plugin.manifest.json # plugin declarations for all built-in generators
|
|
113
|
+
├── abi_codegen_core/ # shared codegen primitives (native exports, required functions)
|
|
114
|
+
├── abi_roslyn_codegen/ # Roslyn source generator for C# P/Invoke + managed API
|
|
115
|
+
├── tests/ # 99 tests covering orchestration, generators, governance
|
|
116
|
+
└── schemas/ # JSON schemas for IDL, config, managed_api
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Config overview (`abi/config.json`)
|
|
120
|
+
|
|
121
|
+
```jsonc
|
|
122
|
+
{
|
|
123
|
+
"targets": {
|
|
124
|
+
"mylib": {
|
|
125
|
+
"header": {
|
|
126
|
+
"path": "include/mylib.h",
|
|
127
|
+
"symbol_prefix": "mylib_",
|
|
128
|
+
"api_macro": "MYLIB_API"
|
|
129
|
+
},
|
|
130
|
+
"codegen": {
|
|
131
|
+
"idl_output": "abi/generated/mylib/mylib.idl.json"
|
|
132
|
+
},
|
|
133
|
+
"baseline_path": "abi/baselines/mylib.json",
|
|
134
|
+
"bindings": {
|
|
135
|
+
"generators": [
|
|
136
|
+
{
|
|
137
|
+
"manifest": "{repo_root}/tools/abi_framework/generator_sdk/plugin.manifest.json",
|
|
138
|
+
"plugins": [
|
|
139
|
+
"abi_framework.python_bindings",
|
|
140
|
+
"abi_framework.rust_ffi",
|
|
141
|
+
"abi_framework.typescript_bindings",
|
|
142
|
+
"abi_framework.go_bindings"
|
|
143
|
+
]
|
|
144
|
+
}
|
|
145
|
+
]
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## ABI error types (SARIF rules)
|
|
153
|
+
|
|
154
|
+
| Rule | Meaning |
|
|
155
|
+
|------|---------|
|
|
156
|
+
| `ABI001` | Function removed from public API |
|
|
157
|
+
| `ABI002` | Function signature changed |
|
|
158
|
+
| `ABI003` | Enum value added, removed, or renumbered |
|
|
159
|
+
| `ABI004` | Struct layout changed |
|
|
160
|
+
| `ABI005` | Bindings metadata mismatch |
|
|
161
|
+
| `ABI006` | Version policy violation |
|
|
162
|
+
| `ABI007` | Warning (non-breaking) |
|
|
163
|
+
|
|
164
|
+
## License
|
|
165
|
+
|
|
166
|
+
MIT
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# abi-forge
|
|
2
|
+
|
|
3
|
+
**Reusable ABI governance and polyglot binding generator for C shared libraries.**
|
|
4
|
+
|
|
5
|
+
From a single C header, abi-forge produces a versioned IDL snapshot and generates bindings for every language you need — with built-in breakage detection and governance.
|
|
6
|
+
|
|
7
|
+
## What it does
|
|
8
|
+
|
|
9
|
+
| Stage | What happens |
|
|
10
|
+
|-------|-------------|
|
|
11
|
+
| **Parse** | Clang-preprocesses your C header into a stable IDL JSON snapshot |
|
|
12
|
+
| **Govern** | Diffs every new snapshot against the baseline; flags removals, signature changes, enum shifts as typed errors (ABI001–ABI007, SARIF-compatible) |
|
|
13
|
+
| **Generate** | Runs your chosen language generators from the IDL: C#, Python, Rust, TypeScript, Go |
|
|
14
|
+
| **Enforce** | CI fails on ABI regressions; waivers require explicit approval with TTL |
|
|
15
|
+
|
|
16
|
+
## Language targets
|
|
17
|
+
|
|
18
|
+
| Language | Output | Mechanism |
|
|
19
|
+
|----------|--------|-----------|
|
|
20
|
+
| **C# (.NET)** | P/Invoke + SafeHandle wrappers + async layer | Roslyn source generator (`generator_sdk/csharp/`) |
|
|
21
|
+
| **Python** | ctypes module with IntEnum, OOP handles, CFUNCTYPE | `generator_sdk/python_bindings_generator.py` |
|
|
22
|
+
| **Rust** | `#[repr(C)]` enums + `extern "C"` block | `generator_sdk/rust_ffi_generator.py` |
|
|
23
|
+
| **TypeScript** | ffi-napi + OOP wrappers with `[Symbol.dispose]` | `generator_sdk/typescript_bindings_generator.py` |
|
|
24
|
+
| **Go** | cgo package with struct wrappers and `runtime.SetFinalizer` | `generator_sdk/go_bindings_generator.py` |
|
|
25
|
+
|
|
26
|
+
## Real-world example
|
|
27
|
+
|
|
28
|
+
[LumenRTC](https://github.com/vitkuz573/LumenRTC) — a .NET wrapper over `libwebrtc` — uses abi-forge to govern its C ABI and generate all language bindings from a single IDL snapshot. See `abi/config.json` there for a complete production configuration.
|
|
29
|
+
|
|
30
|
+
## Quick start
|
|
31
|
+
|
|
32
|
+
### Requirements
|
|
33
|
+
|
|
34
|
+
- Python 3.10+
|
|
35
|
+
- clang (for header parsing)
|
|
36
|
+
- .NET SDK 10+ (optional, only for C# bindings)
|
|
37
|
+
|
|
38
|
+
### Bootstrap a new target
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Add abi-forge as a submodule
|
|
42
|
+
git submodule add https://github.com/vitkuz573/abi-forge.git tools/abi_framework
|
|
43
|
+
|
|
44
|
+
# Bootstrap: scaffolds abi/config.json + initial metadata
|
|
45
|
+
python3 tools/abi_framework/abi_framework.py bootstrap \
|
|
46
|
+
--target mylib \
|
|
47
|
+
--header path/to/mylib.h \
|
|
48
|
+
--namespace MyLib \
|
|
49
|
+
--generate-python \
|
|
50
|
+
--generate-rust
|
|
51
|
+
|
|
52
|
+
# Parse header → IDL snapshot
|
|
53
|
+
python3 tools/abi_framework/abi_framework.py generate \
|
|
54
|
+
--config abi/config.json --skip-binary
|
|
55
|
+
|
|
56
|
+
# Generate all bindings from IDL
|
|
57
|
+
python3 tools/abi_framework/abi_framework.py codegen \
|
|
58
|
+
--config abi/config.json --skip-binary
|
|
59
|
+
|
|
60
|
+
# Lock current IDL as baseline (first time)
|
|
61
|
+
python3 tools/abi_framework/abi_framework.py generate-baseline --target mylib
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### ABI governance in CI
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# Fail on regressions
|
|
68
|
+
python3 tools/abi_framework/abi_framework.py check \
|
|
69
|
+
--config abi/config.json --skip-binary
|
|
70
|
+
|
|
71
|
+
# Full SARIF report for GitHub code scanning
|
|
72
|
+
python3 tools/abi_framework/abi_framework.py check-all \
|
|
73
|
+
--config abi/config.json --skip-binary --sarif-out abi-report.sarif
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Health dashboard
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
python3 tools/abi_framework/abi_framework.py status --config abi/config.json
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Repository structure
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
abi-forge/
|
|
86
|
+
├── abi_framework.py # CLI entry point
|
|
87
|
+
├── src/abi_framework_core/ # core: parse, diff, snapshot, policy, orchestration
|
|
88
|
+
│ └── commands/ # subcommands: generate, codegen, check, release, status, …
|
|
89
|
+
├── generator_sdk/ # language generators (Python, Rust, TypeScript, Go, C#-scaffold)
|
|
90
|
+
│ └── plugin.manifest.json # plugin declarations for all built-in generators
|
|
91
|
+
├── abi_codegen_core/ # shared codegen primitives (native exports, required functions)
|
|
92
|
+
├── abi_roslyn_codegen/ # Roslyn source generator for C# P/Invoke + managed API
|
|
93
|
+
├── tests/ # 99 tests covering orchestration, generators, governance
|
|
94
|
+
└── schemas/ # JSON schemas for IDL, config, managed_api
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Config overview (`abi/config.json`)
|
|
98
|
+
|
|
99
|
+
```jsonc
|
|
100
|
+
{
|
|
101
|
+
"targets": {
|
|
102
|
+
"mylib": {
|
|
103
|
+
"header": {
|
|
104
|
+
"path": "include/mylib.h",
|
|
105
|
+
"symbol_prefix": "mylib_",
|
|
106
|
+
"api_macro": "MYLIB_API"
|
|
107
|
+
},
|
|
108
|
+
"codegen": {
|
|
109
|
+
"idl_output": "abi/generated/mylib/mylib.idl.json"
|
|
110
|
+
},
|
|
111
|
+
"baseline_path": "abi/baselines/mylib.json",
|
|
112
|
+
"bindings": {
|
|
113
|
+
"generators": [
|
|
114
|
+
{
|
|
115
|
+
"manifest": "{repo_root}/tools/abi_framework/generator_sdk/plugin.manifest.json",
|
|
116
|
+
"plugins": [
|
|
117
|
+
"abi_framework.python_bindings",
|
|
118
|
+
"abi_framework.rust_ffi",
|
|
119
|
+
"abi_framework.typescript_bindings",
|
|
120
|
+
"abi_framework.go_bindings"
|
|
121
|
+
]
|
|
122
|
+
}
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## ABI error types (SARIF rules)
|
|
131
|
+
|
|
132
|
+
| Rule | Meaning |
|
|
133
|
+
|------|---------|
|
|
134
|
+
| `ABI001` | Function removed from public API |
|
|
135
|
+
| `ABI002` | Function signature changed |
|
|
136
|
+
| `ABI003` | Enum value added, removed, or renumbered |
|
|
137
|
+
| `ABI004` | Struct layout changed |
|
|
138
|
+
| `ABI005` | Bindings metadata mismatch |
|
|
139
|
+
| `ABI006` | Version policy violation |
|
|
140
|
+
| `ABI007` | Warning (non-breaking) |
|
|
141
|
+
|
|
142
|
+
## License
|
|
143
|
+
|
|
144
|
+
MIT
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .common import load_json_object, write_if_changed
|
|
2
|
+
from .native_exports import NativeExportRenderOptions, render_exports, render_impl_header
|
|
3
|
+
from .required_functions import derive_required_functions, iter_strings
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"NativeExportRenderOptions",
|
|
7
|
+
"derive_required_functions",
|
|
8
|
+
"iter_strings",
|
|
9
|
+
"load_json_object",
|
|
10
|
+
"render_exports",
|
|
11
|
+
"render_impl_header",
|
|
12
|
+
"write_if_changed",
|
|
13
|
+
]
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import difflib
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def load_json_object(path: Path) -> dict[str, Any]:
|
|
10
|
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
11
|
+
if not isinstance(payload, dict):
|
|
12
|
+
raise SystemExit(f"JSON root in '{path}' must be an object")
|
|
13
|
+
return payload
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def write_if_changed(path: Path, content: str, check: bool, dry_run: bool) -> int:
|
|
17
|
+
existing = path.read_text(encoding="utf-8") if path.exists() else ""
|
|
18
|
+
if existing == content:
|
|
19
|
+
return 0
|
|
20
|
+
if check:
|
|
21
|
+
diff = difflib.unified_diff(
|
|
22
|
+
existing.splitlines(),
|
|
23
|
+
content.splitlines(),
|
|
24
|
+
fromfile=f"a/{path}",
|
|
25
|
+
tofile=f"b/{path}",
|
|
26
|
+
lineterm="",
|
|
27
|
+
)
|
|
28
|
+
print("\n".join(diff))
|
|
29
|
+
return 1
|
|
30
|
+
if not dry_run:
|
|
31
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
32
|
+
path.write_text(content, encoding="utf-8")
|
|
33
|
+
return 0
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def normalize_type(value: str) -> str:
|
|
8
|
+
return " ".join(value.replace("\t", " ").split())
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def render_params(parameters: list[dict[str, Any]]) -> str:
|
|
12
|
+
if not parameters:
|
|
13
|
+
return "void"
|
|
14
|
+
parts: list[str] = []
|
|
15
|
+
for index, param in enumerate(parameters):
|
|
16
|
+
name = str(param.get("name") or f"arg{index}")
|
|
17
|
+
c_type = normalize_type(str(param.get("c_type") or "void"))
|
|
18
|
+
if c_type == "...":
|
|
19
|
+
parts.append("...")
|
|
20
|
+
continue
|
|
21
|
+
parts.append(f"{c_type} {name}")
|
|
22
|
+
return ", ".join(parts)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def render_arg_list(parameters: list[dict[str, Any]]) -> str:
|
|
26
|
+
if not parameters:
|
|
27
|
+
return ""
|
|
28
|
+
names: list[str] = []
|
|
29
|
+
for index, param in enumerate(parameters):
|
|
30
|
+
c_type = str(param.get("c_type") or "void")
|
|
31
|
+
if c_type == "...":
|
|
32
|
+
continue
|
|
33
|
+
name = str(param.get("name") or f"arg{index}")
|
|
34
|
+
names.append(name)
|
|
35
|
+
return ", ".join(names)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass(frozen=True)
|
|
39
|
+
class NativeExportRenderOptions:
|
|
40
|
+
header_include: str
|
|
41
|
+
impl_header_include: str
|
|
42
|
+
api_macro: str
|
|
43
|
+
call_macro: str
|
|
44
|
+
impl_prefix: str
|
|
45
|
+
symbol_prefix: str
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def build_impl_name(function_name: str, options: NativeExportRenderOptions) -> str:
|
|
49
|
+
if options.symbol_prefix and function_name.startswith(options.symbol_prefix):
|
|
50
|
+
return options.impl_prefix + function_name[len(options.symbol_prefix):]
|
|
51
|
+
return options.impl_prefix + function_name
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def render_impl_header(functions: list[dict[str, Any]], options: NativeExportRenderOptions) -> str:
|
|
55
|
+
lines: list[str] = []
|
|
56
|
+
lines.append("#pragma once")
|
|
57
|
+
lines.append("")
|
|
58
|
+
lines.append(f"#include \"{options.header_include}\"")
|
|
59
|
+
lines.append("")
|
|
60
|
+
lines.append("#ifdef __cplusplus")
|
|
61
|
+
lines.append("extern \"C\" {")
|
|
62
|
+
lines.append("#endif")
|
|
63
|
+
lines.append("")
|
|
64
|
+
for item in functions:
|
|
65
|
+
name = str(item.get("name") or "")
|
|
66
|
+
if not name:
|
|
67
|
+
continue
|
|
68
|
+
return_type = normalize_type(str(item.get("c_return_type") or "void"))
|
|
69
|
+
parameters = item.get("parameters")
|
|
70
|
+
if not isinstance(parameters, list):
|
|
71
|
+
parameters = []
|
|
72
|
+
signature = render_params(parameters)
|
|
73
|
+
impl_name = build_impl_name(name, options)
|
|
74
|
+
lines.append(f"{return_type} {options.call_macro} {impl_name}({signature});")
|
|
75
|
+
lines.append("")
|
|
76
|
+
lines.append("#ifdef __cplusplus")
|
|
77
|
+
lines.append("}")
|
|
78
|
+
lines.append("#endif")
|
|
79
|
+
lines.append("")
|
|
80
|
+
return "\n".join(lines)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def render_exports(
|
|
84
|
+
functions: list[dict[str, Any]],
|
|
85
|
+
options: NativeExportRenderOptions,
|
|
86
|
+
generator_path: str,
|
|
87
|
+
) -> str:
|
|
88
|
+
lines: list[str] = []
|
|
89
|
+
lines.append("// <auto-generated />")
|
|
90
|
+
lines.append(f"// Generated by {generator_path}")
|
|
91
|
+
lines.append(f"#include \"{options.header_include}\"")
|
|
92
|
+
lines.append(f"#include \"{options.impl_header_include}\"")
|
|
93
|
+
lines.append("")
|
|
94
|
+
lines.append("#ifdef __cplusplus")
|
|
95
|
+
lines.append("extern \"C\" {")
|
|
96
|
+
lines.append("#endif")
|
|
97
|
+
lines.append("")
|
|
98
|
+
for item in functions:
|
|
99
|
+
name = str(item.get("name") or "")
|
|
100
|
+
if not name:
|
|
101
|
+
continue
|
|
102
|
+
return_type = normalize_type(str(item.get("c_return_type") or "void"))
|
|
103
|
+
parameters = item.get("parameters")
|
|
104
|
+
if not isinstance(parameters, list):
|
|
105
|
+
parameters = []
|
|
106
|
+
signature = render_params(parameters)
|
|
107
|
+
args = render_arg_list(parameters)
|
|
108
|
+
impl_name = build_impl_name(name, options)
|
|
109
|
+
lines.append(f"{options.api_macro} {return_type} {options.call_macro} {name}({signature}) {{")
|
|
110
|
+
if return_type == "void":
|
|
111
|
+
if args:
|
|
112
|
+
lines.append(f" {impl_name}({args});")
|
|
113
|
+
else:
|
|
114
|
+
lines.append(f" {impl_name}();")
|
|
115
|
+
lines.append("}")
|
|
116
|
+
else:
|
|
117
|
+
if args:
|
|
118
|
+
lines.append(f" return {impl_name}({args});")
|
|
119
|
+
else:
|
|
120
|
+
lines.append(f" return {impl_name}();")
|
|
121
|
+
lines.append("}")
|
|
122
|
+
lines.append("")
|
|
123
|
+
lines.append("#ifdef __cplusplus")
|
|
124
|
+
lines.append("}")
|
|
125
|
+
lines.append("#endif")
|
|
126
|
+
lines.append("")
|
|
127
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def iter_strings(value: Any) -> list[str]:
|
|
8
|
+
if isinstance(value, str):
|
|
9
|
+
return [value]
|
|
10
|
+
result: list[str] = []
|
|
11
|
+
if isinstance(value, list):
|
|
12
|
+
for item in value:
|
|
13
|
+
result.extend(iter_strings(item))
|
|
14
|
+
elif isinstance(value, dict):
|
|
15
|
+
for item in value.values():
|
|
16
|
+
result.extend(iter_strings(item))
|
|
17
|
+
return result
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _first_capture(match: re.Match[str]) -> str:
|
|
21
|
+
if match.lastindex:
|
|
22
|
+
for index in range(1, match.lastindex + 1):
|
|
23
|
+
value = match.group(index)
|
|
24
|
+
if value:
|
|
25
|
+
return value
|
|
26
|
+
return match.group(0)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def derive_required_functions(
|
|
30
|
+
payload: dict[str, Any],
|
|
31
|
+
idl_names: set[str],
|
|
32
|
+
native_call_patterns: list[str],
|
|
33
|
+
function_name_pattern: str | None = None,
|
|
34
|
+
) -> list[str]:
|
|
35
|
+
compiled_patterns: list[re.Pattern[str]] = []
|
|
36
|
+
for raw in native_call_patterns:
|
|
37
|
+
try:
|
|
38
|
+
compiled_patterns.append(re.compile(raw))
|
|
39
|
+
except re.error as exc:
|
|
40
|
+
raise SystemExit(f"Invalid native call regex pattern '{raw}': {exc}") from exc
|
|
41
|
+
|
|
42
|
+
function_filter: re.Pattern[str] | None = None
|
|
43
|
+
if function_name_pattern:
|
|
44
|
+
try:
|
|
45
|
+
function_filter = re.compile(function_name_pattern)
|
|
46
|
+
except re.error as exc:
|
|
47
|
+
raise SystemExit(f"Invalid function_name_pattern '{function_name_pattern}': {exc}") from exc
|
|
48
|
+
|
|
49
|
+
discovered: set[str] = set()
|
|
50
|
+
for text in iter_strings(payload):
|
|
51
|
+
for pattern in compiled_patterns:
|
|
52
|
+
for match in pattern.finditer(text):
|
|
53
|
+
candidate = _first_capture(match)
|
|
54
|
+
if function_filter and not function_filter.fullmatch(candidate):
|
|
55
|
+
continue
|
|
56
|
+
if candidate in idl_names:
|
|
57
|
+
discovered.add(candidate)
|
|
58
|
+
return sorted(discovered)
|