diff options
-rw-r--r-- | misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj | 32 | ||||
-rw-r--r-- | misc/dist/ios_xcode/godot_ios/godot_ios-Info.plist | 2 | ||||
-rw-r--r-- | modules/gdnative/gdnative.cpp | 20 | ||||
-rw-r--r-- | modules/gdnative/gdnative_library_editor_plugin.cpp | 16 | ||||
-rw-r--r-- | modules/gdnative/register_types.cpp | 108 | ||||
-rw-r--r-- | platform/iphone/export/export.cpp | 112 |
6 files changed, 244 insertions, 46 deletions
diff --git a/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj b/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj index 2ae3564faf..8c92ae2a7c 100644 --- a/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj +++ b/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj @@ -16,6 +16,20 @@ D0BCFE7818AEBFEB004A7AAE /* $binary.pck in Resources */ = {isa = PBXBuildFile; fileRef = D0BCFE7718AEBFEB004A7AAE /* $binary.pck */; }; /* End PBXBuildFile section */ +/* Begin PBXCopyFilesBuildPhase section */ + 90A13CD024AA68E500E8464F /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + $pbx_embeded_frameworks + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 1F1575711F582BE20003B888 /* dylibs */ = {isa = PBXFileReference; lastKnownFileType = folder; name = dylibs; path = "$binary/dylibs"; sourceTree = "<group>"; }; DEADBEEF1F582BE20003B888 /* $binary.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = godot; path = "$binary.a"; sourceTree = "<group>"; }; @@ -105,6 +119,7 @@ D0BCFE3018AEBDA2004A7AAE /* Sources */, D0BCFE3118AEBDA2004A7AAE /* Frameworks */, D0BCFE3218AEBDA2004A7AAE /* Resources */, + 90A13CD024AA68E500E8464F /* Embed Frameworks */, ); buildRules = ( ); @@ -230,7 +245,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 1F1575721F582BE20003B888 /* dylibs in Resources */, D07CD44E1C5D589C00B7FB28 /* Images.xcassets in Resources */, D0BCFE7818AEBFEB004A7AAE /* $binary.pck in Resources */, D0BCFE4618AEBDA2004A7AAE /* InfoPlist.strings in Resources */, @@ -284,7 +298,9 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "$code_sign_identity_debug"; COPY_PHASE_STRIP = NO; ENABLE_BITCODE = NO; - "FRAMEWORK_SEARCH_PATHS[arch=*]" = "$binary/**"; + "FRAMEWORK_SEARCH_PATHS[arch=*]" = ( + "$(PROJECT_DIR)/**", + ); GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; @@ -327,7 +343,9 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "$code_sign_identity_release"; COPY_PHASE_STRIP = YES; ENABLE_BITCODE = NO; - "FRAMEWORK_SEARCH_PATHS[arch=*]" = "$binary/**"; + "FRAMEWORK_SEARCH_PATHS[arch=*]" = ( + "$(PROJECT_DIR)/**", + ); ENABLE_NS_ASSERTIONS = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -357,6 +375,10 @@ DEVELOPMENT_TEAM = $team_id; INFOPLIST_FILE = "$binary/$binary-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)", @@ -383,6 +405,10 @@ DEVELOPMENT_TEAM = $team_id; INFOPLIST_FILE = "$binary/$binary-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)", diff --git a/misc/dist/ios_xcode/godot_ios/godot_ios-Info.plist b/misc/dist/ios_xcode/godot_ios/godot_ios-Info.plist index add2f6c084..e0cad2e7d1 100644 --- a/misc/dist/ios_xcode/godot_ios/godot_ios-Info.plist +++ b/misc/dist/ios_xcode/godot_ios/godot_ios-Info.plist @@ -26,6 +26,8 @@ <string>$signature</string> <key>CFBundleVersion</key> <string>$version</string> + <key>ITSAppUsesNonExemptEncryption</key> + <false /> <key>LSRequiresIPhoneOS</key> <true/> <key>LSSupportsOpeningDocumentsInPlace</key> diff --git a/modules/gdnative/gdnative.cpp b/modules/gdnative/gdnative.cpp index 3d747ba41e..bb2da70c3a 100644 --- a/modules/gdnative/gdnative.cpp +++ b/modules/gdnative/gdnative.cpp @@ -291,8 +291,26 @@ bool GDNative::initialize() { return false; } #ifdef IPHONE_ENABLED - // on iOS we use static linking + // On iOS we use static linking by default. String path = ""; + + // On iOS dylibs is not allowed, but can be replaced with .framework or .xcframework. + // If they are used, we can run dlopen on them. + // They should be located under Frameworks directory, so we need to replace library path. + if (!lib_path.ends_with(".a")) { + path = ProjectSettings::get_singleton()->globalize_path(lib_path); + + if (!FileAccess::exists(path)) { + String lib_name = lib_path.get_basename().get_file(); + String framework_path_format = "Frameworks/$name.framework/$name"; + + Dictionary format_dict; + format_dict["name"] = lib_name; + String framework_path = framework_path_format.format(format_dict, "$_"); + + path = OS::get_singleton()->get_executable_path().get_base_dir().plus_file(framework_path); + } + } #elif defined(ANDROID_ENABLED) // On Android dynamic libraries are located separately from resource assets, // we should pass library name to dlopen(). The library name is flattened diff --git a/modules/gdnative/gdnative_library_editor_plugin.cpp b/modules/gdnative/gdnative_library_editor_plugin.cpp index 5896da4640..1d4d188f23 100644 --- a/modules/gdnative/gdnative_library_editor_plugin.cpp +++ b/modules/gdnative/gdnative_library_editor_plugin.cpp @@ -127,15 +127,24 @@ void GDNativeLibraryEditor::_on_item_button(Object *item, int column, int id) { String section = (id == BUTTON_SELECT_DEPENDENCES || id == BUTTON_CLEAR_DEPENDENCES) ? "dependencies" : "entry"; if (id == BUTTON_SELECT_LIBRARY || id == BUTTON_SELECT_DEPENDENCES) { + TreeItem *treeItem = Object::cast_to<TreeItem>(item)->get_parent(); EditorFileDialog::FileMode mode = EditorFileDialog::FILE_MODE_OPEN_FILE; if (id == BUTTON_SELECT_DEPENDENCES) { mode = EditorFileDialog::FILE_MODE_OPEN_FILES; + } else if (treeItem->get_text(0) == "iOS") { + mode = EditorFileDialog::FILE_MODE_OPEN_ANY; } file_dialog->set_meta("target", target); file_dialog->set_meta("section", section); file_dialog->clear_filters(); - file_dialog->add_filter(Object::cast_to<TreeItem>(item)->get_parent()->get_metadata(0)); + + String filter_string = treeItem->get_metadata(0); + Vector<String> filters = filter_string.split(",", false, 0); + for (int i = 0; i < filters.size(); i++) { + file_dialog->add_filter(filters[i]); + } + file_dialog->set_file_mode(mode); file_dialog->popup_centered_ratio(); @@ -309,7 +318,9 @@ GDNativeLibraryEditor::GDNativeLibraryEditor() { platform_ios.name = "iOS"; platform_ios.entries.push_back("armv7"); platform_ios.entries.push_back("arm64"); - platform_ios.library_extension = "*.dylib"; + // iOS can use both Static and Dynamic libraries. + // Frameworks is actually a folder with files. + platform_ios.library_extension = "*.framework; Framework, *.xcframework; Binary Framework, *.a; Static Library, *.dylib; Dynamic Library"; platforms["iOS"] = platform_ios; } @@ -360,6 +371,7 @@ GDNativeLibraryEditor::GDNativeLibraryEditor() { //file_dialog->set_resizable(true); add_child(file_dialog); file_dialog->connect("file_selected", callable_mp(this, &GDNativeLibraryEditor::_on_library_selected)); + file_dialog->connect("dir_selected", callable_mp(this, &GDNativeLibraryEditor::_on_library_selected)); file_dialog->connect("files_selected", callable_mp(this, &GDNativeLibraryEditor::_on_dependencies_selected)); new_architecture_dialog = memnew(ConfirmationDialog); diff --git a/modules/gdnative/register_types.cpp b/modules/gdnative/register_types.cpp index 136af5bd1e..d1b1513ac3 100644 --- a/modules/gdnative/register_types.cpp +++ b/modules/gdnative/register_types.cpp @@ -142,47 +142,85 @@ void GDNativeExportPlugin::_export_file(const String &p_path, const String &p_ty } } + // Add symbols for staticaly linked libraries on iOS if (p_features.has("iOS")) { - // Register symbols in the "fake" dynamic lookup table, because dlsym does not work well on iOS. - LibrarySymbol expected_symbols[] = { - { "gdnative_init", true }, - { "gdnative_terminate", false }, - { "nativescript_init", false }, - { "nativescript_frame", false }, - { "nativescript_thread_enter", false }, - { "nativescript_thread_exit", false }, - { "gdnative_singleton", false } - }; - String declare_pattern = "extern \"C\" void $name(void)$weak;\n"; - String additional_code = "extern void register_dynamic_symbol(char *name, void *address);\n" - "extern void add_ios_init_callback(void (*cb)());\n"; - String linker_flags = ""; - for (unsigned long i = 0; i < sizeof(expected_symbols) / sizeof(expected_symbols[0]); ++i) { - String full_name = lib->get_symbol_prefix() + expected_symbols[i].name; - String code = declare_pattern.replace("$name", full_name); - code = code.replace("$weak", expected_symbols[i].is_required ? "" : " __attribute__((weak))"); - additional_code += code; - - if (!expected_symbols[i].is_required) { - if (linker_flags.length() > 0) { - linker_flags += " "; + bool should_fake_dynamic = false; + + List<String> entry_keys; + config->get_section_keys("entry", &entry_keys); + + for (List<String>::Element *E = entry_keys.front(); E; E = E->next()) { + String key = E->get(); + + Vector<String> tags = key.split("."); + + bool skip = false; + for (int i = 0; i < tags.size(); i++) { + bool has_feature = p_features.has(tags[i]); + + if (!has_feature) { + skip = true; + break; } - linker_flags += "-Wl,-U,_" + full_name; } - } - additional_code += String("void $prefixinit() {\n").replace("$prefix", lib->get_symbol_prefix()); - String register_pattern = " if (&$name) register_dynamic_symbol((char *)\"$name\", (void *)$name);\n"; - for (unsigned long i = 0; i < sizeof(expected_symbols) / sizeof(expected_symbols[0]); ++i) { - String full_name = lib->get_symbol_prefix() + expected_symbols[i].name; - additional_code += register_pattern.replace("$name", full_name); + if (skip) { + continue; + } + + String entry_lib_path = config->get_value("entry", key); + if (entry_lib_path.begins_with("res://") && entry_lib_path.ends_with(".a")) { + // If we find static library that was used for export + // we should add a fake loopup table. + // In case of dynamic library being used, + // this symbols will not cause any issues with library loading. + should_fake_dynamic = true; + break; + } } - additional_code += "}\n"; - additional_code += String("struct $prefixstruct {$prefixstruct() {add_ios_init_callback($prefixinit);}};\n").replace("$prefix", lib->get_symbol_prefix()); - additional_code += String("$prefixstruct $prefixstruct_instance;\n").replace("$prefix", lib->get_symbol_prefix()); - add_ios_cpp_code(additional_code); - add_ios_linker_flags(linker_flags); + if (should_fake_dynamic) { + // Register symbols in the "fake" dynamic lookup table, because dlsym does not work well on iOS. + LibrarySymbol expected_symbols[] = { + { "gdnative_init", true }, + { "gdnative_terminate", false }, + { "nativescript_init", false }, + { "nativescript_frame", false }, + { "nativescript_thread_enter", false }, + { "nativescript_thread_exit", false }, + { "gdnative_singleton", false } + }; + String declare_pattern = "extern \"C\" void $name(void)$weak;\n"; + String additional_code = "extern void register_dynamic_symbol(char *name, void *address);\n" + "extern void add_ios_init_callback(void (*cb)());\n"; + String linker_flags = ""; + for (unsigned long i = 0; i < sizeof(expected_symbols) / sizeof(expected_symbols[0]); ++i) { + String full_name = lib->get_symbol_prefix() + expected_symbols[i].name; + String code = declare_pattern.replace("$name", full_name); + code = code.replace("$weak", expected_symbols[i].is_required ? "" : " __attribute__((weak))"); + additional_code += code; + + if (!expected_symbols[i].is_required) { + if (linker_flags.length() > 0) { + linker_flags += " "; + } + linker_flags += "-Wl,-U,_" + full_name; + } + } + + additional_code += String("void $prefixinit() {\n").replace("$prefix", lib->get_symbol_prefix()); + String register_pattern = " if (&$name) register_dynamic_symbol((char *)\"$name\", (void *)$name);\n"; + for (unsigned long i = 0; i < sizeof(expected_symbols) / sizeof(expected_symbols[0]); ++i) { + String full_name = lib->get_symbol_prefix() + expected_symbols[i].name; + additional_code += register_pattern.replace("$name", full_name); + } + additional_code += "}\n"; + additional_code += String("struct $prefixstruct {$prefixstruct() {add_ios_init_callback($prefixinit);}};\n").replace("$prefix", lib->get_symbol_prefix()); + additional_code += String("$prefixstruct $prefixstruct_instance;\n").replace("$prefix", lib->get_symbol_prefix()); + + add_ios_cpp_code(additional_code); + add_ios_linker_flags(linker_flags); + } } } diff --git a/platform/iphone/export/export.cpp b/platform/iphone/export/export.cpp index 194e71c9de..4393a4ae9f 100644 --- a/platform/iphone/export/export.cpp +++ b/platform/iphone/export/export.cpp @@ -793,17 +793,33 @@ void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPrese String pbx_frameworks_refs; String pbx_resources_build; String pbx_resources_refs; + String pbx_embeded_frameworks; const String file_info_format = String("$build_id = {isa = PBXBuildFile; fileRef = $ref_id; };\n") + "$ref_id = {isa = PBXFileReference; lastKnownFileType = $file_type; name = \"$name\"; path = \"$file_path\"; sourceTree = \"<group>\"; };\n"; + for (int i = 0; i < p_additional_assets.size(); ++i) { + String additional_asset_info_format = file_info_format; + String build_id = (++current_id).str(); String ref_id = (++current_id).str(); + String framework_id = ""; + const IOSExportAsset &asset = p_additional_assets[i]; String type; if (asset.exported_path.ends_with(".framework")) { + additional_asset_info_format += "$framework_id = {isa = PBXBuildFile; fileRef = $ref_id; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };\n"; + framework_id = (++current_id).str(); + pbx_embeded_frameworks += framework_id + ",\n"; + type = "wrapper.framework"; + } else if (asset.exported_path.ends_with(".xcframework")) { + additional_asset_info_format += "$framework_id = {isa = PBXBuildFile; fileRef = $ref_id; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };\n"; + framework_id = (++current_id).str(); + pbx_embeded_frameworks += framework_id + ",\n"; + + type = "wrapper.xcframework"; } else if (asset.exported_path.ends_with(".dylib")) { type = "compiled.mach-o.dylib"; } else if (asset.exported_path.ends_with(".a")) { @@ -828,7 +844,10 @@ void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPrese format_dict["name"] = asset.exported_path.get_file(); format_dict["file_path"] = asset.exported_path; format_dict["file_type"] = type; - pbx_files += file_info_format.format(format_dict, "$_"); + if (framework_id.length() > 0) { + format_dict["framework_id"] = framework_id; + } + pbx_files += additional_asset_info_format.format(format_dict, "$_"); } // Note, frameworks like gamekit are always included in our project.pbxprof file @@ -862,6 +881,7 @@ void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPrese str = str.replace("$additional_pbx_frameworks_refs", pbx_frameworks_refs); str = str.replace("$additional_pbx_resources_build", pbx_resources_build); str = str.replace("$additional_pbx_resources_refs", pbx_resources_refs); + str = str.replace("$pbx_embeded_frameworks", pbx_embeded_frameworks); CharString cs = str.utf8(); p_project_data.resize(cs.size() - 1); @@ -892,8 +912,39 @@ Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir memdelete(filesystem_da); return ERR_FILE_NOT_FOUND; } - String additional_dir = p_is_framework && asset.ends_with(".dylib") ? "/dylibs/" : "/"; - String destination_dir = p_out_dir + additional_dir + asset.get_base_dir().replace("res://", ""); + + String base_dir = asset.get_base_dir().replace("res://", ""); + String destination_dir; + String destination; + String asset_path; + bool create_framework = false; + + if (p_is_framework && asset.ends_with(".dylib")) { + // For iOS we need to turn .dylib into .framework + // to be able to send application to AppStore + destination_dir = p_out_dir.plus_file("dylibs").plus_file(base_dir); + + String file_name = asset.get_basename().get_file(); + String framework_name = file_name + ".framework"; + + destination_dir = destination_dir.plus_file(framework_name); + destination = destination_dir.plus_file(file_name); + asset_path = destination_dir; + create_framework = true; + } else if (p_is_framework && (asset.ends_with(".framework") || asset.ends_with(".xcframework"))) { + destination_dir = p_out_dir.plus_file("dylibs").plus_file(base_dir); + + String file_name = asset.get_file(); + destination = destination_dir.plus_file(file_name); + asset_path = destination; + } else { + destination_dir = p_out_dir.plus_file(base_dir); + + String file_name = asset.get_file(); + destination = destination_dir.plus_file(file_name); + asset_path = destination; + } + if (!filesystem_da->dir_exists(destination_dir)) { Error make_dir_err = filesystem_da->make_dir_recursive(destination_dir); if (make_dir_err) { @@ -903,15 +954,66 @@ Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir } } - String destination = destination_dir.plus_file(asset.get_file()); Error err = dir_exists ? da->copy_dir(asset, destination) : da->copy(asset, destination); memdelete(da); if (err) { memdelete(filesystem_da); return err; } - IOSExportAsset exported_asset = { destination, p_is_framework }; + IOSExportAsset exported_asset = { asset_path, p_is_framework }; r_exported_assets.push_back(exported_asset); + + if (create_framework) { + String file_name = asset.get_basename().get_file(); + String framework_name = file_name + ".framework"; + + // Performing `install_name_tool -id @rpath/{name}.framework/{name} ./{name}` on dylib + { + List<String> install_name_args; + install_name_args.push_back("-id"); + install_name_args.push_back(String("@rpath").plus_file(framework_name).plus_file(file_name)); + install_name_args.push_back(destination); + + OS::get_singleton()->execute("install_name_tool", install_name_args, true); + } + + // Creating Info.plist + { + String info_plist_format = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n" + "<plist version=\"1.0\">\n" + "<dict>\n" + "<key>CFBundleShortVersionString</key>\n" + "<string>1.0</string>\n" + "<key>CFBundleIdentifier</key>\n" + "<string>com.gdnative.framework.$name</string>\n" + "<key>CFBundleName</key>\n" + "<string>$name</string>\n" + "<key>CFBundleExecutable</key>\n" + "<string>$name</string>\n" + "<key>DTPlatformName</key>\n" + "<string>iphoneos</string>\n" + "<key>CFBundleInfoDictionaryVersion</key>\n" + "<string>6.0</string>\n" + "<key>CFBundleVersion</key>\n" + "<string>1</string>\n" + "<key>CFBundlePackageType</key>\n" + "<string>FMWK</string>\n" + "<key>MinimumOSVersion</key>\n" + "<string>10.0</string>\n" + "</dict>\n" + "</plist>"; + + String info_plist = info_plist_format.replace("$name", file_name); + + FileAccess *f = FileAccess::open(asset_path.plus_file("Info.plist"), FileAccess::WRITE); + if (f) { + f->store_string(info_plist); + f->close(); + memdelete(f); + } + } + } } } memdelete(filesystem_da); |