Ver Fonte

Merge pull request #39996 from naithar/feature/ios-gdnative

[3.2] Add support of iOS's dynamic libraries to GDNative
Rémi Verschelde há 5 anos atrás
pai
commit
24f527b561

+ 29 - 3
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)",

+ 2 - 0
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>

+ 19 - 1
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

+ 17 - 3
modules/gdnative/gdnative_library_editor_plugin.cpp

@@ -141,14 +141,25 @@ void GDNativeLibraryEditor::_on_item_button(Object *item, int column, int id) {
 
 	if (id == BUTTON_SELECT_LIBRARY || id == BUTTON_SELECT_DEPENDENCES) {
 
+		TreeItem *treeItem = Object::cast_to<TreeItem>(item)->get_parent();
 		EditorFileDialog::Mode mode = EditorFileDialog::MODE_OPEN_FILE;
-		if (id == BUTTON_SELECT_DEPENDENCES)
+
+		if (id == BUTTON_SELECT_DEPENDENCES) {
 			mode = EditorFileDialog::MODE_OPEN_FILES;
+		} else if (treeItem->get_text(0) == "iOS") {
+			mode = EditorFileDialog::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_mode(mode);
 		file_dialog->popup_centered_ratio();
 
@@ -332,7 +343,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;
 	}
 
@@ -383,6 +396,7 @@ GDNativeLibraryEditor::GDNativeLibraryEditor() {
 	file_dialog->set_resizable(true);
 	add_child(file_dialog);
 	file_dialog->connect("file_selected", this, "_on_library_selected");
+	file_dialog->connect("dir_selected", this, "_on_library_selected");
 	file_dialog->connect("files_selected", this, "_on_dependencies_selected");
 
 	new_architecture_dialog = memnew(ConfirmationDialog);

+ 74 - 35
modules/gdnative/register_types.cpp

@@ -144,47 +144,86 @@ 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);
+		}
 	}
 }
 

+ 107 - 5
platform/iphone/export/export.cpp

@@ -663,17 +663,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")) {
@@ -698,7 +714,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
@@ -732,6 +751,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);
@@ -762,8 +782,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) {
@@ -773,15 +824,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);