Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions SConstruct
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
91 changes: 72 additions & 19 deletions binding_generator.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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(
Expand All @@ -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):
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"]
Expand Down Expand Up @@ -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"]
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"]:
Expand Down Expand Up @@ -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 = []

Expand Down Expand Up @@ -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 = []

Expand Down Expand Up @@ -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"

Expand Down Expand Up @@ -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))

Expand Down Expand Up @@ -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"

Expand Down Expand Up @@ -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))

Expand Down Expand Up @@ -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))
Expand Down
4 changes: 3 additions & 1 deletion cmake/GodotCPPModule.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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}")
Expand Down
7 changes: 7 additions & 0 deletions cmake/godotcpp.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
28 changes: 28 additions & 0 deletions tools/binding_generator_hooks.py
Original file line number Diff line number Diff line change
@@ -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):
Comment thread
Togira123 marked this conversation as resolved.
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
3 changes: 3 additions & 0 deletions tools/godotcpp.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]),
Expand All @@ -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

Expand Down
Loading