Skip to content

Commit d15d0c8

Browse files
authored
Merge pull request #2023 from Togira123/hook-mvp-v2
[Feature] Hook System for Binding Generation
2 parents 65df905 + 4027147 commit d15d0c8

6 files changed

Lines changed: 121 additions & 20 deletions

File tree

‎SConstruct‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ except Exception:
1919

2020
env.PrependENVPath("PATH", os.getenv("PATH"))
2121

22+
try:
23+
Import("binding_hooks")
24+
except Exception:
25+
# binding_hooks was not exported by the user's env
26+
binding_hooks = None
27+
28+
env["binding_hooks"] = binding_hooks
29+
2230
# Custom options and profile flags.
2331
customs = ["custom.py"]
2432
try:

‎binding_generator.py‎

Lines changed: 72 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
#!/usr/bin/env python
22

3+
import importlib.util
34
import json
45
import re
56
import shutil
7+
import sys
68
from pathlib import Path
79

810
from make_interface_header import generate_gdextension_interface_header
@@ -284,16 +286,45 @@ def print_file_list(api_filepath, output_dir, headers=False, sources=False):
284286

285287

286288
def generate_bindings(
287-
api_filepath, interface_filepath, use_template_get_node, bits="64", precision="single", output_dir="."
289+
api_filepath,
290+
interface_filepath,
291+
use_template_get_node,
292+
bits="64",
293+
precision="single",
294+
output_dir=".",
295+
hooks_path=None,
288296
):
289297
api = {}
290298
with open(api_filepath, encoding="utf-8") as api_file:
291299
api = json.load(api_file)
292-
_generate_bindings(api, api_filepath, interface_filepath, use_template_get_node, bits, precision, output_dir)
300+
custom_hooks = None
301+
if hooks_path:
302+
# load the file dynamically
303+
try:
304+
spec = importlib.util.spec_from_file_location("custom_binding_generator_hooks", hooks_path)
305+
loaded_module = importlib.util.module_from_spec(spec)
306+
sys.modules["custom_binding_generator_hooks"] = loaded_module
307+
spec.loader.exec_module(loaded_module)
308+
# assume the class is named 'CustomBindingGeneratorHooks'
309+
custom_hooks = loaded_module.CustomBindingGeneratorHooks()
310+
except Exception:
311+
raise Exception(
312+
"Failed to load custom binding generator hooks. Make sure your path points to a python file which defines a class named 'BindingGeneratorHooks'"
313+
)
314+
_generate_bindings(
315+
api, api_filepath, interface_filepath, use_template_get_node, bits, precision, output_dir, custom_hooks
316+
)
293317

294318

295319
def _generate_bindings(
296-
api, api_filepath, interface_filepath, use_template_get_node, bits="64", precision="single", output_dir="."
320+
api,
321+
api_filepath,
322+
interface_filepath,
323+
use_template_get_node,
324+
bits="64",
325+
precision="single",
326+
output_dir=".",
327+
hooks=None,
297328
):
298329
if "precision" in api["header"] and precision != api["header"]["precision"]:
299330
raise Exception(
@@ -318,12 +349,12 @@ def _generate_bindings(
318349

319350
generate_gdextension_interface_loader(interface_filepath, target_dir)
320351

321-
generate_global_constants(api, target_dir)
352+
generate_global_constants(api, target_dir, hooks)
322353
generate_version_header(api, target_dir)
323354
generate_global_constant_binds(api, target_dir)
324-
generate_builtin_bindings(api, target_dir, real_t + "_" + bits)
325-
generate_engine_classes_bindings(api, target_dir, use_template_get_node)
326-
generate_utility_functions(api, target_dir)
355+
generate_builtin_bindings(api, target_dir, real_t + "_" + bits, hooks)
356+
generate_engine_classes_bindings(api, target_dir, use_template_get_node, hooks)
357+
generate_utility_functions(api, target_dir, hooks)
327358

328359

329360
def generate_gdextension_interface_loader(interface_filepath, output_dir):
@@ -502,7 +533,7 @@ def generate_gdextension_interface_loader_source(data):
502533
singletons = []
503534

504535

505-
def generate_builtin_bindings(api, output_dir, build_config):
536+
def generate_builtin_bindings(api, output_dir, build_config, hooks=None):
506537
global builtin_classes
507538

508539
core_gen_folder = Path(output_dir) / "include" / "godot_cpp" / "core"
@@ -606,10 +637,10 @@ def generate_builtin_bindings(api, output_dir, build_config):
606637
fully_used_classes.sort()
607638

608639
with header_filename.open("w+", encoding="utf-8") as header_file:
609-
header_file.write(generate_builtin_class_header(builtin_api, size, used_classes, fully_used_classes))
640+
header_file.write(generate_builtin_class_header(builtin_api, size, used_classes, fully_used_classes, hooks))
610641

611642
with source_filename.open("w+", encoding="utf-8") as source_file:
612-
source_file.write(generate_builtin_class_source(builtin_api, size, used_classes, fully_used_classes))
643+
source_file.write(generate_builtin_class_source(builtin_api, size, used_classes, fully_used_classes, hooks))
613644

614645
# Create a header with all builtin types for convenience.
615646
builtin_header_filename = include_gen_folder / "builtin_types.hpp"
@@ -684,7 +715,7 @@ def generate_builtin_class_vararg_method_implements_header(builtin_classes):
684715
return "\n".join(result)
685716

686717

687-
def generate_builtin_class_header(builtin_api, size, used_classes, fully_used_classes):
718+
def generate_builtin_class_header(builtin_api, size, used_classes, fully_used_classes, hooks=None):
688719
result = []
689720

690721
class_name = builtin_api["name"]
@@ -1174,10 +1205,13 @@ def generate_builtin_class_header(builtin_api, size, used_classes, fully_used_cl
11741205

11751206
result.append("")
11761207

1208+
if hooks:
1209+
result = hooks.alter_builtin_class_header(builtin_api, result)
1210+
11771211
return "\n".join(result)
11781212

11791213

1180-
def generate_builtin_class_source(builtin_api, size, used_classes, fully_used_classes):
1214+
def generate_builtin_class_source(builtin_api, size, used_classes, fully_used_classes, hooks=None):
11811215
result = []
11821216

11831217
class_name = builtin_api["name"]
@@ -1489,10 +1523,13 @@ def generate_builtin_class_source(builtin_api, size, used_classes, fully_used_cl
14891523
result.append("} //namespace godot")
14901524
result.append("")
14911525

1526+
if hooks:
1527+
result = hooks.alter_builtin_class_source(builtin_api, result)
1528+
14921529
return "\n".join(result)
14931530

14941531

1495-
def generate_engine_classes_bindings(api, output_dir, use_template_get_node):
1532+
def generate_engine_classes_bindings(api, output_dir, use_template_get_node, hooks=None):
14961533
global engine_classes
14971534
global singletons
14981535
global native_structures
@@ -1684,12 +1721,12 @@ def generate_engine_classes_bindings(api, output_dir, use_template_get_node):
16841721

16851722
with header_filename.open("w+", encoding="utf-8") as header_file:
16861723
header_file.write(
1687-
generate_engine_class_header(class_api, used_classes, fully_used_classes, use_template_get_node)
1724+
generate_engine_class_header(class_api, used_classes, fully_used_classes, use_template_get_node, hooks)
16881725
)
16891726

16901727
with source_filename.open("w+", encoding="utf-8") as source_file:
16911728
source_file.write(
1692-
generate_engine_class_source(class_api, used_classes, fully_used_classes, use_template_get_node)
1729+
generate_engine_class_source(class_api, used_classes, fully_used_classes, use_template_get_node, hooks)
16931730
)
16941731

16951732
for native_struct in api["native_structures"]:
@@ -1748,7 +1785,7 @@ def generate_engine_classes_bindings(api, output_dir, use_template_get_node):
17481785
header_file.write("\n".join(result))
17491786

17501787

1751-
def generate_engine_class_header(class_api, used_classes, fully_used_classes, use_template_get_node):
1788+
def generate_engine_class_header(class_api, used_classes, fully_used_classes, use_template_get_node, hooks):
17521789
global singletons
17531790
result = []
17541791

@@ -2073,10 +2110,13 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
20732110

20742111
result.append("")
20752112

2113+
if hooks:
2114+
result = hooks.alter_engine_class_header(class_api, result)
2115+
20762116
return "\n".join(result)
20772117

20782118

2079-
def generate_engine_class_source(class_api, used_classes, fully_used_classes, use_template_get_node):
2119+
def generate_engine_class_source(class_api, used_classes, fully_used_classes, use_template_get_node, hooks=None):
20802120
global singletons
20812121
result = []
20822122

@@ -2254,10 +2294,13 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us
22542294
result.append("} // namespace godot")
22552295
result.append("")
22562296

2297+
if hooks:
2298+
result = hooks.alter_engine_class_source(class_api, result)
2299+
22572300
return "\n".join(result)
22582301

22592302

2260-
def generate_global_constants(api, output_dir):
2303+
def generate_global_constants(api, output_dir, hooks=None):
22612304
include_gen_folder = Path(output_dir) / "include" / "godot_cpp" / "classes"
22622305
source_gen_folder = Path(output_dir) / "src" / "classes"
22632306

@@ -2318,6 +2361,9 @@ def generate_global_constants(api, output_dir):
23182361

23192362
header.append("")
23202363

2364+
if hooks:
2365+
header = hooks.alter_global_constants(api, header)
2366+
23212367
with header_filename.open("w+", encoding="utf-8") as header_file:
23222368
header_file.write("\n".join(header))
23232369

@@ -2384,7 +2430,7 @@ def generate_global_constant_binds(api, output_dir):
23842430
header_file.write("\n".join(header))
23852431

23862432

2387-
def generate_utility_functions(api, output_dir):
2433+
def generate_utility_functions(api, output_dir, hooks=None):
23882434
include_gen_folder = Path(output_dir) / "include" / "godot_cpp" / "variant"
23892435
source_gen_folder = Path(output_dir) / "src" / "variant"
23902436

@@ -2438,6 +2484,9 @@ def generate_utility_functions(api, output_dir):
24382484
header.append("} // namespace godot")
24392485
header.append("")
24402486

2487+
if hooks:
2488+
header = hooks.alter_utility_functions_header(api, header)
2489+
24412490
with header_filename.open("w+", encoding="utf-8") as header_file:
24422491
header_file.write("\n".join(header))
24432492

@@ -2517,6 +2566,10 @@ def generate_utility_functions(api, output_dir):
25172566
source.append("")
25182567

25192568
source.append("} // namespace godot")
2569+
source.append("")
2570+
2571+
if hooks:
2572+
header = hooks.alter_utility_functions_source(api, source)
25202573

25212574
with source_filename.open("w+", encoding="utf-8") as source_file:
25222575
source_file.write("\n".join(source))

‎cmake/GodotCPPModule.cmake‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ function(
9191
BITS
9292
PRECISION
9393
OUTPUT_DIR
94+
BINDING_HOOK_FILE
9495
)
9596
# This code snippet will be squashed into a single line
9697
set(PYTHON_SCRIPT
@@ -101,7 +102,8 @@ function(
101102
use_template_get_node='${USE_TEMPLATE_GET_NODE}',
102103
bits='${BITS}',
103104
precision='${PRECISION}',
104-
output_dir='${OUTPUT_DIR}')"
105+
output_dir='${OUTPUT_DIR}',
106+
hooks_path='${BINDING_HOOK_FILE}')"
105107
)
106108

107109
message(DEBUG "Python:\n${PYTHON_SCRIPT}")

‎cmake/godotcpp.cmake‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,12 @@ function(godotcpp_options)
145145
"Path to a custom GDExtension API JSON file (takes precedence over `GODOTCPP_GDEXTENSION_DIR` and `GODOTCPP_API_VERSION`) ( /path/to/custom_api_file )"
146146
)
147147

148+
set(GODOTCPP_BINDING_HOOK_FILE
149+
""
150+
CACHE FILEPATH
151+
"Path to a Python file defining custom binding generator hooks. The file has to contain a class named `CustomBindingGeneratorHooks`"
152+
)
153+
148154
#TODO generate_bindings
149155

150156
option(GODOTCPP_GENERATE_TEMPLATE_GET_NODE "Generate a template version of the Node class's get_node. (ON|OFF)" ON)
@@ -309,6 +315,7 @@ function(godotcpp_generate)
309315
"${BITS}"
310316
"${GODOTCPP_PRECISION}"
311317
"${CMAKE_CURRENT_BINARY_DIR}"
318+
"${GODOTCPP_BINDING_HOOK_FILE}"
312319
)
313320

314321
### Platform is derived from the toolchain target

‎tools/binding_generator_hooks.py‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
class BindingGeneratorHooks:
2+
# Alter all class header files generated in `include/godot_cpp/classes`, such as `node_3d.hpp`, `object.hpp`, and `resource.hpp`.
3+
def alter_engine_class_header(self, class_api, lines):
4+
return lines
5+
6+
# Alter all class source files generated in `src/classes`, the `.cpp` counterparts to the headers modified by `alter_engine_class_header`.
7+
def alter_engine_class_source(self, class_api, lines):
8+
return lines
9+
10+
# Alter the `include/godot_cpp/classes/global_constants.hpp` file, which defines many enums.
11+
def alter_global_constants(self, api, lines):
12+
return lines
13+
14+
# Alter the `include/godot_cpp/variant/utility_functions.hpp` file, which contains utility functions such as math or print functions.
15+
def alter_utility_functions_header(self, api, lines):
16+
return lines
17+
18+
# Alter the `src/variant/utility_functions.cpp` file, which provides implementations of the declarations in the header file.
19+
def alter_utility_functions_source(self, api, lines):
20+
return lines
21+
22+
# Alter built-in class headers generated in `include/godot_cpp/variant/`. Built-in classes include `Dictionary`, `PackedInt32Array`, and `StringName`.
23+
def alter_builtin_class_header(self, builtin_api, lines):
24+
return lines
25+
26+
# Alter built-in class sources generated in `src/variant/`, the implementations of the built-in classes.
27+
def alter_builtin_class_source(self, builtin_api, lines):
28+
return lines

‎tools/godotcpp.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,8 @@ def scons_generate_bindings(target, source, env):
162162

163163
api = generate_trimmed_api(str(source[0]), profile_filepath)
164164

165+
binding_hooks = env.get("binding_hooks", None)
166+
165167
_generate_bindings(
166168
api,
167169
str(source[0]),
@@ -170,6 +172,7 @@ def scons_generate_bindings(target, source, env):
170172
"32" if "32" in env["arch"] else "64",
171173
env["precision"],
172174
env["godot_cpp_gen_dir"],
175+
binding_hooks,
173176
)
174177
return None
175178

0 commit comments

Comments
 (0)