Browse Source

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

bruvzg 4 years ago
parent
commit
261b35d63c
1 changed files with 112 additions and 1 deletions
  1. 112 1
      platform/osx/export/export.cpp

+ 112 - 1
platform/osx/export/export.cpp

@@ -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 _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);
 	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 _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);
 	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);
 	void _zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name);
@@ -66,6 +67,28 @@ class EditorExportPlatformOSX : public EditorExportPlatform {
 	bool use_codesign() const { return false; }
 	bool use_codesign() const { return false; }
 	bool use_dmg() const { return false; }
 	bool use_dmg() const { return false; }
 #endif
 #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:
 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);
@@ -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::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::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
 #endif
 
 
 	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/s3tc"), true));
 	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/s3tc"), true));
@@ -387,6 +415,52 @@ void EditorExportPlatformOSX::_fix_plist(const Ref<EditorExportPreset> &p_preset
 	- and then wrap it up in a DMG
 	- 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) {
 Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path, const String &p_ent_path) {
 #ifdef OSX_ENABLED
 #ifdef OSX_ENABLED
 	List<String> args;
 	List<String> args;
@@ -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);
 	Error err = OS::get_singleton()->execute("codesign", args, true, NULL, &str, NULL, true);
 	ERR_FAIL_COND_V(err != OK, err);
 	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) {
 	if (str.find("no identity found") != -1) {
 		EditorNode::add_io_error("codesign: no identity found");
 		EditorNode::add_io_error("codesign: no identity found");
 		return FAILED;
 		return FAILED;
@@ -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.
 		// Clean up temporary .app dir.
 		tmp_app_dir->change_dir(tmp_app_path_name);
 		tmp_app_dir->change_dir(tmp_app_path_name);
 		tmp_app_dir->erase_contents_recursive();
 		tmp_app_dir->erase_contents_recursive();
@@ -1028,6 +1110,35 @@ bool EditorExportPlatformOSX::can_export(const Ref<EditorExportPreset> &p_preset
 	valid = dvalid || rvalid;
 	valid = dvalid || rvalid;
 	r_missing_templates = !valid;
 	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()) {
 	if (!err.empty()) {
 		r_error = err;
 		r_error = err;
 	}
 	}