Skip to content
Merged
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: 7 additions & 1 deletion .github/workflows/build_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ jobs:

env:
SCONS_CACHE: ${{ github.workspace }}/.scons-cache/
SCONSFLAGS: ${{ matrix.sconsflags }} platform=${{ matrix.platform }} arch=${{ matrix.arch }} --jobs=2
SCONSFLAGS: ${{ matrix.sconsflags }} platform=${{ matrix.platform }} arch=${{ matrix.arch }} build_profile=build_profile.json --jobs=2

defaults:
run:
Expand Down Expand Up @@ -196,6 +196,12 @@ jobs:
patch -p1 < misc/patches/scons_path.diff
patch -p1 < misc/patches/gdnantive_arm_warnings.diff

- name: Patch godot-cpp to support build profile.
run: |
patch -p1 < misc/patches/build_profile.diff
patch -p1 < misc/patches/build_profile_4.0.diff
patch -p1 < misc/patches/build_profile_3.x.diff

- name: Print tools versions
run: |
python --version
Expand Down
10 changes: 10 additions & 0 deletions build_profile.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"enabled_classes": [
"WebRTCDataChannelExtension",
"WebRTCPeerConnectionExtension",
"WebRTCDataChannelGDNative",
"WebRTCPeerConnectionGDNative",
"NativeScript",
"GDNativeLibrary"
]
}
238 changes: 238 additions & 0 deletions misc/patches/build_profile.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
diff --git a/godot-cpp/SConstruct b/godot-cpp/SConstruct
index 42f8fc0..9d59d1f 100644
--- a/godot-cpp/SConstruct
+++ b/godot-cpp/SConstruct
@@ -121,6 +121,14 @@ opts.Add(
BoolVariable("generate_bindings", "Force GDExtension API bindings generation. Auto-detected by default.", False)
)
opts.Add(BoolVariable("generate_template_get_node", "Generate a template version of the Node class's get_node.", True))
+opts.Add(
+ PathVariable(
+ "build_profile",
+ "Path to a file containing a feature build profile",
+ default=env.get("build_profile", None),
+ validator=lambda key, val, env: os.path.isfile(normalize_path(val)),
+ )
+)

opts.Add(BoolVariable("build_library", "Build the godot-cpp library.", True))
opts.Add(EnumVariable("precision", "Set the floating-point precision level", "single", ("single", "double")))
diff --git a/godot-cpp/binding_generator.py b/godot-cpp/binding_generator.py
index d04c698..41cf29b 100644
--- a/godot-cpp/binding_generator.py
+++ b/godot-cpp/binding_generator.py
@@ -70,12 +70,14 @@ def generate_wrappers(target):
f.write(txt)


-def get_file_list(api_filepath, output_dir, headers=False, sources=False):
+def get_file_list(api_filepath, output_dir, headers=False, sources=False, profile_filepath=""):
api = {}
files = []
with open(api_filepath, encoding="utf-8") as api_file:
api = json.load(api_file)

+ build_profile = parse_build_profile(profile_filepath, api)
+
core_gen_folder = Path(output_dir) / "gen" / "include" / "godot_cpp" / "core"
include_gen_folder = Path(output_dir) / "gen" / "include" / "godot_cpp"
source_gen_folder = Path(output_dir) / "gen" / "src"
@@ -104,7 +106,7 @@ def get_file_list(api_filepath, output_dir, headers=False, sources=False):
source_filename = source_gen_folder / "classes" / (camel_to_snake(engine_class["name"]) + ".cpp")
if headers:
files.append(str(header_filename.as_posix()))
- if sources:
+ if sources and is_class_included(engine_class["name"], build_profile):
files.append(str(source_filename.as_posix()))

for native_struct in api["native_structures"]:
@@ -134,14 +136,107 @@ def get_file_list(api_filepath, output_dir, headers=False, sources=False):
return files


-def print_file_list(api_filepath, output_dir, headers=False, sources=False):
+def print_file_list(api_filepath, output_dir, headers=False, sources=False, profile_filepath=""):
end = ";"
- for f in get_file_list(api_filepath, output_dir, headers, sources):
+ for f in get_file_list(api_filepath, output_dir, headers, sources, profile_filepath):
print(f, end=end)


+def parse_build_profile(profile_filepath, api):
+ if profile_filepath == "":
+ return {}
+ print("Using feature build profile: " + profile_filepath)
+
+ with open(profile_filepath, encoding="utf-8") as profile_file:
+ profile = json.load(profile_file)
+
+ api_dict = {}
+ parents = {}
+ children = {}
+ for engine_class in api["classes"]:
+ api_dict[engine_class["name"]] = engine_class
+ parent = engine_class.get("inherits", "")
+ child = engine_class["name"]
+ parents[child] = parent
+ if parent == "":
+ continue
+ children[parent] = children.get(parent, [])
+ children[parent].append(child)
+
+ # Parse methods dependencies
+ deps = {}
+ reverse_deps = {}
+ for name, engine_class in api_dict.items():
+ ref_cls = set()
+ for method in engine_class.get("methods", []):
+ rtype = method.get("return_value", {}).get("type", "")
+ args = [a["type"] for a in method.get("arguments", [])]
+ if rtype in api_dict:
+ ref_cls.add(rtype)
+ elif is_enum(rtype) and get_enum_class(rtype) in api_dict:
+ ref_cls.add(get_enum_class(rtype))
+ for arg in args:
+ if arg in api_dict:
+ ref_cls.add(arg)
+ elif is_enum(arg) and get_enum_class(arg) in api_dict:
+ ref_cls.add(get_enum_class(arg))
+ deps[engine_class["name"]] = set(filter(lambda x: x != name, ref_cls))
+ for acls in ref_cls:
+ if acls == name:
+ continue
+ reverse_deps[acls] = reverse_deps.get(acls, set())
+ reverse_deps[acls].add(name)
+
+ included = []
+ front = list(profile.get("enabled_classes", []))
+ if front:
+ # These must always be included
+ front.append("WorkerThreadPool")
+ front.append("ClassDB")
+ front.append("ClassDBSingleton")
+ while front:
+ cls = front.pop()
+ if cls in included:
+ continue
+ included.append(cls)
+ parent = parents.get(cls, "")
+ if parent:
+ front.append(parent)
+ for rcls in deps.get(cls, set()):
+ if rcls in included or rcls in front:
+ continue
+ front.append(rcls)
+
+ excluded = []
+ front = list(profile.get("disabled_classes", []))
+ while front:
+ cls = front.pop()
+ if cls in excluded:
+ continue
+ excluded.append(cls)
+ front += children.get(cls, [])
+ for rcls in reverse_deps.get(cls, set()):
+ if rcls in excluded or rcls in front:
+ continue
+ front.append(rcls)
+
+ if included and excluded:
+ print(
+ "WARNING: Cannot specify both 'enabled_classes' and 'disabled_classes' in build profile. 'disabled_classes' will be ignored."
+ )
+
+ return {
+ "enabled_classes": included,
+ "disabled_classes": excluded,
+ }
+
+
def scons_emit_files(target, source, env):
- files = [env.File(f) for f in get_file_list(str(source[0]), target[0].abspath, True, True)]
+ profile_filepath = env.get("build_profile", "")
+ if profile_filepath and not Path(profile_filepath).is_absolute():
+ profile_filepath = str((Path(env.Dir("#").abspath) / profile_filepath).as_posix())
+
+ files = [env.File(f) for f in get_file_list(str(source[0]), target[0].abspath, True, True, profile_filepath)]
env.Clean(target, files)
env["godot_cpp_gen_dir"] = target[0].abspath
return files, source
@@ -154,11 +249,12 @@ def scons_generate_bindings(target, source, env):
"32" if "32" in env["arch"] else "64",
env["precision"],
env["godot_cpp_gen_dir"],
+ env.get("build_profile", ""),
)
return None


-def generate_bindings(api_filepath, use_template_get_node, bits="64", precision="single", output_dir="."):
+def generate_bindings(api_filepath, use_template_get_node, bits="64", precision="single", output_dir=".", profile_filepath=""):
api = None

target_dir = Path(output_dir) / "gen"
@@ -175,7 +271,7 @@ def generate_bindings(api_filepath, use_template_get_node, bits="64", precision=
generate_global_constants(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_engine_classes_bindings(api, target_dir, use_template_get_node, profile_filepath)
generate_utility_functions(api, target_dir)


@@ -1023,7 +1119,7 @@ def generate_builtin_class_source(builtin_api, size, used_classes, fully_used_cl
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, profile_filepath=""):
global engine_classes
global singletons
global native_structures
@@ -1161,7 +1257,7 @@ def generate_engine_classes_bindings(api, output_dir, use_template_get_node):

register_engine_classes_filename = Path(output_dir) / "src" / "register_engine_classes.cpp"
with register_engine_classes_filename.open("w+", encoding="utf-8") as source_file:
- source_file.write(generate_register_engine_classes_source(api))
+ source_file.write(generate_register_engine_classes_source(api, profile_filepath))

for native_struct in api["native_structures"]:
struct_name = native_struct["name"]
@@ -1585,12 +1681,14 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us
return "\n".join(result)


-def generate_register_engine_classes_source(api):
+def generate_register_engine_classes_source(api, profile_filepath=""):
includes = []
registrations = []

+ build_profile = parse_build_profile(profile_filepath, api)
+
for class_api in api["classes"]:
- if class_api["name"] == "ClassDB":
+ if class_api["name"] == "ClassDB" or not is_class_included(class_api["name"], build_profile):
continue

class_name = class_api["name"]
@@ -2066,6 +2164,20 @@ def is_pod_type(type_name):
]


+def is_class_included(class_name, build_profile):
+ """
+ Check if an engine class should be included.
+ This removes classes according to a build profile of enabled or disabled classes.
+ """
+ included = build_profile.get("enabled_classes", [])
+ excluded = build_profile.get("disabled_classes", [])
+ if included:
+ return class_name in included
+ if excluded:
+ return class_name not in excluded
+ return True
+
+
def is_included_type(type_name):
# Types which we already have implemented.
return is_included_struct_type(type_name) or type_name in ["ObjectID"]
Loading