Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3.x, macOS export] Add notarization support. #49276

Merged
merged 1 commit into from
Jun 3, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 112 additions & 1 deletion platform/osx/export/export.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class EditorExportPlatformOSX : public EditorExportPlatform {
void _fix_plist(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &plist, const String &p_binary);
void _make_icon(const Ref<Image> &p_icon, Vector<uint8_t> &p_data);

Error _notarize(const Ref<EditorExportPreset> &p_preset, const String &p_path);
Error _code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path, const String &p_ent_path);
Error _create_dmg(const String &p_dmg_path, const String &p_pkg_name, const String &p_app_path_name);
void _zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name);
Expand All @@ -66,6 +67,28 @@ class EditorExportPlatformOSX : public EditorExportPlatform {
bool use_codesign() const { return false; }
bool use_dmg() const { return false; }
#endif
bool is_package_name_valid(const String &p_package, String *r_error = nullptr) const {
String pname = p_package;

if (pname.length() == 0) {
if (r_error) {
*r_error = TTR("Identifier is missing.");
}
return false;
}

for (int i = 0; i < pname.length(); i++) {
char32_t c = pname[i];
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '.')) {
if (r_error) {
*r_error = vformat(TTR("The character '%s' is not allowed in Identifier."), String::chr(c));
}
return false;
}
}

return true;
}

protected:
virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features);
Expand Down Expand Up @@ -162,6 +185,11 @@ void EditorExportPlatformOSX::get_export_options(List<ExportOption> *r_options)
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"), ""));
#endif

r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/s3tc"), true));
Expand Down Expand Up @@ -387,6 +415,52 @@ void EditorExportPlatformOSX::_fix_plist(const Ref<EditorExportPreset> &p_preset
- and then wrap it up in a DMG
**/

Error EditorExportPlatformOSX::_notarize(const Ref<EditorExportPreset> &p_preset, const String &p_path) {
#ifdef OSX_ENABLED
List<String> args;

args.push_back("altool");
args.push_back("--notarize-app");

args.push_back("--primary-bundle-id");
args.push_back(p_preset->get("application/identifier"));

args.push_back("--username");
args.push_back(p_preset->get("notarization/apple_id_name"));

args.push_back("--password");
args.push_back(p_preset->get("notarization/apple_id_password"));

args.push_back("--type");
args.push_back("osx");

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;
Error err = OS::get_singleton()->execute("xcrun", args, true, NULL, &str, NULL, true);
ERR_FAIL_COND_V(err != OK, err);

print_line("altool (" + p_path + "):\n" + str);
if (str.find("RequestUUID") == -1) {
EditorNode::add_io_error("altool: " + str);
return FAILED;
} else {
print_line("Note: The notarization process generally takes less than an hour. When the process is completed, you'll receive an email.");
print_line(" You can check progress manually by opening a Terminal and running the following command:");
print_line(" \"xcrun altool --notarization-history 0 -u <your email> -p <app-specific pwd>\"");
}

#endif

return OK;
}

Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path, const String &p_ent_path) {
#ifdef OSX_ENABLED
List<String> args;
Expand Down Expand Up @@ -431,7 +505,7 @@ Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_prese
Error err = OS::get_singleton()->execute("codesign", args, true, NULL, &str, NULL, true);
ERR_FAIL_COND_V(err != OK, err);

print_line("codesign (" + p_path + "): " + str);
print_line("codesign (" + p_path + "):\n" + str);
if (str.find("no identity found") != -1) {
EditorNode::add_io_error("codesign: no identity found");
return FAILED;
Expand Down Expand Up @@ -891,6 +965,14 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
}
}

bool noto_enabled = p_preset->get("notarization/enable");
if (err == OK && noto_enabled) {
if (ep.step("Sending archive for notarization", 4)) {
return ERR_SKIP;
}
err = _notarize(p_preset, p_path);
}

// Clean up temporary .app dir.
tmp_app_dir->change_dir(tmp_app_path_name);
tmp_app_dir->erase_contents_recursive();
Expand Down Expand Up @@ -1028,6 +1110,35 @@ bool EditorExportPlatformOSX::can_export(const Ref<EditorExportPreset> &p_preset
valid = dvalid || rvalid;
r_missing_templates = !valid;

String identifier = p_preset->get("application/identifier");
String pn_err;
if (!is_package_name_valid(identifier, &pn_err)) {
err += TTR("Invalid bundle identifier:") + " " + pn_err + "\n";
valid = false;
}

bool sign_enabled = p_preset->get("codesign/enable");
bool noto_enabled = p_preset->get("notarization/enable");
if (noto_enabled) {
if (!sign_enabled) {
err += TTR("Notarization: code signing required.") + "\n";
valid = false;
}
bool hr_enabled = p_preset->get("codesign/hardened_runtime");
if (!hr_enabled) {
err += TTR("Notarization: hardened runtime required.") + "\n";
valid = false;
}
if (p_preset->get("notarization/apple_id_name") == "") {
err += TTR("Notarization: Apple ID name not specified.") + "\n";
valid = false;
}
if (p_preset->get("notarization/apple_id_password") == "") {
err += TTR("Notarization: Apple ID password not specified.") + "\n";
valid = false;
}
}

if (!err.empty()) {
r_error = err;
}
Expand Down