From 993a61da7c90d59b8a6e0814b4a3611cf0c21f8d Mon Sep 17 00:00:00 2001 From: chippmann Date: Wed, 23 Apr 2025 22:41:41 +0200 Subject: [PATCH] Add support for jniLibs --- src/gd_kotlin.cpp | 38 +++++++++++++++++++++++++- src/gd_kotlin.h | 4 +++ src/kotlin_editor_export_plugin.cpp | 42 +++++++++++++++++++++++++++-- src/kotlin_editor_export_plugin.h | 1 + src/lifecycle/class_loader.cpp | 22 ++++++++++++--- src/lifecycle/paths.h | 14 ++++++++++ 6 files changed, 114 insertions(+), 7 deletions(-) diff --git a/src/gd_kotlin.cpp b/src/gd_kotlin.cpp index 00625d173..5acaae38f 100644 --- a/src/gd_kotlin.cpp +++ b/src/gd_kotlin.cpp @@ -218,7 +218,7 @@ String GDKotlin::copy_new_file_to_user_dir(const String& file_name) { unlink(file_user_path_global.utf8().get_data()); // we do not really care about errors here #endif Ref dir_access {DirAccess::open(USER_DIRECTORY)}; - dir_access->make_dir(JVM_DIRECTORY); + dir_access->make_dir_recursive(JNI_LIBS_PATH); dir_access->copy(file_res_path, file_user_path); #ifndef __ANDROID__ } @@ -285,6 +285,35 @@ bool GDKotlin::initialize_core_library() { return true; } +#ifdef __ANDROID__ +Vector GDKotlin::get_res_files_recursively(const String &path) { + const Ref dir_access = DirAccess::open(String{"res://"} + path); + if (!dir_access.is_valid()) return {}; + + dir_access->list_dir_begin(); + Vector files; + + while (true) { + String file_name = dir_access->get_next(); + if (file_name.is_empty()) break; + + if (file_name == "." || file_name == "..") continue; + + String full_path = path.path_join(file_name); + + if (dir_access->current_is_dir()) { + files.append_array(get_res_files_recursively(full_path)); + } else { + files.push_back(full_path); + } + } + + dir_access->list_dir_end(); + return files; +} +#endif + + bool GDKotlin::load_user_code() { jni::Env env {jni::Jvm::current_env()}; if (user_configuration.vm_type == jni::JvmType::GRAAL_NATIVE_IMAGE) { @@ -297,6 +326,13 @@ bool GDKotlin::load_user_code() { String user_code_path {copy_new_file_to_user_dir(USER_CODE_FILE)}; #endif +#ifdef __ANDROID__ + for (auto file : get_res_files_recursively(JNI_LIBS_PATH)) { + JVM_LOG_INFO("Copying file: %s", file); + copy_new_file_to_user_dir(file); + } +#endif + if (!FileAccess::exists(user_code_path)) { String message {"No main.jar detected at %s. No classes will be loaded. Build the gradle " "project to load classes"}; diff --git a/src/gd_kotlin.h b/src/gd_kotlin.h index f31b87f86..7c4311e98 100644 --- a/src/gd_kotlin.h +++ b/src/gd_kotlin.h @@ -55,6 +55,10 @@ class GDKotlin { static String get_path_to_native_image(); #endif +#ifdef __ANDROID__ + static Vector get_res_files_recursively(const String &path); +#endif + bool load_bootstrap(); void unload_boostrap(); diff --git a/src/kotlin_editor_export_plugin.cpp b/src/kotlin_editor_export_plugin.cpp index 489d4d687..6e32c9274 100644 --- a/src/kotlin_editor_export_plugin.cpp +++ b/src/kotlin_editor_export_plugin.cpp @@ -130,6 +130,13 @@ void KotlinEditorExportPlugin::_export_begin(const HashSet& p_features, _generate_export_configuration_file(jni::JvmType::GRAAL_NATIVE_IMAGE); } } else if (is_android_export) { + const String jni_libs_dir {String(RES_DIRECTORY).path_join(JVM_DIRECTORY).path_join(JNI_LIBS_BASE_DIR)}; + + // add jniLibs dir with native libraries per platform + for (const auto file : _get_files_recursively(jni_libs_dir)) { + files_to_add.push_back(file); + } + _generate_export_configuration_file(jni::JvmType::ART); } else if (is_ios_export) { String base_ios_build_dir {String(RES_DIRECTORY).path_join(JVM_DIRECTORY).path_join("ios")}; @@ -183,17 +190,46 @@ void KotlinEditorExportPlugin::_add_exclude_filter_preset() { get_export_preset()->set_exclude_filter(get_export_preset()->get_exclude_filter() + "," + JVM_CONFIGURATION_PATH); } - if (const String build_dir = String {BUILD_DIRECTORY}.path_join("*"); !get_export_preset()->get_exclude_filter().contains(build_dir)) { + if (const String build_dir = String {BUILD_DIRECTORY}.path_join("*"); + !get_export_preset()->get_exclude_filter().contains(build_dir)) { // exclude build folder get_export_preset()->set_exclude_filter(get_export_preset()->get_exclude_filter() + "," + build_dir); } - if (const String jre_jars = String {"res://"} + JVM_DIRECTORY + "jre-*/**/*.jar"; !get_export_preset()->get_exclude_filter().contains(jre_jars)) { + if (const String jre_jars = String {"res://"} + JVM_DIRECTORY + "jre-*/**/*.jar"; + !get_export_preset()->get_exclude_filter().contains(jre_jars)) { // exclude any jars in the embedded jre get_export_preset()->set_exclude_filter(get_export_preset()->get_exclude_filter() + "," + jre_jars); } } +Vector KotlinEditorExportPlugin::_get_files_recursively(const String &path) { + const Ref dir_access = DirAccess::open(path); + if (!dir_access.is_valid()) return {}; + + dir_access->list_dir_begin(); + Vector files; + + while (true) { + String file_name = dir_access->get_next(); + if (file_name.is_empty()) break; + + if (file_name == "." || file_name == "..") continue; + + String full_path = path.path_join(file_name); + + if (dir_access->current_is_dir()) { + files.append_array(_get_files_recursively(full_path)); + } else { + files.push_back(full_path); + } + } + + dir_access->list_dir_end(); + return files; +} + + String KotlinEditorExportPlugin::get_name() const { return "Godot Kotlin/Jvm"; } @@ -207,4 +243,6 @@ void KotlinEditorExportPlugin::_export_file(const String& p_path, const String& } } + + #endif \ No newline at end of file diff --git a/src/kotlin_editor_export_plugin.h b/src/kotlin_editor_export_plugin.h index c23e83a16..5d712be8a 100644 --- a/src/kotlin_editor_export_plugin.h +++ b/src/kotlin_editor_export_plugin.h @@ -21,6 +21,7 @@ class KotlinEditorExportPlugin : public EditorExportPlugin { private: void _generate_export_configuration_file(jni::JvmType vm_type); void _add_exclude_filter_preset(); + static Vector _get_files_recursively(const String& path); }; #endif// GODOT_JVM_KOTLINEDITOREXPORTPLUGIN_H diff --git a/src/lifecycle/class_loader.cpp b/src/lifecycle/class_loader.cpp index 5fb89d7f4..a62eeb1dc 100644 --- a/src/lifecycle/class_loader.cpp +++ b/src/lifecycle/class_loader.cpp @@ -6,6 +6,9 @@ #include #endif +#include "core/config/project_settings.h" +#include "paths.h" + #include ClassLoader::ClassLoader(jni::Env& p_env, jni::JObject p_wrapped) { @@ -43,11 +46,22 @@ ClassLoader* ClassLoader::create_instance(jni::Env& env, const String& full_jar_ jni::JClass class_loader_cls {env.find_class("dalvik/system/DexClassLoader")}; jni::MethodID ctor {class_loader_cls.get_constructor_method_id(env, "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)V")}; jni::JObject jar_path {env.new_string(full_jar_path.utf8().get_data())}; + + jni::JObject parent_loader; + if (p_parent_loader.is_null()) { + jni::MethodID get_system_loader = class_loader_cls.get_static_method_id(env, "getSystemClassLoader", "()Ljava/lang/ClassLoader;"); + parent_loader = class_loader_cls.call_static_object_method(env, get_system_loader); + } else { + parent_loader = p_parent_loader; + } + + const jni::JObject jni_libs_path {env.new_string(ProjectSettings::get_singleton()->globalize_path(String{"user://"} + JNI_LIBS_PATH).utf8().get_data())}; + jvalue args[4] = { - jni::to_jni_arg(jar_path), - jni::to_jni_arg(jni::JObject(nullptr)), - jni::to_jni_arg(jni::JObject(nullptr)), - jni::to_jni_arg(p_parent_loader) + jni::to_jni_arg(jar_path), // dexPath -> String: the list of jar/apk files containing classes and resources, delimited by File.pathSeparator, which defaults to ":" on Android + jni::to_jni_arg(jni::JObject(nullptr)), // optimizedDirectory -> String: this parameter is deprecated and has no effect since API level 26. + jni::to_jni_arg(jni_libs_path), // librarySearchPath -> String: the list of directories containing native libraries, delimited by File.pathSeparator; may be null + jni::to_jni_arg(parent_loader) // parent -> ClassLoader: the parent class loader }; #else jni::JObject url = to_java_url(env, full_jar_path); diff --git a/src/lifecycle/paths.h b/src/lifecycle/paths.h index bf5e35107..bddc888e8 100644 --- a/src/lifecycle/paths.h +++ b/src/lifecycle/paths.h @@ -4,6 +4,8 @@ // Needs this as a macro if we want to append it to all other paths. #define JVM_DIRECTORY "jvm/" +# define JNI_LIBS_BASE_DIR "android/jniLibs" + static constexpr const char* USER_DIRECTORY {"user://"}; static constexpr const char* RES_DIRECTORY {"res://"}; @@ -85,6 +87,18 @@ static constexpr const char* USER_CODE_FILE {ANDROID_USER_CODE_FILE}; static constexpr const char* GRAAL_NATIVE_IMAGE_FILE {ANDROID_GRAAL_NATIVE_IMAGE_FILE}; static constexpr const char* RELATIVE_JVM_LIB_PATH {ANDROID_RELATIVE_JVM_LIB_PATH}; +#ifdef __aarch64__ +static constexpr const char* JNI_LIBS_PATH {JVM_DIRECTORY JNI_LIBS_BASE_DIR "/" "arm64-v8a"}; +#elif defined(__arm__) +static constexpr const char* JNI_LIBS_PATH {JVM_DIRECTORY JNI_LIBS_BASE_DIR "/" "armeabi"}; +#elif defined(__x86_64__) +static constexpr const char* JNI_LIBS_PATH {JVM_DIRECTORY JNI_LIBS_BASE_DIR "/" "x86_64"}; +#elif defined(__i386__) +static constexpr const char* JNI_LIBS_PATH {JVM_DIRECTORY JNI_LIBS_BASE_DIR "/" "x86"}; +#else +#error "Unsupported architecture" +#endif + #elif IOS_ENABLED static constexpr const char* BOOTSTRAP_FILE {IOS_BOOTSTRAP_FILE};