Skip to content

Commit 2d9f151

Browse files
committed
[Bindings] Build profile now strips methods and skip files
This allows removing dependencies that are not explicitly unused by the gdextension being built.
1 parent 27ffd8c commit 2d9f151

File tree

3 files changed

+86
-41
lines changed

3 files changed

+86
-41
lines changed

binding_generator.py

Lines changed: 81 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -292,37 +292,19 @@ def parse_build_profile(profile_filepath, api):
292292
children[parent] = children.get(parent, [])
293293
children[parent].append(child)
294294

295-
# Parse methods dependencies
296-
deps = {}
297-
reverse_deps = {}
298-
for name, engine_class in api_dict.items():
299-
ref_cls = set()
300-
for method in engine_class.get("methods", []):
301-
rtype = method.get("return_value", {}).get("type", "")
302-
args = [a["type"] for a in method.get("arguments", [])]
303-
if rtype in api_dict:
304-
ref_cls.add(rtype)
305-
elif is_enum(rtype) and get_enum_class(rtype) in api_dict:
306-
ref_cls.add(get_enum_class(rtype))
307-
for arg in args:
308-
if arg in api_dict:
309-
ref_cls.add(arg)
310-
elif is_enum(arg) and get_enum_class(arg) in api_dict:
311-
ref_cls.add(get_enum_class(arg))
312-
deps[engine_class["name"]] = set(filter(lambda x: x != name, ref_cls))
313-
for acls in ref_cls:
314-
if acls == name:
315-
continue
316-
reverse_deps[acls] = reverse_deps.get(acls, set())
317-
reverse_deps[acls].add(name)
318-
319295
included = []
320296
front = list(profile.get("enabled_classes", []))
321297
if front:
322298
# These must always be included
323299
front.append("WorkerThreadPool")
324300
front.append("ClassDB")
325301
front.append("ClassDBSingleton")
302+
# In src/classes/low_level.cpp
303+
front.append("FileAccess")
304+
front.append("Image")
305+
front.append("XMLParser")
306+
# In include/godot_cpp/templates/thread_work_pool.hpp
307+
front.append("Semaphore")
326308
while front:
327309
cls = front.pop()
328310
if cls in included:
@@ -331,10 +313,6 @@ def parse_build_profile(profile_filepath, api):
331313
parent = parents.get(cls, "")
332314
if parent:
333315
front.append(parent)
334-
for rcls in deps.get(cls, set()):
335-
if rcls in included or rcls in front:
336-
continue
337-
front.append(rcls)
338316

339317
excluded = []
340318
front = list(profile.get("disabled_classes", []))
@@ -344,10 +322,6 @@ def parse_build_profile(profile_filepath, api):
344322
continue
345323
excluded.append(cls)
346324
front += children.get(cls, [])
347-
for rcls in reverse_deps.get(cls, set()):
348-
if rcls in excluded or rcls in front:
349-
continue
350-
front.append(rcls)
351325

352326
if included and excluded:
353327
print(
@@ -372,24 +346,33 @@ def scons_emit_files(target, source, env):
372346

373347

374348
def scons_generate_bindings(target, source, env):
349+
profile_filepath = env.get("build_profile", "")
350+
if profile_filepath and not Path(profile_filepath).is_absolute():
351+
profile_filepath = str((Path(env.Dir("#").abspath) / profile_filepath).as_posix())
352+
375353
generate_bindings(
376354
str(source[0]),
377355
env["generate_template_get_node"],
378356
"32" if "32" in env["arch"] else "64",
379357
env["precision"],
380358
env["godot_cpp_gen_dir"],
359+
profile_filepath,
381360
)
382361
return None
383362

384363

385-
def generate_bindings(api_filepath, use_template_get_node, bits="64", precision="single", output_dir="."):
364+
def generate_bindings(
365+
api_filepath, use_template_get_node, bits="64", precision="single", output_dir=".", profile_filepath=""
366+
):
386367
api = None
387368

388369
target_dir = Path(output_dir) / "gen"
389370

390371
with open(api_filepath, encoding="utf-8") as api_file:
391372
api = json.load(api_file)
392373

374+
build_profile = parse_build_profile(profile_filepath, api)
375+
393376
shutil.rmtree(target_dir, ignore_errors=True)
394377
target_dir.mkdir(parents=True)
395378

@@ -400,7 +383,7 @@ def generate_bindings(api_filepath, use_template_get_node, bits="64", precision=
400383
generate_version_header(api, target_dir)
401384
generate_global_constant_binds(api, target_dir)
402385
generate_builtin_bindings(api, target_dir, real_t + "_" + bits)
403-
generate_engine_classes_bindings(api, target_dir, use_template_get_node)
386+
generate_engine_classes_bindings(api, target_dir, use_template_get_node, build_profile)
404387
generate_utility_functions(api, target_dir)
405388

406389

@@ -1378,7 +1361,7 @@ def generate_builtin_class_source(builtin_api, size, used_classes, fully_used_cl
13781361
return "\n".join(result)
13791362

13801363

1381-
def generate_engine_classes_bindings(api, output_dir, use_template_get_node):
1364+
def generate_engine_classes_bindings(api, output_dir, use_template_get_node, build_profile):
13821365
global engine_classes
13831366
global singletons
13841367
global native_structures
@@ -1421,6 +1404,8 @@ def generate_engine_classes_bindings(api, output_dir, use_template_get_node):
14211404

14221405
if "methods" in class_api:
14231406
for method in class_api["methods"]:
1407+
if not is_method_included(method, build_profile):
1408+
continue
14241409
if "arguments" in method:
14251410
for argument in method["arguments"]:
14261411
type_name = argument["type"]
@@ -1566,14 +1551,21 @@ def generate_engine_classes_bindings(api, output_dir, use_template_get_node):
15661551
fully_used_classes = list(fully_used_classes)
15671552
fully_used_classes.sort()
15681553

1554+
if not is_class_included(class_api["name"], build_profile):
1555+
continue
1556+
15691557
with header_filename.open("w+", encoding="utf-8") as header_file:
15701558
header_file.write(
1571-
generate_engine_class_header(class_api, used_classes, fully_used_classes, use_template_get_node)
1559+
generate_engine_class_header(
1560+
class_api, used_classes, fully_used_classes, use_template_get_node, build_profile
1561+
)
15721562
)
15731563

15741564
with source_filename.open("w+", encoding="utf-8") as source_file:
15751565
source_file.write(
1576-
generate_engine_class_source(class_api, used_classes, fully_used_classes, use_template_get_node)
1566+
generate_engine_class_source(
1567+
class_api, used_classes, fully_used_classes, use_template_get_node, build_profile
1568+
)
15771569
)
15781570

15791571
for native_struct in api["native_structures"]:
@@ -1635,7 +1627,7 @@ def generate_engine_classes_bindings(api, output_dir, use_template_get_node):
16351627
header_file.write("\n".join(result))
16361628

16371629

1638-
def generate_engine_class_header(class_api, used_classes, fully_used_classes, use_template_get_node):
1630+
def generate_engine_class_header(class_api, used_classes, fully_used_classes, use_template_get_node, build_profile):
16391631
global singletons
16401632
result = []
16411633

@@ -1735,6 +1727,8 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
17351727

17361728
if "methods" in class_api:
17371729
for method in class_api["methods"]:
1730+
if not is_method_included(method, build_profile):
1731+
continue
17381732
if method["is_virtual"]:
17391733
# Will be done later.
17401734
continue
@@ -1759,6 +1753,8 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
17591753

17601754
# Virtuals now.
17611755
for method in class_api["methods"]:
1756+
if not is_method_included(method, build_profile):
1757+
continue
17621758
if not method["is_virtual"]:
17631759
continue
17641760

@@ -1779,6 +1775,8 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
17791775
result.append(f"\t\t{inherits}::register_virtuals<T, B>();")
17801776
if "methods" in class_api:
17811777
for method in class_api["methods"]:
1778+
if not is_method_included(method, build_profile):
1779+
continue
17821780
if not method["is_virtual"]:
17831781
continue
17841782
method_name = escape_identifier(method["name"])
@@ -1870,6 +1868,8 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
18701868
result.append("\t \\")
18711869

18721870
for method in class_api["methods"]:
1871+
if not is_method_included(method, build_profile):
1872+
continue
18731873
# ClassDBSingleton shouldn't have any static methods, but if some appear later, lets skip them.
18741874
if "is_static" in method and method["is_static"]:
18751875
continue
@@ -1944,7 +1944,7 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
19441944
return "\n".join(result)
19451945

19461946

1947-
def generate_engine_class_source(class_api, used_classes, fully_used_classes, use_template_get_node):
1947+
def generate_engine_class_source(class_api, used_classes, fully_used_classes, use_template_get_node, build_profile):
19481948
global singletons
19491949
result = []
19501950

@@ -2012,6 +2012,8 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us
20122012

20132013
if "methods" in class_api:
20142014
for method in class_api["methods"]:
2015+
if not is_method_included(method, build_profile):
2016+
continue
20152017
if method["is_virtual"]:
20162018
# Will be done later
20172019
continue
@@ -2106,6 +2108,8 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us
21062108

21072109
# Virtuals now.
21082110
for method in class_api["methods"]:
2111+
if not is_method_included(method, build_profile):
2112+
continue
21092113
if not method["is_virtual"]:
21102114
continue
21112115

@@ -2780,6 +2784,44 @@ def is_class_included(class_name, build_profile):
27802784
return True
27812785

27822786

2787+
def is_method_included(method, build_profile):
2788+
"""
2789+
Check if an engine class should be included.
2790+
This removes classes according to a build profile of enabled or disabled classes.
2791+
"""
2792+
global engine_classes
2793+
included = build_profile.get("enabled_classes", [])
2794+
excluded = build_profile.get("disabled_classes", [])
2795+
ref_cls = set()
2796+
rtype = get_base_type(method.get("return_value", {}).get("type", ""))
2797+
args = [get_base_type(a["type"]) for a in method.get("arguments", [])]
2798+
if rtype in engine_classes:
2799+
ref_cls.add(rtype)
2800+
elif is_enum(rtype) and get_enum_class(rtype) in engine_classes:
2801+
ref_cls.add(get_enum_class(rtype))
2802+
for arg in args:
2803+
if arg in engine_classes:
2804+
ref_cls.add(arg)
2805+
elif is_enum(arg) and get_enum_class(arg) in engine_classes:
2806+
ref_cls.add(get_enum_class(arg))
2807+
for acls in ref_cls:
2808+
if len(included) > 0 and acls not in included:
2809+
return False
2810+
elif len(excluded) > 0 and acls in excluded:
2811+
return False
2812+
return True
2813+
2814+
2815+
def get_base_type(type_name):
2816+
if type_name.startswith("const "):
2817+
type_name = type_name[6:]
2818+
if type_name.endswith("*"):
2819+
type_name = type_name[:-1]
2820+
if type_name.startswith("typedarray::"):
2821+
type_name = type_name.replace("typedarray::", "")
2822+
return type_name
2823+
2824+
27832825
def is_included(type_name, current_type):
27842826
"""
27852827
Check if a builtin type should be included.

include/godot_cpp/variant/variant_internal.hpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
#define GODOT_VARIANT_INTERNAL_HPP
3333

3434
#include <gdextension_interface.h>
35-
#include <godot_cpp/classes/gpu_particles3d.hpp>
3635
#include <godot_cpp/variant/variant.hpp>
3736

3837
namespace godot {

test/build_profile.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
{
22
"enabled_classes": [
33
"Control",
4+
"InputEventKey",
45
"Label",
6+
"MultiplayerAPI",
7+
"MultiplayerPeer",
58
"OS",
69
"TileMap",
7-
"InputEventKey"
10+
"TileSet",
11+
"Viewport"
812
]
913
}

0 commit comments

Comments
 (0)