diff --git a/SConstruct b/SConstruct index 3ebdff941..f31ef2153 100644 --- a/SConstruct +++ b/SConstruct @@ -19,6 +19,14 @@ except Exception: env.PrependENVPath("PATH", os.getenv("PATH")) +try: + Import("binding_hooks") +except Exception: + # binding_hooks was not exported by the user's env + binding_hooks = None + +env["binding_hooks"] = binding_hooks + # Custom options and profile flags. customs = ["custom.py"] try: diff --git a/binding_generator.py b/binding_generator.py index 1a401a3b8..3f5607fbc 100644 --- a/binding_generator.py +++ b/binding_generator.py @@ -1,8 +1,10 @@ #!/usr/bin/env python +import importlib.util import json import re import shutil +import sys from pathlib import Path 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): def generate_bindings( - api_filepath, interface_filepath, use_template_get_node, bits="64", precision="single", output_dir="." + api_filepath, + interface_filepath, + use_template_get_node, + bits="64", + precision="single", + output_dir=".", + hooks_path=None, ): api = {} with open(api_filepath, encoding="utf-8") as api_file: api = json.load(api_file) - _generate_bindings(api, api_filepath, interface_filepath, use_template_get_node, bits, precision, output_dir) + custom_hooks = None + if hooks_path: + # load the file dynamically + try: + spec = importlib.util.spec_from_file_location("custom_binding_generator_hooks", hooks_path) + loaded_module = importlib.util.module_from_spec(spec) + sys.modules["custom_binding_generator_hooks"] = loaded_module + spec.loader.exec_module(loaded_module) + # assume the class is named 'CustomBindingGeneratorHooks' + custom_hooks = loaded_module.CustomBindingGeneratorHooks() + except Exception: + raise Exception( + "Failed to load custom binding generator hooks. Make sure your path points to a python file which defines a class named 'BindingGeneratorHooks'" + ) + _generate_bindings( + api, api_filepath, interface_filepath, use_template_get_node, bits, precision, output_dir, custom_hooks + ) def _generate_bindings( - api, api_filepath, interface_filepath, use_template_get_node, bits="64", precision="single", output_dir="." + api, + api_filepath, + interface_filepath, + use_template_get_node, + bits="64", + precision="single", + output_dir=".", + hooks=None, ): if "precision" in api["header"] and precision != api["header"]["precision"]: raise Exception( @@ -318,12 +349,12 @@ def _generate_bindings( generate_gdextension_interface_loader(interface_filepath, target_dir) - generate_global_constants(api, target_dir) + generate_global_constants(api, target_dir, hooks) generate_version_header(api, target_dir) generate_global_constant_binds(api, target_dir) - generate_builtin_bindings(api, target_dir, real_t + "_" + bits) - generate_engine_classes_bindings(api, target_dir, use_template_get_node) - generate_utility_functions(api, target_dir) + generate_builtin_bindings(api, target_dir, real_t + "_" + bits, hooks) + generate_engine_classes_bindings(api, target_dir, use_template_get_node, hooks) + generate_utility_functions(api, target_dir, hooks) def generate_gdextension_interface_loader(interface_filepath, output_dir): @@ -502,7 +533,7 @@ def generate_gdextension_interface_loader_source(data): singletons = [] -def generate_builtin_bindings(api, output_dir, build_config): +def generate_builtin_bindings(api, output_dir, build_config, hooks=None): global builtin_classes core_gen_folder = Path(output_dir) / "include" / "godot_cpp" / "core" @@ -606,10 +637,10 @@ def generate_builtin_bindings(api, output_dir, build_config): fully_used_classes.sort() with header_filename.open("w+", encoding="utf-8") as header_file: - header_file.write(generate_builtin_class_header(builtin_api, size, used_classes, fully_used_classes)) + header_file.write(generate_builtin_class_header(builtin_api, size, used_classes, fully_used_classes, hooks)) with source_filename.open("w+", encoding="utf-8") as source_file: - source_file.write(generate_builtin_class_source(builtin_api, size, used_classes, fully_used_classes)) + source_file.write(generate_builtin_class_source(builtin_api, size, used_classes, fully_used_classes, hooks)) # Create a header with all builtin types for convenience. builtin_header_filename = include_gen_folder / "builtin_types.hpp" @@ -685,7 +716,7 @@ def generate_builtin_class_vararg_method_implements_header(builtin_classes): return "\n".join(result) -def generate_builtin_class_header(builtin_api, size, used_classes, fully_used_classes): +def generate_builtin_class_header(builtin_api, size, used_classes, fully_used_classes, hooks=None): result = [] class_name = builtin_api["name"] @@ -1175,10 +1206,13 @@ def generate_builtin_class_header(builtin_api, size, used_classes, fully_used_cl result.append("") + if hooks: + result = hooks.alter_builtin_class_header(builtin_api, result) + return "\n".join(result) -def generate_builtin_class_source(builtin_api, size, used_classes, fully_used_classes): +def generate_builtin_class_source(builtin_api, size, used_classes, fully_used_classes, hooks=None): result = [] class_name = builtin_api["name"] @@ -1490,10 +1524,13 @@ def generate_builtin_class_source(builtin_api, size, used_classes, fully_used_cl result.append("} //namespace godot") result.append("") + if hooks: + result = hooks.alter_builtin_class_source(builtin_api, result) + return "\n".join(result) -def generate_engine_classes_bindings(api, output_dir, use_template_get_node): +def generate_engine_classes_bindings(api, output_dir, use_template_get_node, hooks=None): global engine_classes global singletons global native_structures @@ -1685,12 +1722,12 @@ def generate_engine_classes_bindings(api, output_dir, use_template_get_node): with header_filename.open("w+", encoding="utf-8") as header_file: header_file.write( - generate_engine_class_header(class_api, used_classes, fully_used_classes, use_template_get_node) + generate_engine_class_header(class_api, used_classes, fully_used_classes, use_template_get_node, hooks) ) with source_filename.open("w+", encoding="utf-8") as source_file: source_file.write( - generate_engine_class_source(class_api, used_classes, fully_used_classes, use_template_get_node) + generate_engine_class_source(class_api, used_classes, fully_used_classes, use_template_get_node, hooks) ) for native_struct in api["native_structures"]: @@ -1749,7 +1786,7 @@ def generate_engine_classes_bindings(api, output_dir, use_template_get_node): header_file.write("\n".join(result)) -def generate_engine_class_header(class_api, used_classes, fully_used_classes, use_template_get_node): +def generate_engine_class_header(class_api, used_classes, fully_used_classes, use_template_get_node, hooks): global singletons result = [] @@ -2068,10 +2105,13 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us result.append("") + if hooks: + result = hooks.alter_engine_class_header(class_api, result) + return "\n".join(result) -def generate_engine_class_source(class_api, used_classes, fully_used_classes, use_template_get_node): +def generate_engine_class_source(class_api, used_classes, fully_used_classes, use_template_get_node, hooks=None): global singletons result = [] @@ -2246,10 +2286,13 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us result.append("} // namespace godot") result.append("") + if hooks: + result = hooks.alter_engine_class_source(class_api, result) + return "\n".join(result) -def generate_global_constants(api, output_dir): +def generate_global_constants(api, output_dir, hooks=None): include_gen_folder = Path(output_dir) / "include" / "godot_cpp" / "classes" source_gen_folder = Path(output_dir) / "src" / "classes" @@ -2310,6 +2353,9 @@ def generate_global_constants(api, output_dir): header.append("") + if hooks: + header = hooks.alter_global_constants(api, header) + with header_filename.open("w+", encoding="utf-8") as header_file: header_file.write("\n".join(header)) @@ -2376,7 +2422,7 @@ def generate_global_constant_binds(api, output_dir): header_file.write("\n".join(header)) -def generate_utility_functions(api, output_dir): +def generate_utility_functions(api, output_dir, hooks=None): include_gen_folder = Path(output_dir) / "include" / "godot_cpp" / "variant" source_gen_folder = Path(output_dir) / "src" / "variant" @@ -2430,6 +2476,9 @@ def generate_utility_functions(api, output_dir): header.append("} // namespace godot") header.append("") + if hooks: + header = hooks.alter_utility_functions_header(api, header) + with header_filename.open("w+", encoding="utf-8") as header_file: header_file.write("\n".join(header)) @@ -2509,6 +2558,10 @@ def generate_utility_functions(api, output_dir): source.append("") source.append("} // namespace godot") + source.append("") + + if hooks: + header = hooks.alter_utility_functions_source(api, source) with source_filename.open("w+", encoding="utf-8") as source_file: source_file.write("\n".join(source)) diff --git a/cmake/GodotCPPModule.cmake b/cmake/GodotCPPModule.cmake index 353978a7e..bc5d6a0dc 100644 --- a/cmake/GodotCPPModule.cmake +++ b/cmake/GodotCPPModule.cmake @@ -91,6 +91,7 @@ function( BITS PRECISION OUTPUT_DIR + BINDING_HOOK_FILE ) # This code snippet will be squashed into a single line set(PYTHON_SCRIPT @@ -101,7 +102,8 @@ function( use_template_get_node='${USE_TEMPLATE_GET_NODE}', bits='${BITS}', precision='${PRECISION}', - output_dir='${OUTPUT_DIR}')" + output_dir='${OUTPUT_DIR}', + hooks_path='${BINDING_HOOK_FILE}')" ) message(DEBUG "Python:\n${PYTHON_SCRIPT}") diff --git a/cmake/godotcpp.cmake b/cmake/godotcpp.cmake index 6158601dd..d5ab69af7 100644 --- a/cmake/godotcpp.cmake +++ b/cmake/godotcpp.cmake @@ -145,6 +145,12 @@ function(godotcpp_options) "Path to a custom GDExtension API JSON file (takes precedence over `GODOTCPP_GDEXTENSION_DIR` and `GODOTCPP_API_VERSION`) ( /path/to/custom_api_file )" ) + set(GODOTCPP_BINDING_HOOK_FILE + "" + CACHE FILEPATH + "Path to a Python file defining custom binding generator hooks. The file has to contain a class named `CustomBindingGeneratorHooks`" + ) + #TODO generate_bindings 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) "${BITS}" "${GODOTCPP_PRECISION}" "${CMAKE_CURRENT_BINARY_DIR}" + "${GODOTCPP_BINDING_HOOK_FILE}" ) ### Platform is derived from the toolchain target diff --git a/tools/binding_generator_hooks.py b/tools/binding_generator_hooks.py new file mode 100644 index 000000000..775088b16 --- /dev/null +++ b/tools/binding_generator_hooks.py @@ -0,0 +1,28 @@ +class BindingGeneratorHooks: + # Alter all class header files generated in `include/godot_cpp/classes`, such as `node_3d.hpp`, `object.hpp`, and `resource.hpp`. + def alter_engine_class_header(self, class_api, lines): + return lines + + # Alter all class source files generated in `src/classes`, the `.cpp` counterparts to the headers modified by `alter_engine_class_header`. + def alter_engine_class_source(self, class_api, lines): + return lines + + # Alter the `include/godot_cpp/classes/global_constants.hpp` file, which defines many enums. + def alter_global_constants(self, api, lines): + return lines + + # Alter the `include/godot_cpp/variant/utility_functions.hpp` file, which contains utility functions such as math or print functions. + def alter_utility_functions_header(self, api, lines): + return lines + + # Alter the `src/variant/utility_functions.cpp` file, which provides implementations of the declarations in the header file. + def alter_utility_functions_source(self, api, lines): + return lines + + # Alter built-in class headers generated in `include/godot_cpp/variant/`. Built-in classes include `Dictionary`, `PackedInt32Array`, and `StringName`. + def alter_builtin_class_header(self, builtin_api, lines): + return lines + + # Alter built-in class sources generated in `src/variant/`, the implementations of the built-in classes. + def alter_builtin_class_source(self, builtin_api, lines): + return lines diff --git a/tools/godotcpp.py b/tools/godotcpp.py index 456c52d0c..7180fa037 100644 --- a/tools/godotcpp.py +++ b/tools/godotcpp.py @@ -164,6 +164,8 @@ def scons_generate_bindings(target, source, env): api = generate_trimmed_api(str(source[0]), profile_filepath) + binding_hooks = env.get("binding_hooks", None) + _generate_bindings( api, str(source[0]), @@ -172,6 +174,7 @@ def scons_generate_bindings(target, source, env): "32" if "32" in env["arch"] else "64", env["precision"], env["godot_cpp_gen_dir"], + binding_hooks, ) return None