diff --git a/misc/dist/osx_template.app/Contents/Info.plist b/misc/dist/osx_template.app/Contents/Info.plist index dd3d2937c..1493456e3 100644 --- a/misc/dist/osx_template.app/Contents/Info.plist +++ b/misc/dist/osx_template.app/Contents/Info.plist @@ -10,8 +10,6 @@ $name CFBundleDisplayName $name - CFBundleGetInfoString - $info CFBundleIconFile icon.icns CFBundleIdentifier @@ -26,6 +24,20 @@ $signature CFBundleVersion $version + DTPlatformBuild + $platfbuild + DTPlatformName + macosx + DTPlatformVersion + $sdkver + DTSDKBuild + $sdkbuild + DTSDKName + $sdkname + DTXcode + $xcodever + DTXcodeBuild + $xcodebuild $usage_descriptions NSHumanReadableCopyright $copyright @@ -38,11 +50,11 @@ $usage_descriptions LSApplicationCategoryType public.app-category.$app_category LSMinimumSystemVersion - 10.12 + $min_version LSMinimumSystemVersionByArchitecture x86_64 - 10.12 + $min_version NSHighResolutionCapable $highres diff --git a/platform/osx/export/export.cpp b/platform/osx/export/export.cpp index 62fada9ed..a7352b854 100644 --- a/platform/osx/export/export.cpp +++ b/platform/osx/export/export.cpp @@ -68,6 +68,7 @@ class EditorExportPlatformOSX : public EditorExportPlatform { Error _export_osx_plugins_for(Ref p_editor_export_plugin, const String &p_app_path_name, DirAccessRef &dir_access, bool p_sign_enabled, const Ref &p_preset, const String &p_ent_path); + Error _create_pkg(const Ref &p_preset, const String &p_pkg_path, const String &p_app_path_name); Error _create_dmg(const String &p_dmg_path, const String &p_pkg_name, const String &p_app_path_name); void _zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name); @@ -122,11 +123,29 @@ public: virtual List get_binary_extensions(const Ref &p_preset) const { List list; - if (use_dmg()) { - list.push_back("dmg"); + + if (p_preset.is_valid()) { + int dist_type = p_preset->get("export/distribution_type"); + if (dist_type == 0) { +#ifdef OSX_ENABLED + list.push_back("dmg"); +#endif + list.push_back("zip"); +#ifndef WINDOWS_ENABLED + list.push_back("app"); +#endif + } else if (dist_type == 1) { +#ifdef OSX_ENABLED + list.push_back("dmg"); +#endif + list.push_back("zip"); + } else if (dist_type == 2) { +#ifdef OSX_ENABLED + list.push_back("pkg"); +#endif + } } - list.push_back("zip"); - list.push_back("app"); + return list; } virtual Error export_project(const Ref &p_preset, bool p_debug, const String &p_path, int p_flags = 0); @@ -167,7 +186,7 @@ bool EditorExportPlatformOSX::get_option_visibility(const EditorExportPreset *p_ int codesign_tool = p_preset->get("codesign/codesign"); switch (codesign_tool) { case 1: { // built-in ad-hoc - if (p_option == "codesign/identity" || p_option == "codesign/certificate_file" || p_option == "codesign/certificate_password" || p_option == "codesign/custom_options") { + if (p_option == "codesign/identity" || p_option == "codesign/certificate_file" || p_option == "codesign/certificate_password" || p_option == "codesign/custom_options" || p_option == "codesign/team_id") { return false; } } break; @@ -184,25 +203,53 @@ bool EditorExportPlatformOSX::get_option_visibility(const EditorExportPreset *p_ } break; #endif default: { // disabled - if (p_option == "codesign/identity" || p_option == "codesign/certificate_file" || p_option == "codesign/certificate_password" || p_option == "codesign/custom_options" || p_option.begins_with("codesign/entitlements")) { + if (p_option == "codesign/identity" || p_option == "codesign/certificate_file" || p_option == "codesign/certificate_password" || p_option == "codesign/custom_options" || p_option.begins_with("codesign/entitlements") || p_option == "codesign/team_id") { return false; } } break; } + // Distribution type. + int dist_type = p_preset->get("export/distribution_type"); + if (dist_type != 2 && p_option == "codesign/installer_identity") { + return false; + } + + if (dist_type == 2 && p_option.begins_with("notarization/")) { + return false; + } + + if (dist_type != 2 && p_option == "codesign/provisioning_profile") { + return false; + } + + String custom_prof = p_preset->get("codesign/entitlements/custom_file"); + if (custom_prof != "" && p_option != "codesign/entitlements/custom_file" && p_option.begins_with("codesign/entitlements/")) { + return false; + } + + // Hide sandbox entitlements. + bool sandbox = p_preset->get("codesign/entitlements/app_sandbox/enabled"); + if (!sandbox && p_option != "codesign/entitlements/app_sandbox/enabled" && p_option.begins_with("codesign/entitlements/app_sandbox/")) { + return false; + } + // Hide irrelevant notarization options. int notary_tool = p_preset->get("notarization/notarization"); switch (notary_tool) { case 1: { // "rcodesign" - if (p_option == "notarization/apple_id_name" || p_option == "notarization/apple_id_password" || p_option == "notarization/apple_team_id") { + if (p_option == "notarization/apple_id_name" || p_option == "notarization/apple_id_password") { return false; } } break; - case 2: { // "altool" + case 2: { // "notarytool" + // All options are visible. + } break; + case 3: { // "altool" // All options are visible. } break; default: { // disabled - if (p_option == "notarization/apple_id_name" || p_option == "notarization/apple_id_password" || p_option == "notarization/apple_team_id" || p_option == "notarization/api_uuid" || p_option == "notarization/api_key") { + if (p_option == "notarization/apple_id_name" || p_option == "notarization/apple_id_password" || p_option == "notarization/api_uuid" || p_option == "notarization/api_key" || p_option == "notarization/api_key_id") { return false; } } break; @@ -213,11 +260,16 @@ bool EditorExportPlatformOSX::get_option_visibility(const EditorExportPreset *p_ } void EditorExportPlatformOSX::get_export_options(List *r_options) { +#ifdef OSX_ENABLED + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "export/distribution_type", PROPERTY_HINT_ENUM, "Testing,Distribution,App Store"), 1, true)); +#else + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "export/distribution_type", PROPERTY_HINT_ENUM, "Testing,Distribution"), 1, true)); +#endif + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/info"), "Made with Pandemonium Engine")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/icon", PROPERTY_HINT_FILE, "*.icns,*.png,*.webp,*.svg"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/icon_interpolation", PROPERTY_HINT_ENUM, "Nearest neighbor,Bilinear,Cubic,Trilinear,Lanczos"), 4)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/identifier", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.game"), "")); @@ -226,19 +278,31 @@ void EditorExportPlatformOSX::get_export_options(List *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/short_version"), "1.0")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/version"), "1.0")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/copyright"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/min_macos_version"), "10.12")); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "display/high_res"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "xcode/platform_build"), "14C18")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "xcode/sdk_version"), "13.1")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "xcode/sdk_build"), "22C55")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "xcode/sdk_name"), "macosx13.1")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "xcode/xcode_version"), "1420")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "xcode/xcode_build"), "14C18")); + #ifdef OSX_ENABLED - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/codesign", PROPERTY_HINT_ENUM, "Disabled,Built-in (ad-hoc only),PyOxidizer rcodesign,Xcode codesign"), 3, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/codesign", PROPERTY_HINT_ENUM, "Disabled,Built-in (ad-hoc only),rcodesign,Xcode codesign"), 3, true)); #else - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/codesign", PROPERTY_HINT_ENUM, "Disabled,Built-in (ad-hoc only),PyOxidizer rcodesign"), 1, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/codesign", PROPERTY_HINT_ENUM, "Disabled,Built-in (ad-hoc only),rcodesign"), 1, true)); #endif + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/installer_identity", PROPERTY_HINT_PLACEHOLDER_TEXT, "3rd Party Mac Developer Installer: (ID)"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/apple_team_id", PROPERTY_HINT_PLACEHOLDER_TEXT, "ID"), "")); // "codesign" only options: r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/identity", PROPERTY_HINT_PLACEHOLDER_TEXT, "Type: Name (ID)"), "")); // "rcodesign" only options: r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/certificate_file", PROPERTY_HINT_GLOBAL_FILE, "*.pfx,*.p12"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/certificate_password"), "")); // "codesign" and "rcodesign" only options: + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/provisioning_profile", PROPERTY_HINT_GLOBAL_FILE, "*.provisionprofile"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/entitlements/custom_file", PROPERTY_HINT_GLOBAL_FILE, "*.plist"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/allow_jit_code_execution"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/allow_unsigned_executable_memory"), false)); @@ -252,7 +316,7 @@ void EditorExportPlatformOSX::get_export_options(List *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/photos_library"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/apple_events"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/debugging"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/app_sandbox/enabled"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/app_sandbox/enabled"), false, true)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/app_sandbox/network_server"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/app_sandbox/network_client"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/app_sandbox/device_usb"), false)); @@ -261,20 +325,21 @@ void EditorExportPlatformOSX::get_export_options(List *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_pictures", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0)); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_music", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0)); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_movies", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_user_selected", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0)); r_options->push_back(ExportOption(PropertyInfo(Variant::POOL_STRING_ARRAY, "codesign/custom_options"), PoolStringArray())); #ifdef OSX_ENABLED - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "notarization/notarization", PROPERTY_HINT_ENUM, "Disabled,PyOxidizer rcodesign,Xcode altool"), 0, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "notarization/notarization", PROPERTY_HINT_ENUM, "Disabled,rcodesign,Xcode notarytool,Xcode altool (deprecated)"), 0, true)); #else - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "notarization/notarization", PROPERTY_HINT_ENUM, "Disabled,PyOxidizer rcodesign"), 0, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "notarization/notarization", PROPERTY_HINT_ENUM, "Disabled,rcodesign"), 0, true)); #endif - // "altool" only options: + // "altool" and "notarytool" only options: r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_id_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Apple ID email"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_id_password", PROPERTY_HINT_PLACEHOLDER_TEXT, "Enable two-factor authentication and provide app-specific password"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_team_id", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide team ID if your Apple ID belongs to multiple teams"), "")); // "altool" and "rcodesign" only options: r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/api_uuid", PROPERTY_HINT_PLACEHOLDER_TEXT, "App Store Connect issuer ID"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/api_key", PROPERTY_HINT_PLACEHOLDER_TEXT, "App Store Connect API key ID"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/api_key", PROPERTY_HINT_GLOBAL_FILE, "*.p8"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/api_key_id", PROPERTY_HINT_PLACEHOLDER_TEXT, "App Store Connect API key ID"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/microphone_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the microphone"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/camera_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the camera"), "")); @@ -473,8 +538,6 @@ void EditorExportPlatformOSX::_fix_plist(const Ref &p_preset strnew += lines[i].replace("$binary", p_binary) + "\n"; } else if (lines[i].find("$name") != -1) { strnew += lines[i].replace("$name", p_binary) + "\n"; - } else if (lines[i].find("$info") != -1) { - strnew += lines[i].replace("$info", p_preset->get("application/info")) + "\n"; } else if (lines[i].find("$identifier") != -1) { strnew += lines[i].replace("$identifier", p_preset->get("application/identifier")) + "\n"; } else if (lines[i].find("$short_version") != -1) { @@ -488,8 +551,22 @@ void EditorExportPlatformOSX::_fix_plist(const Ref &p_preset strnew += lines[i].replace("$app_category", cat.to_lower()) + "\n"; } else if (lines[i].find("$copyright") != -1) { strnew += lines[i].replace("$copyright", p_preset->get("application/copyright")) + "\n"; + } else if (lines[i].find("$min_version") != -1) { + strnew += lines[i].replace("$min_version", p_preset->get("application/min_macos_version")) + "\n"; } else if (lines[i].find("$highres") != -1) { strnew += lines[i].replace("$highres", p_preset->get("display/high_res") ? "\t" : "\t") + "\n"; + } else if (lines[i].find("$platfbuild") != -1) { + strnew += lines[i].replace("$platfbuild", p_preset->get("xcode/platform_build")) + "\n"; + } else if (lines[i].find("$sdkver") != -1) { + strnew += lines[i].replace("$sdkver", p_preset->get("xcode/sdk_version")) + "\n"; + } else if (lines[i].find("$sdkname") != -1) { + strnew += lines[i].replace("$sdkname", p_preset->get("xcode/sdk_name")) + "\n"; + } else if (lines[i].find("$sdkbuild") != -1) { + strnew += lines[i].replace("$sdkbuild", p_preset->get("xcode/sdk_build")) + "\n"; + } else if (lines[i].find("$xcodever") != -1) { + strnew += lines[i].replace("$xcodever", p_preset->get("xcode/xcode_version")) + "\n"; + } else if (lines[i].find("$xcodebuild") != -1) { + strnew += lines[i].replace("$xcodebuild", p_preset->get("xcode/xcode_build")) + "\n"; } else if (lines[i].find("$usage_descriptions") != -1) { String descriptions; if (!((String)p_preset->get("privacy/microphone_usage_description")).empty()) { @@ -587,7 +664,12 @@ Error EditorExportPlatformOSX::_notarize(const Ref &p_preset args.push_back(p_preset->get("notarization/api_uuid")); args.push_back("--api-key"); - args.push_back(p_preset->get("notarization/api_key")); + args.push_back(p_preset->get("notarization/api_key_id")); + + if (p_preset->get("notarization/api_key") != "") { + args.push_back("--api-key-path"); + args.push_back(p_preset->get("notarization/api_key")); + } args.push_back(p_path); @@ -618,7 +700,91 @@ Error EditorExportPlatformOSX::_notarize(const Ref &p_preset } } break; #ifdef OSX_ENABLED - case 2: { // "altool" + case 2: { // "notarytool" + print_verbose("using notarytool notarization..."); + + if (!FileAccess::exists("/usr/bin/xcrun") && !FileAccess::exists("/bin/xcrun")) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("Xcode command line tools are not installed.")); + return Error::FAILED; + } + + List args; + + args.push_back("notarytool"); + args.push_back("submit"); + + args.push_back(p_path); + + if (p_preset->get("notarization/apple_id_name") == "" && p_preset->get("notarization/api_uuid") == "") { + add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("Neither Apple ID name nor App Store Connect issuer ID name not specified.")); + return Error::FAILED; + } + if (p_preset->get("notarization/apple_id_name") != "" && p_preset->get("notarization/api_uuid") != "") { + add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("Both Apple ID name and App Store Connect issuer ID name are specified, only one should be set at the same time.")); + return Error::FAILED; + } + + if (p_preset->get("notarization/apple_id_name") != "") { + if (p_preset->get("notarization/apple_id_password") == "") { + add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("Apple ID password not specified.")); + return Error::FAILED; + } + args.push_back("--apple-id"); + args.push_back(p_preset->get("notarization/apple_id_name")); + + args.push_back("--password"); + args.push_back(p_preset->get("notarization/apple_id_password")); + } else { + if (p_preset->get("notarization/api_key_id") == "") { + add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("App Store Connect API key ID not specified.")); + return Error::FAILED; + } + args.push_back("--issuer"); + args.push_back(p_preset->get("notarization/api_uuid")); + + if (p_preset->get("notarization/api_key") != "") { + args.push_back("--key"); + args.push_back(p_preset->get("notarization/api_key")); + } + + args.push_back("--key-id"); + args.push_back(p_preset->get("notarization/api_key_id")); + } + + args.push_back("--no-progress"); + + if (p_preset->get("codesign/apple_team_id")) { + args.push_back("--team-id"); + args.push_back(p_preset->get("codesign/apple_team_id")); + } + + String str; + int exitcode = 0; + Error err = OS::get_singleton()->execute("xcrun", args, true, NULL, &str, &exitcode, true); + if (err != OK) { + add_message(EXPORT_MESSAGE_WARNING, TTR("Notarization"), TTR("Could not start xcrun executable.")); + return err; + } + + int rq_offset = str.find("id:"); + if (exitcode != 0 || rq_offset == -1) { + print_line("notarytool (" + p_path + "):\n" + str); + add_message(EXPORT_MESSAGE_WARNING, TTR("Notarization"), TTR("Notarization failed, see editor log for details.")); + return Error::FAILED; + } else { + print_verbose("notarytool (" + p_path + "):\n" + str); + int next_nl = str.find("\n", rq_offset); + String request_uuid = (next_nl == -1) ? str.substr(rq_offset + 4, -1) : str.substr(rq_offset + 4, next_nl - rq_offset - 4); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), vformat(TTR("Notarization request UUID: \"%s\""), request_uuid)); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), TTR("The notarization process generally takes less than an hour.")); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t" + TTR("You can check progress manually by opening a Terminal and running the following command:")); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t\t\"xcrun notarytool log --issuer --key-id --key \" or"); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t\t\"xcrun notarytool log --apple-id --password >\""); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t" + TTR("Run the following command to staple the notarization ticket to the exported application (optional):")); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t\t\"xcrun stapler staple \""); + } + } break; + case 3: { // "altool" print_verbose("using altool notarization..."); if (!FileAccess::exists("/usr/bin/xcrun") && !FileAccess::exists("/bin/xcrun")) { @@ -662,15 +828,15 @@ Error EditorExportPlatformOSX::_notarize(const Ref &p_preset args.push_back(p_preset->get("notarization/api_uuid")); args.push_back("--apiKey"); - args.push_back(p_preset->get("notarization/api_key")); + args.push_back(p_preset->get("notarization/api_key_id")); } args.push_back("--type"); args.push_back("osx"); - if (p_preset->get("notarization/apple_team_id")) { + if (p_preset->get("codesign/apple_team_id")) { args.push_back("--asc-provider"); - args.push_back(p_preset->get("notarization/apple_team_id")); + args.push_back(p_preset->get("codesign/apple_team_id")); } args.push_back("--file"); @@ -848,6 +1014,7 @@ Error EditorExportPlatformOSX::_code_sign_directory(const Ref &p_preset, const String &p_ent_path, bool p_should_error_on_non_code_sign) { + static Vector extensions_to_sign; + + if (extensions_to_sign.size() == 0) { + extensions_to_sign.push_back("dylib"); + extensions_to_sign.push_back("framework"); + extensions_to_sign.push_back(""); + } + Error err{ OK }; if (dir_access->dir_exists(p_src_path)) { #ifndef UNIX_ENABLED @@ -912,7 +1087,9 @@ Error EditorExportPlatformOSX::_copy_and_sign_files(DirAccessRef &dir_access, co // If it is a directory, find and sign all dynamic libraries. err = _code_sign_directory(p_preset, p_in_app_path, p_ent_path, p_should_error_on_non_code_sign); } else { - err = _code_sign(p_preset, p_in_app_path, p_ent_path); + if (extensions_to_sign.find(p_in_app_path.get_extension()) > -1) { + err = _code_sign(p_preset, p_in_app_path, p_ent_path); + } } } return err; @@ -935,6 +1112,42 @@ Error EditorExportPlatformOSX::_export_osx_plugins_for(Ref p return error; } +Error EditorExportPlatformOSX::_create_pkg(const Ref &p_preset, const String &p_pkg_path, const String &p_app_path_name) { + List args; + + if (FileAccess::exists(p_pkg_path)) { + OS::get_singleton()->move_to_trash(p_pkg_path); + } + + args.push_back("productbuild"); + args.push_back("--component"); + args.push_back(p_app_path_name); + args.push_back("/Applications"); + String ident = p_preset->get("codesign/installer_identity"); + if (ident != "") { + args.push_back("--timestamp"); + args.push_back("--sign"); + args.push_back(ident); + } + args.push_back("--quiet"); + args.push_back(p_pkg_path); + + String str; + Error err = OS::get_singleton()->execute("xcrun", args, true, nullptr, &str, nullptr, true); + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("PKG Creation"), TTR("Could not start productbuild executable.")); + return err; + } + + print_verbose("productbuild returned: " + str); + if (str.find("productbuild: error:") != -1) { + add_message(EXPORT_MESSAGE_ERROR, TTR("PKG Creation"), TTR("`productbuild` failed.")); + return FAILED; + } + + return OK; +} + Error EditorExportPlatformOSX::_create_dmg(const String &p_dmg_path, const String &p_pkg_name, const String &p_app_path_name) { List args; @@ -1027,12 +1240,16 @@ Error EditorExportPlatformOSX::export_project(const Ref &p_p pkg_name = OS::get_singleton()->get_safe_dir_name(pkg_name); String export_format; - if (use_dmg() && p_path.ends_with("dmg")) { - export_format = "dmg"; - } else if (p_path.ends_with("zip")) { + if (p_path.ends_with("zip")) { export_format = "zip"; } else if (p_path.ends_with("app")) { export_format = "app"; +#ifdef OSX_ENABLED + } else if (p_path.ends_with("dmg")) { + export_format = "dmg"; + } else if (p_path.ends_with("pkg")) { + export_format = "pkg"; +#endif } else { add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Invalid export format.")); return ERR_CANT_CREATE; @@ -1067,16 +1284,25 @@ Error EditorExportPlatformOSX::export_project(const Ref &p_p if (err == OK) { print_verbose("Creating " + tmp_app_path_name + "/Contents/MacOS"); err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/MacOS"); + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create directory \"%s\"."), tmp_app_path_name + "/Contents/MacOS")); + } } if (err == OK) { print_verbose("Creating " + tmp_app_path_name + "/Contents/Frameworks"); err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/Frameworks"); + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create directory \"%s\"."), tmp_app_path_name + "/Contents/Frameworks")); + } } if (err == OK) { print_verbose("Creating " + tmp_app_path_name + "/Contents/Resources"); err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/Resources"); + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create directory \"%s\"."), tmp_app_path_name + "/Contents/Resources")); + } } Vector translations = ProjectSettings::get_singleton()->get("locale/translations"); @@ -1117,6 +1343,9 @@ Error EditorExportPlatformOSX::export_project(const Ref &p_p unz_file_info info; char fname[16384]; ret = unzGetCurrentFileInfo(src_pkg_zip, &info, fname, 16384, nullptr, 0, nullptr, 0); + if (ret != UNZ_OK) { + break; + } String file = String::utf8(fname); @@ -1139,10 +1368,16 @@ Error EditorExportPlatformOSX::export_project(const Ref &p_p file = tmp_app_path_name.plus_file(file); if (err == OK) { err = tmp_app_dir->make_dir_recursive(file.get_base_dir()); + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create directory \"%s\"."), file.get_base_dir())); + } } if (err == OK) { String lnk_data = String::utf8((const char *)data.ptr(), data.size()); err = tmp_app_dir->create_link(lnk_data, file); + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not created symlink \"%s\" -> \"%s\"."), lnk_data, file)); + } print_verbose(vformat("ADDING SYMLINK %s => %s\n", file, lnk_data)); } @@ -1204,6 +1439,9 @@ Error EditorExportPlatformOSX::export_project(const Ref &p_p file = tmp_app_path_name.plus_file(file); if (err == OK) { err = tmp_app_dir->make_dir_recursive(file.get_base_dir()); + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create directory \"%s\"."), file.get_base_dir())); + } } if (err == OK) { FileAccess *f = FileAccess::open(file, FileAccess::WRITE); @@ -1216,6 +1454,7 @@ Error EditorExportPlatformOSX::export_project(const Ref &p_p } memdelete(f); } else { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not open \"%s\"."), file)); err = ERR_CANT_CREATE; } } @@ -1243,6 +1482,7 @@ Error EditorExportPlatformOSX::export_project(const Ref &p_p // See if we can code sign our new package. bool sign_enabled = (p_preset->get("codesign/codesign").operator int() > 0); + bool lib_validation = p_preset->get("codesign/entitlements/disable_library_validation"); String ent_path = p_preset->get("codesign/entitlements/custom_file"); if (sign_enabled && (ent_path == "")) { @@ -1268,7 +1508,7 @@ Error EditorExportPlatformOSX::export_project(const Ref &p_p ent_f->store_line(""); } - if ((bool)p_preset->get("codesign/entitlements/disable_library_validation")) { + if (lib_validation) { ent_f->store_line("com.apple.security.cs.disable-library-validation"); ent_f->store_line(""); } @@ -1305,6 +1545,19 @@ Error EditorExportPlatformOSX::export_project(const Ref &p_p ent_f->store_line(""); } + int dist_type = p_preset->get("export/distribution_type"); + if (dist_type == 2) { + String pprof = p_preset->get("codesign/provisioning_profile"); + String teamid = p_preset->get("codesign/apple_team_id"); + String bid = p_preset->get("application/bundle_identifier"); + if (pprof != "" && teamid != "") { + ent_f->store_line("com.apple.developer.team-identifier"); + ent_f->store_line("" + teamid + ""); + ent_f->store_line("com.apple.application-identifier"); + ent_f->store_line("" + teamid + "." + bid + ""); + } + } + if ((bool)p_preset->get("codesign/entitlements/app_sandbox/enabled")) { ent_f->store_line("com.apple.security.app-sandbox"); ent_f->store_line(""); @@ -1357,6 +1610,14 @@ Error EditorExportPlatformOSX::export_project(const Ref &p_p ent_f->store_line("com.apple.security.files.movies.read-write"); ent_f->store_line(""); } + if ((int)p_preset->get("codesign/entitlements/app_sandbox/files_user_selected") == 1) { + ent_f->store_line("com.apple.security.files.user-selected.read-only"); + ent_f->store_line(""); + } + if ((int)p_preset->get("codesign/entitlements/app_sandbox/files_user_selected") == 2) { + ent_f->store_line("com.apple.security.files.user-selected.read-write"); + ent_f->store_line(""); + } } ent_f->store_line(""); @@ -1388,7 +1649,6 @@ Error EditorExportPlatformOSX::export_project(const Ref &p_p } if (err == OK) { - bool lib_validation = p_preset->get("codesign/entitlements/disable_library_validation"); if ((!dylibs_found.empty() || !shared_objects.empty()) && sign_enabled && ad_hoc && !lib_validation) { add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Ad-hoc signed applications require the 'Disable Library Validation' entitlement to load dynamic libraries.")); err = ERR_CANT_CREATE; @@ -1424,6 +1684,15 @@ Error EditorExportPlatformOSX::export_project(const Ref &p_p } if (err == OK && sign_enabled) { + int dist_type = p_preset->get("export/distribution_type"); + if (dist_type == 2) { + String pprof = p_preset->get("codesign/provisioning_profile").operator String(); + if (pprof != "") { + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + err = da->copy(pprof, tmp_app_path_name + "/Contents/embedded.provisionprofile"); + } + } + if (ep.step(TTR("Code signing bundle"), 2)) { return ERR_SKIP; } @@ -1445,6 +1714,14 @@ Error EditorExportPlatformOSX::export_project(const Ref &p_p } err = _code_sign(p_preset, p_path, ent_path); } + } else if (export_format == "pkg") { + // Create a Installer. + if (err == OK) { + if (ep.step(TTR("Making PKG installer"), 3)) { + return ERR_SKIP; + } + err = _create_pkg(p_preset, p_path, tmp_app_path_name); + } } else if (export_format == "zip") { // Create ZIP. if (err == OK) { @@ -1467,7 +1744,7 @@ Error EditorExportPlatformOSX::export_project(const Ref &p_p bool noto_enabled = (p_preset->get("notarization/notarization").operator int() > 0); if (err == OK && noto_enabled) { - if (export_format == "app") { + if (export_format == "app" || export_format == "pkg") { add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), TTR("Notarization requires the app to be archived first, select the DMG or ZIP export format instead.")); } else { if (ep.step(TTR("Sending archive for notarization"), 4)) { @@ -1681,6 +1958,33 @@ bool EditorExportPlatformOSX::has_valid_project_configuration(const Refget("notarization/notarization"); + int dist_type = p_preset->get("export/distribution_type"); + + if (dist_type == 2) { + String pprof = p_preset->get("codesign/provisioning_profile"); + if (pprof == "") { + err += TTR("Provisioning profile is required for App Store distribution.") + "\n"; + valid = false; + } + String ident = p_preset->get("codesign/installer_identity"); + if (ident == "") { + err += TTR("Installer signing identity is required for App Store distribution.") + "\n"; + valid = false; + } + bool sandbox = p_preset->get("codesign/entitlements/app_sandbox/enabled"); + if (!sandbox) { + err += TTR("App sandbox is required for App Store distribution.") + "\n"; + valid = false; + } + if (codesign_tool == 0) { + err += TTR("Code signing is required for App Store distribution.") + "\n"; + valid = false; + } + if (codesign_tool == 1) { + err += TTR("App Store distribution with ad-hoc code signing is not supported.") + "\n"; + valid = false; + } + } if (notary_tool > 0) { if (ad_hoc) { @@ -1691,7 +1995,7 @@ bool EditorExportPlatformOSX::has_valid_project_configuration(const Ref