2023-01-05 13:25:55 +01:00
/**************************************************************************/
/* export_plugin.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
2021-07-14 22:15:25 +02:00
2022-02-14 14:00:03 +01:00
# include "export_plugin.h"
2021-07-30 12:41:24 +02:00
# include "codesign.h"
2022-10-05 19:31:07 +02:00
# include "lipo.h"
# include "macho.h"
2022-02-14 14:00:03 +01:00
2022-11-20 12:16:16 +01:00
# include "core/io/image_loader.h"
2022-07-31 20:14:15 +02:00
# include "core/string/translation.h"
2022-02-12 02:46:22 +01:00
# include "editor/editor_node.h"
2022-02-14 14:00:03 +01:00
# include "editor/editor_paths.h"
2022-05-08 09:46:53 +02:00
# include "editor/editor_scale.h"
# include "platform/macos/logo_svg.gen.h"
# include "platform/macos/run_icon_svg.gen.h"
2022-02-14 14:00:03 +01:00
2022-05-08 09:46:53 +02:00
# include "modules/modules_enabled.gen.h" // For svg and regex.
# ifdef MODULE_SVG_ENABLED
# include "modules/svg/image_loader_svg.h"
# endif
2021-07-14 22:15:25 +02:00
2022-07-28 00:34:12 +02:00
void EditorExportPlatformMacOS : : get_preset_features ( const Ref < EditorExportPreset > & p_preset , List < String > * r_features ) const {
2023-01-25 12:17:11 +01:00
r_features - > push_back ( p_preset - > get ( " binary_format/architecture " ) ) ;
String architecture = p_preset - > get ( " binary_format/architecture " ) ;
if ( architecture = = " universal " | | architecture = = " x86_64 " ) {
2021-07-14 22:15:25 +02:00
r_features - > push_back ( " s3tc " ) ;
2023-01-25 12:17:11 +01:00
r_features - > push_back ( " bptc " ) ;
} else if ( architecture = = " arm64 " ) {
2021-07-14 22:15:25 +02:00
r_features - > push_back ( " etc2 " ) ;
2023-01-25 12:17:11 +01:00
r_features - > push_back ( " astc " ) ;
} else {
ERR_PRINT ( " Invalid architecture " ) ;
2021-07-14 22:15:25 +02:00
}
}
2022-08-09 17:51:43 +02:00
bool EditorExportPlatformMacOS : : get_export_option_visibility ( const EditorExportPreset * p_preset , const String & p_option , const HashMap < StringName , Variant > & p_options ) const {
// Hide irrelevant code signing options.
if ( p_preset ) {
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 " ) {
return false ;
}
} break ;
case 2 : { // "rcodesign"
if ( p_option = = " codesign/identity " ) {
return false ;
}
} break ;
# ifdef MACOS_ENABLED
case 3 : { // "codesign"
if ( p_option = = " codesign/certificate_file " | | p_option = = " codesign/certificate_password " ) {
return false ;
}
} 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 " ) ) {
return false ;
}
} break ;
}
// 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 " ) {
return false ;
}
} break ;
2022-12-06 08:09:10 +01:00
case 2 : { // "notarytool"
// All options are visible.
} break ;
case 3 : { // "altool"
2022-08-09 17:51:43 +02:00
// All options are visible.
} break ;
default : { // disabled
2022-12-06 08:09:10 +01:00
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 " | | p_option = = " notarization/api_key_id " ) {
2022-08-09 17:51:43 +02:00
return false ;
}
} break ;
2022-01-22 21:50:40 +01:00
}
}
// These entitlements are required to run managed code, and are always enabled in Mono builds.
if ( Engine : : get_singleton ( ) - > has_singleton ( " GodotSharp " ) ) {
if ( p_option = = " codesign/entitlements/allow_jit_code_execution " | | p_option = = " codesign/entitlements/allow_unsigned_executable_memory " | | p_option = = " codesign/entitlements/allow_dyld_environment_variables " ) {
return false ;
}
}
return true ;
}
2022-07-20 08:28:22 +02:00
void EditorExportPlatformMacOS : : get_export_options ( List < ExportOption > * r_options ) {
2022-07-28 00:48:34 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " binary_format/architecture " , PROPERTY_HINT_ENUM , " universal,x86_64,arm64 " , PROPERTY_USAGE_STORAGE ) , " universal " ) ) ;
2021-07-14 22:15:25 +02:00
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 " ) , " " ) ) ;
2023-04-12 15:14:51 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : INT , " debug/export_console_wrapper " , PROPERTY_HINT_ENUM , " No,Debug Only,Debug and Release " ) , 1 ) ) ;
2022-11-17 22:46:48 +01:00
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 ) ) ;
2021-07-14 22:15:25 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " application/bundle_identifier " , PROPERTY_HINT_PLACEHOLDER_TEXT , " com.example.game " ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " application/signature " ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " application/app_category " , PROPERTY_HINT_ENUM , " Business,Developer-tools,Education,Entertainment,Finance,Games,Action-games,Adventure-games,Arcade-games,Board-games,Card-games,Casino-games,Dice-games,Educational-games,Family-games,Kids-games,Music-games,Puzzle-games,Racing-games,Role-playing-games,Simulation-games,Sports-games,Strategy-games,Trivia-games,Word-games,Graphics-design,Healthcare-fitness,Lifestyle,Medical,Music,News,Photography,Productivity,Reference,Social-networking,Sports,Travel,Utilities,Video,Weather " ) , " Games " ) ) ;
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 " ) , " " ) ) ;
2022-03-04 14:04:59 +01:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : DICTIONARY , " application/copyright_localized " , PROPERTY_HINT_LOCALIZABLE_STRING ) , Dictionary ( ) ) ) ;
2023-02-17 17:26:38 +01:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " display/high_res " ) , true ) ) ;
2021-07-14 22:15:25 +02:00
2022-08-09 17:51:43 +02:00
# ifdef MACOS_ENABLED
2022-12-06 08:09:10 +01:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : INT , " codesign/codesign " , PROPERTY_HINT_ENUM , " Disabled,Built-in (ad-hoc only),rcodesign,Xcode codesign " ) , 3 , true ) ) ;
2022-08-09 17:51:43 +02:00
# else
2022-12-06 08:09:10 +01:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : INT , " codesign/codesign " , PROPERTY_HINT_ENUM , " Disabled,Built-in (ad-hoc only),rcodesign " ) , 1 , true ) ) ;
2022-08-09 17:51:43 +02:00
# endif
// "codesign" only options:
2021-07-14 22:15:25 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " codesign/identity " , PROPERTY_HINT_PLACEHOLDER_TEXT , " Type: Name (ID) " ) , " " ) ) ;
2022-08-09 17:51:43 +02:00
// "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 " , PROPERTY_HINT_PASSWORD ) , " " ) ) ;
// "codesign" and "rcodesign" only options:
2021-07-14 22:15:25 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " codesign/entitlements/custom_file " , PROPERTY_HINT_GLOBAL_FILE , " *.plist " ) , " " ) ) ;
2022-01-22 21:50:40 +01:00
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 ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " codesign/entitlements/allow_dyld_environment_variables " ) , false ) ) ;
2021-07-14 22:15:25 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " codesign/entitlements/disable_library_validation " ) , false ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " codesign/entitlements/audio_input " ) , false ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " codesign/entitlements/camera " ) , false ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " codesign/entitlements/location " ) , false ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " codesign/entitlements/address_book " ) , false ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " codesign/entitlements/calendars " ) , false ) ) ;
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/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 ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " codesign/entitlements/app_sandbox/device_bluetooth " ) , false ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : INT , " codesign/entitlements/app_sandbox/files_downloads " , PROPERTY_HINT_ENUM , " No,Read-only,Read-write " ) , 0 ) ) ;
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 ) ) ;
2021-03-31 15:19:15 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : ARRAY , " codesign/entitlements/app_sandbox/helper_executables " , PROPERTY_HINT_ARRAY_TYPE , itos ( Variant : : STRING ) + " / " + itos ( PROPERTY_HINT_GLOBAL_FILE ) + " : " ) , Array ( ) ) ) ;
2021-07-14 22:15:25 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : PACKED_STRING_ARRAY , " codesign/custom_options " ) , PackedStringArray ( ) ) ) ;
2022-08-09 17:51:43 +02:00
# ifdef MACOS_ENABLED
2022-12-06 08:09:10 +01:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : INT , " notarization/notarization " , PROPERTY_HINT_ENUM , " Disabled,rcodesign,Xcode notarytool,Xcode altool (deprecated) " ) , 0 , true ) ) ;
2022-08-09 17:51:43 +02:00
# else
2022-12-06 08:09:10 +01:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : INT , " notarization/notarization " , PROPERTY_HINT_ENUM , " Disabled,rcodesign " ) , 0 , true ) ) ;
2022-08-09 17:51:43 +02:00
# endif
2022-12-06 08:09:10 +01:00
// "altool" and "notarytool" only options:
2021-07-14 22:15:25 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " notarization/apple_id_name " , PROPERTY_HINT_PLACEHOLDER_TEXT , " Apple ID email " ) , " " ) ) ;
2022-08-09 17:51:43 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " notarization/apple_id_password " , PROPERTY_HINT_PASSWORD , " Enable two-factor authentication and provide app-specific password " ) , " " ) ) ;
2021-07-14 22:15:25 +02:00
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 " ) , " " ) ) ;
2022-12-06 08:09:10 +01:00
// "altool", "notarytool" and "rcodesign" only options:
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " notarization/api_uuid " , PROPERTY_HINT_PLACEHOLDER_TEXT , " App Store Connect issuer ID UUID " ) , " " ) ) ;
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 " ) , " " ) ) ;
2022-08-09 17:51:43 +02:00
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 : : DICTIONARY , " privacy/microphone_usage_description_localized " , PROPERTY_HINT_LOCALIZABLE_STRING ) , Dictionary ( ) ) ) ;
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 " ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : DICTIONARY , " privacy/camera_usage_description_localized " , PROPERTY_HINT_LOCALIZABLE_STRING ) , Dictionary ( ) ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " privacy/location_usage_description " , PROPERTY_HINT_PLACEHOLDER_TEXT , " Provide a message if you need to use the location information " ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : DICTIONARY , " privacy/location_usage_description_localized " , PROPERTY_HINT_LOCALIZABLE_STRING ) , Dictionary ( ) ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " privacy/address_book_usage_description " , PROPERTY_HINT_PLACEHOLDER_TEXT , " Provide a message if you need to use the address book " ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : DICTIONARY , " privacy/address_book_usage_description_localized " , PROPERTY_HINT_LOCALIZABLE_STRING ) , Dictionary ( ) ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " privacy/calendar_usage_description " , PROPERTY_HINT_PLACEHOLDER_TEXT , " Provide a message if you need to use the calendar " ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : DICTIONARY , " privacy/calendar_usage_description_localized " , PROPERTY_HINT_LOCALIZABLE_STRING ) , Dictionary ( ) ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " privacy/photos_library_usage_description " , PROPERTY_HINT_PLACEHOLDER_TEXT , " Provide a message if you need to use the photo library " ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : DICTIONARY , " privacy/photos_library_usage_description_localized " , PROPERTY_HINT_LOCALIZABLE_STRING ) , Dictionary ( ) ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " privacy/desktop_folder_usage_description " , PROPERTY_HINT_PLACEHOLDER_TEXT , " Provide a message if you need to use Desktop folder " ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : DICTIONARY , " privacy/desktop_folder_usage_description_localized " , PROPERTY_HINT_LOCALIZABLE_STRING ) , Dictionary ( ) ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " privacy/documents_folder_usage_description " , PROPERTY_HINT_PLACEHOLDER_TEXT , " Provide a message if you need to use Documents folder " ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : DICTIONARY , " privacy/documents_folder_usage_description_localized " , PROPERTY_HINT_LOCALIZABLE_STRING ) , Dictionary ( ) ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " privacy/downloads_folder_usage_description " , PROPERTY_HINT_PLACEHOLDER_TEXT , " Provide a message if you need to use Downloads folder " ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : DICTIONARY , " privacy/downloads_folder_usage_description_localized " , PROPERTY_HINT_LOCALIZABLE_STRING ) , Dictionary ( ) ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " privacy/network_volumes_usage_description " , PROPERTY_HINT_PLACEHOLDER_TEXT , " Provide a message if you need to use network volumes " ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : DICTIONARY , " privacy/network_volumes_usage_description_localized " , PROPERTY_HINT_LOCALIZABLE_STRING ) , Dictionary ( ) ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " privacy/removable_volumes_usage_description " , PROPERTY_HINT_PLACEHOLDER_TEXT , " Provide a message if you need to use removable volumes " ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : DICTIONARY , " privacy/removable_volumes_usage_description_localized " , PROPERTY_HINT_LOCALIZABLE_STRING ) , Dictionary ( ) ) ) ;
2021-07-14 22:15:25 +02:00
2022-05-08 09:46:53 +02:00
String run_script = " #!/usr/bin/env bash \n "
" unzip -o -q \" {temp_dir}/{archive_name} \" -d \" {temp_dir} \" \n "
" open \" {temp_dir}/{exe_name}.app \" --args {cmd_args} " ;
String cleanup_script = " #!/usr/bin/env bash \n "
" kill $(pgrep -x -f \" {temp_dir}/{exe_name}.app/Contents/MacOS/{exe_name} {cmd_args} \" ) \n "
" rm -rf \" {temp_dir} \" " ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " ssh_remote_deploy/enabled " ) , false ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " ssh_remote_deploy/host " ) , " user@host_ip " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " ssh_remote_deploy/port " ) , " 22 " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " ssh_remote_deploy/extra_args_ssh " , PROPERTY_HINT_MULTILINE_TEXT ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " ssh_remote_deploy/extra_args_scp " , PROPERTY_HINT_MULTILINE_TEXT ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " ssh_remote_deploy/run_script " , PROPERTY_HINT_MULTILINE_TEXT ) , run_script ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " ssh_remote_deploy/cleanup_script " , PROPERTY_HINT_MULTILINE_TEXT ) , cleanup_script ) ) ;
2021-07-14 22:15:25 +02:00
}
void _rgba8_to_packbits_encode ( int p_ch , int p_size , Vector < uint8_t > & p_source , Vector < uint8_t > & p_dest ) {
int src_len = p_size * p_size ;
Vector < uint8_t > result ;
result . resize ( src_len * 1.25 ) ; //temp vector for rle encoded data, make it 25% larger for worst case scenario
int res_size = 0 ;
uint8_t buf [ 128 ] ;
int buf_size = 0 ;
int i = 0 ;
while ( i < src_len ) {
uint8_t cur = p_source . ptr ( ) [ i * 4 + p_ch ] ;
if ( i < src_len - 2 ) {
if ( ( p_source . ptr ( ) [ ( i + 1 ) * 4 + p_ch ] = = cur ) & & ( p_source . ptr ( ) [ ( i + 2 ) * 4 + p_ch ] = = cur ) ) {
if ( buf_size > 0 ) {
result . write [ res_size + + ] = ( uint8_t ) ( buf_size - 1 ) ;
memcpy ( & result . write [ res_size ] , & buf , buf_size ) ;
res_size + = buf_size ;
buf_size = 0 ;
}
uint8_t lim = i + 130 > = src_len ? src_len - i - 1 : 130 ;
bool hit_lim = true ;
for ( int j = 3 ; j < = lim ; j + + ) {
if ( p_source . ptr ( ) [ ( i + j ) * 4 + p_ch ] ! = cur ) {
hit_lim = false ;
i = i + j - 1 ;
result . write [ res_size + + ] = ( uint8_t ) ( j - 3 + 0x80 ) ;
result . write [ res_size + + ] = cur ;
break ;
}
}
if ( hit_lim ) {
result . write [ res_size + + ] = ( uint8_t ) ( lim - 3 + 0x80 ) ;
result . write [ res_size + + ] = cur ;
i = i + lim ;
}
} else {
buf [ buf_size + + ] = cur ;
if ( buf_size = = 128 ) {
result . write [ res_size + + ] = ( uint8_t ) ( buf_size - 1 ) ;
memcpy ( & result . write [ res_size ] , & buf , buf_size ) ;
res_size + = buf_size ;
buf_size = 0 ;
}
}
} else {
buf [ buf_size + + ] = cur ;
result . write [ res_size + + ] = ( uint8_t ) ( buf_size - 1 ) ;
memcpy ( & result . write [ res_size ] , & buf , buf_size ) ;
res_size + = buf_size ;
buf_size = 0 ;
}
i + + ;
}
int ofs = p_dest . size ( ) ;
p_dest . resize ( p_dest . size ( ) + res_size ) ;
memcpy ( & p_dest . write [ ofs ] , result . ptr ( ) , res_size ) ;
}
2022-11-17 22:46:48 +01:00
void EditorExportPlatformMacOS : : _make_icon ( const Ref < EditorExportPreset > & p_preset , const Ref < Image > & p_icon , Vector < uint8_t > & p_data ) {
2021-07-14 22:15:25 +02:00
Ref < ImageTexture > it = memnew ( ImageTexture ) ;
Vector < uint8_t > data ;
data . resize ( 8 ) ;
data . write [ 0 ] = ' i ' ;
data . write [ 1 ] = ' c ' ;
data . write [ 2 ] = ' n ' ;
data . write [ 3 ] = ' s ' ;
struct MacOSIconInfo {
const char * name ;
const char * mask_name ;
bool is_png ;
int size ;
} ;
static const MacOSIconInfo icon_infos [ ] = {
2021-08-12 21:45:33 +02:00
{ " ic10 " , " " , true , 1024 } , //1024× 1024 32-bit PNG and 512× 512@2x 32-bit "retina" PNG
2021-07-14 22:15:25 +02:00
{ " ic09 " , " " , true , 512 } , //512× 512 32-bit PNG
2021-08-12 21:45:33 +02:00
{ " ic14 " , " " , true , 512 } , //256× 256@2x 32-bit "retina" PNG
2021-07-14 22:15:25 +02:00
{ " ic08 " , " " , true , 256 } , //256× 256 32-bit PNG
2021-08-12 21:45:33 +02:00
{ " ic13 " , " " , true , 256 } , //128× 128@2x 32-bit "retina" PNG
{ " ic07 " , " " , true , 128 } , //128× 128 32-bit PNG
{ " ic12 " , " " , true , 64 } , //32× 32@2× 32-bit "retina" PNG
{ " ic11 " , " " , true , 32 } , //16× 16@2× 32-bit "retina" PNG
{ " il32 " , " l8mk " , false , 32 } , //32× 32 24-bit RLE + 8-bit uncompressed mask
{ " is32 " , " s8mk " , false , 16 } //16× 16 24-bit RLE + 8-bit uncompressed mask
2021-07-14 22:15:25 +02:00
} ;
for ( uint64_t i = 0 ; i < ( sizeof ( icon_infos ) / sizeof ( icon_infos [ 0 ] ) ) ; + + i ) {
Ref < Image > copy = p_icon ; // does this make sense? doesn't this just increase the reference count instead of making a copy? Do we even need a copy?
copy - > convert ( Image : : FORMAT_RGBA8 ) ;
2022-11-17 22:46:48 +01:00
copy - > resize ( icon_infos [ i ] . size , icon_infos [ i ] . size , ( Image : : Interpolation ) ( p_preset - > get ( " application/icon_interpolation " ) . operator int ( ) ) ) ;
2021-07-14 22:15:25 +02:00
if ( icon_infos [ i ] . is_png ) {
// Encode PNG icon.
2022-05-04 01:49:20 +02:00
it - > set_image ( copy ) ;
2022-08-30 02:34:01 +02:00
String path = EditorPaths : : get_singleton ( ) - > get_cache_dir ( ) . path_join ( " icon.png " ) ;
2022-06-03 01:33:42 +02:00
ResourceSaver : : save ( it , path ) ;
2021-07-14 22:15:25 +02:00
2022-04-12 09:12:40 +02:00
{
Ref < FileAccess > f = FileAccess : : open ( path , FileAccess : : READ ) ;
if ( f . is_null ( ) ) {
// Clean up generated file.
DirAccess : : remove_file_or_error ( path ) ;
2022-06-03 17:34:24 +02:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Icon Creation " ) , vformat ( TTR ( " Could not open icon file \" %s \" . " ) , path ) ) ;
return ;
2022-04-12 09:12:40 +02:00
}
2021-07-14 22:15:25 +02:00
2022-04-12 09:12:40 +02:00
int ofs = data . size ( ) ;
uint64_t len = f - > get_length ( ) ;
data . resize ( data . size ( ) + len + 8 ) ;
f - > get_buffer ( & data . write [ ofs + 8 ] , len ) ;
len + = 8 ;
len = BSWAP32 ( len ) ;
memcpy ( & data . write [ ofs ] , icon_infos [ i ] . name , 4 ) ;
encode_uint32 ( len , & data . write [ ofs + 4 ] ) ;
}
2021-07-14 22:15:25 +02:00
// Clean up generated file.
DirAccess : : remove_file_or_error ( path ) ;
} else {
Vector < uint8_t > src_data = copy - > get_data ( ) ;
//encode 24bit RGB RLE icon
{
int ofs = data . size ( ) ;
data . resize ( data . size ( ) + 8 ) ;
_rgba8_to_packbits_encode ( 0 , icon_infos [ i ] . size , src_data , data ) ; // encode R
_rgba8_to_packbits_encode ( 1 , icon_infos [ i ] . size , src_data , data ) ; // encode G
_rgba8_to_packbits_encode ( 2 , icon_infos [ i ] . size , src_data , data ) ; // encode B
int len = data . size ( ) - ofs ;
len = BSWAP32 ( len ) ;
memcpy ( & data . write [ ofs ] , icon_infos [ i ] . name , 4 ) ;
encode_uint32 ( len , & data . write [ ofs + 4 ] ) ;
}
//encode 8bit mask uncompressed icon
{
int ofs = data . size ( ) ;
int len = copy - > get_width ( ) * copy - > get_height ( ) ;
data . resize ( data . size ( ) + len + 8 ) ;
for ( int j = 0 ; j < len ; j + + ) {
data . write [ ofs + 8 + j ] = src_data . ptr ( ) [ j * 4 + 3 ] ;
}
len + = 8 ;
len = BSWAP32 ( len ) ;
memcpy ( & data . write [ ofs ] , icon_infos [ i ] . mask_name , 4 ) ;
encode_uint32 ( len , & data . write [ ofs + 4 ] ) ;
}
}
}
uint32_t total_len = data . size ( ) ;
total_len = BSWAP32 ( total_len ) ;
encode_uint32 ( total_len , & data . write [ 4 ] ) ;
p_data = data ;
}
2022-07-20 08:28:22 +02:00
void EditorExportPlatformMacOS : : _fix_plist ( const Ref < EditorExportPreset > & p_preset , Vector < uint8_t > & plist , const String & p_binary ) {
2021-07-14 22:15:25 +02:00
String str ;
String strnew ;
str . parse_utf8 ( ( const char * ) plist . ptr ( ) , plist . size ( ) ) ;
Vector < String > lines = str . split ( " \n " ) ;
for ( int i = 0 ; i < lines . size ( ) ; i + + ) {
if ( lines [ i ] . find ( " $binary " ) ! = - 1 ) {
strnew + = lines [ i ] . replace ( " $binary " , p_binary ) + " \n " ;
} else if ( lines [ i ] . find ( " $name " ) ! = - 1 ) {
2022-10-18 16:43:37 +02:00
strnew + = lines [ i ] . replace ( " $name " , GLOBAL_GET ( " application/config/name " ) ) + " \n " ;
2021-07-14 22:15:25 +02:00
} else if ( lines [ i ] . find ( " $bundle_identifier " ) ! = - 1 ) {
strnew + = lines [ i ] . replace ( " $bundle_identifier " , p_preset - > get ( " application/bundle_identifier " ) ) + " \n " ;
} else if ( lines [ i ] . find ( " $short_version " ) ! = - 1 ) {
strnew + = lines [ i ] . replace ( " $short_version " , p_preset - > get ( " application/short_version " ) ) + " \n " ;
} else if ( lines [ i ] . find ( " $version " ) ! = - 1 ) {
strnew + = lines [ i ] . replace ( " $version " , p_preset - > get ( " application/version " ) ) + " \n " ;
} else if ( lines [ i ] . find ( " $signature " ) ! = - 1 ) {
strnew + = lines [ i ] . replace ( " $signature " , p_preset - > get ( " application/signature " ) ) + " \n " ;
} else if ( lines [ i ] . find ( " $app_category " ) ! = - 1 ) {
String cat = p_preset - > get ( " application/app_category " ) ;
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 ( " $highres " ) ! = - 1 ) {
2021-07-30 12:41:24 +02:00
strnew + = lines [ i ] . replace ( " $highres " , p_preset - > get ( " display/high_res " ) ? " \t <true/> " : " \t <false/> " ) + " \n " ;
} else if ( lines [ i ] . find ( " $usage_descriptions " ) ! = - 1 ) {
String descriptions ;
if ( ! ( ( String ) p_preset - > get ( " privacy/microphone_usage_description " ) ) . is_empty ( ) ) {
descriptions + = " \t <key>NSMicrophoneUsageDescription</key> \n " ;
descriptions + = " \t <string> " + ( String ) p_preset - > get ( " privacy/microphone_usage_description " ) + " </string> \n " ;
}
if ( ! ( ( String ) p_preset - > get ( " privacy/camera_usage_description " ) ) . is_empty ( ) ) {
descriptions + = " \t <key>NSCameraUsageDescription</key> \n " ;
descriptions + = " \t <string> " + ( String ) p_preset - > get ( " privacy/camera_usage_description " ) + " </string> \n " ;
}
if ( ! ( ( String ) p_preset - > get ( " privacy/location_usage_description " ) ) . is_empty ( ) ) {
descriptions + = " \t <key>NSLocationUsageDescription</key> \n " ;
descriptions + = " \t <string> " + ( String ) p_preset - > get ( " privacy/location_usage_description " ) + " </string> \n " ;
}
if ( ! ( ( String ) p_preset - > get ( " privacy/address_book_usage_description " ) ) . is_empty ( ) ) {
descriptions + = " \t <key>NSContactsUsageDescription</key> \n " ;
descriptions + = " \t <string> " + ( String ) p_preset - > get ( " privacy/address_book_usage_description " ) + " </string> \n " ;
}
if ( ! ( ( String ) p_preset - > get ( " privacy/calendar_usage_description " ) ) . is_empty ( ) ) {
descriptions + = " \t <key>NSCalendarsUsageDescription</key> \n " ;
descriptions + = " \t <string> " + ( String ) p_preset - > get ( " privacy/calendar_usage_description " ) + " </string> \n " ;
}
if ( ! ( ( String ) p_preset - > get ( " privacy/photos_library_usage_description " ) ) . is_empty ( ) ) {
descriptions + = " \t <key>NSPhotoLibraryUsageDescription</key> \n " ;
descriptions + = " \t <string> " + ( String ) p_preset - > get ( " privacy/photos_library_usage_description " ) + " </string> \n " ;
}
if ( ! ( ( String ) p_preset - > get ( " privacy/desktop_folder_usage_description " ) ) . is_empty ( ) ) {
descriptions + = " \t <key>NSDesktopFolderUsageDescription</key> \n " ;
descriptions + = " \t <string> " + ( String ) p_preset - > get ( " privacy/desktop_folder_usage_description " ) + " </string> \n " ;
}
if ( ! ( ( String ) p_preset - > get ( " privacy/documents_folder_usage_description " ) ) . is_empty ( ) ) {
descriptions + = " \t <key>NSDocumentsFolderUsageDescription</key> \n " ;
descriptions + = " \t <string> " + ( String ) p_preset - > get ( " privacy/documents_folder_usage_description " ) + " </string> \n " ;
}
if ( ! ( ( String ) p_preset - > get ( " privacy/downloads_folder_usage_description " ) ) . is_empty ( ) ) {
descriptions + = " \t <key>NSDownloadsFolderUsageDescription</key> \n " ;
descriptions + = " \t <string> " + ( String ) p_preset - > get ( " privacy/downloads_folder_usage_description " ) + " </string> \n " ;
}
if ( ! ( ( String ) p_preset - > get ( " privacy/network_volumes_usage_description " ) ) . is_empty ( ) ) {
descriptions + = " \t <key>NSNetworkVolumesUsageDescription</key> \n " ;
descriptions + = " \t <string> " + ( String ) p_preset - > get ( " privacy/network_volumes_usage_description " ) + " </string> \n " ;
}
if ( ! ( ( String ) p_preset - > get ( " privacy/removable_volumes_usage_description " ) ) . is_empty ( ) ) {
descriptions + = " \t <key>NSRemovableVolumesUsageDescription</key> \n " ;
descriptions + = " \t <string> " + ( String ) p_preset - > get ( " privacy/removable_volumes_usage_description " ) + " </string> \n " ;
}
if ( ! descriptions . is_empty ( ) ) {
strnew + = lines [ i ] . replace ( " $usage_descriptions " , descriptions ) ;
}
2021-07-14 22:15:25 +02:00
} else {
strnew + = lines [ i ] + " \n " ;
}
}
CharString cs = strnew . utf8 ( ) ;
plist . resize ( cs . size ( ) - 1 ) ;
for ( int i = 0 ; i < cs . size ( ) - 1 ; i + + ) {
plist . write [ i ] = cs [ i ] ;
}
}
/**
2022-07-20 08:28:22 +02:00
* If we ' re running the macOS version of the Godot editor we ' ll :
2021-10-28 15:43:36 +02:00
* - export our application bundle to a temporary folder
* - attempt to code sign it
* - and then wrap it up in a DMG
*/
2021-07-14 22:15:25 +02:00
2022-07-20 08:28:22 +02:00
Error EditorExportPlatformMacOS : : _notarize ( const Ref < EditorExportPreset > & p_preset , const String & p_path ) {
2022-08-09 17:51:43 +02:00
int notary_tool = p_preset - > get ( " notarization/notarization " ) ;
switch ( notary_tool ) {
case 1 : { // "rcodesign"
print_verbose ( " using rcodesign notarization... " ) ;
2022-10-18 16:43:37 +02:00
String rcodesign = EDITOR_GET ( " export/macos/rcodesign " ) . operator String ( ) ;
2022-08-09 17:51:43 +02:00
if ( rcodesign . is_empty ( ) ) {
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Notarization " ) , TTR ( " rcodesign path is not set. Configure rcodesign path in the Editor Settings (Export > macOS > rcodesign). " ) ) ;
return Error : : FAILED ;
}
List < String > args ;
args . push_back ( " notary-submit " ) ;
if ( p_preset - > get ( " notarization/api_uuid " ) = = " " ) {
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Notarization " ) , TTR ( " App Store Connect issuer ID name not specified. " ) ) ;
return Error : : FAILED ;
}
if ( p_preset - > get ( " notarization/api_key " ) = = " " ) {
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Notarization " ) , TTR ( " App Store Connect API key ID not specified. " ) ) ;
return Error : : FAILED ;
}
args . push_back ( " --api-issuer " ) ;
args . push_back ( p_preset - > get ( " notarization/api_uuid " ) ) ;
args . push_back ( " --api-key " ) ;
2022-12-06 08:09:10 +01:00
args . push_back ( p_preset - > get ( " notarization/api_key_id " ) ) ;
if ( ! p_preset - > get ( " notarization/api_key " ) . operator String ( ) . is_empty ( ) ) {
args . push_back ( " --api-key-path " ) ;
args . push_back ( p_preset - > get ( " notarization/api_key " ) ) ;
}
2022-08-09 17:51:43 +02:00
args . push_back ( p_path ) ;
String str ;
int exitcode = 0 ;
Error err = OS : : get_singleton ( ) - > execute ( rcodesign , args , & str , & exitcode , true ) ;
if ( err ! = OK ) {
add_message ( EXPORT_MESSAGE_WARNING , TTR ( " Notarization " ) , TTR ( " Could not start rcodesign executable. " ) ) ;
return err ;
}
int rq_offset = str . find ( " created submission ID: " ) ;
if ( exitcode ! = 0 | | rq_offset = = - 1 ) {
print_line ( " rcodesign ( " + 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 ( " rcodesign ( " + p_path + " ): \n " + str ) ;
int next_nl = str . find ( " \n " , rq_offset ) ;
2022-12-06 08:09:10 +01:00
String request_uuid = ( next_nl = = - 1 ) ? str . substr ( rq_offset + 23 , - 1 ) : str . substr ( rq_offset + 23 , next_nl - rq_offset - 23 ) ;
2022-08-09 17:51:43 +02:00
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 \" rcodesign notary-log --api-issuer <api uuid> --api-key <api key> <request uuid> \" " ) ;
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 \" rcodesign staple <app path> \" " ) ;
}
} break ;
2022-07-20 08:28:22 +02:00
# ifdef MACOS_ENABLED
2022-12-06 08:09:10 +01:00
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 < String > 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 " ) . operator String ( ) . is_empty ( ) ) {
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 ( " notarization/apple_team_id " ) ) {
args . push_back ( " --team-id " ) ;
args . push_back ( p_preset - > get ( " notarization/apple_team_id " ) ) ;
}
String str ;
int exitcode = 0 ;
Error err = OS : : get_singleton ( ) - > execute ( " xcrun " , args , & 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 <request uuid> --issuer <api uuid> --key-id <api key id> --key <api key path> \" or " ) ;
add_message ( EXPORT_MESSAGE_INFO , TTR ( " Notarization " ) , " \t \t \" xcrun notarytool log <request uuid> --apple-id <your email> --password <app-specific pwd>> \" " ) ;
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 <app path> \" " ) ;
}
} break ;
case 3 : { // "altool"
2022-08-09 17:51:43 +02:00
print_verbose ( " using altool notarization... " ) ;
2021-07-14 22:15:25 +02:00
2022-08-09 17:51:43 +02:00
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 ;
}
2021-07-14 22:15:25 +02:00
2022-08-09 17:51:43 +02:00
List < String > args ;
2021-07-14 22:15:25 +02:00
2022-08-09 17:51:43 +02:00
args . push_back ( " altool " ) ;
args . push_back ( " --notarize-app " ) ;
2021-07-14 22:15:25 +02:00
2022-08-09 17:51:43 +02:00
args . push_back ( " --primary-bundle-id " ) ;
args . push_back ( p_preset - > get ( " application/bundle_identifier " ) ) ;
2021-07-14 22:15:25 +02:00
2022-08-09 17:51:43 +02:00
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 ;
}
2021-07-14 22:15:25 +02:00
2022-08-09 17:51:43 +02:00
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 ( " --username " ) ;
args . push_back ( p_preset - > get ( " notarization/apple_id_name " ) ) ;
2021-07-14 22:15:25 +02:00
2022-08-09 17:51:43 +02:00
args . push_back ( " --password " ) ;
args . push_back ( p_preset - > get ( " notarization/apple_id_password " ) ) ;
} else {
if ( p_preset - > get ( " notarization/api_key " ) = = " " ) {
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Notarization " ) , TTR ( " App Store Connect API key ID not specified. " ) ) ;
return Error : : FAILED ;
}
args . push_back ( " --apiIssuer " ) ;
args . push_back ( p_preset - > get ( " notarization/api_uuid " ) ) ;
2021-07-14 22:15:25 +02:00
2022-08-09 17:51:43 +02:00
args . push_back ( " --apiKey " ) ;
2022-12-06 08:09:10 +01:00
args . push_back ( p_preset - > get ( " notarization/api_key_id " ) ) ;
2022-08-09 17:51:43 +02:00
}
2021-07-14 22:15:25 +02:00
2022-08-09 17:51:43 +02:00
args . push_back ( " --type " ) ;
args . push_back ( " osx " ) ;
2021-07-14 22:15:25 +02:00
2022-08-09 17:51:43 +02:00
if ( p_preset - > get ( " notarization/apple_team_id " ) ) {
args . push_back ( " --asc-provider " ) ;
args . push_back ( p_preset - > get ( " notarization/apple_team_id " ) ) ;
}
2021-07-14 22:15:25 +02:00
2022-08-09 17:51:43 +02:00
args . push_back ( " --file " ) ;
args . push_back ( p_path ) ;
String str ;
int exitcode = 0 ;
Error err = OS : : get_singleton ( ) - > execute ( " xcrun " , args , & str , & exitcode , true ) ;
if ( err ! = OK ) {
add_message ( EXPORT_MESSAGE_WARNING , TTR ( " Notarization " ) , TTR ( " Could not start xcrun executable. " ) ) ;
return err ;
}
2022-12-06 08:09:10 +01:00
int rq_offset = str . find ( " RequestUUID: " ) ;
2022-08-09 17:51:43 +02:00
if ( exitcode ! = 0 | | rq_offset = = - 1 ) {
print_line ( " xcrun altool ( " + 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 ( " xcrun altool ( " + p_path + " ): \n " + str ) ;
int next_nl = str . find ( " \n " , rq_offset ) ;
2022-12-06 08:09:10 +01:00
String request_uuid = ( next_nl = = - 1 ) ? str . substr ( rq_offset + 13 , - 1 ) : str . substr ( rq_offset + 13 , next_nl - rq_offset - 13 ) ;
2022-08-09 17:51:43 +02:00
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. When the process is completed, you'll receive an email. " ) ) ;
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 altool --notarization-history 0 -u <your email> -p <app-specific pwd> \" " ) ;
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 <app path> \" " ) ;
}
} break ;
# endif
default : {
} ;
}
2021-07-14 22:15:25 +02:00
return OK ;
}
2022-07-20 08:28:22 +02:00
Error EditorExportPlatformMacOS : : _code_sign ( const Ref < EditorExportPreset > & p_preset , const String & p_path , const String & p_ent_path , bool p_warn ) {
2022-08-09 17:51:43 +02:00
int codesign_tool = p_preset - > get ( " codesign/codesign " ) ;
switch ( codesign_tool ) {
case 1 : { // built-in ad-hoc
print_verbose ( " using built-in codesign... " ) ;
2021-07-30 12:41:24 +02:00
# ifdef MODULE_REGEX_ENABLED
2022-08-09 17:51:43 +02:00
String error_msg ;
Error err = CodeSign : : codesign ( false , true , p_path , p_ent_path , error_msg ) ;
if ( err ! = OK ) {
add_message ( EXPORT_MESSAGE_WARNING , TTR ( " Code Signing " ) , vformat ( TTR ( " Built-in CodeSign failed with error \" %s \" . " ) , error_msg ) ) ;
return Error : : FAILED ;
}
2021-07-30 12:41:24 +02:00
# else
2022-08-09 17:51:43 +02:00
add_message ( EXPORT_MESSAGE_WARNING , TTR ( " Code Signing " ) , TTR ( " Built-in CodeSign require regex module. " ) ) ;
2021-07-30 12:41:24 +02:00
# endif
2022-08-09 17:51:43 +02:00
} break ;
case 2 : { // "rcodesign"
print_verbose ( " using rcodesign codesign... " ) ;
2022-10-18 16:43:37 +02:00
String rcodesign = EDITOR_GET ( " export/macos/rcodesign " ) . operator String ( ) ;
2022-08-09 17:51:43 +02:00
if ( rcodesign . is_empty ( ) ) {
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Code Signing " ) , TTR ( " Xrcodesign path is not set. Configure rcodesign path in the Editor Settings (Export > macOS > rcodesign). " ) ) ;
return Error : : FAILED ;
2021-07-30 12:41:24 +02:00
}
2022-08-09 17:51:43 +02:00
List < String > args ;
args . push_back ( " sign " ) ;
if ( p_path . get_extension ( ) ! = " dmg " ) {
args . push_back ( " --entitlements-xml-path " ) ;
args . push_back ( p_ent_path ) ;
}
String certificate_file = p_preset - > get ( " codesign/certificate_file " ) ;
String certificate_pass = p_preset - > get ( " codesign/certificate_password " ) ;
2023-03-03 23:20:36 +01:00
if ( ! certificate_file . is_empty ( ) & & ! certificate_pass . is_empty ( ) ) {
2022-08-09 17:51:43 +02:00
args . push_back ( " --p12-file " ) ;
args . push_back ( certificate_file ) ;
args . push_back ( " --p12-password " ) ;
args . push_back ( certificate_pass ) ;
}
args . push_back ( " -v " ) ; /* provide some more feedback */
args . push_back ( p_path ) ;
String str ;
int exitcode = 0 ;
Error err = OS : : get_singleton ( ) - > execute ( rcodesign , args , & str , & exitcode , true ) ;
if ( err ! = OK ) {
add_message ( EXPORT_MESSAGE_WARNING , TTR ( " Code Signing " ) , TTR ( " Could not start rcodesign executable. " ) ) ;
return err ;
}
if ( exitcode ! = 0 ) {
print_line ( " rcodesign ( " + p_path + " ): \n " + str ) ;
add_message ( EXPORT_MESSAGE_WARNING , TTR ( " Code Signing " ) , TTR ( " Code signing failed, see editor log for details. " ) ) ;
return Error : : FAILED ;
2021-07-30 12:41:24 +02:00
} else {
2022-08-09 17:51:43 +02:00
print_verbose ( " rcodesign ( " + p_path + " ): \n " + str ) ;
}
} break ;
# ifdef MACOS_ENABLED
case 3 : { // "codesign"
print_verbose ( " using xcode codesign... " ) ;
if ( ! FileAccess : : exists ( " /usr/bin/codesign " ) & & ! FileAccess : : exists ( " /bin/codesign " ) ) {
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Code Signing " ) , TTR ( " Xcode command line tools are not installed. " ) ) ;
return Error : : FAILED ;
}
bool ad_hoc = ( p_preset - > get ( " codesign/identity " ) = = " " | | p_preset - > get ( " codesign/identity " ) = = " - " ) ;
List < String > args ;
if ( ! ad_hoc ) {
args . push_back ( " --timestamp " ) ;
2021-07-30 12:41:24 +02:00
args . push_back ( " --options " ) ;
args . push_back ( " runtime " ) ;
}
2021-07-14 22:15:25 +02:00
2022-08-09 17:51:43 +02:00
if ( p_path . get_extension ( ) ! = " dmg " ) {
args . push_back ( " --entitlements " ) ;
args . push_back ( p_ent_path ) ;
2021-07-30 12:41:24 +02:00
}
2021-07-14 22:15:25 +02:00
2022-08-09 17:51:43 +02:00
PackedStringArray user_args = p_preset - > get ( " codesign/custom_options " ) ;
for ( int i = 0 ; i < user_args . size ( ) ; i + + ) {
String user_arg = user_args [ i ] . strip_edges ( ) ;
if ( ! user_arg . is_empty ( ) ) {
args . push_back ( user_arg ) ;
}
}
2021-07-14 22:15:25 +02:00
2022-08-09 17:51:43 +02:00
args . push_back ( " -s " ) ;
if ( ad_hoc ) {
args . push_back ( " - " ) ;
} else {
args . push_back ( p_preset - > get ( " codesign/identity " ) ) ;
}
2021-07-14 22:15:25 +02:00
2022-08-09 17:51:43 +02:00
args . push_back ( " -v " ) ; /* provide some more feedback */
2021-07-30 12:41:24 +02:00
args . push_back ( " -f " ) ;
2021-07-14 22:15:25 +02:00
2022-08-09 17:51:43 +02:00
args . push_back ( p_path ) ;
2021-07-14 22:15:25 +02:00
2022-08-09 17:51:43 +02:00
String str ;
int exitcode = 0 ;
2021-07-14 22:15:25 +02:00
2022-11-20 22:27:23 +01:00
Error err = OS : : get_singleton ( ) - > execute ( " codesign " , args , & str , & exitcode , true ) ;
2022-08-09 17:51:43 +02:00
if ( err ! = OK ) {
add_message ( EXPORT_MESSAGE_WARNING , TTR ( " Code Signing " ) , TTR ( " Could not start codesign executable, make sure Xcode command line tools are installed. " ) ) ;
return err ;
}
if ( exitcode ! = 0 ) {
print_line ( " codesign ( " + p_path + " ): \n " + str ) ;
add_message ( EXPORT_MESSAGE_WARNING , TTR ( " Code Signing " ) , TTR ( " Code signing failed, see editor log for details. " ) ) ;
return Error : : FAILED ;
} else {
print_verbose ( " codesign ( " + p_path + " ): \n " + str ) ;
}
} break ;
# endif
default : {
} ;
2021-07-30 12:41:24 +02:00
}
2022-08-09 17:51:43 +02:00
return OK ;
2021-07-14 22:15:25 +02:00
}
2022-07-20 08:28:22 +02:00
Error EditorExportPlatformMacOS : : _code_sign_directory ( const Ref < EditorExportPreset > & p_preset , const String & p_path ,
2021-12-18 10:21:08 +01:00
const String & p_ent_path , bool p_should_error_on_non_code ) {
2022-07-20 08:28:22 +02:00
# ifdef MACOS_ENABLED
2021-12-18 10:21:08 +01:00
static Vector < String > extensions_to_sign ;
if ( extensions_to_sign . is_empty ( ) ) {
extensions_to_sign . push_back ( " dylib " ) ;
extensions_to_sign . push_back ( " framework " ) ;
2022-10-05 19:31:07 +02:00
extensions_to_sign . push_back ( " " ) ;
2021-12-18 10:21:08 +01:00
}
Error dir_access_error ;
2022-03-23 10:08:58 +01:00
Ref < DirAccess > dir_access { DirAccess : : open ( p_path , & dir_access_error ) } ;
2021-12-18 10:21:08 +01:00
if ( dir_access_error ! = OK ) {
return dir_access_error ;
}
dir_access - > list_dir_begin ( ) ;
String current_file { dir_access - > get_next ( ) } ;
while ( ! current_file . is_empty ( ) ) {
2022-08-30 02:34:01 +02:00
String current_file_path { p_path . path_join ( current_file ) } ;
2021-12-18 10:21:08 +01:00
if ( current_file = = " .. " | | current_file = = " . " ) {
current_file = dir_access - > get_next ( ) ;
continue ;
}
if ( extensions_to_sign . find ( current_file . get_extension ( ) ) > - 1 ) {
2022-03-10 08:48:25 +01:00
Error code_sign_error { _code_sign ( p_preset , current_file_path , p_ent_path , false ) } ;
2021-12-18 10:21:08 +01:00
if ( code_sign_error ! = OK ) {
return code_sign_error ;
}
2022-10-05 19:31:07 +02:00
if ( is_executable ( current_file_path ) ) {
// chmod with 0755 if the file is executable.
FileAccess : : set_unix_permissions ( current_file_path , 0755 ) ;
}
2021-12-18 10:21:08 +01:00
} else if ( dir_access - > current_is_dir ( ) ) {
Error code_sign_error { _code_sign_directory ( p_preset , current_file_path , p_ent_path , p_should_error_on_non_code ) } ;
if ( code_sign_error ! = OK ) {
return code_sign_error ;
}
} else if ( p_should_error_on_non_code ) {
2022-06-03 17:34:24 +02:00
add_message ( EXPORT_MESSAGE_WARNING , TTR ( " Code Signing " ) , vformat ( TTR ( " Cannot sign file %s. " ) , current_file ) ) ;
2021-12-18 10:21:08 +01:00
return Error : : FAILED ;
}
current_file = dir_access - > get_next ( ) ;
}
# endif
return OK ;
}
2022-07-20 08:28:22 +02:00
Error EditorExportPlatformMacOS : : _copy_and_sign_files ( Ref < DirAccess > & dir_access , const String & p_src_path ,
2021-12-18 10:21:08 +01:00
const String & p_in_app_path , bool p_sign_enabled ,
const Ref < EditorExportPreset > & p_preset , const String & p_ent_path ,
bool p_should_error_on_non_code_sign ) {
2022-10-05 19:31:07 +02:00
static Vector < String > extensions_to_sign ;
if ( extensions_to_sign . is_empty ( ) ) {
extensions_to_sign . push_back ( " dylib " ) ;
extensions_to_sign . push_back ( " framework " ) ;
extensions_to_sign . push_back ( " " ) ;
}
2021-12-18 10:21:08 +01:00
Error err { OK } ;
if ( dir_access - > dir_exists ( p_src_path ) ) {
# ifndef UNIX_ENABLED
2022-06-03 17:34:24 +02:00
add_message ( EXPORT_MESSAGE_INFO , TTR ( " Export " ) , vformat ( TTR ( " Relative symlinks are not supported, exported \" %s \" might be broken! " ) , p_src_path . get_file ( ) ) ) ;
2021-12-18 10:21:08 +01:00
# endif
print_verbose ( " export framework: " + p_src_path + " -> " + p_in_app_path ) ;
err = dir_access - > make_dir_recursive ( p_in_app_path ) ;
if ( err = = OK ) {
err = dir_access - > copy_dir ( p_src_path , p_in_app_path , - 1 , true ) ;
}
} else {
print_verbose ( " export dylib: " + p_src_path + " -> " + p_in_app_path ) ;
err = dir_access - > copy ( p_src_path , p_in_app_path ) ;
}
if ( err = = OK & & p_sign_enabled ) {
if ( dir_access - > dir_exists ( p_src_path ) & & p_src_path . get_extension ( ) . is_empty ( ) ) {
// 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 {
2022-10-05 19:31:07 +02:00
if ( extensions_to_sign . find ( p_in_app_path . get_extension ( ) ) > - 1 ) {
err = _code_sign ( p_preset , p_in_app_path , p_ent_path , false ) ;
}
if ( is_executable ( p_in_app_path ) ) {
// chmod with 0755 if the file is executable.
FileAccess : : set_unix_permissions ( p_in_app_path , 0755 ) ;
}
2021-12-18 10:21:08 +01:00
}
}
return err ;
}
2022-07-20 08:28:22 +02:00
Error EditorExportPlatformMacOS : : _export_macos_plugins_for ( Ref < EditorExportPlugin > p_editor_export_plugin ,
2022-03-23 10:08:58 +01:00
const String & p_app_path_name , Ref < DirAccess > & dir_access ,
2021-12-18 10:21:08 +01:00
bool p_sign_enabled , const Ref < EditorExportPreset > & p_preset ,
const String & p_ent_path ) {
Error error { OK } ;
2022-07-20 08:28:22 +02:00
const Vector < String > & macos_plugins { p_editor_export_plugin - > get_macos_plugin_files ( ) } ;
for ( int i = 0 ; i < macos_plugins . size ( ) ; + + i ) {
String src_path { ProjectSettings : : get_singleton ( ) - > globalize_path ( macos_plugins [ i ] ) } ;
2021-12-18 10:21:08 +01:00
String path_in_app { p_app_path_name + " /Contents/PlugIns/ " + src_path . get_file ( ) } ;
error = _copy_and_sign_files ( dir_access , src_path , path_in_app , p_sign_enabled , p_preset , p_ent_path , false ) ;
if ( error ! = OK ) {
break ;
}
}
return error ;
}
2022-07-20 08:28:22 +02:00
Error EditorExportPlatformMacOS : : _create_dmg ( const String & p_dmg_path , const String & p_pkg_name , const String & p_app_path_name ) {
2021-07-14 22:15:25 +02:00
List < String > args ;
if ( FileAccess : : exists ( p_dmg_path ) ) {
OS : : get_singleton ( ) - > move_to_trash ( p_dmg_path ) ;
}
args . push_back ( " create " ) ;
args . push_back ( p_dmg_path ) ;
args . push_back ( " -volname " ) ;
args . push_back ( p_pkg_name ) ;
args . push_back ( " -fs " ) ;
args . push_back ( " HFS+ " ) ;
args . push_back ( " -srcfolder " ) ;
args . push_back ( p_app_path_name ) ;
String str ;
Error err = OS : : get_singleton ( ) - > execute ( " hdiutil " , args , & str , nullptr , true ) ;
2022-06-03 17:34:24 +02:00
if ( err ! = OK ) {
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " DMG Creation " ) , TTR ( " Could not start hdiutil executable. " ) ) ;
return err ;
}
2021-07-14 22:15:25 +02:00
2021-07-30 12:41:24 +02:00
print_verbose ( " hdiutil returned: " + str ) ;
2021-07-14 22:15:25 +02:00
if ( str . find ( " create failed " ) ! = - 1 ) {
if ( str . find ( " File exists " ) ! = - 1 ) {
2022-06-03 17:34:24 +02:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " DMG Creation " ) , TTR ( " `hdiutil create` failed - file exists. " ) ) ;
2021-07-14 22:15:25 +02:00
} else {
2022-06-03 17:34:24 +02:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " DMG Creation " ) , TTR ( " `hdiutil create` failed. " ) ) ;
2021-07-14 22:15:25 +02:00
}
return FAILED ;
}
return OK ;
}
2022-05-08 09:46:53 +02:00
bool EditorExportPlatformMacOS : : is_shebang ( const String & p_path ) const {
2022-10-05 19:31:07 +02:00
Ref < FileAccess > fb = FileAccess : : open ( p_path , FileAccess : : READ ) ;
ERR_FAIL_COND_V_MSG ( fb . is_null ( ) , false , vformat ( " Can't open file: \" %s \" . " , p_path ) ) ;
uint16_t magic = fb - > get_16 ( ) ;
return ( magic = = 0x2123 ) ;
}
bool EditorExportPlatformMacOS : : is_executable ( const String & p_path ) const {
2022-05-08 09:46:53 +02:00
return MachO : : is_macho ( p_path ) | | LipO : : is_lipo ( p_path ) | | is_shebang ( p_path ) ;
2022-10-05 19:31:07 +02:00
}
2022-07-20 08:28:22 +02:00
Error EditorExportPlatformMacOS : : _export_debug_script ( const Ref < EditorExportPreset > & p_preset , const String & p_app_name , const String & p_pkg_name , const String & p_path ) {
2022-03-23 10:08:58 +01:00
Ref < FileAccess > f = FileAccess : : open ( p_path , FileAccess : : WRITE ) ;
2022-06-03 17:34:24 +02:00
if ( f . is_null ( ) ) {
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Debug Script Export " ) , vformat ( TTR ( " Could not open file \" %s \" . " ) , p_path ) ) ;
return ERR_CANT_CREATE ;
}
2022-02-23 07:44:38 +01:00
f - > store_line ( " #!/bin/sh " ) ;
f - > store_line ( " echo -ne ' \\ 033c \\ 033]0; " + p_app_name + " \\ a' " ) ;
2022-07-21 12:50:37 +02:00
f - > store_line ( " " ) ;
f - > store_line ( " function app_realpath() { " ) ;
f - > store_line ( " SOURCE=$1 " ) ;
f - > store_line ( " while [ -h \" $SOURCE \" ]; do " ) ;
f - > store_line ( " DIR=$(dirname \" $SOURCE \" ) " ) ;
f - > store_line ( " SOURCE=$(readlink \" $SOURCE \" ) " ) ;
f - > store_line ( " [[ $SOURCE != /* ]] && SOURCE=$DIR/$SOURCE " ) ;
f - > store_line ( " done " ) ;
f - > store_line ( " echo \" $( cd -P \" $( dirname \" $SOURCE \" ) \" >/dev/null 2>&1 && pwd ) \" " ) ;
f - > store_line ( " } " ) ;
f - > store_line ( " " ) ;
f - > store_line ( " BASE_PATH= \" $(app_realpath \" ${BASH_SOURCE[0]} \" ) \" " ) ;
f - > store_line ( " \" $BASE_PATH/ " + p_pkg_name + " \" \" $@ \" " ) ;
f - > store_line ( " " ) ;
2022-02-23 07:44:38 +01:00
return OK ;
}
2022-07-20 08:28:22 +02:00
Error EditorExportPlatformMacOS : : export_project ( const Ref < EditorExportPreset > & p_preset , bool p_debug , const String & p_path , int p_flags ) {
2021-07-14 22:15:25 +02:00
ExportNotifier notifier ( * this , p_preset , p_debug , p_path , p_flags ) ;
String src_pkg_name ;
2022-07-20 08:28:22 +02:00
EditorProgress ep ( " export " , " Exporting for macOS " , 3 , true ) ;
2021-07-14 22:15:25 +02:00
if ( p_debug ) {
src_pkg_name = p_preset - > get ( " custom_template/debug " ) ;
} else {
src_pkg_name = p_preset - > get ( " custom_template/release " ) ;
}
2021-12-09 10:42:46 +01:00
if ( src_pkg_name . is_empty ( ) ) {
2021-07-14 22:15:25 +02:00
String err ;
2022-07-20 08:28:22 +02:00
src_pkg_name = find_export_template ( " macos.zip " , & err ) ;
2021-12-09 10:42:46 +01:00
if ( src_pkg_name . is_empty ( ) ) {
2022-06-03 17:34:24 +02:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Prepare Templates " ) , TTR ( " Export template not found. " ) ) ;
2021-07-14 22:15:25 +02:00
return ERR_FILE_NOT_FOUND ;
}
}
if ( ! DirAccess : : exists ( p_path . get_base_dir ( ) ) ) {
2022-06-03 17:34:24 +02:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Prepare Templates " ) , TTR ( " The given export path doesn't exist. " ) ) ;
2021-07-14 22:15:25 +02:00
return ERR_FILE_BAD_PATH ;
}
2022-05-11 14:15:58 +02:00
Ref < FileAccess > io_fa ;
zlib_filefunc_def io = zipio_create_io ( & io_fa ) ;
2021-07-14 22:15:25 +02:00
2021-07-30 12:41:24 +02:00
if ( ep . step ( TTR ( " Creating app bundle " ) , 0 ) ) {
2021-07-14 22:15:25 +02:00
return ERR_SKIP ;
}
unzFile src_pkg_zip = unzOpen2 ( src_pkg_name . utf8 ( ) . get_data ( ) , & io ) ;
if ( ! src_pkg_zip ) {
2022-06-03 17:34:24 +02:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Prepare Templates " ) , vformat ( TTR ( " Could not find template app to export: \" %s \" . " ) , src_pkg_name ) ) ;
2021-07-14 22:15:25 +02:00
return ERR_FILE_NOT_FOUND ;
}
int ret = unzGoToFirstFile ( src_pkg_zip ) ;
2022-07-28 00:48:34 +02:00
String architecture = p_preset - > get ( " binary_format/architecture " ) ;
String binary_to_use = " godot_macos_ " + String ( p_debug ? " debug " : " release " ) + " . " + architecture ;
2021-07-14 22:15:25 +02:00
String pkg_name ;
2022-10-18 16:43:37 +02:00
if ( String ( GLOBAL_GET ( " application/config/name " ) ) ! = " " ) {
pkg_name = String ( GLOBAL_GET ( " application/config/name " ) ) ;
2021-07-14 22:15:25 +02:00
} else {
pkg_name = " Unnamed " ;
}
pkg_name = OS : : get_singleton ( ) - > get_safe_dir_name ( pkg_name ) ;
2021-07-30 12:41:24 +02:00
String export_format ;
if ( use_dmg ( ) & & p_path . ends_with ( " dmg " ) ) {
export_format = " dmg " ;
} else if ( p_path . ends_with ( " zip " ) ) {
export_format = " zip " ;
} else if ( p_path . ends_with ( " app " ) ) {
export_format = " app " ;
} else {
2022-06-03 17:34:24 +02:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export " ) , TTR ( " Invalid export format. " ) ) ;
2021-07-30 12:41:24 +02:00
return ERR_CANT_CREATE ;
}
2021-07-14 22:15:25 +02:00
// Create our application bundle.
String tmp_app_dir_name = pkg_name + " .app " ;
2022-02-23 07:44:38 +01:00
String tmp_base_path_name ;
2021-07-30 12:41:24 +02:00
String tmp_app_path_name ;
2022-02-23 07:44:38 +01:00
String scr_path ;
2021-07-30 12:41:24 +02:00
if ( export_format = = " app " ) {
2022-02-23 07:44:38 +01:00
tmp_base_path_name = p_path . get_base_dir ( ) ;
2021-07-30 12:41:24 +02:00
tmp_app_path_name = p_path ;
2022-02-23 07:44:38 +01:00
scr_path = p_path . get_basename ( ) + " .command " ;
2021-07-30 12:41:24 +02:00
} else {
2022-08-30 02:34:01 +02:00
tmp_base_path_name = EditorPaths : : get_singleton ( ) - > get_cache_dir ( ) . path_join ( pkg_name ) ;
tmp_app_path_name = tmp_base_path_name . path_join ( tmp_app_dir_name ) ;
scr_path = tmp_base_path_name . path_join ( pkg_name + " .command " ) ;
2021-07-30 12:41:24 +02:00
}
2022-02-23 07:44:38 +01:00
2021-07-30 12:41:24 +02:00
print_verbose ( " Exporting to " + tmp_app_path_name ) ;
2021-07-14 22:15:25 +02:00
Error err = OK ;
2022-03-23 10:08:58 +01:00
Ref < DirAccess > tmp_app_dir = DirAccess : : create_for_path ( tmp_base_path_name ) ;
if ( tmp_app_dir . is_null ( ) ) {
2022-06-03 17:34:24 +02:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export " ) , vformat ( TTR ( " Could not create directory: \" %s \" . " ) , tmp_base_path_name ) ) ;
2021-07-14 22:15:25 +02:00
err = ERR_CANT_CREATE ;
}
2022-08-09 17:51:43 +02:00
if ( FileAccess : : exists ( scr_path ) ) {
DirAccess : : remove_file_or_error ( scr_path ) ;
}
2022-02-23 07:44:38 +01:00
if ( DirAccess : : exists ( tmp_app_path_name ) ) {
2022-06-03 17:34:24 +02:00
String old_dir = tmp_app_dir - > get_current_dir ( ) ;
2021-07-30 12:41:24 +02:00
if ( tmp_app_dir - > change_dir ( tmp_app_path_name ) = = OK ) {
tmp_app_dir - > erase_contents_recursive ( ) ;
2022-06-03 17:34:24 +02:00
tmp_app_dir - > change_dir ( old_dir ) ;
2021-07-30 12:41:24 +02:00
}
}
2021-03-31 15:19:15 +02:00
Array helpers = p_preset - > get ( " codesign/entitlements/app_sandbox/helper_executables " ) ;
2021-07-14 22:15:25 +02:00
// Create our folder structure.
if ( err = = OK ) {
2021-07-30 12:41:24 +02:00
print_verbose ( " Creating " + tmp_app_path_name + " /Contents/MacOS " ) ;
2021-07-14 22:15:25 +02:00
err = tmp_app_dir - > make_dir_recursive ( tmp_app_path_name + " /Contents/MacOS " ) ;
2022-06-03 17:34:24 +02:00
if ( err ! = OK ) {
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export " ) , vformat ( TTR ( " Could not create directory \" %s \" . " ) , tmp_app_path_name + " /Contents/MacOS " ) ) ;
}
2021-07-14 22:15:25 +02:00
}
if ( err = = OK ) {
2021-07-30 12:41:24 +02:00
print_verbose ( " Creating " + tmp_app_path_name + " /Contents/Frameworks " ) ;
2021-07-14 22:15:25 +02:00
err = tmp_app_dir - > make_dir_recursive ( tmp_app_path_name + " /Contents/Frameworks " ) ;
2022-06-03 17:34:24 +02:00
if ( err ! = OK ) {
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export " ) , vformat ( TTR ( " Could not create directory \" %s \" . " ) , tmp_app_path_name + " /Contents/Frameworks " ) ) ;
}
2021-07-14 22:15:25 +02:00
}
2021-03-31 15:19:15 +02:00
if ( ( err = = OK ) & & helpers . size ( ) > 0 ) {
print_line ( " Creating " + tmp_app_path_name + " /Contents/Helpers " ) ;
err = tmp_app_dir - > make_dir_recursive ( tmp_app_path_name + " /Contents/Helpers " ) ;
2022-06-03 17:34:24 +02:00
if ( err ! = OK ) {
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export " ) , vformat ( TTR ( " Could not create directory \" %s \" . " ) , tmp_app_path_name + " /Contents/Helpers " ) ) ;
}
2021-03-31 15:19:15 +02:00
}
2021-07-14 22:15:25 +02:00
if ( err = = OK ) {
2021-07-30 12:41:24 +02:00
print_verbose ( " Creating " + tmp_app_path_name + " /Contents/Resources " ) ;
2021-07-14 22:15:25 +02:00
err = tmp_app_dir - > make_dir_recursive ( tmp_app_path_name + " /Contents/Resources " ) ;
2022-06-03 17:34:24 +02:00
if ( err ! = OK ) {
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export " ) , vformat ( TTR ( " Could not create directory \" %s \" . " ) , tmp_app_path_name + " /Contents/Resources " ) ) ;
}
2021-07-14 22:15:25 +02:00
}
2022-10-18 16:43:37 +02:00
Dictionary appnames = GLOBAL_GET ( " application/config/name_localized " ) ;
2022-03-04 14:04:59 +01:00
Dictionary microphone_usage_descriptions = p_preset - > get ( " privacy/microphone_usage_description_localized " ) ;
Dictionary camera_usage_descriptions = p_preset - > get ( " privacy/camera_usage_description_localized " ) ;
Dictionary location_usage_descriptions = p_preset - > get ( " privacy/location_usage_description_localized " ) ;
Dictionary address_book_usage_descriptions = p_preset - > get ( " privacy/address_book_usage_description_localized " ) ;
Dictionary calendar_usage_descriptions = p_preset - > get ( " privacy/calendar_usage_description_localized " ) ;
Dictionary photos_library_usage_descriptions = p_preset - > get ( " privacy/photos_library_usage_description_localized " ) ;
Dictionary desktop_folder_usage_descriptions = p_preset - > get ( " privacy/desktop_folder_usage_description_localized " ) ;
Dictionary documents_folder_usage_descriptions = p_preset - > get ( " privacy/documents_folder_usage_description_localized " ) ;
Dictionary downloads_folder_usage_descriptions = p_preset - > get ( " privacy/downloads_folder_usage_description_localized " ) ;
Dictionary network_volumes_usage_descriptions = p_preset - > get ( " privacy/network_volumes_usage_description_localized " ) ;
Dictionary removable_volumes_usage_descriptions = p_preset - > get ( " privacy/removable_volumes_usage_description_localized " ) ;
Dictionary copyrights = p_preset - > get ( " application/copyright_localized " ) ;
2022-10-18 16:43:37 +02:00
Vector < String > translations = GLOBAL_GET ( " internationalization/locale/translations " ) ;
2022-02-03 08:43:11 +01:00
if ( translations . size ( ) > 0 ) {
{
String fname = tmp_app_path_name + " /Contents/Resources/en.lproj " ;
tmp_app_dir - > make_dir_recursive ( fname ) ;
2022-03-23 10:08:58 +01:00
Ref < FileAccess > f = FileAccess : : open ( fname + " /InfoPlist.strings " , FileAccess : : WRITE ) ;
2022-03-04 14:04:59 +01:00
f - > store_line ( " /* Localized versions of Info.plist keys */ " ) ;
f - > store_line ( " " ) ;
2022-10-18 16:43:37 +02:00
f - > store_line ( " CFBundleDisplayName = \" " + GLOBAL_GET ( " application/config/name " ) . operator String ( ) + " \" ; " ) ;
2022-03-04 14:04:59 +01:00
if ( ! ( ( String ) p_preset - > get ( " privacy/microphone_usage_description " ) ) . is_empty ( ) ) {
f - > store_line ( " NSMicrophoneUsageDescription = \" " + p_preset - > get ( " privacy/microphone_usage_description " ) . operator String ( ) + " \" ; " ) ;
}
if ( ! ( ( String ) p_preset - > get ( " privacy/camera_usage_description " ) ) . is_empty ( ) ) {
f - > store_line ( " NSCameraUsageDescription = \" " + p_preset - > get ( " privacy/camera_usage_description " ) . operator String ( ) + " \" ; " ) ;
}
if ( ! ( ( String ) p_preset - > get ( " privacy/location_usage_description " ) ) . is_empty ( ) ) {
f - > store_line ( " NSLocationUsageDescription = \" " + p_preset - > get ( " privacy/location_usage_description " ) . operator String ( ) + " \" ; " ) ;
}
if ( ! ( ( String ) p_preset - > get ( " privacy/address_book_usage_description " ) ) . is_empty ( ) ) {
f - > store_line ( " NSContactsUsageDescription = \" " + p_preset - > get ( " privacy/address_book_usage_description " ) . operator String ( ) + " \" ; " ) ;
}
if ( ! ( ( String ) p_preset - > get ( " privacy/calendar_usage_description " ) ) . is_empty ( ) ) {
f - > store_line ( " NSCalendarsUsageDescription = \" " + p_preset - > get ( " privacy/calendar_usage_description " ) . operator String ( ) + " \" ; " ) ;
}
if ( ! ( ( String ) p_preset - > get ( " privacy/photos_library_usage_description " ) ) . is_empty ( ) ) {
f - > store_line ( " NSPhotoLibraryUsageDescription = \" " + p_preset - > get ( " privacy/photos_library_usage_description " ) . operator String ( ) + " \" ; " ) ;
}
if ( ! ( ( String ) p_preset - > get ( " privacy/desktop_folder_usage_description " ) ) . is_empty ( ) ) {
f - > store_line ( " NSDesktopFolderUsageDescription = \" " + p_preset - > get ( " privacy/desktop_folder_usage_description " ) . operator String ( ) + " \" ; " ) ;
}
if ( ! ( ( String ) p_preset - > get ( " privacy/documents_folder_usage_description " ) ) . is_empty ( ) ) {
f - > store_line ( " NSDocumentsFolderUsageDescription = \" " + p_preset - > get ( " privacy/documents_folder_usage_description " ) . operator String ( ) + " \" ; " ) ;
}
if ( ! ( ( String ) p_preset - > get ( " privacy/downloads_folder_usage_description " ) ) . is_empty ( ) ) {
f - > store_line ( " NSDownloadsFolderUsageDescription = \" " + p_preset - > get ( " privacy/downloads_folder_usage_description " ) . operator String ( ) + " \" ; " ) ;
}
if ( ! ( ( String ) p_preset - > get ( " privacy/network_volumes_usage_description " ) ) . is_empty ( ) ) {
f - > store_line ( " NSNetworkVolumesUsageDescription = \" " + p_preset - > get ( " privacy/network_volumes_usage_description " ) . operator String ( ) + " \" ; " ) ;
}
if ( ! ( ( String ) p_preset - > get ( " privacy/removable_volumes_usage_description " ) ) . is_empty ( ) ) {
f - > store_line ( " NSRemovableVolumesUsageDescription = \" " + p_preset - > get ( " privacy/removable_volumes_usage_description " ) . operator String ( ) + " \" ; " ) ;
}
f - > store_line ( " NSHumanReadableCopyright = \" " + p_preset - > get ( " application/copyright " ) . operator String ( ) + " \" ; " ) ;
2022-02-03 08:43:11 +01:00
}
2022-08-17 15:11:56 +02:00
HashSet < String > languages ;
2022-02-03 08:43:11 +01:00
for ( const String & E : translations ) {
Ref < Translation > tr = ResourceLoader : : load ( E ) ;
2022-08-17 15:11:56 +02:00
if ( tr . is_valid ( ) & & tr - > get_locale ( ) ! = " en " ) {
languages . insert ( tr - > get_locale ( ) ) ;
}
}
for ( const String & lang : languages ) {
String fname = tmp_app_path_name + " /Contents/Resources/ " + lang + " .lproj " ;
tmp_app_dir - > make_dir_recursive ( fname ) ;
Ref < FileAccess > f = FileAccess : : open ( fname + " /InfoPlist.strings " , FileAccess : : WRITE ) ;
f - > store_line ( " /* Localized versions of Info.plist keys */ " ) ;
f - > store_line ( " " ) ;
if ( appnames . has ( lang ) ) {
f - > store_line ( " CFBundleDisplayName = \" " + appnames [ lang ] . operator String ( ) + " \" ; " ) ;
}
if ( microphone_usage_descriptions . has ( lang ) ) {
f - > store_line ( " NSMicrophoneUsageDescription = \" " + microphone_usage_descriptions [ lang ] . operator String ( ) + " \" ; " ) ;
}
if ( camera_usage_descriptions . has ( lang ) ) {
f - > store_line ( " NSCameraUsageDescription = \" " + camera_usage_descriptions [ lang ] . operator String ( ) + " \" ; " ) ;
}
if ( location_usage_descriptions . has ( lang ) ) {
f - > store_line ( " NSLocationUsageDescription = \" " + location_usage_descriptions [ lang ] . operator String ( ) + " \" ; " ) ;
}
if ( address_book_usage_descriptions . has ( lang ) ) {
f - > store_line ( " NSContactsUsageDescription = \" " + address_book_usage_descriptions [ lang ] . operator String ( ) + " \" ; " ) ;
}
if ( calendar_usage_descriptions . has ( lang ) ) {
f - > store_line ( " NSCalendarsUsageDescription = \" " + calendar_usage_descriptions [ lang ] . operator String ( ) + " \" ; " ) ;
}
if ( photos_library_usage_descriptions . has ( lang ) ) {
f - > store_line ( " NSPhotoLibraryUsageDescription = \" " + photos_library_usage_descriptions [ lang ] . operator String ( ) + " \" ; " ) ;
}
if ( desktop_folder_usage_descriptions . has ( lang ) ) {
f - > store_line ( " NSDesktopFolderUsageDescription = \" " + desktop_folder_usage_descriptions [ lang ] . operator String ( ) + " \" ; " ) ;
}
if ( documents_folder_usage_descriptions . has ( lang ) ) {
f - > store_line ( " NSDocumentsFolderUsageDescription = \" " + documents_folder_usage_descriptions [ lang ] . operator String ( ) + " \" ; " ) ;
}
if ( downloads_folder_usage_descriptions . has ( lang ) ) {
f - > store_line ( " NSDownloadsFolderUsageDescription = \" " + downloads_folder_usage_descriptions [ lang ] . operator String ( ) + " \" ; " ) ;
}
if ( network_volumes_usage_descriptions . has ( lang ) ) {
f - > store_line ( " NSNetworkVolumesUsageDescription = \" " + network_volumes_usage_descriptions [ lang ] . operator String ( ) + " \" ; " ) ;
}
if ( removable_volumes_usage_descriptions . has ( lang ) ) {
f - > store_line ( " NSRemovableVolumesUsageDescription = \" " + removable_volumes_usage_descriptions [ lang ] . operator String ( ) + " \" ; " ) ;
}
if ( copyrights . has ( lang ) ) {
f - > store_line ( " NSHumanReadableCopyright = \" " + copyrights [ lang ] . operator String ( ) + " \" ; " ) ;
2022-02-03 08:43:11 +01:00
}
}
}
2021-07-14 22:15:25 +02:00
// Now process our template.
bool found_binary = false ;
while ( ret = = UNZ_OK & & err = = OK ) {
// Get filename.
unz_file_info info ;
char fname [ 16384 ] ;
ret = unzGetCurrentFileInfo ( src_pkg_zip , & info , fname , 16384 , nullptr , 0 , nullptr , 0 ) ;
2022-04-05 12:40:26 +02:00
if ( ret ! = UNZ_OK ) {
break ;
}
2021-07-14 22:15:25 +02:00
2022-01-05 13:27:11 +01:00
String file = String : : utf8 ( fname ) ;
2021-07-14 22:15:25 +02:00
Vector < uint8_t > data ;
data . resize ( info . uncompressed_size ) ;
// Read.
unzOpenCurrentFile ( src_pkg_zip ) ;
unzReadCurrentFile ( src_pkg_zip , data . ptrw ( ) , data . size ( ) ) ;
unzCloseCurrentFile ( src_pkg_zip ) ;
// Write.
2022-07-20 08:28:22 +02:00
file = file . replace_first ( " macos_template.app/ " , " " ) ;
2021-07-14 22:15:25 +02:00
2021-07-30 12:41:24 +02:00
if ( ( ( info . external_fa > > 16L ) & 0120000 ) = = 0120000 ) {
# ifndef UNIX_ENABLED
2022-06-03 17:34:24 +02:00
add_message ( EXPORT_MESSAGE_INFO , TTR ( " Export " ) , TTR ( " Relative symlinks are not supported on this OS, the exported project might be broken! " ) ) ;
2021-07-30 12:41:24 +02:00
# endif
// Handle symlinks in the archive.
2022-08-30 02:34:01 +02:00
file = tmp_app_path_name . path_join ( file ) ;
2021-07-30 12:41:24 +02:00
if ( err = = OK ) {
err = tmp_app_dir - > make_dir_recursive ( file . get_base_dir ( ) ) ;
2022-06-03 17:34:24 +02:00
if ( err ! = OK ) {
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export " ) , vformat ( TTR ( " Could not create directory \" %s \" . " ) , file . get_base_dir ( ) ) ) ;
}
2021-07-30 12:41:24 +02:00
}
if ( err = = OK ) {
String lnk_data = String : : utf8 ( ( const char * ) data . ptr ( ) , data . size ( ) ) ;
err = tmp_app_dir - > create_link ( lnk_data , file ) ;
2022-06-03 17:34:24 +02:00
if ( err ! = OK ) {
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export " ) , vformat ( TTR ( " Could not created symlink \" %s \" -> \" %s \" . " ) , lnk_data , file ) ) ;
}
2021-07-30 12:41:24 +02:00
print_verbose ( vformat ( " ADDING SYMLINK %s => %s \n " , file , lnk_data ) ) ;
}
ret = unzGoToNextFile ( src_pkg_zip ) ;
continue ; // next
}
2021-07-14 22:15:25 +02:00
if ( file = = " Contents/Info.plist " ) {
_fix_plist ( p_preset , data , pkg_name ) ;
}
if ( file . begins_with ( " Contents/MacOS/godot_ " ) ) {
if ( file ! = " Contents/MacOS/ " + binary_to_use ) {
ret = unzGoToNextFile ( src_pkg_zip ) ;
continue ; // skip
}
found_binary = true ;
file = " Contents/MacOS/ " + pkg_name ;
}
if ( file = = " Contents/Resources/icon.icns " ) {
// See if there is an icon.
String iconpath ;
if ( p_preset - > get ( " application/icon " ) ! = " " ) {
iconpath = p_preset - > get ( " application/icon " ) ;
} else {
2022-10-18 16:43:37 +02:00
iconpath = GLOBAL_GET ( " application/config/icon " ) ;
2021-07-14 22:15:25 +02:00
}
2021-12-09 10:42:46 +01:00
if ( ! iconpath . is_empty ( ) ) {
2021-07-14 22:15:25 +02:00
if ( iconpath . get_extension ( ) = = " icns " ) {
2022-03-23 10:08:58 +01:00
Ref < FileAccess > icon = FileAccess : : open ( iconpath , FileAccess : : READ ) ;
if ( icon . is_valid ( ) ) {
2021-07-14 22:15:25 +02:00
data . resize ( icon - > get_length ( ) ) ;
icon - > get_buffer ( & data . write [ 0 ] , icon - > get_length ( ) ) ;
}
} else {
2022-11-20 12:16:16 +01:00
Ref < Image > icon ;
icon . instantiate ( ) ;
err = ImageLoader : : load_image ( iconpath , icon ) ;
if ( err = = OK & & ! icon - > is_empty ( ) ) {
2022-11-17 22:46:48 +01:00
_make_icon ( p_preset , icon , data ) ;
2021-07-14 22:15:25 +02:00
}
}
}
}
if ( data . size ( ) > 0 ) {
2021-07-30 12:41:24 +02:00
print_verbose ( " ADDING: " + file + " size: " + itos ( data . size ( ) ) ) ;
2021-07-14 22:15:25 +02:00
// Write it into our application bundle.
2022-08-30 02:34:01 +02:00
file = tmp_app_path_name . path_join ( file ) ;
2021-07-14 22:15:25 +02:00
if ( err = = OK ) {
err = tmp_app_dir - > make_dir_recursive ( file . get_base_dir ( ) ) ;
2022-06-03 17:34:24 +02:00
if ( err ! = OK ) {
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export " ) , vformat ( TTR ( " Could not create directory \" %s \" . " ) , file . get_base_dir ( ) ) ) ;
}
2021-07-14 22:15:25 +02:00
}
if ( err = = OK ) {
2022-03-23 10:08:58 +01:00
Ref < FileAccess > f = FileAccess : : open ( file , FileAccess : : WRITE ) ;
if ( f . is_valid ( ) ) {
2021-07-14 22:15:25 +02:00
f - > store_buffer ( data . ptr ( ) , data . size ( ) ) ;
2022-07-01 23:34:27 +02:00
f . unref ( ) ;
2022-10-05 19:31:07 +02:00
if ( is_executable ( file ) ) {
2021-07-14 22:15:25 +02:00
// chmod with 0755 if the file is executable.
FileAccess : : set_unix_permissions ( file , 0755 ) ;
}
} else {
2022-06-03 17:34:24 +02:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export " ) , vformat ( TTR ( " Could not open \" %s \" . " ) , file ) ) ;
2021-07-14 22:15:25 +02:00
err = ERR_CANT_CREATE ;
}
}
}
ret = unzGoToNextFile ( src_pkg_zip ) ;
}
// We're done with our source zip.
unzClose ( src_pkg_zip ) ;
if ( ! found_binary ) {
2022-06-03 17:34:24 +02:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export " ) , vformat ( TTR ( " Requested template binary \" %s \" not found. It might be missing from your template archive. " ) , binary_to_use ) ) ;
2021-07-14 22:15:25 +02:00
err = ERR_FILE_NOT_FOUND ;
}
2023-04-12 15:14:51 +02:00
// Save console wrapper.
2022-02-23 07:44:38 +01:00
if ( err = = OK ) {
2023-04-12 15:14:51 +02:00
int con_scr = p_preset - > get ( " debug/export_console_wrapper " ) ;
2022-02-23 07:44:38 +01:00
if ( ( con_scr = = 1 & & p_debug ) | | ( con_scr = = 2 ) ) {
err = _export_debug_script ( p_preset , pkg_name , tmp_app_path_name . get_file ( ) + " /Contents/MacOS/ " + pkg_name , scr_path ) ;
FileAccess : : set_unix_permissions ( scr_path , 0755 ) ;
2022-06-03 17:34:24 +02:00
if ( err ! = OK ) {
2023-04-12 15:14:51 +02:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export " ) , TTR ( " Could not create console wrapper. " ) ) ;
2022-06-03 17:34:24 +02:00
}
2022-02-23 07:44:38 +01:00
}
}
2021-07-14 22:15:25 +02:00
if ( err = = OK ) {
2021-07-30 12:41:24 +02:00
if ( ep . step ( TTR ( " Making PKG " ) , 1 ) ) {
2021-07-14 22:15:25 +02:00
return ERR_SKIP ;
}
2022-10-05 19:31:07 +02:00
// See if we can code sign our new package.
bool sign_enabled = ( p_preset - > get ( " codesign/codesign " ) . operator int ( ) > 0 ) ;
bool ad_hoc = false ;
int codesign_tool = p_preset - > get ( " codesign/codesign " ) ;
switch ( codesign_tool ) {
case 1 : { // built-in ad-hoc
ad_hoc = true ;
} break ;
case 2 : { // "rcodesign"
ad_hoc = p_preset - > get ( " codesign/certificate_file " ) . operator String ( ) . is_empty ( ) | | p_preset - > get ( " codesign/certificate_password " ) . operator String ( ) . is_empty ( ) ;
} break ;
# ifdef MACOS_ENABLED
case 3 : { // "codesign"
ad_hoc = ( p_preset - > get ( " codesign/identity " ) = = " " | | p_preset - > get ( " codesign/identity " ) = = " - " ) ;
} break ;
# endif
default : {
} ;
}
2021-07-14 22:15:25 +02:00
String pack_path = tmp_app_path_name + " /Contents/Resources/ " + pkg_name + " .pck " ;
Vector < SharedObject > shared_objects ;
2022-03-10 08:48:25 +01:00
err = save_pack ( p_preset , p_debug , pack_path , & shared_objects ) ;
2021-07-14 22:15:25 +02:00
2022-10-05 19:31:07 +02:00
bool lib_validation = p_preset - > get ( " codesign/entitlements/disable_library_validation " ) ;
if ( ! shared_objects . is_empty ( ) & & sign_enabled & & ad_hoc & & ! lib_validation ) {
add_message ( EXPORT_MESSAGE_INFO , TTR ( " Entitlements Modified " ) , TTR ( " Ad-hoc signed applications require the 'Disable Library Validation' entitlement to load dynamic libraries. " ) ) ;
lib_validation = true ;
}
2021-07-14 22:15:25 +02:00
String ent_path = p_preset - > get ( " codesign/entitlements/custom_file " ) ;
2022-08-30 02:34:01 +02:00
String hlp_ent_path = EditorPaths : : get_singleton ( ) - > get_cache_dir ( ) . path_join ( pkg_name + " _helper.entitlements " ) ;
2021-12-09 10:42:46 +01:00
if ( sign_enabled & & ( ent_path . is_empty ( ) ) ) {
2022-08-30 02:34:01 +02:00
ent_path = EditorPaths : : get_singleton ( ) - > get_cache_dir ( ) . path_join ( pkg_name + " .entitlements " ) ;
2021-07-14 22:15:25 +02:00
2022-03-23 10:08:58 +01:00
Ref < FileAccess > ent_f = FileAccess : : open ( ent_path , FileAccess : : WRITE ) ;
if ( ent_f . is_valid ( ) ) {
2021-07-14 22:15:25 +02:00
ent_f - > store_line ( " <?xml version= \" 1.0 \" encoding= \" UTF-8 \" ?> " ) ;
ent_f - > store_line ( " <!DOCTYPE plist PUBLIC \" -//Apple//DTD PLIST 1.0//EN \" \" http://www.apple.com/DTDs/PropertyList-1.0.dtd \" > " ) ;
ent_f - > store_line ( " <plist version= \" 1.0 \" > " ) ;
ent_f - > store_line ( " <dict> " ) ;
if ( Engine : : get_singleton ( ) - > has_singleton ( " GodotSharp " ) ) {
// These entitlements are required to run managed code, and are always enabled in Mono builds.
ent_f - > store_line ( " <key>com.apple.security.cs.allow-jit</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
ent_f - > store_line ( " <key>com.apple.security.cs.allow-unsigned-executable-memory</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
ent_f - > store_line ( " <key>com.apple.security.cs.allow-dyld-environment-variables</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
} else {
if ( ( bool ) p_preset - > get ( " codesign/entitlements/allow_jit_code_execution " ) ) {
ent_f - > store_line ( " <key>com.apple.security.cs.allow-jit</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( bool ) p_preset - > get ( " codesign/entitlements/allow_unsigned_executable_memory " ) ) {
ent_f - > store_line ( " <key>com.apple.security.cs.allow-unsigned-executable-memory</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( bool ) p_preset - > get ( " codesign/entitlements/allow_dyld_environment_variables " ) ) {
ent_f - > store_line ( " <key>com.apple.security.cs.allow-dyld-environment-variables</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
}
2022-10-05 19:31:07 +02:00
if ( lib_validation ) {
2021-07-14 22:15:25 +02:00
ent_f - > store_line ( " <key>com.apple.security.cs.disable-library-validation</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( bool ) p_preset - > get ( " codesign/entitlements/audio_input " ) ) {
ent_f - > store_line ( " <key>com.apple.security.device.audio-input</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( bool ) p_preset - > get ( " codesign/entitlements/camera " ) ) {
ent_f - > store_line ( " <key>com.apple.security.device.camera</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( bool ) p_preset - > get ( " codesign/entitlements/location " ) ) {
ent_f - > store_line ( " <key>com.apple.security.personal-information.location</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( bool ) p_preset - > get ( " codesign/entitlements/address_book " ) ) {
ent_f - > store_line ( " <key>com.apple.security.personal-information.addressbook</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( bool ) p_preset - > get ( " codesign/entitlements/calendars " ) ) {
ent_f - > store_line ( " <key>com.apple.security.personal-information.calendars</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( bool ) p_preset - > get ( " codesign/entitlements/photos_library " ) ) {
ent_f - > store_line ( " <key>com.apple.security.personal-information.photos-library</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( bool ) p_preset - > get ( " codesign/entitlements/apple_events " ) ) {
ent_f - > store_line ( " <key>com.apple.security.automation.apple-events</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( bool ) p_preset - > get ( " codesign/entitlements/debugging " ) ) {
ent_f - > store_line ( " <key>com.apple.security.get-task-allow</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( bool ) p_preset - > get ( " codesign/entitlements/app_sandbox/enabled " ) ) {
ent_f - > store_line ( " <key>com.apple.security.app-sandbox</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
if ( ( bool ) p_preset - > get ( " codesign/entitlements/app_sandbox/network_server " ) ) {
ent_f - > store_line ( " <key>com.apple.security.network.server</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( bool ) p_preset - > get ( " codesign/entitlements/app_sandbox/network_client " ) ) {
ent_f - > store_line ( " <key>com.apple.security.network.client</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( bool ) p_preset - > get ( " codesign/entitlements/app_sandbox/device_usb " ) ) {
ent_f - > store_line ( " <key>com.apple.security.device.usb</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( bool ) p_preset - > get ( " codesign/entitlements/app_sandbox/device_bluetooth " ) ) {
ent_f - > store_line ( " <key>com.apple.security.device.bluetooth</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( int ) p_preset - > get ( " codesign/entitlements/app_sandbox/files_downloads " ) = = 1 ) {
ent_f - > store_line ( " <key>com.apple.security.files.downloads.read-only</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( int ) p_preset - > get ( " codesign/entitlements/app_sandbox/files_downloads " ) = = 2 ) {
ent_f - > store_line ( " <key>com.apple.security.files.downloads.read-write</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( int ) p_preset - > get ( " codesign/entitlements/app_sandbox/files_pictures " ) = = 1 ) {
ent_f - > store_line ( " <key>com.apple.security.files.pictures.read-only</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( int ) p_preset - > get ( " codesign/entitlements/app_sandbox/files_pictures " ) = = 2 ) {
ent_f - > store_line ( " <key>com.apple.security.files.pictures.read-write</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( int ) p_preset - > get ( " codesign/entitlements/app_sandbox/files_music " ) = = 1 ) {
ent_f - > store_line ( " <key>com.apple.security.files.music.read-only</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( int ) p_preset - > get ( " codesign/entitlements/app_sandbox/files_music " ) = = 2 ) {
ent_f - > store_line ( " <key>com.apple.security.files.music.read-write</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( int ) p_preset - > get ( " codesign/entitlements/app_sandbox/files_movies " ) = = 1 ) {
ent_f - > store_line ( " <key>com.apple.security.files.movies.read-only</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
if ( ( int ) p_preset - > get ( " codesign/entitlements/app_sandbox/files_movies " ) = = 2 ) {
ent_f - > store_line ( " <key>com.apple.security.files.movies.read-write</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
}
}
ent_f - > store_line ( " </dict> " ) ;
ent_f - > store_line ( " </plist> " ) ;
} else {
2022-06-03 17:34:24 +02:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Code Signing " ) , TTR ( " Could not create entitlements file. " ) ) ;
2021-07-14 22:15:25 +02:00
err = ERR_CANT_CREATE ;
}
2021-03-31 15:19:15 +02:00
if ( ( err = = OK ) & & helpers . size ( ) > 0 ) {
ent_f = FileAccess : : open ( hlp_ent_path , FileAccess : : WRITE ) ;
2022-03-23 10:08:58 +01:00
if ( ent_f . is_valid ( ) ) {
2021-03-31 15:19:15 +02:00
ent_f - > store_line ( " <?xml version= \" 1.0 \" encoding= \" UTF-8 \" ?> " ) ;
ent_f - > store_line ( " <!DOCTYPE plist PUBLIC \" -//Apple//DTD PLIST 1.0//EN \" \" http://www.apple.com/DTDs/PropertyList-1.0.dtd \" > " ) ;
ent_f - > store_line ( " <plist version= \" 1.0 \" > " ) ;
ent_f - > store_line ( " <dict> " ) ;
ent_f - > store_line ( " <key>com.apple.security.app-sandbox</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
ent_f - > store_line ( " <key>com.apple.security.inherit</key> " ) ;
ent_f - > store_line ( " <true/> " ) ;
ent_f - > store_line ( " </dict> " ) ;
ent_f - > store_line ( " </plist> " ) ;
} else {
2022-06-03 17:34:24 +02:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Code Signing " ) , TTR ( " Could not create helper entitlements file. " ) ) ;
2021-03-31 15:19:15 +02:00
err = ERR_CANT_CREATE ;
}
}
}
if ( ( err = = OK ) & & helpers . size ( ) > 0 ) {
2022-03-23 10:08:58 +01:00
Ref < DirAccess > da = DirAccess : : create ( DirAccess : : ACCESS_FILESYSTEM ) ;
2021-03-31 15:19:15 +02:00
for ( int i = 0 ; i < helpers . size ( ) ; i + + ) {
String hlp_path = helpers [ i ] ;
err = da - > copy ( hlp_path , tmp_app_path_name + " /Contents/Helpers/ " + hlp_path . get_file ( ) ) ;
if ( err = = OK & & sign_enabled ) {
2022-03-10 08:48:25 +01:00
err = _code_sign ( p_preset , tmp_app_path_name + " /Contents/Helpers/ " + hlp_path . get_file ( ) , hlp_ent_path , false ) ;
2021-03-31 15:19:15 +02:00
}
FileAccess : : set_unix_permissions ( tmp_app_path_name + " /Contents/Helpers/ " + hlp_path . get_file ( ) , 0755 ) ;
}
2021-07-14 22:15:25 +02:00
}
2021-07-30 12:41:24 +02:00
2021-07-14 22:15:25 +02:00
if ( err = = OK ) {
2022-03-23 10:08:58 +01:00
Ref < DirAccess > da = DirAccess : : create ( DirAccess : : ACCESS_FILESYSTEM ) ;
2021-07-14 22:15:25 +02:00
for ( int i = 0 ; i < shared_objects . size ( ) ; i + + ) {
String src_path = ProjectSettings : : get_singleton ( ) - > globalize_path ( shared_objects [ i ] . path ) ;
2022-03-10 08:48:25 +01:00
if ( shared_objects [ i ] . target . is_empty ( ) ) {
String path_in_app = tmp_app_path_name + " /Contents/Frameworks/ " + src_path . get_file ( ) ;
err = _copy_and_sign_files ( da , src_path , path_in_app , sign_enabled , p_preset , ent_path , true ) ;
} else {
2022-10-05 19:31:07 +02:00
String path_in_app = tmp_app_path_name . path_join ( shared_objects [ i ] . target ) ;
tmp_app_dir - > make_dir_recursive ( path_in_app ) ;
err = _copy_and_sign_files ( da , src_path , path_in_app . path_join ( src_path . get_file ( ) ) , sign_enabled , p_preset , ent_path , false ) ;
2022-03-10 08:48:25 +01:00
}
2021-12-18 10:21:08 +01:00
if ( err ! = OK ) {
break ;
2021-07-14 22:15:25 +02:00
}
2021-12-18 10:21:08 +01:00
}
Vector < Ref < EditorExportPlugin > > export_plugins { EditorExport : : get_singleton ( ) - > get_export_plugins ( ) } ;
for ( int i = 0 ; i < export_plugins . size ( ) ; + + i ) {
2022-07-20 08:28:22 +02:00
err = _export_macos_plugins_for ( export_plugins [ i ] , tmp_app_path_name , da , sign_enabled , p_preset , ent_path ) ;
2021-12-18 10:21:08 +01:00
if ( err ! = OK ) {
break ;
2021-07-14 22:15:25 +02:00
}
}
}
if ( err = = OK & & sign_enabled ) {
2021-07-30 12:41:24 +02:00
if ( ep . step ( TTR ( " Code signing bundle " ) , 2 ) ) {
2021-07-14 22:15:25 +02:00
return ERR_SKIP ;
}
2021-07-30 12:41:24 +02:00
err = _code_sign ( p_preset , tmp_app_path_name , ent_path ) ;
2021-07-14 22:15:25 +02:00
}
if ( export_format = = " dmg " ) {
// Create a DMG.
if ( err = = OK ) {
2021-07-30 12:41:24 +02:00
if ( ep . step ( TTR ( " Making DMG " ) , 3 ) ) {
2021-07-14 22:15:25 +02:00
return ERR_SKIP ;
}
2022-02-23 07:44:38 +01:00
err = _create_dmg ( p_path , pkg_name , tmp_base_path_name ) ;
2021-07-14 22:15:25 +02:00
}
// Sign DMG.
2021-07-30 12:41:24 +02:00
if ( err = = OK & & sign_enabled & & ! ad_hoc ) {
if ( ep . step ( TTR ( " Code signing DMG " ) , 3 ) ) {
2021-07-14 22:15:25 +02:00
return ERR_SKIP ;
}
2022-03-10 08:48:25 +01:00
err = _code_sign ( p_preset , p_path , ent_path , false ) ;
2021-07-14 22:15:25 +02:00
}
2021-07-30 12:41:24 +02:00
} else if ( export_format = = " zip " ) {
2021-07-14 22:15:25 +02:00
// Create ZIP.
if ( err = = OK ) {
2021-07-30 12:41:24 +02:00
if ( ep . step ( TTR ( " Making ZIP " ) , 3 ) ) {
2021-07-14 22:15:25 +02:00
return ERR_SKIP ;
}
if ( FileAccess : : exists ( p_path ) ) {
OS : : get_singleton ( ) - > move_to_trash ( p_path ) ;
}
2022-05-11 14:15:58 +02:00
Ref < FileAccess > io_fa_dst ;
zlib_filefunc_def io_dst = zipio_create_io ( & io_fa_dst ) ;
2021-07-14 22:15:25 +02:00
zipFile zip = zipOpen2 ( p_path . utf8 ( ) . get_data ( ) , APPEND_STATUS_CREATE , nullptr , & io_dst ) ;
2022-05-08 09:46:53 +02:00
zip_folder_recursive ( zip , tmp_base_path_name , " " , pkg_name ) ;
2021-07-14 22:15:25 +02:00
zipClose ( zip , nullptr ) ;
}
}
2022-08-09 17:51:43 +02:00
bool noto_enabled = ( p_preset - > get ( " notarization/notarization " ) . operator int ( ) > 0 ) ;
2021-07-14 22:15:25 +02:00
if ( err = = OK & & noto_enabled ) {
2021-07-30 12:41:24 +02:00
if ( export_format = = " app " ) {
2022-06-03 17:34:24 +02:00
add_message ( EXPORT_MESSAGE_INFO , TTR ( " Notarization " ) , TTR ( " Notarization requires the app to be archived first, select the DMG or ZIP export format instead. " ) ) ;
2021-07-30 12:41:24 +02:00
} else {
if ( ep . step ( TTR ( " Sending archive for notarization " ) , 4 ) ) {
return ERR_SKIP ;
}
err = _notarize ( p_preset , p_path ) ;
2021-07-14 22:15:25 +02:00
}
}
2021-03-31 15:19:15 +02:00
// Clean up temporary entitlements files.
2022-08-09 17:51:43 +02:00
if ( FileAccess : : exists ( hlp_ent_path ) ) {
DirAccess : : remove_file_or_error ( hlp_ent_path ) ;
}
2021-03-31 15:19:15 +02:00
2021-07-30 12:41:24 +02:00
// Clean up temporary .app dir and generated entitlements.
if ( ( String ) ( p_preset - > get ( " codesign/entitlements/custom_file " ) ) = = " " ) {
tmp_app_dir - > remove ( ent_path ) ;
}
if ( export_format ! = " app " ) {
2022-02-23 07:44:38 +01:00
if ( tmp_app_dir - > change_dir ( tmp_base_path_name ) = = OK ) {
2021-07-30 12:41:24 +02:00
tmp_app_dir - > erase_contents_recursive ( ) ;
tmp_app_dir - > change_dir ( " .. " ) ;
2022-02-23 07:44:38 +01:00
tmp_app_dir - > remove ( pkg_name ) ;
2021-07-30 12:41:24 +02:00
}
}
2021-07-14 22:15:25 +02:00
}
return err ;
}
2022-07-17 21:26:03 +02:00
bool EditorExportPlatformMacOS : : has_valid_export_configuration ( const Ref < EditorExportPreset > & p_preset , String & r_error , bool & r_missing_templates ) const {
2021-07-14 22:15:25 +02:00
String err ;
bool valid = false ;
2022-01-10 12:18:59 +01:00
// Look for export templates (custom templates).
bool dvalid = false ;
bool rvalid = false ;
2021-07-14 22:15:25 +02:00
if ( p_preset - > get ( " custom_template/debug " ) ! = " " ) {
dvalid = FileAccess : : exists ( p_preset - > get ( " custom_template/debug " ) ) ;
if ( ! dvalid ) {
err + = TTR ( " Custom debug template not found. " ) + " \n " ;
}
}
if ( p_preset - > get ( " custom_template/release " ) ! = " " ) {
rvalid = FileAccess : : exists ( p_preset - > get ( " custom_template/release " ) ) ;
if ( ! rvalid ) {
err + = TTR ( " Custom release template not found. " ) + " \n " ;
}
}
2023-01-25 12:17:11 +01:00
String architecture = p_preset - > get ( " binary_format/architecture " ) ;
if ( architecture = = " universal " | | architecture = = " x86_64 " ) {
const String bc_error = test_bc ( ) ;
if ( ! bc_error . is_empty ( ) ) {
valid = false ;
err + = bc_error ;
}
} else if ( architecture = = " arm64 " ) {
const String etc_error = test_etc2 ( ) ;
if ( ! etc_error . is_empty ( ) ) {
valid = false ;
err + = etc_error ;
}
} else {
ERR_PRINT ( " Invalid architecture " ) ;
}
2022-01-10 12:18:59 +01:00
// Look for export templates (official templates, check only is custom templates are not set).
if ( ! dvalid | | ! rvalid ) {
2022-07-20 08:28:22 +02:00
dvalid = exists_export_template ( " macos.zip " , & err ) ;
2022-01-10 12:18:59 +01:00
rvalid = dvalid ; // Both in the same ZIP.
}
2021-07-14 22:15:25 +02:00
valid = dvalid | | rvalid ;
r_missing_templates = ! valid ;
2022-07-17 21:26:03 +02:00
if ( ! err . is_empty ( ) ) {
r_error = err ;
}
return valid ;
}
bool EditorExportPlatformMacOS : : has_valid_project_configuration ( const Ref < EditorExportPreset > & p_preset , String & r_error ) const {
String err ;
bool valid = true ;
2021-07-14 22:15:25 +02:00
String identifier = p_preset - > get ( " application/bundle_identifier " ) ;
String pn_err ;
if ( ! is_package_name_valid ( identifier , & pn_err ) ) {
err + = TTR ( " Invalid bundle identifier: " ) + " " + pn_err + " \n " ;
valid = false ;
}
2022-08-09 17:51:43 +02:00
bool ad_hoc = false ;
int codesign_tool = p_preset - > get ( " codesign/codesign " ) ;
switch ( codesign_tool ) {
case 1 : { // built-in ad-hoc
ad_hoc = true ;
} break ;
case 2 : { // "rcodesign"
ad_hoc = p_preset - > get ( " codesign/certificate_file " ) . operator String ( ) . is_empty ( ) | | p_preset - > get ( " codesign/certificate_password " ) . operator String ( ) . is_empty ( ) ;
} break ;
2022-07-20 08:28:22 +02:00
# ifdef MACOS_ENABLED
2022-08-09 17:51:43 +02:00
case 3 : { // "codesign"
ad_hoc = ( p_preset - > get ( " codesign/identity " ) = = " " | | p_preset - > get ( " codesign/identity " ) = = " - " ) ;
} break ;
# endif
default : {
} ;
2021-07-30 12:41:24 +02:00
}
2022-08-09 17:51:43 +02:00
int notary_tool = p_preset - > get ( " notarization/notarization " ) ;
2021-07-30 12:41:24 +02:00
2022-08-09 17:51:43 +02:00
if ( notary_tool > 0 ) {
2022-01-10 12:18:59 +01:00
if ( ad_hoc ) {
2022-02-16 09:55:45 +01:00
err + = TTR ( " Notarization: Notarization with an ad-hoc signature is not supported. " ) + " \n " ;
2022-01-10 12:18:59 +01:00
valid = false ;
}
2022-08-09 17:51:43 +02:00
if ( codesign_tool = = 0 ) {
2022-01-10 12:18:59 +01:00
err + = TTR ( " Notarization: Code signing is required for notarization. " ) + " \n " ;
valid = false ;
}
2022-12-06 08:09:10 +01:00
if ( notary_tool = = 2 | | notary_tool = = 3 ) {
2022-08-09 17:51:43 +02:00
if ( ! FileAccess : : exists ( " /usr/bin/xcrun " ) & & ! FileAccess : : exists ( " /bin/xcrun " ) ) {
err + = TTR ( " Notarization: Xcode command line tools are not installed. " ) + " \n " ;
valid = false ;
}
if ( p_preset - > get ( " notarization/apple_id_name " ) = = " " & & p_preset - > get ( " notarization/api_uuid " ) = = " " ) {
err + = TTR ( " Notarization: Neither Apple ID name nor App Store Connect issuer ID name not specified. " ) + " \n " ;
valid = false ;
} else if ( p_preset - > get ( " notarization/apple_id_name " ) ! = " " & & p_preset - > get ( " notarization/api_uuid " ) ! = " " ) {
err + = TTR ( " Notarization: Both Apple ID name and App Store Connect issuer ID name are specified, only one should be set at the same time. " ) + " \n " ;
valid = false ;
} else {
if ( p_preset - > get ( " notarization/apple_id_name " ) ! = " " ) {
if ( p_preset - > get ( " notarization/apple_id_password " ) = = " " ) {
err + = TTR ( " Notarization: Apple ID password not specified. " ) + " \n " ;
2022-11-30 09:38:53 +01:00
valid = false ;
2022-08-09 17:51:43 +02:00
}
}
if ( p_preset - > get ( " notarization/api_uuid " ) ! = " " ) {
2022-12-06 08:09:10 +01:00
if ( p_preset - > get ( " notarization/api_key_id " ) = = " " ) {
2022-08-09 17:51:43 +02:00
err + = TTR ( " Notarization: App Store Connect API key ID not specified. " ) + " \n " ;
valid = false ;
}
}
}
2023-01-12 23:00:07 +01:00
if ( notary_tool = = 2 & & p_preset - > get ( " notarization/apple_team_id " ) = = " " ) {
err + = TTR ( " Notarization: Apple Team ID not specified. " ) + " \n " ;
valid = false ;
}
2022-08-09 17:51:43 +02:00
} else if ( notary_tool = = 1 ) {
if ( p_preset - > get ( " notarization/api_uuid " ) = = " " ) {
err + = TTR ( " Notarization: App Store Connect issuer ID name not specified. " ) + " \n " ;
valid = false ;
}
2022-12-06 08:09:10 +01:00
if ( p_preset - > get ( " notarization/api_key_id " ) = = " " ) {
2022-08-09 17:51:43 +02:00
err + = TTR ( " Notarization: App Store Connect API key ID not specified. " ) + " \n " ;
valid = false ;
}
2022-10-18 16:43:37 +02:00
String rcodesign = EDITOR_GET ( " export/macos/rcodesign " ) . operator String ( ) ;
2022-08-09 17:51:43 +02:00
if ( rcodesign . is_empty ( ) ) {
err + = TTR ( " Notarization: rcodesign path is not set. Configure rcodesign path in the Editor Settings (Export > macOS > rcodesign). " ) + " \n " ;
valid = false ;
}
2021-07-14 22:15:25 +02:00
}
2022-01-10 12:18:59 +01:00
} else {
2022-02-16 09:55:45 +01:00
err + = TTR ( " Warning: Notarization is disabled. The exported project will be blocked by Gatekeeper if it's downloaded from an unknown source. " ) + " \n " ;
2022-08-09 17:51:43 +02:00
if ( codesign_tool = = 0 ) {
2022-02-16 09:55:45 +01:00
err + = TTR ( " Code signing is disabled. The exported project will not run on Macs with enabled Gatekeeper and Apple Silicon powered Macs. " ) + " \n " ;
2022-01-10 12:18:59 +01:00
}
2021-07-14 22:15:25 +02:00
}
2021-07-30 12:41:24 +02:00
2022-08-09 17:51:43 +02:00
if ( codesign_tool > 0 ) {
if ( ad_hoc ) {
err + = TTR ( " Code signing: Using ad-hoc signature. The exported project will be blocked by Gatekeeper " ) + " \n " ;
}
if ( codesign_tool = = 3 ) {
if ( ! FileAccess : : exists ( " /usr/bin/codesign " ) & & ! FileAccess : : exists ( " /bin/codesign " ) ) {
err + = TTR ( " Code signing: Xcode command line tools are not installed. " ) + " \n " ;
valid = false ;
}
} else if ( codesign_tool = = 2 ) {
2022-10-18 16:43:37 +02:00
String rcodesign = EDITOR_GET ( " export/macos/rcodesign " ) . operator String ( ) ;
2022-08-09 17:51:43 +02:00
if ( rcodesign . is_empty ( ) ) {
err + = TTR ( " Code signing: rcodesign path is not set. Configure rcodesign path in the Editor Settings (Export > macOS > rcodesign). " ) + " \n " ;
valid = false ;
}
}
2021-07-30 12:41:24 +02:00
if ( ( bool ) p_preset - > get ( " codesign/entitlements/audio_input " ) & & ( ( String ) p_preset - > get ( " privacy/microphone_usage_description " ) ) . is_empty ( ) ) {
err + = TTR ( " Privacy: Microphone access is enabled, but usage description is not specified. " ) + " \n " ;
valid = false ;
}
if ( ( bool ) p_preset - > get ( " codesign/entitlements/camera " ) & & ( ( String ) p_preset - > get ( " privacy/camera_usage_description " ) ) . is_empty ( ) ) {
err + = TTR ( " Privacy: Camera access is enabled, but usage description is not specified. " ) + " \n " ;
valid = false ;
}
if ( ( bool ) p_preset - > get ( " codesign/entitlements/location " ) & & ( ( String ) p_preset - > get ( " privacy/location_usage_description " ) ) . is_empty ( ) ) {
err + = TTR ( " Privacy: Location information access is enabled, but usage description is not specified. " ) + " \n " ;
valid = false ;
}
if ( ( bool ) p_preset - > get ( " codesign/entitlements/address_book " ) & & ( ( String ) p_preset - > get ( " privacy/address_book_usage_description " ) ) . is_empty ( ) ) {
err + = TTR ( " Privacy: Address book access is enabled, but usage description is not specified. " ) + " \n " ;
valid = false ;
}
if ( ( bool ) p_preset - > get ( " codesign/entitlements/calendars " ) & & ( ( String ) p_preset - > get ( " privacy/calendar_usage_description " ) ) . is_empty ( ) ) {
err + = TTR ( " Privacy: Calendar access is enabled, but usage description is not specified. " ) + " \n " ;
valid = false ;
}
if ( ( bool ) p_preset - > get ( " codesign/entitlements/photos_library " ) & & ( ( String ) p_preset - > get ( " privacy/photos_library_usage_description " ) ) . is_empty ( ) ) {
err + = TTR ( " Privacy: Photo library access is enabled, but usage description is not specified. " ) + " \n " ;
valid = false ;
}
}
2021-07-14 22:15:25 +02:00
if ( ! err . is_empty ( ) ) {
r_error = err ;
}
return valid ;
}
2022-05-08 09:46:53 +02:00
Ref < Texture2D > EditorExportPlatformMacOS : : get_run_icon ( ) const {
return run_icon ;
}
bool EditorExportPlatformMacOS : : poll_export ( ) {
Ref < EditorExportPreset > preset ;
for ( int i = 0 ; i < EditorExport : : get_singleton ( ) - > get_export_preset_count ( ) ; i + + ) {
Ref < EditorExportPreset > ep = EditorExport : : get_singleton ( ) - > get_export_preset ( i ) ;
if ( ep - > is_runnable ( ) & & ep - > get_platform ( ) = = this ) {
preset = ep ;
break ;
}
}
int prev = menu_options ;
menu_options = ( preset . is_valid ( ) & & preset - > get ( " ssh_remote_deploy/enabled " ) . operator bool ( ) ) ;
if ( ssh_pid ! = 0 | | ! cleanup_commands . is_empty ( ) ) {
if ( menu_options = = 0 ) {
cleanup ( ) ;
} else {
menu_options + = 1 ;
}
}
return menu_options ! = prev ;
}
Ref < ImageTexture > EditorExportPlatformMacOS : : get_option_icon ( int p_index ) const {
return p_index = = 1 ? stop_icon : EditorExportPlatform : : get_option_icon ( p_index ) ;
}
int EditorExportPlatformMacOS : : get_options_count ( ) const {
return menu_options ;
}
String EditorExportPlatformMacOS : : get_option_label ( int p_index ) const {
return ( p_index ) ? TTR ( " Stop and uninstall " ) : TTR ( " Run on remote macOS system " ) ;
}
String EditorExportPlatformMacOS : : get_option_tooltip ( int p_index ) const {
return ( p_index ) ? TTR ( " Stop and uninstall running project from the remote system " ) : TTR ( " Run exported project on remote macOS system " ) ;
}
void EditorExportPlatformMacOS : : cleanup ( ) {
if ( ssh_pid ! = 0 & & OS : : get_singleton ( ) - > is_process_running ( ssh_pid ) ) {
print_line ( " Terminating connection... " ) ;
OS : : get_singleton ( ) - > kill ( ssh_pid ) ;
OS : : get_singleton ( ) - > delay_usec ( 1000 ) ;
}
if ( ! cleanup_commands . is_empty ( ) ) {
print_line ( " Stopping and deleting previous version... " ) ;
for ( const SSHCleanupCommand & cmd : cleanup_commands ) {
if ( cmd . wait ) {
ssh_run_on_remote ( cmd . host , cmd . port , cmd . ssh_args , cmd . cmd_args ) ;
} else {
ssh_run_on_remote_no_wait ( cmd . host , cmd . port , cmd . ssh_args , cmd . cmd_args ) ;
}
}
}
ssh_pid = 0 ;
cleanup_commands . clear ( ) ;
}
Error EditorExportPlatformMacOS : : run ( const Ref < EditorExportPreset > & p_preset , int p_device , int p_debug_flags ) {
cleanup ( ) ;
if ( p_device ) { // Stop command, cleanup only.
return OK ;
}
EditorProgress ep ( " run " , TTR ( " Running... " ) , 5 ) ;
const String dest = EditorPaths : : get_singleton ( ) - > get_cache_dir ( ) . path_join ( " macos " ) ;
Ref < DirAccess > da = DirAccess : : create ( DirAccess : : ACCESS_FILESYSTEM ) ;
if ( ! da - > dir_exists ( dest ) ) {
Error err = da - > make_dir_recursive ( dest ) ;
if ( err ! = OK ) {
EditorNode : : get_singleton ( ) - > show_warning ( TTR ( " Could not create temp directory: " ) + " \n " + dest ) ;
return err ;
}
}
String pkg_name ;
if ( String ( ProjectSettings : : get_singleton ( ) - > get ( " application/config/name " ) ) ! = " " ) {
pkg_name = String ( ProjectSettings : : get_singleton ( ) - > get ( " application/config/name " ) ) ;
} else {
pkg_name = " Unnamed " ;
}
pkg_name = OS : : get_singleton ( ) - > get_safe_dir_name ( pkg_name ) ;
String host = p_preset - > get ( " ssh_remote_deploy/host " ) . operator String ( ) ;
String port = p_preset - > get ( " ssh_remote_deploy/port " ) . operator String ( ) ;
if ( port . is_empty ( ) ) {
port = " 22 " ;
}
2023-01-27 09:09:30 +01:00
Vector < String > extra_args_ssh = p_preset - > get ( " ssh_remote_deploy/extra_args_ssh " ) . operator String ( ) . split ( " " , false ) ;
Vector < String > extra_args_scp = p_preset - > get ( " ssh_remote_deploy/extra_args_scp " ) . operator String ( ) . split ( " " , false ) ;
2022-05-08 09:46:53 +02:00
const String basepath = dest . path_join ( " tmp_macos_export " ) ;
# define CLEANUP_AND_RETURN(m_err) \
{ \
if ( da - > file_exists ( basepath + " .zip " ) ) { \
da - > remove ( basepath + " .zip " ) ; \
} \
if ( da - > file_exists ( basepath + " _start.sh " ) ) { \
da - > remove ( basepath + " _start.sh " ) ; \
} \
if ( da - > file_exists ( basepath + " _clean.sh " ) ) { \
da - > remove ( basepath + " _clean.sh " ) ; \
} \
return m_err ; \
} \
( ( void ) 0 )
if ( ep . step ( TTR ( " Exporting project... " ) , 1 ) ) {
return ERR_SKIP ;
}
Error err = export_project ( p_preset , true , basepath + " .zip " , p_debug_flags ) ;
if ( err ! = OK ) {
DirAccess : : remove_file_or_error ( basepath + " .zip " ) ;
return err ;
}
String cmd_args ;
{
Vector < String > cmd_args_list ;
gen_debug_flags ( cmd_args_list , p_debug_flags ) ;
for ( int i = 0 ; i < cmd_args_list . size ( ) ; i + + ) {
if ( i ! = 0 ) {
cmd_args + = " " ;
}
cmd_args + = cmd_args_list [ i ] ;
}
}
const bool use_remote = ( p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG ) | | ( p_debug_flags & DEBUG_FLAG_DUMB_CLIENT ) ;
int dbg_port = EditorSettings : : get_singleton ( ) - > get ( " network/debug/remote_port " ) ;
print_line ( " Creating temporary directory... " ) ;
ep . step ( TTR ( " Creating temporary directory... " ) , 2 ) ;
String temp_dir ;
err = ssh_run_on_remote ( host , port , extra_args_ssh , " mktemp -d " , & temp_dir ) ;
if ( err ! = OK | | temp_dir . is_empty ( ) ) {
CLEANUP_AND_RETURN ( err ) ;
}
print_line ( " Uploading archive... " ) ;
ep . step ( TTR ( " Uploading archive... " ) , 3 ) ;
err = ssh_push_to_remote ( host , port , extra_args_scp , basepath + " .zip " , temp_dir ) ;
if ( err ! = OK ) {
CLEANUP_AND_RETURN ( err ) ;
}
{
String run_script = p_preset - > get ( " ssh_remote_deploy/run_script " ) ;
run_script = run_script . replace ( " {temp_dir} " , temp_dir ) ;
run_script = run_script . replace ( " {archive_name} " , basepath . get_file ( ) + " .zip " ) ;
run_script = run_script . replace ( " {exe_name} " , pkg_name ) ;
run_script = run_script . replace ( " {cmd_args} " , cmd_args ) ;
Ref < FileAccess > f = FileAccess : : open ( basepath + " _start.sh " , FileAccess : : WRITE ) ;
if ( f . is_null ( ) ) {
CLEANUP_AND_RETURN ( err ) ;
}
f - > store_string ( run_script ) ;
}
{
String clean_script = p_preset - > get ( " ssh_remote_deploy/cleanup_script " ) ;
clean_script = clean_script . replace ( " {temp_dir} " , temp_dir ) ;
clean_script = clean_script . replace ( " {archive_name} " , basepath . get_file ( ) + " .zip " ) ;
clean_script = clean_script . replace ( " {exe_name} " , pkg_name ) ;
clean_script = clean_script . replace ( " {cmd_args} " , cmd_args ) ;
Ref < FileAccess > f = FileAccess : : open ( basepath + " _clean.sh " , FileAccess : : WRITE ) ;
if ( f . is_null ( ) ) {
CLEANUP_AND_RETURN ( err ) ;
}
f - > store_string ( clean_script ) ;
}
print_line ( " Uploading scripts... " ) ;
ep . step ( TTR ( " Uploading scripts... " ) , 4 ) ;
err = ssh_push_to_remote ( host , port , extra_args_scp , basepath + " _start.sh " , temp_dir ) ;
if ( err ! = OK ) {
CLEANUP_AND_RETURN ( err ) ;
}
err = ssh_run_on_remote ( host , port , extra_args_ssh , vformat ( " chmod +x \" %s/%s \" " , temp_dir , basepath . get_file ( ) + " _start.sh " ) ) ;
if ( err ! = OK | | temp_dir . is_empty ( ) ) {
CLEANUP_AND_RETURN ( err ) ;
}
err = ssh_push_to_remote ( host , port , extra_args_scp , basepath + " _clean.sh " , temp_dir ) ;
if ( err ! = OK ) {
CLEANUP_AND_RETURN ( err ) ;
}
err = ssh_run_on_remote ( host , port , extra_args_ssh , vformat ( " chmod +x \" %s/%s \" " , temp_dir , basepath . get_file ( ) + " _clean.sh " ) ) ;
if ( err ! = OK | | temp_dir . is_empty ( ) ) {
CLEANUP_AND_RETURN ( err ) ;
}
print_line ( " Starting project... " ) ;
ep . step ( TTR ( " Starting project... " ) , 5 ) ;
err = ssh_run_on_remote_no_wait ( host , port , extra_args_ssh , vformat ( " \" %s/%s \" " , temp_dir , basepath . get_file ( ) + " _start.sh " ) , & ssh_pid , ( use_remote ) ? dbg_port : - 1 ) ;
if ( err ! = OK ) {
CLEANUP_AND_RETURN ( err ) ;
}
cleanup_commands . clear ( ) ;
cleanup_commands . push_back ( SSHCleanupCommand ( host , port , extra_args_ssh , vformat ( " \" %s/%s \" " , temp_dir , basepath . get_file ( ) + " _clean.sh " ) ) ) ;
print_line ( " Project started. " ) ;
CLEANUP_AND_RETURN ( OK ) ;
# undef CLEANUP_AND_RETURN
2021-07-14 22:15:25 +02:00
}
2022-05-08 09:46:53 +02:00
EditorExportPlatformMacOS : : EditorExportPlatformMacOS ( ) {
# ifdef MODULE_SVG_ENABLED
Ref < Image > img = memnew ( Image ) ;
const bool upsample = ! Math : : is_equal_approx ( Math : : round ( EDSCALE ) , EDSCALE ) ;
ImageLoaderSVG img_loader ;
img_loader . create_image_from_string ( img , _macos_logo_svg , EDSCALE , upsample , false ) ;
logo = ImageTexture : : create_from_image ( img ) ;
img_loader . create_image_from_string ( img , _macos_run_icon_svg , EDSCALE , upsample , false ) ;
run_icon = ImageTexture : : create_from_image ( img ) ;
# endif
Ref < Theme > theme = EditorNode : : get_singleton ( ) - > get_editor_theme ( ) ;
if ( theme . is_valid ( ) ) {
stop_icon = theme - > get_icon ( SNAME ( " Stop " ) , SNAME ( " EditorIcons " ) ) ;
} else {
stop_icon . instantiate ( ) ;
}
2021-07-14 22:15:25 +02:00
}