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.
Files changed (49) hide show
  1. abi_forge-1.0.0/PKG-INFO +166 -0
  2. abi_forge-1.0.0/README.md +144 -0
  3. abi_forge-1.0.0/abi_codegen_core/src/abi_codegen_core/__init__.py +13 -0
  4. abi_forge-1.0.0/abi_codegen_core/src/abi_codegen_core/common.py +33 -0
  5. abi_forge-1.0.0/abi_codegen_core/src/abi_codegen_core/native_exports.py +127 -0
  6. abi_forge-1.0.0/abi_codegen_core/src/abi_codegen_core/required_functions.py +58 -0
  7. abi_forge-1.0.0/abi_forge.egg-info/PKG-INFO +166 -0
  8. abi_forge-1.0.0/abi_forge.egg-info/SOURCES.txt +47 -0
  9. abi_forge-1.0.0/abi_forge.egg-info/dependency_links.txt +1 -0
  10. abi_forge-1.0.0/abi_forge.egg-info/entry_points.txt +5 -0
  11. abi_forge-1.0.0/abi_forge.egg-info/top_level.txt +3 -0
  12. abi_forge-1.0.0/generator_sdk/__init__.py +5 -0
  13. abi_forge-1.0.0/generator_sdk/external_generator_stub.py +33 -0
  14. abi_forge-1.0.0/generator_sdk/go_bindings_generator.py +634 -0
  15. abi_forge-1.0.0/generator_sdk/managed_api_codegen.py +386 -0
  16. abi_forge-1.0.0/generator_sdk/managed_api_metadata_generator.py +202 -0
  17. abi_forge-1.0.0/generator_sdk/managed_api_scaffold_generator.py +682 -0
  18. abi_forge-1.0.0/generator_sdk/managed_bindings_scaffold_generator.py +121 -0
  19. abi_forge-1.0.0/generator_sdk/native_exports_generator.py +80 -0
  20. abi_forge-1.0.0/generator_sdk/plugin.manifest.json +376 -0
  21. abi_forge-1.0.0/generator_sdk/python_bindings_generator.py +726 -0
  22. abi_forge-1.0.0/generator_sdk/rust_ffi_generator.py +488 -0
  23. abi_forge-1.0.0/generator_sdk/symbol_contract_generator.py +346 -0
  24. abi_forge-1.0.0/generator_sdk/typescript_bindings_generator.py +634 -0
  25. abi_forge-1.0.0/pyproject.toml +49 -0
  26. abi_forge-1.0.0/setup.cfg +4 -0
  27. abi_forge-1.0.0/src/abi_framework_core/__init__.py +3 -0
  28. abi_forge-1.0.0/src/abi_framework_core/_core_base.py +1491 -0
  29. abi_forge-1.0.0/src/abi_framework_core/_core_codegen.py +1403 -0
  30. abi_forge-1.0.0/src/abi_framework_core/_core_compare.py +1076 -0
  31. abi_forge-1.0.0/src/abi_framework_core/_core_orchestration.py +318 -0
  32. abi_forge-1.0.0/src/abi_framework_core/_core_plugins.py +467 -0
  33. abi_forge-1.0.0/src/abi_framework_core/_core_policy.py +541 -0
  34. abi_forge-1.0.0/src/abi_framework_core/_core_snapshot.py +382 -0
  35. abi_forge-1.0.0/src/abi_framework_core/cli.py +411 -0
  36. abi_forge-1.0.0/src/abi_framework_core/commands/__init__.py +50 -0
  37. abi_forge-1.0.0/src/abi_framework_core/commands/bootstrap.py +187 -0
  38. abi_forge-1.0.0/src/abi_framework_core/commands/common.py +42 -0
  39. abi_forge-1.0.0/src/abi_framework_core/commands/generation.py +276 -0
  40. abi_forge-1.0.0/src/abi_framework_core/commands/governance.py +356 -0
  41. abi_forge-1.0.0/src/abi_framework_core/commands/performance.py +210 -0
  42. abi_forge-1.0.0/src/abi_framework_core/commands/plugins.py +288 -0
  43. abi_forge-1.0.0/src/abi_framework_core/commands/release.py +351 -0
  44. abi_forge-1.0.0/src/abi_framework_core/commands/status.py +204 -0
  45. abi_forge-1.0.0/src/abi_framework_core/commands/targets.py +391 -0
  46. abi_forge-1.0.0/src/abi_framework_core/commands/verification.py +250 -0
  47. abi_forge-1.0.0/src/abi_framework_core/core.py +10 -0
  48. abi_forge-1.0.0/tests/test_abi_framework.py +2775 -0
  49. abi_forge-1.0.0/tests/test_plugin_manifest.py +211 -0
@@ -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)