[macOS export, 3.x] Simplify code signing options, add support for rcodesign tool for signing and notarization.

This commit is contained in:
bruvzg 2022-09-19 10:55:24 +03:00
parent edc196f926
commit 47d2b4e732
No known key found for this signature in database
GPG key ID: 7960FCF39844EC38
5 changed files with 471 additions and 223 deletions

View file

@ -64,6 +64,9 @@ bool EditorExportPreset::_set(const StringName &p_name, const Variant &p_value)
if (values.has(p_name)) { if (values.has(p_name)) {
values[p_name] = p_value; values[p_name] = p_value;
EditorExport::singleton->save_presets(); EditorExport::singleton->save_presets();
if (update_visibility[p_name]) {
property_list_changed_notify();
}
return true; return true;
} }
@ -81,7 +84,7 @@ bool EditorExportPreset::_get(const StringName &p_name, Variant &r_ret) const {
void EditorExportPreset::_get_property_list(List<PropertyInfo> *p_list) const { void EditorExportPreset::_get_property_list(List<PropertyInfo> *p_list) const {
for (const List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) { for (const List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) {
if (platform->get_option_visibility(E->get().name, values)) { if (platform->get_option_visibility(this, E->get().name, values)) {
p_list->push_back(E->get()); p_list->push_back(E->get());
} }
} }
@ -451,6 +454,7 @@ Ref<EditorExportPreset> EditorExportPlatform::create_preset() {
for (List<ExportOption>::Element *E = options.front(); E; E = E->next()) { for (List<ExportOption>::Element *E = options.front(); E; E = E->next()) {
preset->properties.push_back(E->get().option); preset->properties.push_back(E->get().option);
preset->values[E->get().option.name] = E->get().default_value; preset->values[E->get().option.name] = E->get().default_value;
preset->update_visibility[E->get().option.name] = E->get().update_visibility;
} }
return preset; return preset;
@ -1567,12 +1571,14 @@ void EditorExport::update_export_presets() {
// Clear the preset properties and values prior to reloading // Clear the preset properties and values prior to reloading
preset->properties.clear(); preset->properties.clear();
preset->values.clear(); preset->values.clear();
preset->update_visibility.clear();
for (List<EditorExportPlatform::ExportOption>::Element *E = options.front(); E; E = E->next()) { for (List<EditorExportPlatform::ExportOption>::Element *E = options.front(); E; E = E->next()) {
preset->properties.push_back(E->get().option); preset->properties.push_back(E->get().option);
StringName option_name = E->get().option.name; StringName option_name = E->get().option.name;
preset->values[option_name] = previous_values.has(option_name) ? previous_values[option_name] : E->get().default_value; preset->values[option_name] = previous_values.has(option_name) ? previous_values[option_name] : E->get().default_value;
preset->update_visibility[option_name] = E->get().update_visibility;
} }
} }
} }

View file

@ -75,6 +75,7 @@ private:
List<PropertyInfo> properties; List<PropertyInfo> properties;
Map<StringName, Variant> values; Map<StringName, Variant> values;
Map<StringName, bool> update_visibility;
String name; String name;
@ -226,10 +227,12 @@ public:
struct ExportOption { struct ExportOption {
PropertyInfo option; PropertyInfo option;
Variant default_value; Variant default_value;
bool update_visibility = false;
ExportOption(const PropertyInfo &p_info, const Variant &p_default) : ExportOption(const PropertyInfo &p_info, const Variant &p_default, bool p_update_visibility = false) :
option(p_info), option(p_info),
default_value(p_default) { default_value(p_default),
update_visibility(p_update_visibility) {
} }
ExportOption() {} ExportOption() {}
}; };
@ -245,13 +248,13 @@ public:
messages.push_back(msg); messages.push_back(msg);
switch (p_type) { switch (p_type) {
case EXPORT_MESSAGE_INFO: { case EXPORT_MESSAGE_INFO: {
print_line(vformat("%s: %s\n", msg.category, msg.text)); print_line(vformat("%s: %s", msg.category, msg.text));
} break; } break;
case EXPORT_MESSAGE_WARNING: { case EXPORT_MESSAGE_WARNING: {
WARN_PRINT(vformat("%s: %s\n", msg.category, msg.text)); WARN_PRINT(vformat("%s: %s", msg.category, msg.text));
} break; } break;
case EXPORT_MESSAGE_ERROR: { case EXPORT_MESSAGE_ERROR: {
ERR_PRINT(vformat("%s: %s\n", msg.category, msg.text)); ERR_PRINT(vformat("%s: %s", msg.category, msg.text));
} break; } break;
default: default:
break; break;
@ -279,7 +282,7 @@ public:
virtual void get_export_options(List<ExportOption> *r_options) = 0; virtual void get_export_options(List<ExportOption> *r_options) = 0;
virtual bool should_update_export_options() { return false; } virtual bool should_update_export_options() { return false; }
virtual bool get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const { return true; } virtual bool get_option_visibility(const EditorExportPreset *p_preset, const String &p_option, const Map<StringName, Variant> &p_options) const { return true; }
virtual String get_os_name() const = 0; virtual String get_os_name() const = 0;
virtual String get_name() const = 0; virtual String get_name() const = 0;

View file

@ -106,6 +106,7 @@ EditorPropertyNameProcessor::EditorPropertyNameProcessor() {
capitalize_string_remaps["aabb"] = "AABB"; capitalize_string_remaps["aabb"] = "AABB";
capitalize_string_remaps["adb"] = "ADB"; capitalize_string_remaps["adb"] = "ADB";
capitalize_string_remaps["ao"] = "AO"; capitalize_string_remaps["ao"] = "AO";
capitalize_string_remaps["api"] = "API";
capitalize_string_remaps["apk"] = "APK"; capitalize_string_remaps["apk"] = "APK";
capitalize_string_remaps["arm64-v8a"] = "arm64-v8a"; capitalize_string_remaps["arm64-v8a"] = "arm64-v8a";
capitalize_string_remaps["armeabi-v7a"] = "armeabi-v7a"; capitalize_string_remaps["armeabi-v7a"] = "armeabi-v7a";
@ -191,11 +192,14 @@ EditorPropertyNameProcessor::EditorPropertyNameProcessor() {
capitalize_string_remaps["opengl"] = "OpenGL"; capitalize_string_remaps["opengl"] = "OpenGL";
capitalize_string_remaps["opentype"] = "OpenType"; capitalize_string_remaps["opentype"] = "OpenType";
capitalize_string_remaps["openxr"] = "OpenXR"; capitalize_string_remaps["openxr"] = "OpenXR";
capitalize_string_remaps["osslsigncode"] = "osslsigncode";
capitalize_string_remaps["pck"] = "PCK"; capitalize_string_remaps["pck"] = "PCK";
capitalize_string_remaps["png"] = "PNG"; capitalize_string_remaps["png"] = "PNG";
capitalize_string_remaps["po2"] = "(Power of 2)"; // Unit. capitalize_string_remaps["po2"] = "(Power of 2)"; // Unit.
capitalize_string_remaps["pvrtc"] = "PVRTC"; capitalize_string_remaps["pvrtc"] = "PVRTC";
capitalize_string_remaps["pvs"] = "PVS"; capitalize_string_remaps["pvs"] = "PVS";
capitalize_string_remaps["rcedit"] = "rcedit";
capitalize_string_remaps["rcodesign"] = "rcodesign";
capitalize_string_remaps["rgb"] = "RGB"; capitalize_string_remaps["rgb"] = "RGB";
capitalize_string_remaps["rid"] = "RID"; capitalize_string_remaps["rid"] = "RID";
capitalize_string_remaps["rmb"] = "RMB"; capitalize_string_remaps["rmb"] = "RMB";
@ -206,6 +210,7 @@ EditorPropertyNameProcessor::EditorPropertyNameProcessor() {
capitalize_string_remaps["sdk"] = "SDK"; capitalize_string_remaps["sdk"] = "SDK";
capitalize_string_remaps["sec"] = "(sec)"; // Unit. capitalize_string_remaps["sec"] = "(sec)"; // Unit.
capitalize_string_remaps["selectedframe"] = "Selected Frame"; capitalize_string_remaps["selectedframe"] = "Selected Frame";
capitalize_string_remaps["signtool"] = "signtool";
capitalize_string_remaps["sms"] = "SMS"; capitalize_string_remaps["sms"] = "SMS";
capitalize_string_remaps["srgb"] = "sRGB"; capitalize_string_remaps["srgb"] = "sRGB";
capitalize_string_remaps["ssao"] = "SSAO"; capitalize_string_remaps["ssao"] = "SSAO";
@ -239,6 +244,7 @@ EditorPropertyNameProcessor::EditorPropertyNameProcessor() {
capitalize_string_remaps["webp"] = "WebP"; capitalize_string_remaps["webp"] = "WebP";
capitalize_string_remaps["webrtc"] = "WebRTC"; capitalize_string_remaps["webrtc"] = "WebRTC";
capitalize_string_remaps["websocket"] = "WebSocket"; capitalize_string_remaps["websocket"] = "WebSocket";
capitalize_string_remaps["wine"] = "wine";
capitalize_string_remaps["wifi"] = "Wi-Fi"; capitalize_string_remaps["wifi"] = "Wi-Fi";
capitalize_string_remaps["x86"] = "x86"; capitalize_string_remaps["x86"] = "x86";
capitalize_string_remaps["xr"] = "XR"; capitalize_string_remaps["xr"] = "XR";

View file

@ -102,7 +102,7 @@ class EditorExportPlatformOSX : public EditorExportPlatform {
protected: protected:
virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features); virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features);
virtual void get_export_options(List<ExportOption> *r_options); virtual void get_export_options(List<ExportOption> *r_options);
virtual bool get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const; virtual bool get_option_visibility(const EditorExportPreset *p_preset, const String &p_option, const Map<StringName, Variant> &p_options) const;
public: public:
virtual String get_name() const { return "Mac OSX"; } virtual String get_name() const { return "Mac OSX"; }
@ -150,11 +150,51 @@ void EditorExportPlatformOSX::get_preset_features(const Ref<EditorExportPreset>
r_features->push_back("64"); r_features->push_back("64");
} }
bool EditorExportPlatformOSX::get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const { bool EditorExportPlatformOSX::get_option_visibility(const EditorExportPreset *p_preset, const String &p_option, const Map<StringName, Variant> &p_options) const {
// These options are not supported by built-in codesign, used on non macOS host. // Hide irrelevant code signing options.
if (!OS::get_singleton()->has_feature("OSX")) { if (p_preset) {
if (p_option == "codesign/identity" || p_option == "codesign/timestamp" || p_option == "codesign/hardened_runtime" || p_option == "codesign/custom_options" || p_option.begins_with("notarization/")) { int codesign_tool = p_preset->get("codesign/codesign");
return false; 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 OSX_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;
case 2: { // "altool"
// All options are visible.
} break;
default: { // disabled
if (p_option == "notarization/apple_id_name" || p_option == "notarization/apple_id_password" || p_option == "notarization/apple_team_id" || p_option == "notarization/api_uuid" || p_option == "notarization/api_key") {
return false;
}
} break;
} }
} }
@ -181,6 +221,55 @@ void EditorExportPlatformOSX::get_export_options(List<ExportOption> *r_options)
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/version"), "1.0")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/version"), "1.0"));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/copyright"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/copyright"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "display/high_res"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "display/high_res"), false));
#ifdef OSX_ENABLED
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/codesign", PROPERTY_HINT_ENUM, "Disabled,Built-in (ad-hoc only),PyOxidizer rcodesign,Xcode codesign"), 3, true));
#else
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/codesign", PROPERTY_HINT_ENUM, "Disabled,Built-in (ad-hoc only),PyOxidizer rcodesign"), 1, true));
#endif
// "codesign" only options:
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/identity", PROPERTY_HINT_PLACEHOLDER_TEXT, "Type: Name (ID)"), ""));
// "rcodesign" only options:
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/certificate_file", PROPERTY_HINT_GLOBAL_FILE, "*.pfx,*.p12"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/certificate_password"), ""));
// "codesign" and "rcodesign" only options:
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/entitlements/custom_file", PROPERTY_HINT_GLOBAL_FILE, "*.plist"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/allow_jit_code_execution"), false));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/allow_unsigned_executable_memory"), false));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/allow_dyld_environment_variables"), false));
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));
r_options->push_back(ExportOption(PropertyInfo(Variant::POOL_STRING_ARRAY, "codesign/custom_options"), PoolStringArray()));
#ifdef OSX_ENABLED
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "notarization/notarization", PROPERTY_HINT_ENUM, "Disabled,PyOxidizer rcodesign,Xcode altool"), 0, true));
#else
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "notarization/notarization", PROPERTY_HINT_ENUM, "Disabled,PyOxidizer rcodesign"), 0, true));
#endif
// "altool" only options:
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_id_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Apple ID email"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_id_password", PROPERTY_HINT_PLACEHOLDER_TEXT, "Enable two-factor authentication and provide app-specific password"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_team_id", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide team ID if your Apple ID belongs to multiple teams"), ""));
// "altool" and "rcodesign" only options:
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/api_uuid", PROPERTY_HINT_PLACEHOLDER_TEXT, "App Store Connect issuer ID"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/api_key", PROPERTY_HINT_PLACEHOLDER_TEXT, "App Store Connect API key ID"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/microphone_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the microphone"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/microphone_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the microphone"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/camera_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the camera"), "")); 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::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::STRING, "privacy/location_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the location information"), ""));
@ -193,44 +282,6 @@ void EditorExportPlatformOSX::get_export_options(List<ExportOption> *r_options)
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::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::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::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::BOOL, "codesign/enable"), true));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/identity", PROPERTY_HINT_PLACEHOLDER_TEXT, "Type: Name (ID)"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/timestamp"), true));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/hardened_runtime"), true));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/replace_existing_signature"), true));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/entitlements/custom_file", PROPERTY_HINT_GLOBAL_FILE, "*.plist"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/allow_jit_code_execution"), false));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/allow_unsigned_executable_memory"), false));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/allow_dyld_environment_variables"), false));
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));
r_options->push_back(ExportOption(PropertyInfo(Variant::POOL_STRING_ARRAY, "codesign/custom_options"), PoolStringArray()));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "notarization/enable"), false));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_id_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Apple ID email"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_id_password", PROPERTY_HINT_PLACEHOLDER_TEXT, "Enable two-factor authentication and provide app-specific password"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_team_id", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide team ID if your Apple ID belongs to multiple teams"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/s3tc"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/s3tc"), true));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/etc"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/etc"), false));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/etc2"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/etc2"), false));
@ -502,151 +553,285 @@ void EditorExportPlatformOSX::_fix_plist(const Ref<EditorExportPreset> &p_preset
**/ **/
Error EditorExportPlatformOSX::_notarize(const Ref<EditorExportPreset> &p_preset, const String &p_path) { Error EditorExportPlatformOSX::_notarize(const Ref<EditorExportPreset> &p_preset, const String &p_path) {
int notary_tool = p_preset->get("notarization/notarization");
switch (notary_tool) {
case 1: { // "rcodesign"
print_verbose("using rcodesign notarization...");
String rcodesign = EditorSettings::get_singleton()->get("export/macos/rcodesign").operator String();
if (rcodesign.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");
args.push_back(p_preset->get("notarization/api_key"));
args.push_back(p_path);
String str;
int exitcode = 0;
Error err = OS::get_singleton()->execute(rcodesign, args, true, NULL, &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);
String request_uuid = (next_nl == -1) ? str.substr(rq_offset + 14, -1) : str.substr(rq_offset + 14, next_nl - rq_offset - 14);
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;
#ifdef OSX_ENABLED #ifdef OSX_ENABLED
List<String> args; case 2: { // "altool"
print_verbose("using altool notarization...");
args.push_back("altool"); if (!FileAccess::exists("/usr/bin/xcrun") && !FileAccess::exists("/bin/xcrun")) {
args.push_back("--notarize-app"); add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("Xcode command line tools are not installed."));
return Error::FAILED;
}
args.push_back("--primary-bundle-id"); List<String> args;
args.push_back(p_preset->get("application/identifier"));
args.push_back("--username"); args.push_back("altool");
args.push_back(p_preset->get("notarization/apple_id_name")); args.push_back("--notarize-app");
args.push_back("--password"); args.push_back("--primary-bundle-id");
args.push_back(p_preset->get("notarization/apple_id_password")); args.push_back(p_preset->get("application/bundle_identifier"));
args.push_back("--type"); if (p_preset->get("notarization/apple_id_name") == "" && p_preset->get("notarization/api_uuid") == "") {
args.push_back("osx"); 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_team_id")) { if (p_preset->get("notarization/apple_id_name") != "") {
args.push_back("--asc-provider"); if (p_preset->get("notarization/apple_id_password") == "") {
args.push_back(p_preset->get("notarization/apple_team_id")); 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"));
args.push_back("--file"); args.push_back("--password");
args.push_back(p_path); 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"));
String str; args.push_back("--apiKey");
Error err = OS::get_singleton()->execute("xcrun", args, true, NULL, &str, NULL, true); args.push_back(p_preset->get("notarization/api_key"));
if (err != OK || (str.find("not found") != -1) || (str.find("not recognized") != -1)) { }
add_message(EXPORT_MESSAGE_WARNING, TTR("Notarization"), TTR("Could not start xcrun executable."));
return err;
}
print_verbose("altool (" + p_path + "):\n" + str); args.push_back("--type");
int rq_offset = str.find("RequestUUID"); args.push_back("osx");
if (rq_offset == -1) {
add_message(EXPORT_MESSAGE_WARNING, TTR("Notarization"), TTR("Notarization failed."));
return FAILED;
} else {
int next_nl = str.find("\n", rq_offset);
String request_uuid = (next_nl == -1) ? str.substr(rq_offset + 14, -1) : str.substr(rq_offset + 14, next_nl - rq_offset - 14);
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>\"");
}
if (p_preset->get("notarization/apple_team_id")) {
args.push_back("--asc-provider");
args.push_back(p_preset->get("notarization/apple_team_id"));
}
args.push_back("--file");
args.push_back(p_path);
String str;
int exitcode = 0;
Error err = OS::get_singleton()->execute("xcrun", args, true, NULL, &str, &exitcode, true);
if (err != OK) {
add_message(EXPORT_MESSAGE_WARNING, TTR("Notarization"), TTR("Could not start xcrun executable."));
return err;
}
int rq_offset = str.find("RequestUUID");
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);
String request_uuid = (next_nl == -1) ? str.substr(rq_offset + 14, -1) : str.substr(rq_offset + 14, next_nl - rq_offset - 14);
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 #endif
default: {
};
}
return OK; return OK;
} }
Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path, const String &p_ent_path) { Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path, const String &p_ent_path) {
bool force_builtin_codesign = EditorSettings::get_singleton()->get("export/macos/force_builtin_codesign"); int codesign_tool = p_preset->get("codesign/codesign");
bool ad_hoc = (p_preset->get("codesign/identity") == "" || p_preset->get("codesign/identity") == "-"); switch (codesign_tool) {
case 1: { // built-in ad-hoc
if ((!FileAccess::exists("/usr/bin/codesign") && !FileAccess::exists("/bin/codesign")) || force_builtin_codesign) { print_verbose("using built-in codesign...");
print_verbose("using built-in codesign...");
#ifdef MODULE_REGEX_ENABLED #ifdef MODULE_REGEX_ENABLED
#ifdef OSX_ENABLED String error_msg;
if (p_preset->get("codesign/timestamp")) { Error err = CodeSign::codesign(false, true, p_path, p_ent_path, error_msg);
add_message(EXPORT_MESSAGE_INFO, TTR("Code Signing"), TTR("Timestamping is not compatible with ad-hoc signature, and was disabled!")); if (err != OK) {
} add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("Built-in CodeSign failed with error \"%s\"."), error_msg));
if (p_preset->get("codesign/hardened_runtime")) { return Error::FAILED;
add_message(EXPORT_MESSAGE_INFO, TTR("Code Signing"), TTR("Hardened Runtime is not compatible with ad-hoc signature, and was disabled!"));
}
#endif
String error_msg;
Error err = CodeSign::codesign(false, p_preset->get("codesign/replace_existing_signature"), 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 FAILED;
}
#else
add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Built-in CodeSign require regex module."));
#endif
return OK;
} else {
print_verbose("using external codesign...");
List<String> args;
if (p_preset->get("codesign/timestamp")) {
if (ad_hoc) {
add_message(EXPORT_MESSAGE_INFO, TTR("Code Signing"), TTR("Timestamping is not compatible with ad-hoc signature, and was disabled!"));
} else {
args.push_back("--timestamp");
} }
} #else
if (p_preset->get("codesign/hardened_runtime")) { add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Built-in CodeSign require regex module."));
if (ad_hoc) { #endif
add_message(EXPORT_MESSAGE_INFO, TTR("Code Signing"), TTR("Hardened Runtime is not compatible with ad-hoc signature, and was disabled!")); } break;
case 2: { // "rcodesign"
print_verbose("using rcodesign codesign...");
String rcodesign = EditorSettings::get_singleton()->get("export/macos/rcodesign").operator String();
if (rcodesign.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;
}
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");
if (!certificate_file.empty() && !certificate_file.empty()) {
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, true, NULL, &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;
} else { } else {
print_verbose("rcodesign (" + p_path + "):\n" + str);
}
} break;
#ifdef OSX_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");
args.push_back("--options"); args.push_back("--options");
args.push_back("runtime"); args.push_back("runtime");
} }
}
if (p_path.get_extension() != "dmg") { if (p_path.get_extension() != "dmg") {
args.push_back("--entitlements"); args.push_back("--entitlements");
args.push_back(p_ent_path); args.push_back(p_ent_path);
}
PoolStringArray 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.empty()) {
args.push_back(user_arg);
} }
}
args.push_back("-s"); PoolStringArray user_args = p_preset->get("codesign/custom_options");
if (ad_hoc) { for (int i = 0; i < user_args.size(); i++) {
args.push_back("-"); String user_arg = user_args[i].strip_edges();
} else { if (!user_arg.empty()) {
args.push_back(p_preset->get("codesign/identity")); args.push_back(user_arg);
} }
}
args.push_back("-v"); /* provide some more feedback */ args.push_back("-s");
if (ad_hoc) {
args.push_back("-");
} else {
args.push_back(p_preset->get("codesign/identity"));
}
if (p_preset->get("codesign/replace_existing_signature")) { args.push_back("-v"); /* provide some more feedback */
args.push_back("-f"); args.push_back("-f");
}
args.push_back(p_path); args.push_back(p_path);
String str; String str;
Error err = OS::get_singleton()->execute("codesign", args, true, NULL, &str, NULL, true); int exitcode = 0;
if (err != OK || (str.find("not found") != -1) || (str.find("not recognized") != -1)) {
add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Could not start codesign executable, make sure Xcode command line tools are installed."));
return err;
}
print_verbose("codesign (" + p_path + "):\n" + str); Error err = OS::get_singleton()->execute("codesign", args, true, NULL, &str, NULL, true);
if (str.find("no identity found") != -1) { if (err != OK) {
add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("No identity found.")); add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Could not start codesign executable, make sure Xcode command line tools are installed."));
return FAILED; return err;
} }
if ((str.find("unrecognized blob type") != -1) || (str.find("cannot read entitlement data") != -1)) {
add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Invalid entitlements file.")); if (exitcode != 0) {
return FAILED; 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 OK; return Error::FAILED;
} else {
print_verbose("codesign (" + p_path + "):\n" + str);
}
} break;
#endif
default: {
};
} }
return OK;
} }
Error EditorExportPlatformOSX::_code_sign_directory(const Ref<EditorExportPreset> &p_preset, const String &p_path, Error EditorExportPlatformOSX::_code_sign_directory(const Ref<EditorExportPreset> &p_preset, const String &p_path,
@ -1066,7 +1251,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
err = save_pack(p_preset, pack_path, &shared_objects); err = save_pack(p_preset, pack_path, &shared_objects);
// See if we can code sign our new package. // See if we can code sign our new package.
bool sign_enabled = p_preset->get("codesign/enable"); bool sign_enabled = (p_preset->get("codesign/codesign").operator int() > 0);
String ent_path = p_preset->get("codesign/entitlements/custom_file"); String ent_path = p_preset->get("codesign/entitlements/custom_file");
if (sign_enabled && (ent_path == "")) { if (sign_enabled && (ent_path == "")) {
@ -1202,14 +1387,25 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
} }
} }
bool ad_hoc = true; bool ad_hoc = false;
if (err == OK) { 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().empty() || p_preset->get("codesign/certificate_password").operator String().empty();
} break;
#ifdef OSX_ENABLED #ifdef OSX_ENABLED
String sign_identity = p_preset->get("codesign/identity"); case 3: { // "codesign"
#else ad_hoc = (p_preset->get("codesign/identity") == "" || p_preset->get("codesign/identity") == "-");
String sign_identity = "-"; } break;
#endif #endif
ad_hoc = (sign_identity == "" || sign_identity == "-"); default: {
};
}
if (err == OK) {
bool lib_validation = p_preset->get("codesign/entitlements/disable_library_validation"); bool lib_validation = p_preset->get("codesign/entitlements/disable_library_validation");
if ((!dylibs_found.empty() || !shared_objects.empty()) && sign_enabled && ad_hoc && !lib_validation) { if ((!dylibs_found.empty() || !shared_objects.empty()) && sign_enabled && ad_hoc && !lib_validation) {
add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Ad-hoc signed applications require the 'Disable Library Validation' entitlement to load dynamic libraries.")); add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Ad-hoc signed applications require the 'Disable Library Validation' entitlement to load dynamic libraries."));
@ -1287,8 +1483,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
} }
} }
#ifdef OSX_ENABLED bool noto_enabled = (p_preset->get("notarization/notarization").operator int() > 0);
bool noto_enabled = p_preset->get("notarization/enable");
if (err == OK && noto_enabled) { if (err == OK && noto_enabled) {
if (export_format == "app") { if (export_format == "app") {
add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), TTR("Notarization requires the app to be archived first, select the DMG or ZIP export format instead.")); add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), TTR("Notarization requires the app to be archived first, select the DMG or ZIP export format instead."));
@ -1299,7 +1494,6 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
err = _notarize(p_preset, p_path); err = _notarize(p_preset, p_path);
} }
} }
#endif
// Clean up temporary .app dir and generated entitlements. // Clean up temporary .app dir and generated entitlements.
if ((String)(p_preset->get("codesign/entitlements/custom_file")) == "") { if ((String)(p_preset->get("codesign/entitlements/custom_file")) == "") {
@ -1481,65 +1675,98 @@ bool EditorExportPlatformOSX::has_valid_project_configuration(const Ref<EditorEx
valid = false; valid = false;
} }
bool sign_enabled = p_preset->get("codesign/enable"); 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().empty() || p_preset->get("codesign/certificate_password").operator String().empty();
} break;
#ifdef OSX_ENABLED #ifdef OSX_ENABLED
bool noto_enabled = p_preset->get("notarization/enable"); case 3: { // "codesign"
bool ad_hoc = ((p_preset->get("codesign/identity") == "") || (p_preset->get("codesign/identity") == "-")); ad_hoc = (p_preset->get("codesign/identity") == "" || p_preset->get("codesign/identity") == "-");
} break;
if (!ad_hoc && (bool)EditorSettings::get_singleton()->get("export/macos/force_builtin_codesign")) { #endif
err += TTR("Warning: Built-in \"codesign\" is selected in the Editor Settings. Code signing is limited to ad-hoc signature only.") + "\n"; default: {
} };
if (!ad_hoc && !FileAccess::exists("/usr/bin/codesign") && !FileAccess::exists("/bin/codesign")) {
err += TTR("Warning: Xcode command line tools are not installed, using built-in \"codesign\". Code signing is limited to ad-hoc signature only.") + "\n";
} }
int notary_tool = p_preset->get("notarization/notarization");
if (noto_enabled) { if (notary_tool > 0) {
if (ad_hoc) { if (ad_hoc) {
err += TTR("Notarization: Notarization with an ad-hoc signature is not supported.") + "\n"; err += TTR("Notarization: Notarization with an ad-hoc signature is not supported.") + "\n";
valid = false; valid = false;
} }
if (!sign_enabled) { if (codesign_tool == 0) {
err += TTR("Notarization: Code signing is required for notarization.") + "\n"; err += TTR("Notarization: Code signing is required for notarization.") + "\n";
valid = false; valid = false;
} }
if (!(bool)p_preset->get("codesign/hardened_runtime")) { if (notary_tool == 2) {
err += TTR("Notarization: Hardened runtime is required for notarization.") + "\n"; if (!FileAccess::exists("/usr/bin/xcrun") && !FileAccess::exists("/bin/xcrun")) {
valid = false; err += TTR("Notarization: Xcode command line tools are not installed.") + "\n";
} valid = false;
if (!(bool)p_preset->get("codesign/timestamp")) { }
err += TTR("Notarization: Timestamp runtime is required for notarization.") + "\n"; if (p_preset->get("notarization/apple_id_name") == "" && p_preset->get("notarization/api_uuid") == "") {
valid = false; err += TTR("Notarization: Neither Apple ID name nor App Store Connect issuer ID name not specified.") + "\n";
} valid = false;
if (p_preset->get("notarization/apple_id_name") == "") { } else if (p_preset->get("notarization/apple_id_name") != "" && p_preset->get("notarization/api_uuid") != "") {
err += TTR("Notarization: Apple ID name not specified.") + "\n"; 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; valid = false;
} } else {
if (p_preset->get("notarization/apple_id_password") == "") { if (p_preset->get("notarization/apple_id_name") != "") {
err += TTR("Notarization: Apple ID password not specified.") + "\n"; if (p_preset->get("notarization/apple_id_password") == "") {
valid = false; err += TTR("Notarization: Apple ID password not specified.") + "\n";
}
valid = false;
}
if (p_preset->get("notarization/api_uuid") != "") {
if (p_preset->get("notarization/api_key") == "") {
err += TTR("Notarization: App Store Connect API key ID not specified.") + "\n";
valid = false;
}
}
}
} 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;
}
if (p_preset->get("notarization/api_key") == "") {
err += TTR("Notarization: App Store Connect API key ID not specified.") + "\n";
valid = false;
}
String rcodesign = EditorSettings::get_singleton()->get("export/macos/rcodesign").operator String();
if (rcodesign.empty()) {
err += TTR("Notarization: rcodesign path is not set. Configure rcodesign path in the Editor Settings (Export > macOS > rcodesign).") + "\n";
valid = false;
}
} }
} else { } else {
err += TTR("Warning: Notarization is disabled. The exported project will be blocked by Gatekeeper if it's downloaded from an unknown source.") + "\n"; err += TTR("Warning: Notarization is disabled. The exported project will be blocked by Gatekeeper if it's downloaded from an unknown source.") + "\n";
if (!sign_enabled) { if (codesign_tool == 0) {
err += TTR("Code signing is disabled. The exported project will not run on Macs with enabled Gatekeeper and Apple Silicon powered Macs.") + "\n"; err += TTR("Code signing is disabled. The exported project will not run on Macs with enabled Gatekeeper and Apple Silicon powered Macs.") + "\n";
} else {
if ((bool)p_preset->get("codesign/hardened_runtime") && ad_hoc) {
err += TTR("Hardened Runtime is not compatible with ad-hoc signature, and will be disabled!") + "\n";
}
if ((bool)p_preset->get("codesign/timestamp") && ad_hoc) {
err += TTR("Timestamping is not compatible with ad-hoc signature, and will be disabled!") + "\n";
}
} }
} }
#else
err += TTR("Warning: Notarization is not supported from this OS. The exported project will be blocked by Gatekeeper if it's downloaded from an unknown source.") + "\n";
if (!sign_enabled) {
err += TTR("Code signing is disabled. The exported project will not run on Macs with enabled Gatekeeper and Apple Silicon powered Macs.") + "\n";
}
#endif
if (sign_enabled) { 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) {
String rcodesign = EditorSettings::get_singleton()->get("export/macos/rcodesign").operator String();
if (rcodesign.empty()) {
err += TTR("Code signing: rcodesign path is not set. Configure rcodesign path in the Editor Settings (Export > macOS > rcodesign).") + "\n";
valid = false;
}
}
if ((bool)p_preset->get("codesign/entitlements/audio_input") && ((String)p_preset->get("privacy/microphone_usage_description")).empty()) { if ((bool)p_preset->get("codesign/entitlements/audio_input") && ((String)p_preset->get("privacy/microphone_usage_description")).empty()) {
err += TTR("Privacy: Microphone access is enabled, but usage description is not specified.") + "\n"; err += TTR("Privacy: Microphone access is enabled, but usage description is not specified.") + "\n";
valid = false; valid = false;
@ -1582,9 +1809,11 @@ EditorExportPlatformOSX::~EditorExportPlatformOSX() {
} }
void register_osx_exporter() { void register_osx_exporter() {
#ifndef ANDROID_ENABLED EDITOR_DEF("export/macos/rcodesign", "");
EDITOR_DEF("export/macos/force_builtin_codesign", false); #ifdef WINDOWS_ENABLED
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::BOOL, "export/macos/force_builtin_codesign", PROPERTY_HINT_NONE)); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/macos/rcodesign", PROPERTY_HINT_GLOBAL_FILE, "*.exe"));
#else
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/macos/rcodesign", PROPERTY_HINT_GLOBAL_FILE));
#endif #endif
Ref<EditorExportPlatformOSX> platform; Ref<EditorExportPlatformOSX> platform;

View file

@ -47,7 +47,7 @@ public:
virtual Error modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags); virtual Error modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags);
virtual Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size); virtual Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size);
virtual void get_export_options(List<ExportOption> *r_options); virtual void get_export_options(List<ExportOption> *r_options);
virtual bool get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const; virtual bool get_option_visibility(const EditorExportPreset *p_preset, const String &p_option, const Map<StringName, Variant> &p_options) const;
virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const; virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const;
virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const; virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const;
}; };
@ -89,7 +89,7 @@ Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset>
return err; return err;
} }
bool EditorExportPlatformWindows::get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const { bool EditorExportPlatformWindows::get_option_visibility(const EditorExportPreset *p_preset, const String &p_option, const Map<StringName, Variant> &p_options) const {
// This option is not supported by "osslsigncode", used on non-Windows host. // This option is not supported by "osslsigncode", used on non-Windows host.
if (!OS::get_singleton()->has_feature("Windows") && p_option == "codesign/identity_type") { if (!OS::get_singleton()->has_feature("Windows") && p_option == "codesign/identity_type") {
return false; return false;
@ -206,7 +206,7 @@ Error EditorExportPlatformWindows::_rcedit_add_data(const Ref<EditorExportPreset
String str; String str;
Error err = OS::get_singleton()->execute(rcedit_path, args, true, nullptr, &str, nullptr, true); Error err = OS::get_singleton()->execute(rcedit_path, args, true, nullptr, &str, nullptr, true);
if (err != OK || (str.find("not found") != -1) || (str.find("not recognized") != -1)) { if (err != OK || (str.find("not found") != -1) || (str.find("not recognized") != -1)) {
add_message(EXPORT_MESSAGE_WARNING, TTR("Resources Modification"), TTR("Could not start rcedit executable. Configure rcedit path in the Editor Settings (Export > Windows > Rcedit), or disable \"Application > Modify Resources\" in the export preset.")); add_message(EXPORT_MESSAGE_WARNING, TTR("Resources Modification"), TTR("Could not start rcedit executable. Configure rcedit path in the Editor Settings (Export > Windows > rcedit), or disable \"Application > Modify Resources\" in the export preset."));
return err; return err;
} }
print_line("rcedit (" + p_path + "): " + str); print_line("rcedit (" + p_path + "): " + str);
@ -354,7 +354,11 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p
String str; String str;
Error err = OS::get_singleton()->execute(signtool_path, args, true, nullptr, &str, nullptr, true); Error err = OS::get_singleton()->execute(signtool_path, args, true, nullptr, &str, nullptr, true);
if (err != OK || (str.find("not found") != -1) || (str.find("not recognized") != -1)) { if (err != OK || (str.find("not found") != -1) || (str.find("not recognized") != -1)) {
add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Could not start signtool executable. Configure signtool path in the Editor Settings (Export > Windows > Signtool), or disable \"Codesign\" in the export preset.")); #ifndef WINDOWS_ENABLED
add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Could not start signtool executable. Configure signtool path in the Editor Settings (Export > Windows > signtool), or disable \"Codesign\" in the export preset."));
#else
add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Could not start osslsigncode executable. Configure signtool path in the Editor Settings (Export > Windows > osslsigncode), or disable \"Codesign\" in the export preset."));
#endif
return err; return err;
} }
@ -393,7 +397,7 @@ bool EditorExportPlatformWindows::has_valid_export_configuration(const Ref<Edito
String rcedit_path = EditorSettings::get_singleton()->get("export/windows/rcedit"); String rcedit_path = EditorSettings::get_singleton()->get("export/windows/rcedit");
if (p_preset->get("application/modify_resources") && rcedit_path.empty()) { if (p_preset->get("application/modify_resources") && rcedit_path.empty()) {
err += TTR("The rcedit tool must be configured in the Editor Settings (Export > Windows > Rcedit) to change the icon or app information data.") + "\n"; err += TTR("The rcedit tool must be configured in the Editor Settings (Export > Windows > rcedit) to change the icon or app information data.") + "\n";
} }
if (!err.empty()) { if (!err.empty()) {