Skip to content

Commit 4bd052c

Browse files
committed
Generate godot compat for dual build
generate compat generate compat Update ci.yml Update binding_generator.py generate compat generate compat lint python files Update compat_generator.py update docs Update binding_generator.py Update module_converter.py also collect defines Add module converter file that converts module based projects to godot_compat Update ci.yml update docs Update compat_generator.py lint python files generate compat generate compat generate compat generate compat Update ci.yml fix path issue when caling from outside Update binding_generator.py update to also take missing classes/structs Update binding_generator.py Generate godot compat for dual build generate compat generate compat Update ci.yml Update binding_generator.py generate compat generate compat lint python files Update compat_generator.py update docs Update binding_generator.py Update module_converter.py also collect defines Add module converter file that converts module based projects to godot_compat Update ci.yml update docs Update compat_generator.py lint python files generate compat generate compat generate compat generate compat Update ci.yml fix path issue when caling from outside Add support for build profiles. Allow enabling or disabling specific classes (which will not be built). Allow forwarding from `ClassDB` to `ClassDBSingleton` to support enumerations update to also take missing classes/structs Update binding_generator.py update update naming of files add godot mappings. update and run output_header_mapping.json Update README.md make godot_compat work without a file generated fix the test Update binding_generator.py Update binding_generator.py Update binding_generator.py use files from include too Update README.md lint lint lint Update CMakeLists.txt update to use all. fix linting a bit update wip fix posix path Update CMakeLists.txt Update binding_generator.py add using namespace godot; everywhere to includes fix includes Try fixes. generate new include files 123 Update binding_generator.py Update binding_generator.py Update binding_generator.py
1 parent 1cce4d1 commit 4bd052c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+716
-64
lines changed

CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ execute_process(COMMAND "${Python3_EXECUTABLE}" "-c" "import binding_generator;
143143
)
144144

145145
add_custom_command(OUTPUT ${GENERATED_FILES_LIST}
146-
COMMAND "${Python3_EXECUTABLE}" "-c" "import binding_generator; binding_generator.generate_bindings(\"${GODOT_GDEXTENSION_API_FILE}\", \"${GENERATE_BINDING_PARAMETERS}\", \"${BITS}\", \"${FLOAT_PRECISION}\", \"${CMAKE_CURRENT_BINARY_DIR}\")"
146+
COMMAND "${Python3_EXECUTABLE}" "-c" "import binding_generator; binding_generator.generate_bindings(\"${GODOT_GDEXTENSION_API_FILE}\", \"${GENERATE_BINDING_PARAMETERS}\", \"${BITS}\", \"${FLOAT_PRECISION}\", \"${CMAKE_CURRENT_BINARY_DIR}\", \"${GODOT_REPO}\")"
147147
VERBATIM
148148
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
149149
MAIN_DEPENDENCY ${GODOT_GDEXTENSION_API_FILE}

README.md

+61
Original file line numberDiff line numberDiff line change
@@ -147,3 +147,64 @@ generic reusable template.
147147

148148
Or checkout the code for the [Summator example](https://github.com/paddy-exe/GDExtensionSummator)
149149
as shown in the [official documentation](https://docs.godotengine.org/en/latest/tutorials/scripting/gdextension/gdextension_cpp_example.html).
150+
151+
## Godot and Godot Cpp Compatibility
152+
153+
If you intend to target both building as a GDExtension and as a module using godot repo, you can generate compatibility includes that will target either GDExtension or module, based on the GODOT_MODULE_COMPAT define.
154+
155+
If you want such headers, when running the build command, `scons`, pass the `godot_repo` param with the path to the godot repository. Eg. if you have the godot repository cloned at path `../godot`, then do:
156+
157+
```sh
158+
scons godot_repo="../godot"
159+
```
160+
161+
This will generate something like this:
162+
```
163+
gen/include/godot_cpp/..
164+
gen/include/godot_compat/..
165+
```
166+
167+
Now, all you need to do is when writing your addon/module, replace includes like these:
168+
169+
```cpp
170+
#include <godot_cpp/classes/a_star_grid2d.hpp>
171+
```
172+
173+
with
174+
175+
```cpp
176+
#include <godot_compat/classes/a_star_grid2d.hpp>
177+
```
178+
179+
Inside, this file will have code for both godot and godot-cpp:
180+
181+
```cpp
182+
#ifdef GODOT_MODULE_COMPAT
183+
#include <core/math/a_star_grid_2d.h>
184+
#else
185+
#include <godot_cpp/classes/a_star_grid2d.hpp>
186+
#endif
187+
```
188+
189+
### Manually generate mapping files
190+
191+
The mappings can be manually generated by running the `compat_generator.py` script.
192+
193+
Example of how to run `compat_generator.py`:
194+
195+
```sh
196+
git clone godotengine/godot
197+
python compat_generator.py godot
198+
```
199+
200+
The first argument of `compat_generator.py` is the folder where the repo is (can be godot or godot-cpp repo). If this folder is not given, the current directory is assumed. The output of this is either `output_header_mapping_godot.json` or `output_header_mapping_godot_cpp.json`
201+
202+
### Manually match the mapping files
203+
204+
If you want to manually match the godot mapping file with the godot-cpp one, you can do that by running:
205+
206+
```sh
207+
python header_matcher.py
208+
```
209+
210+
This will generate the `header_matches.json` file with matches from godot and godot_cpp repo.

binding_generator.py

+58-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
import shutil
66
from pathlib import Path
77

8+
from compat_generator import map_header_files
9+
from header_matcher import match_headers
10+
811

912
def generate_mod_version(argcount, const=False, returns=False):
1013
s = """
@@ -378,11 +381,14 @@ def scons_generate_bindings(target, source, env):
378381
"32" if "32" in env["arch"] else "64",
379382
env["precision"],
380383
env["godot_cpp_gen_dir"],
384+
env["godot_repo"],
381385
)
382386
return None
383387

384388

385-
def generate_bindings(api_filepath, use_template_get_node, bits="64", precision="single", output_dir="."):
389+
def generate_bindings(
390+
api_filepath, use_template_get_node, bits="64", precision="single", output_dir=".", godot_repo=""
391+
):
386392
api = None
387393

388394
target_dir = Path(output_dir) / "gen"
@@ -402,6 +408,8 @@ def generate_bindings(api_filepath, use_template_get_node, bits="64", precision=
402408
generate_builtin_bindings(api, target_dir, real_t + "_" + bits)
403409
generate_engine_classes_bindings(api, target_dir, use_template_get_node)
404410
generate_utility_functions(api, target_dir)
411+
if godot_repo != "":
412+
generate_compat_includes(godot_repo, output_dir, target_dir)
405413

406414

407415
CLASS_ALIASES = {
@@ -1545,6 +1553,55 @@ def generate_engine_classes_bindings(api, output_dir, use_template_get_node):
15451553
header_file.write("\n".join(result))
15461554

15471555

1556+
def generate_compat_includes(godot_repo: Path, output_dir: Path, target_dir: Path):
1557+
# Uncomment this to change include files
1558+
# target_dir = output_dir
1559+
file_types_mapping_godot_cpp_gen = map_header_files(target_dir)
1560+
file_types_mapping_godot = map_header_files(godot_repo)
1561+
# Match the headers
1562+
file_types_mapping = match_headers(file_types_mapping_godot_cpp_gen, file_types_mapping_godot)
1563+
for file_godot_cpp_name, file_godot_names in file_types_mapping.items():
1564+
result = []
1565+
with (Path(target_dir) / Path(file_godot_cpp_name)).open("r", encoding="utf-8") as header_file:
1566+
content = header_file.readlines()
1567+
# Find the last line (#endif guard)
1568+
last_endif_idx = len(content) - 1
1569+
# Look for the marker for the header guard (usually #define)
1570+
marker_pos = next((i for i, line in enumerate(content) if line.startswith("#define")), -1)
1571+
1572+
if marker_pos != -1:
1573+
# Add content before the last #endif
1574+
before_marker = content[: marker_pos + 1] # Include up to the marker line
1575+
after_marker = content[marker_pos + 1 : last_endif_idx] # Content excluding the final #endif
1576+
1577+
result.extend(before_marker) # Append original content up to marker
1578+
result.append("\n") # Blank line for separation
1579+
1580+
# Insert the #ifdef block
1581+
result.append("#ifdef GODOT_MODULE_COMPAT\n")
1582+
if len(file_godot_names) == 0:
1583+
print("No header found for", file_godot_cpp_name)
1584+
for file_godot_name in file_godot_names:
1585+
result.append(f"#include <{Path(file_godot_name).as_posix()}>\n")
1586+
result.append("#else\n")
1587+
1588+
# for line in after_marker:
1589+
# if line.strip() not in {"namespace godot {", "} // namespace godot"}:
1590+
# result.append(line)
1591+
result.extend(after_marker)
1592+
1593+
# Add the namespace and endif
1594+
result.append("#endif\n")
1595+
1596+
# Finally, append the original final #endif line
1597+
result.append(content[last_endif_idx].strip())
1598+
1599+
else:
1600+
print(f"Marker not found in {file_godot_cpp_name}")
1601+
with (Path(target_dir) / Path(file_godot_cpp_name)).open("w+", encoding="utf-8") as header_file:
1602+
header_file.write("".join(result))
1603+
1604+
15481605
def generate_engine_class_header(class_api, used_classes, fully_used_classes, use_template_get_node):
15491606
global singletons
15501607
result = []

compat_generator.py

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#!/usr/bin/env python
2+
3+
import json
4+
import os
5+
import re
6+
import sys
7+
8+
9+
def parse_header_file(file_path):
10+
types = {"classes": [], "structs": [], "defines": [], "enums": []}
11+
12+
with open(file_path, "r", encoding="utf-8") as file:
13+
content = file.read()
14+
15+
# Regular expressions to match different types
16+
struct_pattern = r"struct.*\s(\S+)\s*\{"
17+
type_pattern = r"typedef.*\s(\S+)\s*\;"
18+
enum_pattern = r"enum.*\s(\S+)\s*\{"
19+
class_pattern = r"class\s+[\w\s]*?([a-zA-Z_]\w*)\s*[:{]"
20+
define_pattern = r"#define\s+([a-zA-Z_]\w*)"
21+
22+
# Extract classes
23+
types["classes"] += re.findall(class_pattern, content)
24+
25+
# Extract structs
26+
struct_names = re.findall(struct_pattern, content)
27+
types["structs"].extend(struct_names)
28+
type_names = re.findall(type_pattern, content)
29+
types["structs"].extend(type_names)
30+
enum_names = re.findall(enum_pattern, content)
31+
types["enums"].extend(enum_names)
32+
33+
# Extract defines
34+
define_matches = re.findall(define_pattern, content)
35+
types["defines"] += define_matches
36+
37+
# Debug the case where no classes or structs are found
38+
# if len(types["classes"]) == 0 and len(types["structs"]) == 0 and len(types["defines"]) == 0:
39+
# print(f"{file_path} missing things")
40+
return types
41+
42+
43+
def map_header_files(directory):
44+
file_types_mapping = {}
45+
for root, dirs, files in os.walk(directory):
46+
if "thirdparty" in dirs:
47+
dirs.remove("thirdparty")
48+
if "tests" in dirs:
49+
dirs.remove("tests")
50+
if "test" in dirs:
51+
dirs.remove("test")
52+
if "misc" in dirs:
53+
dirs.remove("misc")
54+
if "gdextension" in dirs:
55+
dirs.remove("gdextension")
56+
for file in files:
57+
if file.endswith(".h") or file.endswith(".hpp"):
58+
relative_path = os.path.relpath(root, directory)
59+
file_path = os.path.join(root, file)
60+
file_types_mapping[f"{relative_path}/{file}"] = parse_header_file(file_path)
61+
62+
return file_types_mapping
63+
64+
65+
if __name__ == "__main__":
66+
# Get current directory for godot-cpp
67+
current_directory = os.path.join(os.getcwd(), "")
68+
mapping_name = ""
69+
if len(sys.argv) > 1:
70+
mapping_name = "_godot"
71+
current_directory = os.path.join(os.getcwd(), sys.argv[1])
72+
73+
file_types_mapping = map_header_files(current_directory)
74+
with open(f"output_header_mapping{mapping_name}.json", "w") as json_file:
75+
json.dump(file_types_mapping, json_file, indent=4)

header_matcher.py

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import json
2+
import os
3+
4+
from compat_generator import map_header_files
5+
6+
7+
def match_headers(mapping1, mapping2):
8+
matches = {}
9+
for header_file, data1 in mapping1.items():
10+
for header_file2, data2 in mapping2.items():
11+
# Check if classes/defines/structs in header_file1 are present in header_file2
12+
if header_file not in matches:
13+
matches[header_file] = []
14+
if (
15+
any(class_name in data2["classes"] for class_name in data1["classes"])
16+
or any(define_name in data2["defines"] for define_name in data1["defines"])
17+
or any(define_name in data2["structs"] for define_name in data1["structs"])
18+
): # or
19+
# any(define_name in data2["enums"] for define_name in data1["enums"])):
20+
matches[header_file].append(header_file2)
21+
return matches
22+
23+
24+
if __name__ == "__main__":
25+
# Load the two header mappings
26+
with open("output_header_mapping_godot.json", "r") as file:
27+
mapping_godot = json.load(file)
28+
file_types_mapping_godot_cpp_gen = map_header_files(os.path.join(os.getcwd(), "gen", "include"))
29+
matches = match_headers(file_types_mapping_godot_cpp_gen, mapping_godot)
30+
31+
# Optionally, you can save the matches to a file
32+
with open("header_matches.json", "w") as outfile:
33+
json.dump(matches, outfile, indent=4)

include/godot_cpp/classes/editor_plugin_registration.hpp

+6-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@
3131
#ifndef GODOT_EDITOR_PLUGIN_REGISTRATION_HPP
3232
#define GODOT_EDITOR_PLUGIN_REGISTRATION_HPP
3333

34+
#ifdef GODOT_MODULE_COMPAT
35+
#include <editor/plugins/editor_plugin.h>
36+
#else
37+
3438
#include <godot_cpp/templates/vector.hpp>
3539

3640
namespace godot {
@@ -59,4 +63,5 @@ class EditorPlugins {
5963

6064
} // namespace godot
6165

62-
#endif // GODOT_EDITOR_PLUGIN_REGISTRATION_HPP
66+
#endif
67+
#endif // GODOT_EDITOR_PLUGIN_REGISTRATION_HPP

include/godot_cpp/classes/ref.hpp

+11-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@
3131
#ifndef GODOT_REF_HPP
3232
#define GODOT_REF_HPP
3333

34+
#ifdef GODOT_MODULE_COMPAT
35+
#include <core/object/ref_counted.h>
36+
#include <core/variant/binder_common.h>
37+
#include <core/variant/typed_dictionary.h>
38+
#include <core/variant/method_ptrcall.h>
39+
#include <core/variant/type_info.h>
40+
#include <core/variant/typed_array.h>
41+
#else
42+
3443
#include <godot_cpp/core/defs.hpp>
3544

3645
#include <godot_cpp/classes/object.hpp>
@@ -281,4 +290,5 @@ struct GetTypeInfo<const Ref<T> &, typename EnableIf<TypeInherits<RefCounted, T>
281290

282291
} // namespace godot
283292

284-
#endif // GODOT_REF_HPP
293+
#endif
294+
#endif // GODOT_REF_HPP

include/godot_cpp/classes/wrapped.hpp

+6-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@
3131
#ifndef GODOT_WRAPPED_HPP
3232
#define GODOT_WRAPPED_HPP
3333

34+
#ifdef GODOT_MODULE_COMPAT
35+
#include <core/object/object.h>
36+
#else
37+
3438
#include <godot_cpp/core/memory.hpp>
3539

3640
#include <godot_cpp/core/property_info.hpp>
@@ -511,4 +515,5 @@ public:
511515
#define GDVIRTUAL_IS_OVERRIDDEN(m_name) _gdvirtual_##m_name##_overridden()
512516
#define GDVIRTUAL_IS_OVERRIDDEN_PTR(m_obj, m_name) m_obj->_gdvirtual_##m_name##_overridden()
513517

514-
#endif // GODOT_WRAPPED_HPP
518+
#endif
519+
#endif // GODOT_WRAPPED_HPP

include/godot_cpp/core/binder_common.hpp

+11-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@
3131
#ifndef GODOT_BINDER_COMMON_HPP
3232
#define GODOT_BINDER_COMMON_HPP
3333

34+
#ifdef GODOT_MODULE_COMPAT
35+
#include <core/object/ref_counted.h>
36+
#include <core/variant/binder_common.h>
37+
#include <core/variant/typed_dictionary.h>
38+
#include <core/variant/method_ptrcall.h>
39+
#include <core/variant/type_info.h>
40+
#include <core/variant/typed_array.h>
41+
#else
42+
3443
#include <gdextension_interface.h>
3544

3645
#include <godot_cpp/core/method_ptrcall.hpp>
@@ -693,4 +702,5 @@ void call_with_ptr_args_static_method_ret(R (*p_method)(P...), const GDExtension
693702
#include <godot_cpp/classes/global_constants_binds.hpp>
694703
#include <godot_cpp/variant/builtin_binds.hpp>
695704

696-
#endif // GODOT_BINDER_COMMON_HPP
705+
#endif
706+
#endif // GODOT_BINDER_COMMON_HPP

include/godot_cpp/core/builtin_ptrcall.hpp

+5-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@
3131
#ifndef GODOT_BUILTIN_PTRCALL_HPP
3232
#define GODOT_BUILTIN_PTRCALL_HPP
3333

34+
#ifdef GODOT_MODULE_COMPAT
35+
#else
36+
3437
#include <gdextension_interface.h>
3538
#include <godot_cpp/core/object.hpp>
3639

@@ -89,4 +92,5 @@ T _call_builtin_ptr_getter(const GDExtensionPtrGetter getter, GDExtensionConstTy
8992

9093
} // namespace godot
9194

92-
#endif // GODOT_BUILTIN_PTRCALL_HPP
95+
#endif
96+
#endif // GODOT_BUILTIN_PTRCALL_HPP

0 commit comments

Comments
 (0)