Skip to content

Commit 0dbd5fc

Browse files
committed
Add hook system for binding generation
1 parent ba0edfe commit 0dbd5fc

6 files changed

Lines changed: 114 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"
@@ -685,7 +716,7 @@ def generate_builtin_class_vararg_method_implements_header(builtin_classes):
685716
return "\n".join(result)
686717

687718

688-
def generate_builtin_class_header(builtin_api, size, used_classes, fully_used_classes):
719+
def generate_builtin_class_header(builtin_api, size, used_classes, fully_used_classes, hooks=None):
689720
result = []
690721

691722
class_name = builtin_api["name"]
@@ -1175,10 +1206,13 @@ def generate_builtin_class_header(builtin_api, size, used_classes, fully_used_cl
11751206

11761207
result.append("")
11771208

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

11801214

1181-
def generate_builtin_class_source(builtin_api, size, used_classes, fully_used_classes):
1215+
def generate_builtin_class_source(builtin_api, size, used_classes, fully_used_classes, hooks=None):
11821216
result = []
11831217

11841218
class_name = builtin_api["name"]
@@ -1490,10 +1524,13 @@ def generate_builtin_class_source(builtin_api, size, used_classes, fully_used_cl
14901524
result.append("} //namespace godot")
14911525
result.append("")
14921526

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

14951532

1496-
def generate_engine_classes_bindings(api, output_dir, use_template_get_node):
1533+
def generate_engine_classes_bindings(api, output_dir, use_template_get_node, hooks=None):
14971534
global engine_classes
14981535
global singletons
14991536
global native_structures
@@ -1685,12 +1722,12 @@ def generate_engine_classes_bindings(api, output_dir, use_template_get_node):
16851722

16861723
with header_filename.open("w+", encoding="utf-8") as header_file:
16871724
header_file.write(
1688-
generate_engine_class_header(class_api, used_classes, fully_used_classes, use_template_get_node)
1725+
generate_engine_class_header(class_api, used_classes, fully_used_classes, use_template_get_node, hooks)
16891726
)
16901727

16911728
with source_filename.open("w+", encoding="utf-8") as source_file:
16921729
source_file.write(
1693-
generate_engine_class_source(class_api, used_classes, fully_used_classes, use_template_get_node)
1730+
generate_engine_class_source(class_api, used_classes, fully_used_classes, use_template_get_node, hooks)
16941731
)
16951732

16961733
for native_struct in api["native_structures"]:
@@ -1749,7 +1786,7 @@ def generate_engine_classes_bindings(api, output_dir, use_template_get_node):
17491786
header_file.write("\n".join(result))
17501787

17511788

1752-
def generate_engine_class_header(class_api, used_classes, fully_used_classes, use_template_get_node):
1789+
def generate_engine_class_header(class_api, used_classes, fully_used_classes, use_template_get_node, hooks):
17531790
global singletons
17541791
result = []
17551792

@@ -2068,10 +2105,13 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
20682105

20692106
result.append("")
20702107

2108+
if hooks:
2109+
result = hooks.alter_engine_class_header(class_api, result)
2110+
20712111
return "\n".join(result)
20722112

20732113

2074-
def generate_engine_class_source(class_api, used_classes, fully_used_classes, use_template_get_node):
2114+
def generate_engine_class_source(class_api, used_classes, fully_used_classes, use_template_get_node, hooks=None):
20752115
global singletons
20762116
result = []
20772117

@@ -2246,10 +2286,13 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us
22462286
result.append("} // namespace godot")
22472287
result.append("")
22482288

2289+
if hooks:
2290+
result = hooks.alter_engine_class_source(class_api, result)
2291+
22492292
return "\n".join(result)
22502293

22512294

2252-
def generate_global_constants(api, output_dir):
2295+
def generate_global_constants(api, output_dir, hooks=None):
22532296
include_gen_folder = Path(output_dir) / "include" / "godot_cpp" / "classes"
22542297
source_gen_folder = Path(output_dir) / "src" / "classes"
22552298

@@ -2310,6 +2353,9 @@ def generate_global_constants(api, output_dir):
23102353

23112354
header.append("")
23122355

2356+
if hooks:
2357+
header = hooks.alter_global_constants(api, header)
2358+
23132359
with header_filename.open("w+", encoding="utf-8") as header_file:
23142360
header_file.write("\n".join(header))
23152361

@@ -2376,7 +2422,7 @@ def generate_global_constant_binds(api, output_dir):
23762422
header_file.write("\n".join(header))
23772423

23782424

2379-
def generate_utility_functions(api, output_dir):
2425+
def generate_utility_functions(api, output_dir, hooks=None):
23802426
include_gen_folder = Path(output_dir) / "include" / "godot_cpp" / "variant"
23812427
source_gen_folder = Path(output_dir) / "src" / "variant"
23822428

@@ -2430,6 +2476,9 @@ def generate_utility_functions(api, output_dir):
24302476
header.append("} // namespace godot")
24312477
header.append("")
24322478

2479+
if hooks:
2480+
header = hooks.alter_utility_functions_header(api, header)
2481+
24332482
with header_filename.open("w+", encoding="utf-8") as header_file:
24342483
header_file.write("\n".join(header))
24352484

@@ -2509,6 +2558,10 @@ def generate_utility_functions(api, output_dir):
25092558
source.append("")
25102559

25112560
source.append("} // namespace godot")
2561+
source.append("")
2562+
2563+
if hooks:
2564+
header = hooks.alter_utility_functions_source(api, source)
25122565

25132566
with source_filename.open("w+", encoding="utf-8") as source_file:
25142567
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: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
class BindingGeneratorHooks:
2+
def alter_engine_class_header(self, class_api, lines):
3+
return lines
4+
5+
def alter_engine_class_source(self, class_api, lines):
6+
return lines
7+
8+
def alter_global_constants(self, api, lines):
9+
return lines
10+
11+
def alter_utility_functions_header(self, api, lines):
12+
return lines
13+
14+
def alter_utility_functions_source(self, api, lines):
15+
return lines
16+
17+
def alter_builtin_class_header(self, builtin_api, lines):
18+
return lines
19+
20+
def alter_builtin_class_source(self, builtin_api, lines):
21+
return lines

tools/godotcpp.py

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

165165
api = generate_trimmed_api(str(source[0]), profile_filepath)
166166

167+
binding_hooks = env.get("binding_hooks", None)
168+
167169
_generate_bindings(
168170
api,
169171
str(source[0]),
@@ -172,6 +174,7 @@ def scons_generate_bindings(target, source, env):
172174
"32" if "32" in env["arch"] else "64",
173175
env["precision"],
174176
env["godot_cpp_gen_dir"],
177+
binding_hooks,
175178
)
176179
return None
177180

0 commit comments

Comments
 (0)