From 68338ed576c663d1fbb1ee0fcbe28939e3abf95d Mon Sep 17 00:00:00 2001 From: Fabio Alessandrelli Date: Wed, 8 Jan 2025 02:29:46 +0100 Subject: [PATCH] [godot-cpp] Update versions and patches Update godot-cpp to 4.1.3 Update build profile patches to latest iteration --- .github/workflows/build_release.yml | 9 + godot-cpp | 2 +- misc/patches/build_profile.diff | 363 ++++++++++++++++------------ misc/patches/build_profile_3.x.diff | 322 +++++++++++++++--------- tools/cmake.py | 10 +- tools/openssl.py | 8 +- 6 files changed, 441 insertions(+), 273 deletions(-) diff --git a/.github/workflows/build_release.yml b/.github/workflows/build_release.yml index cfadeac..b220b06 100644 --- a/.github/workflows/build_release.yml +++ b/.github/workflows/build_release.yml @@ -4,6 +4,8 @@ on: [push, pull_request] env: # Only used for the cache key. Increment version to force clean build. GODOT_BASE_BRANCH: master + # Hardcoding this seem to be the only way + ANDROID_NDK_ROOT: /usr/local/lib/android/sdk/ndk/23.2.8568313/ jobs: static-checks: @@ -196,6 +198,13 @@ jobs: patch -p1 < misc/patches/scons_path.diff patch -p1 < misc/patches/gdnantive_arm_warnings.diff + - name: Setup Android dependencies + if: ${{ matrix.platform == 'android' }} + uses: nttld/setup-ndk@v1 + with: + ndk-version: r23c + link-to-sdk: true + - name: Patch godot-cpp to support build profile. run: | patch -p1 < misc/patches/build_profile.diff diff --git a/godot-cpp b/godot-cpp index d627942..631cd5f 160000 --- a/godot-cpp +++ b/godot-cpp @@ -1 +1 @@ -Subproject commit d627942b647105aff600225a59dffa65de206247 +Subproject commit 631cd5fe37d4e6df6e5eb66eb4435feca12708cc diff --git a/misc/patches/build_profile.diff b/misc/patches/build_profile.diff index bdfaa60..ce31fd8 100644 --- a/misc/patches/build_profile.diff +++ b/misc/patches/build_profile.diff @@ -1,67 +1,56 @@ -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 +index 18db9fd..e0c8caf 100644 --- a/godot-cpp/binding_generator.py +++ b/godot-cpp/binding_generator.py -@@ -70,12 +70,14 @@ def generate_wrappers(target): - f.write(txt) +@@ -72,9 +72,13 @@ def generate_wrappers(target): - --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=""): + def get_file_list(api_filepath, output_dir, headers=False, sources=False): api = {} - files = [] +- files = [] with open(api_filepath, encoding="utf-8") as api_file: api = json.load(api_file) - -+ build_profile = parse_build_profile(profile_filepath, api) ++ return _get_file_list(api, output_dir, headers, sources) + ++ ++def _get_file_list(api, output_dir, headers=False, sources=False): ++ files = [] + 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 +@@ -158,13 +162,15 @@ def scons_generate_bindings(target, source, env): + return None --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 generate_bindings(api_filepath, use_template_get_node, bits="64", precision="single", output_dir="."): +- api = None +- +- target_dir = Path(output_dir) / "gen" +- ++def _generate_bindings(api_filepath, use_template_get_node, bits="64", precision="single", output_dir="."): ++ api = {} + with open(api_filepath, encoding="utf-8") as api_file: + api = json.load(api_file) ++ _generate_bindings(api, use_template_get_node, bits, precision, output_dir) ++ ++ ++def _generate_bindings(api, use_template_get_node, bits="64", precision="single", output_dir="."): ++ target_dir = Path(output_dir) / "gen" + shutil.rmtree(target_dir, ignore_errors=True) + target_dir.mkdir(parents=True) +diff --git a/godot-cpp/build_profile.py b/godot-cpp/build_profile.py +new file mode 100644 +index 0000000..b4d19de +--- /dev/null ++++ b/godot-cpp/build_profile.py +@@ -0,0 +1,183 @@ ++import json ++import sys ++ ++ +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) @@ -79,30 +68,6 @@ index d04c698..41cf29b 100644 + 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: @@ -110,6 +75,12 @@ index d04c698..41cf29b 100644 + front.append("WorkerThreadPool") + front.append("ClassDB") + front.append("ClassDBSingleton") ++ # In src/classes/low_level.cpp ++ front.append("FileAccess") ++ front.append("Image") ++ front.append("XMLParser") ++ # In include/godot_cpp/templates/thread_work_pool.hpp ++ front.append("Semaphore") + while front: + cls = front.pop() + if cls in included: @@ -118,10 +89,6 @@ index d04c698..41cf29b 100644 + 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", [])) @@ -131,10 +98,6 @@ index d04c698..41cf29b 100644 + 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( @@ -147,78 +110,40 @@ index d04c698..41cf29b 100644 + } + + - 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()) ++def generate_trimmed_api(source_api_filepath, profile_filepath): ++ with open(source_api_filepath, encoding="utf-8") as api_file: ++ api = json.load(api_file) ++ ++ if profile_filepath == "": ++ return api + -+ 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): - ] - - ++ engine_classes = {} ++ for class_api in api["classes"]: ++ engine_classes[class_api["name"]] = class_api["is_refcounted"] ++ for native_struct in api["native_structures"]: ++ if native_struct["name"] == "ObjectID": ++ continue ++ engine_classes[native_struct["name"]] = False ++ ++ classes = [] ++ for class_api in api["classes"]: ++ if not is_class_included(class_api["name"], build_profile): ++ continue ++ if "methods" in class_api: ++ methods = [] ++ for method in class_api["methods"]: ++ if not is_method_included(method, build_profile, engine_classes): ++ continue ++ methods.append(method) ++ class_api["methods"] = methods ++ classes.append(class_api) ++ api["classes"] = classes ++ ++ return api ++ ++ +def is_class_included(class_name, build_profile): + """ + Check if an engine class should be included. @@ -233,6 +158,140 @@ index d04c698..41cf29b 100644 + 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"] ++def is_method_included(method, build_profile, engine_classes): ++ """ ++ Check if an engine class method should be included. ++ This removes methods according to a build profile of enabled or disabled classes. ++ """ ++ included = build_profile.get("enabled_classes", []) ++ excluded = build_profile.get("disabled_classes", []) ++ ref_cls = set() ++ rtype = get_base_type(method.get("return_value", {}).get("type", "")) ++ args = [get_base_type(a["type"]) for a in method.get("arguments", [])] ++ if rtype in engine_classes: ++ ref_cls.add(rtype) ++ elif is_enum(rtype) and get_enum_class(rtype) in engine_classes: ++ ref_cls.add(get_enum_class(rtype)) ++ for arg in args: ++ if arg in engine_classes: ++ ref_cls.add(arg) ++ elif is_enum(arg) and get_enum_class(arg) in engine_classes: ++ ref_cls.add(get_enum_class(arg)) ++ for acls in ref_cls: ++ if len(included) > 0 and acls not in included: ++ return False ++ elif len(excluded) > 0 and acls in excluded: ++ return False ++ return True ++ ++ ++def is_enum(type_name): ++ return type_name.startswith("enum::") or type_name.startswith("bitfield::") ++ ++ ++def get_enum_class(enum_name: str): ++ if "." in enum_name: ++ if is_bitfield(enum_name): ++ return enum_name.replace("bitfield::", "").split(".")[0] ++ else: ++ return enum_name.replace("enum::", "").split(".")[0] ++ else: ++ return "GlobalConstants" ++ ++ ++def get_base_type(type_name): ++ if type_name.startswith("const "): ++ type_name = type_name[6:] ++ if type_name.endswith("*"): ++ type_name = type_name[:-1] ++ if type_name.startswith("typedarray::"): ++ type_name = type_name.replace("typedarray::", "") ++ return type_name ++ ++ ++def is_bitfield(type_name): ++ return type_name.startswith("bitfield::") ++ ++ ++if __name__ == "__main__": ++ if len(sys.argv) < 3 or len(sys.argv) > 4: ++ print("Usage: %s BUILD_PROFILE INPUT_JSON [OUTPUT_JSON]" % (sys.argv[0])) ++ sys.exit(1) ++ profile = sys.argv[1] ++ infile = sys.argv[2] ++ outfile = sys.argv[3] if len(sys.argv) > 3 else "" ++ api = generate_trimmed_api(infile, profile) ++ ++ if outfile: ++ with open(outfile, "w", encoding="utf-8") as f: ++ json.dump(api, f) ++ else: ++ json.dump(api, sys.stdout) +diff --git a/godot-cpp/tools/godotcpp.py b/godot-cpp/tools/godotcpp.py +index efe632a..993aa81 100644 +--- a/godot-cpp/tools/godotcpp.py ++++ b/godot-cpp/tools/godotcpp.py +@@ -5,7 +5,8 @@ from SCons.Tool import Tool + from SCons.Builder import Builder + from SCons.Errors import UserError + +-from binding_generator import scons_generate_bindings, scons_emit_files ++from binding_generator import _generate_bindings, _get_file_list, get_file_list ++from build_profile import generate_trimmed_api + + + def add_sources(sources, dir, extension): +@@ -14,6 +15,37 @@ def add_sources(sources, dir, extension): + sources.append(dir + "/" + f) + + ++def scons_emit_files(target, source, env): ++ profile_filepath = env.get("build_profile", "") ++ if profile_filepath: ++ profile_filepath = normalize_path(profile_filepath, env) ++ ++ # Always clean all files ++ env.Clean(target, [env.File(f) for f in get_file_list(str(source[0]), target[0].abspath, True, True)]) ++ ++ api = generate_trimmed_api(str(source[0]), profile_filepath) ++ files = [env.File(f) for f in _get_file_list(api, target[0].abspath, True, True)] ++ env["godot_cpp_gen_dir"] = target[0].abspath ++ return files, source ++ ++ ++def scons_generate_bindings(target, source, env): ++ profile_filepath = env.get("build_profile", "") ++ if profile_filepath: ++ profile_filepath = normalize_path(profile_filepath, env) ++ ++ api = generate_trimmed_api(str(source[0]), profile_filepath) ++ ++ _generate_bindings( ++ api, ++ env["generate_template_get_node"], ++ "32" if "32" in env["arch"] else "64", ++ env["precision"], ++ env["godot_cpp_gen_dir"], ++ ) ++ return None ++ ++ + def normalize_path(val, env): + return val if os.path.isabs(val) else os.path.join(env.Dir("#").abspath, val) + +@@ -175,6 +207,15 @@ def options(opts, env): + ) + ) + ++ opts.Add( ++ PathVariable( ++ "build_profile", ++ "Path to a file containing a feature build profile", ++ default=env.get("build_profile", None), ++ validator=validate_file, ++ ) ++ ) ++ + opts.Add( + BoolVariable( + "disable_exceptions", diff --git a/misc/patches/build_profile_3.x.diff b/misc/patches/build_profile_3.x.diff index bbd3f66..9eb465a 100644 --- a/misc/patches/build_profile_3.x.diff +++ b/misc/patches/build_profile_3.x.diff @@ -1,8 +1,52 @@ diff --git a/godot-cpp-3.x/SConstruct b/godot-cpp-3.x/SConstruct -index f653d54..44f66a5 100644 +index f653d54..483f190 100644 --- a/godot-cpp-3.x/SConstruct +++ b/godot-cpp-3.x/SConstruct -@@ -170,6 +170,14 @@ opts.Add( +@@ -3,7 +3,42 @@ + import os + import sys + import subprocess +-from binding_generator import scons_generate_bindings, scons_emit_files ++from binding_generator import _generate_bindings, _get_file_list, get_file_list ++from build_profile import generate_trimmed_api ++ ++ ++def normalize_path(val): ++ return val if os.path.isabs(val) else os.path.join(env.Dir("#").abspath, val) ++ ++ ++def scons_emit_files(target, source, env): ++ profile_filepath = env.get("build_profile", "") ++ if profile_filepath: ++ profile_filepath = normalize_path(profile_filepath) ++ ++ # Always clean all files ++ env.Clean(target, [env.File(f) for f in get_file_list(str(source[0]), target[0].abspath, True, True)]) ++ ++ api = generate_trimmed_api(str(source[0]), profile_filepath) ++ files = [env.File(f) for f in _get_file_list(api, target[0].abspath, True, True)] ++ env["godot_cpp_gen_dir"] = target[0].abspath ++ return files, source ++ ++ ++def scons_generate_bindings(target, source, env): ++ profile_filepath = env.get("build_profile", "") ++ if profile_filepath: ++ profile_filepath = normalize_path(profile_filepath) ++ ++ api = generate_trimmed_api(str(source[0]), profile_filepath) ++ ++ _generate_bindings( ++ api, ++ env["generate_template_get_node"], ++ env["godot_cpp_gen_dir"], ++ ) ++ return None ++ + + if sys.version_info < (3,): + +@@ -170,6 +205,14 @@ opts.Add( True, ) ) @@ -11,38 +55,65 @@ index f653d54..44f66a5 100644 + "build_profile", + "Path to a file containing a feature build profile", + default=env.get("build_profile", None), -+ validator=lambda key, val, env: val if os.path.isabs(val) else os.path.join(env.Dir("#").abspath, val), ++ validator=lambda key, val, env: os.path.isfile(normalize_path(val)), + ) +) opts.Add(BoolVariable("build_library", "Build the godot-cpp library.", True)) diff --git a/godot-cpp-3.x/binding_generator.py b/godot-cpp-3.x/binding_generator.py -index da4bb61..e5bd040 100644 +index da4bb61..1348a51 100644 --- a/godot-cpp-3.x/binding_generator.py +++ b/godot-cpp-3.x/binding_generator.py -@@ -12,14 +12,115 @@ def correct_method_name(method_list): - method["name"] = "get_node_internal" +@@ -16,10 +16,16 @@ classes = [] -+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 get_file_list(api_filepath, output_dir, headers=False, sources=False): +- global classes +- files = [] ++ api = {} + with open(api_filepath) as api_file: + classes = json.load(api_file) ++ return _get_file_list(classes, output_dir, headers, sources) ++ ++ ++def _get_file_list(api, output_dir, headers=False, sources=False): ++ global classes ++ classes = api ++ files = [] + include_gen_folder = Path(output_dir) / "include" / "gen" + source_gen_folder = Path(output_dir) / "src" / "gen" + for _class in classes: +@@ -58,9 +64,15 @@ def scons_generate_bindings(target, source, env): + + + def generate_bindings(api_filepath, use_template_get_node, output_dir="."): +- global classes ++ classes = {} + with open(api_filepath) as api_file: + classes = json.load(api_file) ++ _generate_bindings(classes, use_template_get_node, output_dir) ++ ++ ++def _generate_bindings(api, use_template_get_node, output_dir="."): ++ global classes ++ classes = api + + icalls = set() + include_gen_folder = Path(output_dir) / "include" / "gen" +diff --git a/godot-cpp-3.x/build_profile.py b/godot-cpp-3.x/build_profile.py +new file mode 100644 +index 0000000..5518f64 +--- /dev/null ++++ b/godot-cpp-3.x/build_profile.py +@@ -0,0 +1,177 @@ ++import json ++import sys + + +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) @@ -60,32 +131,19 @@ index da4bb61..e5bd040 100644 + 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) -+ for arg in args: -+ if arg in api_dict: -+ ref_cls.add(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("ConfigFile") ++ front.append("WorkerThreadPool") + front.append("ClassDB") ++ front.append("ClassDBSingleton") ++ # In src/classes/low_level.cpp ++ front.append("FileAccess") ++ front.append("Image") ++ front.append("XMLParser") ++ # In include/godot_cpp/templates/thread_work_pool.hpp ++ front.append("Semaphore") + while front: + cls = front.pop() + if cls in included: @@ -94,10 +152,6 @@ index da4bb61..e5bd040 100644 + 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", [])) @@ -107,10 +161,6 @@ index da4bb61..e5bd040 100644 + 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( @@ -123,74 +173,114 @@ index da4bb61..e5bd040 100644 + } + + - classes = [] - - --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=""): - global classes ++def generate_trimmed_api(source_api_filepath, profile_filepath): ++ with open(source_api_filepath, encoding="utf-8") as api_file: ++ api = json.load(api_file) + - files = [] - with open(api_filepath) as api_file: - classes = json.load(api_file) -+ build_profile = parse_build_profile(profile_filepath, classes) ++ if profile_filepath == "": ++ return api + - include_gen_folder = Path(output_dir) / "include" / "gen" - source_gen_folder = Path(output_dir) / "src" / "gen" - for _class in classes: -@@ -27,7 +128,7 @@ def get_file_list(api_filepath, output_dir, headers=False, sources=False): - source_filename = source_gen_folder / (strip_name(_class["name"]) + ".cpp") - if headers: - files.append(str(header_filename.as_posix())) -- if sources: -+ if sources and is_class_included(_class["name"], build_profile): - files.append(str(source_filename.as_posix())) - icall_header_filename = include_gen_folder / "__icalls.hpp" - register_types_filename = source_gen_folder / "__register_types.cpp" -@@ -40,27 +141,32 @@ 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): -- for f in get_file_list(api_filepath, output_dir, headers, sources): -+def print_file_list(api_filepath, output_dir, headers=False, sources=False, profile_filepath=""): -+ for f in get_file_list(api_filepath, output_dir, headers, sources, profile_filepath): - print(f, end=";") - - - 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()) ++ build_profile = parse_build_profile(profile_filepath, api) + -+ 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 - - - def scons_generate_bindings(target, source, env): -- generate_bindings(str(source[0]), env["generate_template_get_node"], env["godot_cpp_gen_dir"]) -+ generate_bindings(str(source[0]), env["generate_template_get_node"], env["godot_cpp_gen_dir"], env["build_profile"]) - return None - - --def generate_bindings(api_filepath, use_template_get_node, output_dir="."): -+def generate_bindings(api_filepath, use_template_get_node, output_dir=".", profile_filepath=""): - global classes - with open(api_filepath) as api_file: - classes = json.load(api_file) -+ build_profile = parse_build_profile(profile_filepath, classes) - - icalls = set() - include_gen_folder = Path(output_dir) / "include" / "gen" -@@ -97,7 +203,7 @@ def generate_bindings(api_filepath, use_template_get_node, output_dir="."): - - init_method_bindings_filename = source_gen_folder / "__init_method_bindings.cpp" - with init_method_bindings_filename.open("w+") as init_method_bindings_file: -- init_method_bindings_file.write(generate_init_method_bindings(classes)) -+ init_method_bindings_file.write(generate_init_method_bindings(list(filter(lambda x: is_class_included(x["name"], build_profile), classes)))) - - - def is_reference_type(t): ++ engine_classes = {} ++ for class_api in api: ++ engine_classes[class_api["name"]] = class_api["is_reference"] ++ ++ classes = [] ++ for class_api in api: ++ if not is_class_included(class_api["name"], build_profile): ++ continue ++ if "methods" in class_api: ++ methods = [] ++ for method in class_api["methods"]: ++ if not is_method_included(method, build_profile, engine_classes): ++ continue ++ methods.append(method) ++ class_api["methods"] = methods ++ classes.append(class_api) ++ return classes ++ ++ ++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_method_included(method, build_profile, engine_classes): ++ """ ++ Check if an engine class method should be included. ++ This removes methods according to a build profile of enabled or disabled classes. ++ """ ++ included = build_profile.get("enabled_classes", []) ++ excluded = build_profile.get("disabled_classes", []) ++ ref_cls = set() ++ rtype = get_base_type(method.get("return_type", "")) ++ args = [get_base_type(a["type"]) for a in method.get("arguments", [])] ++ if rtype in engine_classes: ++ ref_cls.add(rtype) ++ elif is_enum(rtype) and get_enum_class(rtype) in engine_classes: ++ ref_cls.add(get_enum_class(rtype)) ++ for arg in args: ++ if arg in engine_classes: ++ ref_cls.add(arg) ++ elif is_enum(arg) and get_enum_class(arg) in engine_classes: ++ ref_cls.add(get_enum_class(arg)) ++ for acls in ref_cls: ++ if len(included) > 0 and acls not in included: ++ return False ++ elif len(excluded) > 0 and acls in excluded: ++ return False ++ return True ++ ++ ++def is_enum(type_name): ++ return type_name.startswith("enum::") or type_name.startswith("bitfield::") ++ ++ ++def get_enum_class(enum_name: str): ++ if "." in enum_name: ++ if is_bitfield(enum_name): ++ return enum_name.replace("bitfield::", "").split(".")[0] ++ else: ++ return enum_name.replace("enum::", "").split(".")[0] ++ else: ++ return "GlobalConstants" ++ ++ ++def get_base_type(type_name): ++ if type_name.startswith("const "): ++ type_name = type_name[6:] ++ if type_name.endswith("*"): ++ type_name = type_name[:-1] ++ if type_name.startswith("typedarray::"): ++ type_name = type_name.replace("typedarray::", "") ++ return type_name ++ ++ ++def is_bitfield(type_name): ++ return type_name.startswith("bitfield::") ++ ++ ++if __name__ == "__main__": ++ if len(sys.argv) < 3 or len(sys.argv) > 4: ++ print("Usage: %s BUILD_PROFILE INPUT_JSON [OUTPUT_JSON]" % (sys.argv[0])) ++ sys.exit(1) ++ profile = sys.argv[1] ++ infile = sys.argv[2] ++ outfile = sys.argv[3] if len(sys.argv) > 3 else "" ++ api = generate_trimmed_api(infile, profile) ++ ++ if outfile: ++ with open(outfile, "w", encoding="utf-8") as f: ++ json.dump(api, f) ++ else: ++ json.dump(api, sys.stdout) diff --git a/tools/cmake.py b/tools/cmake.py index 3ab7921..5f170fd 100644 --- a/tools/cmake.py +++ b/tools/cmake.py @@ -5,6 +5,11 @@ import SCons.Builder import SCons.Action +# This must be kept in sync with the value in https://github.com/godotengine/godot/blob/master/platform/android/detect.py#L58. +def get_ndk_version(): + return "23.2.8568313" + + def cmake_default_flags(env): if env.get("cmake_default_flags", ""): return SCons.Util.CLVar(env["cmake_default_flags"]) @@ -28,9 +33,8 @@ def cmake_default_flags(env): config["CMAKE_SYSTEM_VERSION"] = api config["CMAKE_ANDROID_ARCH_ABI"] = abi config["ANDROID_ABI"] = abi - config["CMAKE_TOOLCHAIN_FILE"] = "%s/build/cmake/android.toolchain.cmake" % env.get( - "ANDROID_NDK_ROOT", os.environ.get("ANDROID_NDK_ROOT", "") - ) + ndk_root = os.environ.get("ANDROID_NDK_ROOT", env.get("ANDROID_HOME", "") + "/ndk/" + get_ndk_version()) + config["CMAKE_TOOLCHAIN_FILE"] = "%s/build/cmake/android.toolchain.cmake" % ndk_root config["CMAKE_ANDROID_STL_TYPE"] = "c++_static" elif env["platform"] == "linux": diff --git a/tools/openssl.py b/tools/openssl.py index 4312ff3..2731564 100644 --- a/tools/openssl.py +++ b/tools/openssl.py @@ -6,6 +6,11 @@ from SCons.Defaults import Mkdir from SCons.Variables import PathVariable, BoolVariable +# This must be kept in sync with the value in https://github.com/godotengine/godot/blob/master/platform/android/detect.py#L58. +def get_ndk_version(): + return "23.2.8568313" + + def ssl_platform_target(env): targets = {} platform = env["platform"] @@ -264,7 +269,8 @@ def generate(env): if cc_path and cc_path not in env["ENV"]: env.PrependENVPath("PATH", cc_path) if "ANDROID_NDK_ROOT" not in env["ENV"]: - env["ENV"]["ANDROID_NDK_ROOT"] = env.get("ANDROID_NDK_ROOT", os.environ.get("ANDROID_NDK_ROOT", "")) + ndk_root = os.environ.get("ANDROID_NDK_ROOT", env.get("ANDROID_HOME", "") + "/ndk/" + get_ndk_version()) + env["ENV"]["ANDROID_NDK_ROOT"] = ndk_root env["SSL_SOURCE"] = env.Dir(env["openssl_source"]).abspath env["SSL_BUILD"] = env.Dir(env["openssl_build"] + "/{}/{}".format(env["platform"], env["arch"])).abspath