Pārlūkot izejas kodu

Add readable export errors.

bruvzg 3 gadi atpakaļ
vecāks
revīzija
e00571b194

+ 104 - 10
editor/editor_export.cpp

@@ -46,6 +46,7 @@
 #include "editor/editor_file_system.h"
 #include "editor/editor_node.h"
 #include "editor/editor_paths.h"
+#include "editor/editor_scale.h"
 #include "editor/editor_settings.h"
 #include "editor/plugins/script_editor_plugin.h"
 #include "scene/resources/resource_format_text.h"
@@ -252,6 +253,83 @@ String EditorExportPreset::get_script_encryption_key() const {
 
 ///////////////////////////////////
 
+bool EditorExportPlatform::fill_log_messages(RichTextLabel *p_log, Error p_err) {
+	bool has_messages = false;
+
+	int msg_count = get_message_count();
+
+	p_log->add_text(TTR("Project export for platform:") + " ");
+	p_log->add_image(get_logo(), 16 * EDSCALE, 16 * EDSCALE, Color(1.0, 1.0, 1.0), INLINE_ALIGNMENT_CENTER);
+	p_log->add_text(" ");
+	p_log->add_text(get_name());
+	p_log->add_text(" - ");
+	if (p_err == OK) {
+		if (get_worst_message_type() >= EditorExportPlatform::EXPORT_MESSAGE_WARNING) {
+			p_log->add_image(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("StatusWarning"), SNAME("EditorIcons")), 16 * EDSCALE, 16 * EDSCALE, Color(1.0, 1.0, 1.0), INLINE_ALIGNMENT_CENTER);
+			p_log->add_text(" ");
+			p_log->add_text(TTR("Completed with errors."));
+			has_messages = true;
+		} else {
+			p_log->add_image(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("StatusSuccess"), SNAME("EditorIcons")), 16 * EDSCALE, 16 * EDSCALE, Color(1.0, 1.0, 1.0), INLINE_ALIGNMENT_CENTER);
+			p_log->add_text(" ");
+			p_log->add_text(TTR("Completed sucessfully."));
+			if (msg_count > 0) {
+				has_messages = true;
+			}
+		}
+	} else {
+		p_log->add_image(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("StatusError"), SNAME("EditorIcons")), 16 * EDSCALE, 16 * EDSCALE, Color(1.0, 1.0, 1.0), INLINE_ALIGNMENT_CENTER);
+		p_log->add_text(" ");
+		p_log->add_text(TTR("Failed."));
+		has_messages = true;
+	}
+	p_log->add_newline();
+
+	if (msg_count) {
+		p_log->push_table(2);
+		p_log->set_table_column_expand(0, false);
+		p_log->set_table_column_expand(1, true);
+		for (int m = 0; m < msg_count; m++) {
+			EditorExportPlatform::ExportMessage msg = get_message(m);
+			Color color = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("font_color"), SNAME("Label"));
+			Ref<Texture> icon;
+
+			switch (msg.msg_type) {
+				case EditorExportPlatform::EXPORT_MESSAGE_INFO: {
+					color = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("font_color"), SNAME("Editor")) * Color(1, 1, 1, 0.6);
+				} break;
+				case EditorExportPlatform::EXPORT_MESSAGE_WARNING: {
+					icon = EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Warning"), SNAME("EditorIcons"));
+					color = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("warning_color"), SNAME("Editor"));
+				} break;
+				case EditorExportPlatform::EXPORT_MESSAGE_ERROR: {
+					icon = EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Error"), SNAME("EditorIcons"));
+					color = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("error_color"), SNAME("Editor"));
+				} break;
+				default:
+					break;
+			}
+
+			p_log->push_cell();
+			p_log->add_text("\t");
+			if (icon.is_valid()) {
+				p_log->add_image(icon);
+			}
+			p_log->pop();
+
+			p_log->push_cell();
+			p_log->push_color(color);
+			p_log->add_text(vformat("[%s]: %s", msg.category, msg.text));
+			p_log->pop();
+			p_log->pop();
+		}
+		p_log->pop();
+		p_log->add_newline();
+	}
+	p_log->add_newline();
+	return has_messages;
+}
+
 void EditorExportPlatform::gen_debug_flags(Vector<String> &r_flags, int p_flags) {
 	String host = EditorSettings::get_singleton()->get("network/debug/remote_host");
 	int remote_port = (int)EditorSettings::get_singleton()->get("network/debug/remote_port");
@@ -1134,7 +1212,10 @@ Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, b
 
 	String tmppath = EditorPaths::get_singleton()->get_cache_dir().plus_file("packtmp");
 	Ref<FileAccess> ftmp = FileAccess::open(tmppath, FileAccess::WRITE);
-	ERR_FAIL_COND_V_MSG(ftmp.is_null(), ERR_CANT_CREATE, "Cannot create file '" + tmppath + "'.");
+	if (ftmp.is_null()) {
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Save PCK"), vformat(TTR("Cannot create file \"%s\"."), tmppath));
+		return ERR_CANT_CREATE;
+	}
 
 	PackData pd;
 	pd.ep = &ep;
@@ -1149,7 +1230,7 @@ Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, b
 
 	if (err != OK) {
 		DirAccess::remove_file_or_error(tmppath);
-		ERR_PRINT("Failed to export project files");
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Save PCK"), TTR("Failed to export project files."));
 		return err;
 	}
 
@@ -1162,14 +1243,16 @@ Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, b
 		f = FileAccess::open(p_path, FileAccess::WRITE);
 		if (f.is_null()) {
 			DirAccess::remove_file_or_error(tmppath);
-			ERR_FAIL_V(ERR_CANT_CREATE);
+			add_message(EXPORT_MESSAGE_ERROR, TTR("Save PCK"), vformat(TTR("Can't open file to read from path \"%s\"."), tmppath));
+			return ERR_CANT_CREATE;
 		}
 	} else {
 		// Append to executable
 		f = FileAccess::open(p_path, FileAccess::READ_WRITE);
 		if (f.is_null()) {
 			DirAccess::remove_file_or_error(tmppath);
-			ERR_FAIL_V(ERR_FILE_CANT_OPEN);
+			add_message(EXPORT_MESSAGE_ERROR, TTR("Save PCK"), vformat(TTR("Can't open executable file from path \"%s\"."), tmppath));
+			return ERR_FILE_CANT_OPEN;
 		}
 
 		f->seek_end();
@@ -1245,10 +1328,16 @@ Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, b
 			}
 		}
 		fae.instantiate();
-		ERR_FAIL_COND_V(fae.is_null(), ERR_SKIP);
+		if (fae.is_null()) {
+			add_message(EXPORT_MESSAGE_ERROR, TTR("Save PCK"), TTR("Can't create encrypted file."));
+			return ERR_CANT_CREATE;
+		}
 
 		err = fae->open_and_parse(f, key, FileAccessEncrypted::MODE_WRITE_AES256, false);
-		ERR_FAIL_COND_V(err != OK, ERR_SKIP);
+		if (err != OK) {
+			add_message(EXPORT_MESSAGE_ERROR, TTR("Save PCK"), TTR("Can't open encrypted file to write."));
+			return ERR_CANT_CREATE;
+		}
 
 		fhead = fae;
 	}
@@ -1293,7 +1382,8 @@ Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, b
 	ftmp = FileAccess::open(tmppath, FileAccess::READ);
 	if (ftmp.is_null()) {
 		DirAccess::remove_file_or_error(tmppath);
-		ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Can't open file to read from path '" + String(tmppath) + "'.");
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Save PCK"), vformat(TTR("Can't open file to read from path \"%s\"."), tmppath));
+		return ERR_CANT_CREATE;
 	}
 
 	const int bufsize = 16384;
@@ -1344,7 +1434,7 @@ Error EditorExportPlatform::save_zip(const Ref<EditorExportPreset> &p_preset, bo
 
 	Error err = export_project_files(p_preset, p_debug, _save_zip_file, &zd);
 	if (err != OK && err != ERR_SKIP) {
-		ERR_PRINT("Failed to export project files");
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Save ZIP"), TTR("Failed to export project files."));
 	}
 
 	zipClose(zip, nullptr);
@@ -1835,6 +1925,7 @@ Error EditorExportPlatformPC::export_project(const Ref<EditorExportPreset> &p_pr
 
 Error EditorExportPlatformPC::prepare_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
 	if (!DirAccess::exists(p_path.get_base_dir())) {
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Template"), TTR("The given export path doesn't exist."));
 		return ERR_FILE_BAD_PATH;
 	}
 
@@ -1850,13 +1941,16 @@ Error EditorExportPlatformPC::prepare_template(const Ref<EditorExportPreset> &p_
 	}
 
 	if (!template_path.is_empty() && !FileAccess::exists(template_path)) {
-		EditorNode::get_singleton()->show_warning(TTR("Template file not found:") + "\n" + template_path);
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Template"), vformat(TTR("Template file not found: \"%s\"."), template_path));
 		return ERR_FILE_NOT_FOUND;
 	}
 
 	Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
 	da->make_dir_recursive(p_path.get_base_dir());
 	Error err = da->copy(template_path, p_path, get_chmod_flags());
+	if (err != OK) {
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Template"), TTR("Failed to copy export template."));
+	}
 
 	return err;
 }
@@ -1876,7 +1970,7 @@ Error EditorExportPlatformPC::export_project_data(const Ref<EditorExportPreset>
 	Error err = save_pack(p_preset, p_debug, pck_path, &so_files, p_preset->get("binary_format/embed_pck"), &embedded_pos, &embedded_size);
 	if (err == OK && p_preset->get("binary_format/embed_pck")) {
 		if (embedded_size >= 0x100000000 && !p_preset->get("binary_format/64_bits")) {
-			EditorNode::get_singleton()->show_warning(TTR("On 32-bit exports the embedded PCK cannot be bigger than 4 GiB."));
+			add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding"), TTR("On 32-bit exports the embedded PCK cannot be bigger than 4 GiB."));
 			return ERR_INVALID_PARAMETER;
 		}
 

+ 58 - 1
editor/editor_export.h

@@ -33,6 +33,7 @@
 
 #include "core/io/dir_access.h"
 #include "core/io/resource.h"
+#include "scene/gui/rich_text_label.h"
 #include "scene/main/node.h"
 #include "scene/main/timer.h"
 #include "scene/resources/texture.h"
@@ -170,6 +171,19 @@ public:
 	typedef Error (*EditorExportSaveFunction)(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key);
 	typedef Error (*EditorExportSaveSharedObject)(void *p_userdata, const SharedObject &p_so);
 
+	enum ExportMessageType {
+		EXPORT_MESSAGE_NONE,
+		EXPORT_MESSAGE_INFO,
+		EXPORT_MESSAGE_WARNING,
+		EXPORT_MESSAGE_ERROR,
+	};
+
+	struct ExportMessage {
+		ExportMessageType msg_type;
+		String category;
+		String text;
+	};
+
 private:
 	struct SavedData {
 		uint64_t ofs = 0;
@@ -200,6 +214,8 @@ private:
 		Vector<String> features_pv;
 	};
 
+	Vector<ExportMessage> messages;
+
 	void _export_find_resources(EditorFileSystemDirectory *p_dir, HashSet<String> &p_paths);
 	void _export_find_dependencies(const String &p_path, HashSet<String> &p_paths);
 
@@ -240,6 +256,47 @@ public:
 
 	virtual Ref<EditorExportPreset> create_preset();
 
+	virtual void clear_messages() { messages.clear(); }
+	virtual void add_message(ExportMessageType p_type, const String &p_category, const String &p_message) {
+		ExportMessage msg;
+		msg.category = p_category;
+		msg.text = p_message;
+		msg.msg_type = p_type;
+		messages.push_back(msg);
+		switch (p_type) {
+			case EXPORT_MESSAGE_INFO: {
+				print_line(vformat("%s: %s\n", msg.category, msg.text));
+			} break;
+			case EXPORT_MESSAGE_WARNING: {
+				WARN_PRINT(vformat("%s: %s\n", msg.category, msg.text));
+			} break;
+			case EXPORT_MESSAGE_ERROR: {
+				ERR_PRINT(vformat("%s: %s\n", msg.category, msg.text));
+			} break;
+			default:
+				break;
+		}
+	}
+
+	virtual int get_message_count() const {
+		return messages.size();
+	}
+
+	virtual ExportMessage get_message(int p_index) const {
+		ERR_FAIL_INDEX_V(p_index, messages.size(), ExportMessage());
+		return messages[p_index];
+	}
+
+	virtual ExportMessageType get_worst_message_type() const {
+		ExportMessageType worst_type = EXPORT_MESSAGE_NONE;
+		for (int i = 0; i < messages.size(); i++) {
+			worst_type = MAX(worst_type, messages[i].msg_type);
+		}
+		return worst_type;
+	}
+
+	virtual bool fill_log_messages(RichTextLabel *p_log, Error p_err);
+
 	virtual void get_export_options(List<ExportOption> *r_options) = 0;
 	virtual bool should_update_export_options() { return false; }
 	virtual bool get_export_option_visibility(const String &p_option, const HashMap<StringName, Variant> &p_options) const { return true; }
@@ -459,7 +516,7 @@ public:
 	int get_chmod_flags() const;
 	void set_chmod_flags(int p_flags);
 
-	virtual Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) const {
+	virtual Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) {
 		return Error::OK;
 	}
 };

+ 5 - 12
editor/editor_node.cpp

@@ -968,21 +968,14 @@ void EditorNode::_fs_changed() {
 						ERR_PRINT(vformat("Cannot export project with preset \"%s\" due to configuration errors:\n%s", preset_name, config_error));
 						err = missing_templates ? ERR_FILE_NOT_FOUND : ERR_UNCONFIGURED;
 					} else {
+						platform->clear_messages();
 						err = platform->export_project(export_preset, export_defer.debug, export_path);
 					}
 				}
-				switch (err) {
-					case OK:
-						break;
-					case ERR_FILE_NOT_FOUND:
-						export_error = vformat("Project export failed for preset \"%s\". The export template appears to be missing.", preset_name);
-						break;
-					case ERR_FILE_BAD_PATH:
-						export_error = vformat("Project export failed for preset \"%s\". The target path \"%s\" appears to be invalid.", preset_name, export_path);
-						break;
-					default:
-						export_error = vformat("Project export failed with error code %d for preset \"%s\".", (int)err, preset_name);
-						break;
+				if (err != OK) {
+					export_error = vformat("Project export for preset \"%s\" failed.", preset_name);
+				} else if (platform->get_worst_message_type() >= EditorExportPlatform::EXPORT_MESSAGE_WARNING) {
+					export_error = vformat("Project export for preset \"%s\" completed with errors.", preset_name);
 				}
 			}
 		}

+ 16 - 1
editor/editor_run_native.cpp

@@ -151,7 +151,13 @@ Error EditorRunNative::run_native(int p_idx, int p_platform) {
 		flags |= EditorExportPlatform::DEBUG_FLAG_VIEW_NAVIGATION;
 	}
 
-	return eep->run(preset, p_idx, flags);
+	eep->clear_messages();
+	Error err = eep->run(preset, p_idx, flags);
+	result_dialog_log->clear();
+	if (eep->fill_log_messages(result_dialog_log, err)) {
+		result_dialog->popup_centered_ratio(0.5);
+	}
+	return err;
 }
 
 void EditorRunNative::resume_run_native() {
@@ -167,6 +173,15 @@ bool EditorRunNative::is_deploy_debug_remote_enabled() const {
 }
 
 EditorRunNative::EditorRunNative() {
+	result_dialog = memnew(AcceptDialog);
+	result_dialog->set_title(TTR("Project Run"));
+	result_dialog_log = memnew(RichTextLabel);
+	result_dialog_log->set_custom_minimum_size(Size2(300, 80) * EDSCALE);
+	result_dialog->add_child(result_dialog_log);
+
+	add_child(result_dialog);
+	result_dialog->hide();
+
 	set_process(true);
 	resume_idx = 0;
 	resume_platform = 0;

+ 5 - 0
editor/editor_run_native.h

@@ -32,11 +32,16 @@
 #define EDITOR_RUN_NATIVE_H
 
 #include "scene/gui/box_container.h"
+#include "scene/gui/dialogs.h"
 #include "scene/gui/menu_button.h"
+#include "scene/gui/rich_text_label.h"
 
 class EditorRunNative : public HBoxContainer {
 	GDCLASS(EditorRunNative, HBoxContainer);
 
+	RichTextLabel *result_dialog_log = nullptr;
+	AcceptDialog *result_dialog = nullptr;
+
 	HashMap<int, MenuButton *> menus;
 	bool first = true;
 

+ 23 - 23
editor/project_export.cpp

@@ -928,17 +928,13 @@ void ProjectExportDialog::_export_project_to_path(const String &p_path) {
 	ERR_FAIL_COND(platform.is_null());
 	current->set_export_path(p_path);
 
+	platform->clear_messages();
 	Error err = platform->export_project(current, export_debug->is_pressed(), p_path, 0);
-	if (err != OK && err != ERR_SKIP) {
-		if (err == ERR_FILE_NOT_FOUND) {
-			error_dialog->set_text(vformat(TTR("Failed to export the project for platform '%s'.\nExport templates seem to be missing or invalid."), platform->get_name()));
-		} else { // Assume misconfiguration. FIXME: Improve error handling and preset config validation.
-			error_dialog->set_text(vformat(TTR("Failed to export the project for platform '%s'.\nThis might be due to a configuration issue in the export preset or your export settings."), platform->get_name()));
+	result_dialog_log->clear();
+	if (err != ERR_SKIP) {
+		if (platform->fill_log_messages(result_dialog_log, err)) {
+			result_dialog->popup_centered_ratio(0.5);
 		}
-
-		ERR_PRINT(vformat("Failed to export the project for platform '%s'.", platform->get_name()));
-		error_dialog->show();
-		error_dialog->popup_centered(Size2(300, 80));
 	}
 }
 
@@ -957,6 +953,8 @@ void ProjectExportDialog::_export_all(bool p_debug) {
 	String mode = p_debug ? TTR("Debug") : TTR("Release");
 	EditorProgress ep("exportall", TTR("Exporting All") + " " + mode, EditorExport::get_singleton()->get_export_preset_count(), true);
 
+	bool show_dialog = false;
+	result_dialog_log->clear();
 	for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) {
 		Ref<EditorExportPreset> preset = EditorExport::get_singleton()->get_export_preset(i);
 		ERR_FAIL_COND(preset.is_null());
@@ -965,17 +963,16 @@ void ProjectExportDialog::_export_all(bool p_debug) {
 
 		ep.step(preset->get_name(), i);
 
+		platform->clear_messages();
 		Error err = platform->export_project(preset, p_debug, preset->get_export_path(), 0);
-		if (err != OK && err != ERR_SKIP) {
-			if (err == ERR_FILE_BAD_PATH) {
-				error_dialog->set_text(TTR("The given export path doesn't exist:") + "\n" + preset->get_export_path().get_base_dir());
-			} else {
-				error_dialog->set_text(TTR("Export templates for this platform are missing/corrupted:") + " " + platform->get_name());
-			}
-			error_dialog->show();
-			error_dialog->popup_centered(Size2(300, 80));
-			ERR_PRINT("Failed to export project");
+		if (err == ERR_SKIP) {
+			return;
 		}
+		bool has_messages = platform->fill_log_messages(result_dialog_log, err);
+		show_dialog = show_dialog || has_messages;
+	}
+	if (show_dialog) {
+		result_dialog->popup_centered_ratio(0.5);
 	}
 }
 
@@ -1248,11 +1245,14 @@ ProjectExportDialog::ProjectExportDialog() {
 	export_error2->add_theme_color_override("font_color", EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("error_color"), SNAME("Editor")));
 	export_error2->set_text(String::utf8("•  ") + TTR("Export templates for this platform are missing:") + " ");
 
-	error_dialog = memnew(AcceptDialog);
-	error_dialog->set_title(TTR("Error"));
-	error_dialog->set_text(TTR("Export templates for this platform are missing/corrupted:") + " ");
-	main_vb->add_child(error_dialog);
-	error_dialog->hide();
+	result_dialog = memnew(AcceptDialog);
+	result_dialog->set_title(TTR("Project Export"));
+	result_dialog_log = memnew(RichTextLabel);
+	result_dialog_log->set_custom_minimum_size(Size2(300, 80) * EDSCALE);
+	result_dialog->add_child(result_dialog_log);
+
+	main_vb->add_child(result_dialog);
+	result_dialog->hide();
 
 	LinkButton *download_templates = memnew(LinkButton);
 	download_templates->set_text(TTR("Manage Export Templates"));

+ 2 - 1
editor/project_export.h

@@ -73,7 +73,8 @@ private:
 	Button *button_export = nullptr;
 	bool updating = false;
 
-	AcceptDialog *error_dialog = nullptr;
+	RichTextLabel *result_dialog_log = nullptr;
+	AcceptDialog *result_dialog = nullptr;
 	ConfirmationDialog *delete_confirm = nullptr;
 
 	OptionButton *export_filter = nullptr;

+ 38 - 31
platform/android/export/export_plugin.cpp

@@ -1818,7 +1818,7 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset,
 	String can_export_error;
 	bool can_export_missing_templates;
 	if (!can_export(p_preset, can_export_error, can_export_missing_templates)) {
-		EditorNode::add_io_error(can_export_error);
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Run"), can_export_error);
 		return ERR_UNCONFIGURED;
 	}
 
@@ -1897,7 +1897,7 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset,
 	err = OS::get_singleton()->execute(adb, args, &output, &rv, true);
 	print_verbose(output);
 	if (err || rv != 0) {
-		EditorNode::add_io_error(vformat(TTR("Could not install to device: %s"), output));
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Run"), vformat(TTR("Could not install to device: %s"), output));
 		CLEANUP_AND_RETURN(ERR_CANT_CREATE);
 	}
 
@@ -1975,7 +1975,7 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset,
 	err = OS::get_singleton()->execute(adb, args, &output, &rv, true);
 	print_verbose(output);
 	if (err || rv != 0) {
-		EditorNode::add_io_error(TTR("Could not execute on device."));
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Run"), TTR("Could not execute on device."));
 		CLEANUP_AND_RETURN(ERR_CANT_CREATE);
 	}
 
@@ -2030,7 +2030,7 @@ String EditorExportPlatformAndroid::get_apksigner_path() {
 	da->list_dir_end();
 
 	if (apksigner_path.is_empty()) {
-		EditorNode::get_singleton()->show_warning(TTR("Unable to find the 'apksigner' tool."));
+		print_error("Unable to find the 'apksigner' tool.");
 	}
 
 	return apksigner_path;
@@ -2335,7 +2335,7 @@ Error EditorExportPlatformAndroid::sign_apk(const Ref<EditorExportPreset> &p_pre
 	String apksigner = get_apksigner_path();
 	print_verbose("Starting signing of the " + export_label + " binary using " + apksigner);
 	if (!FileAccess::exists(apksigner)) {
-		EditorNode::add_io_error(vformat(TTR("'apksigner' could not be found.\nPlease check the command is available in the Android SDK build-tools directory.\nThe resulting %s is unsigned."), export_label));
+		add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("'apksigner' could not be found. Please check that the command is available in the Android SDK build-tools directory. The resulting %s is unsigned."), export_label));
 		return OK;
 	}
 
@@ -2368,7 +2368,7 @@ Error EditorExportPlatformAndroid::sign_apk(const Ref<EditorExportPreset> &p_pre
 	}
 
 	if (!FileAccess::exists(keystore)) {
-		EditorNode::add_io_error(TTR("Could not find keystore, unable to export."));
+		add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Could not find keystore, unable to export."));
 		return ERR_FILE_CANT_OPEN;
 	}
 
@@ -2389,10 +2389,14 @@ Error EditorExportPlatformAndroid::sign_apk(const Ref<EditorExportPreset> &p_pre
 	}
 	int retval;
 	output.clear();
-	OS::get_singleton()->execute(apksigner, args, &output, &retval, true);
+	Error err = OS::get_singleton()->execute(apksigner, args, &output, &retval, true);
+	if (err != OK) {
+		add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Could not start apksigner executable."));
+		return err;
+	}
 	print_verbose(output);
 	if (retval) {
-		EditorNode::add_io_error(vformat(TTR("'apksigner' returned with error #%d"), retval));
+		add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("'apksigner' returned with error #%d"), retval));
 		return ERR_CANT_CREATE;
 	}
 
@@ -2409,10 +2413,14 @@ Error EditorExportPlatformAndroid::sign_apk(const Ref<EditorExportPreset> &p_pre
 	}
 
 	output.clear();
-	OS::get_singleton()->execute(apksigner, args, &output, &retval, true);
+	err = OS::get_singleton()->execute(apksigner, args, &output, &retval, true);
+	if (err != OK) {
+		add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Could not start apksigner executable."));
+		return err;
+	}
 	print_verbose(output);
 	if (retval) {
-		EditorNode::add_io_error(vformat(TTR("'apksigner' verification of %s failed."), export_label));
+		add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("'apksigner' verification of %s failed."), export_label));
 		return ERR_CANT_CREATE;
 	}
 
@@ -2520,22 +2528,21 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
 
 	if (export_format == EXPORT_FORMAT_AAB) {
 		if (!p_path.ends_with(".aab")) {
-			EditorNode::get_singleton()->show_warning(TTR("Invalid filename! Android App Bundle requires the *.aab extension."));
+			add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Invalid filename! Android App Bundle requires the *.aab extension."));
 			return ERR_UNCONFIGURED;
 		}
 		if (apk_expansion) {
-			EditorNode::get_singleton()->show_warning(TTR("APK Expansion not compatible with Android App Bundle."));
+			add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("APK Expansion not compatible with Android App Bundle."));
 			return ERR_UNCONFIGURED;
 		}
 	}
 	if (export_format == EXPORT_FORMAT_APK && !p_path.ends_with(".apk")) {
-		EditorNode::get_singleton()->show_warning(
-				TTR("Invalid filename! Android APK requires the *.apk extension."));
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Invalid filename! Android APK requires the *.apk extension."));
 		return ERR_UNCONFIGURED;
 	}
 	if (export_format > EXPORT_FORMAT_AAB || export_format < EXPORT_FORMAT_APK) {
-		EditorNode::add_io_error(TTR("Unsupported export format!\n"));
-		return ERR_UNCONFIGURED; //TODO: is this the right error?
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Unsupported export format!"));
+		return ERR_UNCONFIGURED;
 	}
 
 	if (use_custom_build) {
@@ -2545,13 +2552,13 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
 			print_verbose("Checking build version..");
 			Ref<FileAccess> f = FileAccess::open("res://android/.build_version", FileAccess::READ);
 			if (f.is_null()) {
-				EditorNode::get_singleton()->show_warning(TTR("Trying to build from a custom built template, but no version info for it exists. Please reinstall from the 'Project' menu."));
+				add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Trying to build from a custom built template, but no version info for it exists. Please reinstall from the 'Project' menu."));
 				return ERR_UNCONFIGURED;
 			}
 			String version = f->get_line().strip_edges();
 			print_verbose("- build version: " + version);
 			if (version != VERSION_FULL_CONFIG) {
-				EditorNode::get_singleton()->show_warning(vformat(TTR("Android build version mismatch:\n   Template installed: %s\n   Godot Version: %s\nPlease reinstall Android build template from 'Project' menu."), version, VERSION_FULL_CONFIG));
+				add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Android build version mismatch: Template installed: %s, Godot version: %s. Please reinstall Android build template from 'Project' menu."), version, VERSION_FULL_CONFIG));
 				return ERR_UNCONFIGURED;
 			}
 		}
@@ -2564,7 +2571,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
 		String project_name = get_project_name(p_preset->get("package/name"));
 		err = _create_project_name_strings_files(p_preset, project_name); //project name localization.
 		if (err != OK) {
-			EditorNode::add_io_error(TTR("Unable to overwrite res://android/build/res/*.xml files with project name"));
+			add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Unable to overwrite res://android/build/res/*.xml files with project name."));
 		}
 		// Copies the project icon files into the appropriate Gradle project directory.
 		_copy_icons_to_gradle_project(p_preset, processed_splash_config_xml, splash_image, splash_bg_color_image, main_image, foreground, background);
@@ -2581,7 +2588,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
 			user_data.debug = p_debug;
 			err = export_project_files(p_preset, p_debug, rename_and_store_file_in_gradle_project, &user_data, copy_gradle_so);
 			if (err != OK) {
-				EditorNode::add_io_error(TTR("Could not export project files to gradle project\n"));
+				add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Could not export project files to gradle project."));
 				return err;
 			}
 			if (user_data.libs.size() > 0) {
@@ -2593,7 +2600,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
 			print_verbose("Saving apk expansion file..");
 			err = save_apk_expansion_file(p_preset, p_debug, p_path);
 			if (err != OK) {
-				EditorNode::add_io_error(TTR("Could not write expansion package file!"));
+				add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Could not write expansion package file!"));
 				return err;
 			}
 		}
@@ -2678,7 +2685,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
 					debug_keystore = OS::get_singleton()->get_resource_dir().plus_file(debug_keystore).simplify_path();
 				}
 				if (!FileAccess::exists(debug_keystore)) {
-					EditorNode::add_io_error(TTR("Could not find keystore, unable to export."));
+					add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Could not find keystore, unable to export."));
 					return ERR_FILE_CANT_OPEN;
 				}
 
@@ -2694,7 +2701,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
 					release_keystore = OS::get_singleton()->get_resource_dir().plus_file(release_keystore).simplify_path();
 				}
 				if (!FileAccess::exists(release_keystore)) {
-					EditorNode::add_io_error(TTR("Could not find keystore, unable to export."));
+					add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Could not find keystore, unable to export."));
 					return ERR_FILE_CANT_OPEN;
 				}
 
@@ -2706,7 +2713,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
 
 		int result = EditorNode::get_singleton()->execute_and_show_output(TTR("Building Android Project (gradle)"), build_command, cmdline);
 		if (result != 0) {
-			EditorNode::get_singleton()->show_warning(TTR("Building of Android project failed, check output for the error.\nAlternatively visit docs.godotengine.org for Android build documentation."));
+			add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Building of Android project failed, check output for the error. Alternatively visit docs.godotengine.org for Android build documentation."));
 			return ERR_CANT_CREATE;
 		}
 
@@ -2736,7 +2743,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
 		print_verbose("Copying Android binary using gradle command: " + String("\n") + build_command + " " + join_list(copy_args, String(" ")));
 		int copy_result = EditorNode::get_singleton()->execute_and_show_output(TTR("Moving output"), build_command, copy_args);
 		if (copy_result != 0) {
-			EditorNode::get_singleton()->show_warning(TTR("Unable to copy and rename export file, check gradle project directory for outputs."));
+			add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Unable to copy and rename export file, check gradle project directory for outputs."));
 			return ERR_CANT_CREATE;
 		}
 
@@ -2758,7 +2765,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
 			src_apk = find_export_template("android_release.apk");
 		}
 		if (src_apk.is_empty()) {
-			EditorNode::add_io_error(vformat(TTR("Package not found: %s"), src_apk));
+			add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Package not found: \"%s\"."), src_apk));
 			return ERR_FILE_NOT_FOUND;
 		}
 	}
@@ -2776,7 +2783,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
 
 	unzFile pkg = unzOpen2(src_apk.utf8().get_data(), &io);
 	if (!pkg) {
-		EditorNode::add_io_error(vformat(TTR("Could not find template APK to export:\n%s"), src_apk));
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not find template APK to export: \"%s\"."), src_apk));
 		return ERR_FILE_NOT_FOUND;
 	}
 
@@ -2907,7 +2914,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
 
 	if (!invalid_abis.is_empty()) {
 		String unsupported_arch = String(", ").join(invalid_abis);
-		EditorNode::add_io_error(vformat(TTR("Missing libraries in the export template for the selected architectures: %s.\nPlease build a template with all required libraries, or uncheck the missing architectures in the export preset."), unsupported_arch));
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Missing libraries in the export template for the selected architectures: %s. Please build a template with all required libraries, or uncheck the missing architectures in the export preset."), unsupported_arch));
 		CLEANUP_AND_RETURN(ERR_FILE_NOT_FOUND);
 	}
 
@@ -2925,7 +2932,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
 		if (apk_expansion) {
 			err = save_apk_expansion_file(p_preset, p_debug, p_path);
 			if (err != OK) {
-				EditorNode::add_io_error(TTR("Could not write expansion package file!"));
+				add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Could not write expansion package file!"));
 				return err;
 			}
 		} else {
@@ -2938,7 +2945,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
 
 	if (err != OK) {
 		unzClose(pkg);
-		EditorNode::add_io_error(TTR("Could not export project files"));
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not export project files.")));
 		CLEANUP_AND_RETURN(ERR_SKIP);
 	}
 
@@ -2974,7 +2981,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
 
 	unzFile tmp_unaligned = unzOpen2(tmp_unaligned_path.utf8().get_data(), &io);
 	if (!tmp_unaligned) {
-		EditorNode::add_io_error(TTR("Could not unzip temporary unaligned APK."));
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not unzip temporary unaligned APK.")));
 		CLEANUP_AND_RETURN(ERR_FILE_NOT_FOUND);
 	}
 

+ 7 - 3
platform/iphone/export/export_plugin.cpp

@@ -817,7 +817,11 @@ Error EditorExportPlatformIOS::_codesign(String p_file, void *p_userdata) {
 		codesign_args.push_back("-s");
 		codesign_args.push_back(sign_id);
 		codesign_args.push_back(p_file);
-		return OS::get_singleton()->execute("codesign", codesign_args);
+		String str;
+		Error err = OS::get_singleton()->execute("codesign", codesign_args, &str, nullptr, true);
+		print_verbose("codesign (" + p_file + "):\n" + str);
+
+		return err;
 	}
 	return OK;
 }
@@ -1392,7 +1396,7 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
 		String err;
 		src_pkg_name = find_export_template("iphone.zip", &err);
 		if (src_pkg_name.is_empty()) {
-			EditorNode::add_io_error(err);
+			add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), TTR("Export template not found."));
 			return ERR_FILE_NOT_FOUND;
 		}
 	}
@@ -1487,7 +1491,7 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
 	zlib_filefunc_def io = zipio_create_io(&io_fa);
 	unzFile src_pkg_zip = unzOpen2(src_pkg_name.utf8().get_data(), &io);
 	if (!src_pkg_zip) {
-		EditorNode::add_io_error("Could not open export template (not a zip file?):\n" + src_pkg_name);
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), TTR("Could not open export template (not a zip file?): \"%s\".", src_pkg_name));
 		return ERR_CANT_OPEN;
 	}
 

+ 23 - 17
platform/javascript/export/export_plugin.cpp

@@ -38,12 +38,12 @@ Error EditorExportPlatformJavaScript::_extract_template(const String &p_template
 	unzFile pkg = unzOpen2(p_template.utf8().get_data(), &io);
 
 	if (!pkg) {
-		EditorNode::get_singleton()->show_warning(TTR("Could not open template for export:") + "\n" + p_template);
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), vformat(TTR("Could not open template for export: \"%s\"."), p_template));
 		return ERR_FILE_NOT_FOUND;
 	}
 
 	if (unzGoToFirstFile(pkg) != UNZ_OK) {
-		EditorNode::get_singleton()->show_warning(TTR("Invalid export template:") + "\n" + p_template);
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), vformat(TTR("Invalid export template: \"%s\"."), p_template));
 		unzClose(pkg);
 		return ERR_FILE_CORRUPT;
 	}
@@ -56,6 +56,11 @@ Error EditorExportPlatformJavaScript::_extract_template(const String &p_template
 
 		String file = String::utf8(fname);
 
+		// Skip folders.
+		if (file.ends_with("/")) {
+			continue;
+		}
+
 		// Skip service worker and offline page if not exporting pwa.
 		if (!pwa && (file == "godot.service.worker.js" || file == "godot.offline.html")) {
 			continue;
@@ -72,7 +77,7 @@ Error EditorExportPlatformJavaScript::_extract_template(const String &p_template
 		String dst = p_dir.plus_file(file.replace("godot", p_name));
 		Ref<FileAccess> f = FileAccess::open(dst, FileAccess::WRITE);
 		if (f.is_null()) {
-			EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + dst);
+			add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), vformat(TTR("Could not write file: \"%s\"."), dst));
 			unzClose(pkg);
 			return ERR_FILE_CANT_WRITE;
 		}
@@ -86,7 +91,7 @@ Error EditorExportPlatformJavaScript::_extract_template(const String &p_template
 Error EditorExportPlatformJavaScript::_write_or_error(const uint8_t *p_content, int p_size, String p_path) {
 	Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::WRITE);
 	if (f.is_null()) {
-		EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + p_path);
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not write file: \"%s\"."), p_path));
 		return ERR_FILE_CANT_WRITE;
 	}
 	f->store_buffer(p_content, p_size);
@@ -163,7 +168,7 @@ Error EditorExportPlatformJavaScript::_add_manifest_icon(const String &p_path, c
 		icon.instantiate();
 		const Error err = ImageLoader::load_image(p_icon, icon);
 		if (err != OK) {
-			EditorNode::get_singleton()->show_warning(TTR("Could not read file:") + "\n" + p_icon);
+			add_message(EXPORT_MESSAGE_ERROR, TTR("Icon Creation"), vformat(TTR("Could not read file: \"%s\"."), p_icon));
 			return err;
 		}
 		if (icon->get_width() != p_size || icon->get_height() != p_size) {
@@ -175,7 +180,7 @@ Error EditorExportPlatformJavaScript::_add_manifest_icon(const String &p_path, c
 	}
 	const Error err = icon->save_png(icon_dest);
 	if (err != OK) {
-		EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + icon_dest);
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Icon Creation"), vformat(TTR("Could not write file: \"%s\"."), icon_dest));
 		return err;
 	}
 	Dictionary icon_dict;
@@ -233,7 +238,7 @@ Error EditorExportPlatformJavaScript::_build_pwa(const Ref<EditorExportPreset> &
 	{
 		Ref<FileAccess> f = FileAccess::open(sw_path, FileAccess::READ);
 		if (f.is_null()) {
-			EditorNode::get_singleton()->show_warning(TTR("Could not read file:") + "\n" + sw_path);
+			add_message(EXPORT_MESSAGE_ERROR, TTR("PWA"), vformat(TTR("Could not read file: \"%s\"."), sw_path));
 			return ERR_FILE_CANT_READ;
 		}
 		sw.resize(f->get_length());
@@ -252,7 +257,7 @@ Error EditorExportPlatformJavaScript::_build_pwa(const Ref<EditorExportPreset> &
 		const String offline_dest = dir.plus_file(name + ".offline.html");
 		err = da->copy(ProjectSettings::get_singleton()->globalize_path(offline_page), offline_dest);
 		if (err != OK) {
-			EditorNode::get_singleton()->show_warning(TTR("Could not read file:") + "\n" + offline_dest);
+			add_message(EXPORT_MESSAGE_ERROR, TTR("PWA"), vformat(TTR("Could not read file: \"%s\"."), offline_dest));
 			return err;
 		}
 	}
@@ -438,7 +443,7 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
 	}
 
 	if (!template_path.is_empty() && !FileAccess::exists(template_path)) {
-		EditorNode::get_singleton()->show_warning(TTR("Template file not found:") + "\n" + template_path);
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), vformat(TTR("Template file not found: \"%s\"."), template_path));
 		return ERR_FILE_NOT_FOUND;
 	}
 
@@ -447,7 +452,7 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
 	String pck_path = base_path + ".pck";
 	Error error = save_pack(p_preset, p_debug, pck_path, &shared_objects);
 	if (error != OK) {
-		EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + pck_path);
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not write file: \"%s\"."), pck_path));
 		return error;
 	}
 
@@ -457,7 +462,7 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
 			String dst = base_dir.plus_file(shared_objects[i].path.get_file());
 			error = da->copy(shared_objects[i].path, dst);
 			if (error != OK) {
-				EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + shared_objects[i].path.get_file());
+				add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not write file: \"%s\"."), shared_objects[i].path.get_file()));
 				return error;
 			}
 		}
@@ -485,7 +490,7 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
 	Vector<uint8_t> html;
 	f = FileAccess::open(html_path, FileAccess::READ);
 	if (f.is_null()) {
-		EditorNode::get_singleton()->show_warning(TTR("Could not read HTML shell:") + "\n" + html_path);
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not read HTML shell: \"%s\"."), html_path));
 		return ERR_FILE_CANT_READ;
 	}
 	html.resize(f->get_length());
@@ -503,7 +508,7 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
 	Ref<Image> splash = _get_project_splash();
 	const String splash_png_path = base_path + ".png";
 	if (splash->save_png(splash_png_path) != OK) {
-		EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + splash_png_path);
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not write file: \"%s\"."), splash_png_path));
 		return ERR_FILE_CANT_WRITE;
 	}
 
@@ -513,13 +518,13 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
 		Ref<Image> favicon = _get_project_icon();
 		const String favicon_png_path = base_path + ".icon.png";
 		if (favicon->save_png(favicon_png_path) != OK) {
-			EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + favicon_png_path);
+			add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not write file: \"%s\"."), favicon_png_path));
 			return ERR_FILE_CANT_WRITE;
 		}
 		favicon->resize(180, 180);
 		const String apple_icon_png_path = base_path + ".apple-touch-icon.png";
 		if (favicon->save_png(apple_icon_png_path) != OK) {
-			EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + apple_icon_png_path);
+			add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not write file: \"%s\"."), apple_icon_png_path));
 			return ERR_FILE_CANT_WRITE;
 		}
 	}
@@ -579,10 +584,11 @@ Error EditorExportPlatformJavaScript::run(const Ref<EditorExportPreset> &p_prese
 	if (!da->dir_exists(dest)) {
 		Error err = da->make_dir_recursive(dest);
 		if (err != OK) {
-			EditorNode::get_singleton()->show_warning(TTR("Could not create HTTP server directory:") + "\n" + dest);
+			add_message(EXPORT_MESSAGE_ERROR, TTR("Run"), vformat(TTR("Could not create HTTP server directory: %s."), dest));
 			return err;
 		}
 	}
+
 	const String basepath = dest.plus_file("tmp_js_export");
 	Error err = export_project(p_preset, true, basepath + ".html", p_debug_flags);
 	if (err != OK) {
@@ -625,7 +631,7 @@ Error EditorExportPlatformJavaScript::run(const Ref<EditorExportPreset> &p_prese
 		err = server->listen(bind_port, bind_ip, use_ssl, ssl_key, ssl_cert);
 	}
 	if (err != OK) {
-		EditorNode::get_singleton()->show_warning(TTR("Error starting HTTP server:") + "\n" + itos(err));
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Run"), vformat(TTR("Error starting HTTP server: %d."), err));
 		return err;
 	}
 

+ 16 - 4
platform/linuxbsd/export/export_plugin.cpp

@@ -35,7 +35,10 @@
 
 Error EditorExportPlatformLinuxBSD::_export_debug_script(const Ref<EditorExportPreset> &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path) {
 	Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::WRITE);
-	ERR_FAIL_COND_V(f.is_null(), ERR_CANT_CREATE);
+	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;
+	}
 
 	f->store_line("#!/bin/sh");
 	f->store_line("echo -ne '\\033c\\033]0;" + p_app_name + "\\a'");
@@ -67,6 +70,9 @@ Error EditorExportPlatformLinuxBSD::export_project(const Ref<EditorExportPreset>
 			String scr_path = p_path.get_basename() + ".sh";
 			err = _export_debug_script(p_preset, app_name, p_path.get_file(), scr_path);
 			FileAccess::set_unix_permissions(scr_path, 0755);
+			if (err != OK) {
+				add_message(EXPORT_MESSAGE_ERROR, TTR("Debug Script Export"), TTR("Could not create console script."));
+			}
 		}
 	}
 
@@ -98,11 +104,12 @@ List<String> EditorExportPlatformLinuxBSD::get_binary_extensions(const Ref<Edito
 	return list;
 }
 
-Error EditorExportPlatformLinuxBSD::fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) const {
+Error EditorExportPlatformLinuxBSD::fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) {
 	// Patch the header of the "pck" section in the ELF file so that it corresponds to the embedded data
 
 	Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ_WRITE);
 	if (f.is_null()) {
+		add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding"), vformat(TTR("Failed to open executable file \"%s\"."), p_path));
 		return ERR_CANT_OPEN;
 	}
 
@@ -110,6 +117,7 @@ Error EditorExportPlatformLinuxBSD::fixup_embedded_pck(const String &p_path, int
 	{
 		uint32_t magic = f->get_32();
 		if (magic != 0x464c457f) { // 0x7F + "ELF"
+			add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding"), TTR("Executable file header corrupted."));
 			return ERR_FILE_CORRUPT;
 		}
 	}
@@ -119,7 +127,7 @@ Error EditorExportPlatformLinuxBSD::fixup_embedded_pck(const String &p_path, int
 	int bits = f->get_8() * 32;
 
 	if (bits == 32 && p_embedded_size >= 0x100000000) {
-		ERR_FAIL_V_MSG(ERR_INVALID_DATA, "32-bit executables cannot have embedded data >= 4 GiB.");
+		add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding"), TTR("32-bit executables cannot have embedded data >= 4 GiB."));
 	}
 
 	// Get info about the section header table
@@ -196,5 +204,9 @@ Error EditorExportPlatformLinuxBSD::fixup_embedded_pck(const String &p_path, int
 
 	memfree(strings);
 
-	return found ? OK : ERR_FILE_CORRUPT;
+	if (!found) {
+		add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding"), TTR("Executable \"pck\" section not found."));
+		return ERR_FILE_CORRUPT;
+	}
+	return OK;
 }

+ 1 - 1
platform/linuxbsd/export/export_plugin.h

@@ -46,7 +46,7 @@ public:
 	virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override;
 	virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
 	virtual String get_template_file_name(const String &p_target, const String &p_arch) const override;
-	virtual Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) const override;
+	virtual Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) override;
 };
 
 #endif

+ 81 - 32
platform/osx/export/export_plugin.cpp

@@ -261,7 +261,8 @@ void EditorExportPlatformOSX::_make_icon(const Ref<Image> &p_icon, Vector<uint8_
 				if (f.is_null()) {
 					// Clean up generated file.
 					DirAccess::remove_file_or_error(path);
-					ERR_FAIL();
+					add_message(EXPORT_MESSAGE_ERROR, TTR("Icon Creation"), vformat(TTR("Could not open icon file \"%s\"."), path));
+					return;
 				}
 
 				int ofs = data.size();
@@ -441,18 +442,25 @@ Error EditorExportPlatformOSX::_notarize(const Ref<EditorExportPreset> &p_preset
 
 	String str;
 	Error err = OS::get_singleton()->execute("xcrun", args, &str, nullptr, true);
-	ERR_FAIL_COND_V(err != OK, err);
+	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);
-	if (str.find("RequestUUID") == -1) {
-		EditorNode::add_io_error("altool: " + str);
+	int rq_offset = str.find("RequestUUID");
+	if (rq_offset == -1) {
+		add_message(EXPORT_MESSAGE_WARNING, TTR("Notarization"), TTR("Notarization failed."));
 		return FAILED;
 	} else {
-		print_line(TTR("Note: The notarization process generally takes less than an hour. When the process is completed, you'll receive an email."));
-		print_line("      " + TTR("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>\"");
-		print_line("      " + TTR("Run the following command to staple the notarization ticket to the exported application (optional):"));
-		print_line("          \"xcrun stapler staple <app path>\"");
+		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>\"");
 	}
 
 #endif
@@ -470,21 +478,21 @@ Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_prese
 
 #ifdef OSX_ENABLED
 		if (p_preset->get("codesign/timestamp") && p_warn) {
-			WARN_PRINT("Timestamping is not compatible with ad-hoc signature, and was disabled!");
+			add_message(EXPORT_MESSAGE_INFO, TTR("Code Signing"), TTR("Timestamping is not compatible with ad-hoc signature, and was disabled!"));
 		}
 		if (p_preset->get("codesign/hardened_runtime") && p_warn) {
-			WARN_PRINT("Hardened Runtime is not compatible with ad-hoc signature, and was disabled!");
+			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) {
-			EditorNode::add_io_error("Built-in CodeSign: " + error_msg);
+			add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("Built-in CodeSign failed with error \"%s\"."), error_msg));
 			return FAILED;
 		}
 #else
-		ERR_FAIL_V_MSG(FAILED, "Built-in CodeSign require regex module");
+		add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Built-in CodeSign require regex module."));
 #endif
 		return OK;
 	} else {
@@ -493,7 +501,7 @@ Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_prese
 		if (p_preset->get("codesign/timestamp")) {
 			if (ad_hoc) {
 				if (p_warn) {
-					WARN_PRINT("Timestamping is not compatible with ad-hoc signature, and was disabled!");
+					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");
@@ -502,7 +510,7 @@ Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_prese
 		if (p_preset->get("codesign/hardened_runtime")) {
 			if (ad_hoc) {
 				if (p_warn) {
-					WARN_PRINT("Hardened Runtime is not compatible with ad-hoc signature, and was disabled!");
+					add_message(EXPORT_MESSAGE_INFO, TTR("Code Signing"), TTR("Hardened Runtime is not compatible with ad-hoc signature, and was disabled!"));
 				}
 			} else {
 				args.push_back("--options");
@@ -540,15 +548,18 @@ Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_prese
 
 		String str;
 		Error err = OS::get_singleton()->execute("codesign", args, &str, nullptr, true);
-		ERR_FAIL_COND_V(err != OK, err);
+		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);
 		if (str.find("no identity found") != -1) {
-			EditorNode::add_io_error("CodeSign: " + TTR("No identity found."));
+			add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("No identity found."));
 			return FAILED;
 		}
 		if ((str.find("unrecognized blob type") != -1) || (str.find("cannot read entitlement data") != -1)) {
-			EditorNode::add_io_error("CodeSign: " + TTR("Invalid entitlements file."));
+			add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Invalid entitlements file."));
 			return FAILED;
 		}
 		return OK;
@@ -593,7 +604,7 @@ Error EditorExportPlatformOSX::_code_sign_directory(const Ref<EditorExportPreset
 				return code_sign_error;
 			}
 		} else if (p_should_error_on_non_code) {
-			ERR_PRINT(vformat("Cannot sign file %s.", current_file));
+			add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("Cannot sign file %s."), current_file));
 			return Error::FAILED;
 		}
 
@@ -611,7 +622,7 @@ Error EditorExportPlatformOSX::_copy_and_sign_files(Ref<DirAccess> &dir_access,
 	Error err{ OK };
 	if (dir_access->dir_exists(p_src_path)) {
 #ifndef UNIX_ENABLED
-		WARN_PRINT("Relative symlinks are not supported, exported " + p_src_path.get_file() + " might be broken!");
+		add_message(EXPORT_MESSAGE_INFO, TTR("Export"), vformat(TTR("Relative symlinks are not supported, exported \"%s\" might be broken!"), p_src_path.get_file()));
 #endif
 		print_verbose("export framework: " + p_src_path + " -> " + p_in_app_path);
 		err = dir_access->make_dir_recursive(p_in_app_path);
@@ -668,14 +679,17 @@ Error EditorExportPlatformOSX::_create_dmg(const String &p_dmg_path, const Strin
 
 	String str;
 	Error err = OS::get_singleton()->execute("hdiutil", args, &str, nullptr, true);
-	ERR_FAIL_COND_V(err != OK, err);
+	if (err != OK) {
+		add_message(EXPORT_MESSAGE_ERROR, TTR("DMG Creation"), TTR("Could not start hdiutil executable."));
+		return err;
+	}
 
 	print_verbose("hdiutil returned: " + str);
 	if (str.find("create failed") != -1) {
 		if (str.find("File exists") != -1) {
-			EditorNode::add_io_error("hdiutil: " + TTR("DMG creation failed, file already exists."));
+			add_message(EXPORT_MESSAGE_ERROR, TTR("DMG Creation"), TTR("`hdiutil create` failed - file exists."));
 		} else {
-			EditorNode::add_io_error("hdiutil: " + TTR("DMG create failed."));
+			add_message(EXPORT_MESSAGE_ERROR, TTR("DMG Creation"), TTR("`hdiutil create` failed."));
 		}
 		return FAILED;
 	}
@@ -685,7 +699,10 @@ Error EditorExportPlatformOSX::_create_dmg(const String &p_dmg_path, const Strin
 
 Error EditorExportPlatformOSX::_export_debug_script(const Ref<EditorExportPreset> &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path) {
 	Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::WRITE);
-	ERR_FAIL_COND_V(f.is_null(), ERR_CANT_CREATE);
+	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;
+	}
 
 	f->store_line("#!/bin/sh");
 	f->store_line("echo -ne '\\033c\\033]0;" + p_app_name + "\\a'");
@@ -713,12 +730,13 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
 		String err;
 		src_pkg_name = find_export_template("osx.zip", &err);
 		if (src_pkg_name.is_empty()) {
-			EditorNode::add_io_error(err);
+			add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), TTR("Export template not found."));
 			return ERR_FILE_NOT_FOUND;
 		}
 	}
 
 	if (!DirAccess::exists(p_path.get_base_dir())) {
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), TTR("The given export path doesn't exist."));
 		return ERR_FILE_BAD_PATH;
 	}
 
@@ -731,7 +749,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
 
 	unzFile src_pkg_zip = unzOpen2(src_pkg_name.utf8().get_data(), &io);
 	if (!src_pkg_zip) {
-		EditorNode::add_io_error(TTR("Could not find template app to export:") + "\n" + src_pkg_name);
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), vformat(TTR("Could not find template app to export: \"%s\"."), src_pkg_name));
 		return ERR_FILE_NOT_FOUND;
 	}
 
@@ -756,7 +774,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
 	} else if (p_path.ends_with("app")) {
 		export_format = "app";
 	} else {
-		EditorNode::add_io_error("Invalid export format");
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Invalid export format."));
 		return ERR_CANT_CREATE;
 	}
 
@@ -781,13 +799,16 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
 
 	Ref<DirAccess> tmp_app_dir = DirAccess::create_for_path(tmp_base_path_name);
 	if (tmp_app_dir.is_null()) {
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create directory: \"%s\"."), tmp_base_path_name));
 		err = ERR_CANT_CREATE;
 	}
 
 	DirAccess::remove_file_or_error(scr_path);
 	if (DirAccess::exists(tmp_app_path_name)) {
+		String old_dir = tmp_app_dir->get_current_dir();
 		if (tmp_app_dir->change_dir(tmp_app_path_name) == OK) {
 			tmp_app_dir->erase_contents_recursive();
+			tmp_app_dir->change_dir(old_dir);
 		}
 	}
 
@@ -797,21 +818,33 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
 	if (err == OK) {
 		print_verbose("Creating " + tmp_app_path_name + "/Contents/MacOS");
 		err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/MacOS");
+		if (err != OK) {
+			add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create directory \"%s\"."), tmp_app_path_name + "/Contents/MacOS"));
+		}
 	}
 
 	if (err == OK) {
 		print_verbose("Creating " + tmp_app_path_name + "/Contents/Frameworks");
 		err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/Frameworks");
+		if (err != OK) {
+			add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create directory \"%s\"."), tmp_app_path_name + "/Contents/Frameworks"));
+		}
 	}
 
 	if ((err == OK) && 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");
+		if (err != OK) {
+			add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create directory \"%s\"."), tmp_app_path_name + "/Contents/Helpers"));
+		}
 	}
 
 	if (err == OK) {
 		print_verbose("Creating " + tmp_app_path_name + "/Contents/Resources");
 		err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/Resources");
+		if (err != OK) {
+			add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create directory \"%s\"."), tmp_app_path_name + "/Contents/Resources"));
+		}
 	}
 
 	Dictionary appnames = ProjectSettings::get_singleton()->get("application/config/name_localized");
@@ -955,16 +988,22 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
 
 		if (((info.external_fa >> 16L) & 0120000) == 0120000) {
 #ifndef UNIX_ENABLED
-			WARN_PRINT(vformat("Relative symlinks are not supported on this OS, the exported project might be broken!"));
+			add_message(EXPORT_MESSAGE_INFO, TTR("Export"), TTR("Relative symlinks are not supported on this OS, the exported project might be broken!"));
 #endif
 			// Handle symlinks in the archive.
 			file = tmp_app_path_name.plus_file(file);
 			if (err == OK) {
 				err = tmp_app_dir->make_dir_recursive(file.get_base_dir());
+				if (err != OK) {
+					add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create directory \"%s\"."), file.get_base_dir()));
+				}
 			}
 			if (err == OK) {
 				String lnk_data = String::utf8((const char *)data.ptr(), data.size());
 				err = tmp_app_dir->create_link(lnk_data, file);
+				if (err != OK) {
+					add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not created symlink \"%s\" -> \"%s\"."), lnk_data, file));
+				}
 				print_verbose(vformat("ADDING SYMLINK %s => %s\n", file, lnk_data));
 			}
 
@@ -1039,6 +1078,9 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
 			file = tmp_app_path_name.plus_file(file);
 			if (err == OK) {
 				err = tmp_app_dir->make_dir_recursive(file.get_base_dir());
+				if (err != OK) {
+					add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create directory \"%s\"."), file.get_base_dir()));
+				}
 			}
 			if (err == OK) {
 				Ref<FileAccess> f = FileAccess::open(file, FileAccess::WRITE);
@@ -1049,6 +1091,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
 						FileAccess::set_unix_permissions(file, 0755);
 					}
 				} else {
+					add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not open \"%s\"."), file));
 					err = ERR_CANT_CREATE;
 				}
 			}
@@ -1061,7 +1104,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
 	unzClose(src_pkg_zip);
 
 	if (!found_binary) {
-		ERR_PRINT(vformat("Requested template binary '%s' not found. It might be missing from your template archive.", binary_to_use));
+		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));
 		err = ERR_FILE_NOT_FOUND;
 	}
 
@@ -1071,6 +1114,9 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
 		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);
+			if (err != OK) {
+				add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Could not create console script."));
+			}
 		}
 	}
 
@@ -1214,6 +1260,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
 				ent_f->store_line("</dict>");
 				ent_f->store_line("</plist>");
 			} else {
+				add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Could not create entitlements file."));
 				err = ERR_CANT_CREATE;
 			}
 
@@ -1231,6 +1278,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
 					ent_f->store_line("</dict>");
 					ent_f->store_line("</plist>");
 				} else {
+					add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Could not create helper entitlements file."));
 					err = ERR_CANT_CREATE;
 				}
 			}
@@ -1258,7 +1306,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
 			ad_hoc = (sign_identity == "" || sign_identity == "-");
 			bool lib_validation = p_preset->get("codesign/entitlements/disable_library_validation");
 			if ((!dylibs_found.is_empty() || !shared_objects.is_empty()) && sign_enabled && ad_hoc && !lib_validation) {
-				ERR_PRINT("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."));
 				err = ERR_CANT_CREATE;
 			}
 		}
@@ -1342,7 +1390,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
 		bool noto_enabled = p_preset->get("notarization/enable");
 		if (err == OK && noto_enabled) {
 			if (export_format == "app") {
-				WARN_PRINT("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."));
 			} else {
 				if (ep.step(TTR("Sending archive for notarization"), 4)) {
 					return ERR_SKIP;
@@ -1467,7 +1515,8 @@ void EditorExportPlatformOSX::_zip_folder_recursive(zipFile &p_zip, const String
 
 			Ref<FileAccess> fa = FileAccess::open(dir.plus_file(f), FileAccess::READ);
 			if (fa.is_null()) {
-				ERR_FAIL_MSG(vformat("Can't open file to read from path \"%s\".", dir.plus_file(f)));
+				add_message(EXPORT_MESSAGE_ERROR, TTR("ZIP Creation"), vformat(TTR("Could not open file to read from path \"%s\"."), dir.plus_file(f)));
+				return;
 			}
 			const int bufsize = 16384;
 			uint8_t buf[bufsize];

+ 50 - 22
platform/windows/export/export_plugin.cpp

@@ -43,7 +43,10 @@ Error EditorExportPlatformWindows::sign_shared_object(const Ref<EditorExportPres
 
 Error EditorExportPlatformWindows::_export_debug_script(const Ref<EditorExportPreset> &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path) {
 	Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::WRITE);
-	ERR_FAIL_COND_V(f.is_null(), ERR_CANT_CREATE);
+	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;
+	}
 
 	f->store_line("@echo off");
 	f->store_line("title \"" + p_app_name + "\"");
@@ -55,10 +58,9 @@ Error EditorExportPlatformWindows::_export_debug_script(const Ref<EditorExportPr
 
 Error EditorExportPlatformWindows::modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
 	if (p_preset->get("application/modify_resources")) {
-		return _rcedit_add_data(p_preset, p_path);
-	} else {
-		return OK;
+		_rcedit_add_data(p_preset, p_path);
 	}
+	return OK;
 }
 
 Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
@@ -68,12 +70,15 @@ Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset>
 	}
 	Error err = EditorExportPlatformPC::export_project(p_preset, p_debug, pck_path, p_flags);
 	if (p_preset->get("codesign/enable") && err == OK) {
-		err = _code_sign(p_preset, pck_path);
+		_code_sign(p_preset, pck_path);
 	}
 
 	if (p_preset->get("binary_format/embed_pck") && err == OK) {
 		Ref<DirAccess> tmp_dir = DirAccess::create_for_path(p_path.get_base_dir());
 		err = tmp_dir->rename(pck_path, p_path);
+		if (err != OK) {
+			add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding"), vformat(TTR("Failed to rename temporary file \"%s\"."), pck_path));
+		}
 	}
 
 	String app_name;
@@ -89,7 +94,9 @@ Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset>
 		int con_scr = p_preset->get("debug/export_console_script");
 		if ((con_scr == 1 && p_debug) || (con_scr == 2)) {
 			String scr_path = p_path.get_basename() + ".cmd";
-			err = _export_debug_script(p_preset, app_name, p_path.get_file(), scr_path);
+			if (_export_debug_script(p_preset, app_name, p_path.get_file(), scr_path) != OK) {
+				add_message(EXPORT_MESSAGE_ERROR, TTR("Debug Script Export"), TTR("Could not create console script."));
+			}
 		}
 	}
 
@@ -142,7 +149,7 @@ Error EditorExportPlatformWindows::_rcedit_add_data(const Ref<EditorExportPreset
 	String rcedit_path = EditorSettings::get_singleton()->get("export/windows/rcedit");
 
 	if (rcedit_path != String() && !FileAccess::exists(rcedit_path)) {
-		ERR_PRINT("Could not find rcedit executable at " + rcedit_path + ", aborting.");
+		add_message(EXPORT_MESSAGE_WARNING, TTR("Resources Modification"), vformat(TTR("Could not find rcedit executable at \"%s\"."), rcedit_path));
 		return ERR_FILE_NOT_FOUND;
 	}
 
@@ -155,7 +162,7 @@ Error EditorExportPlatformWindows::_rcedit_add_data(const Ref<EditorExportPreset
 	String wine_path = EditorSettings::get_singleton()->get("export/windows/wine");
 
 	if (!wine_path.is_empty() && !FileAccess::exists(wine_path)) {
-		ERR_PRINT("Could not find wine executable at " + wine_path + ", aborting.");
+		add_message(EXPORT_MESSAGE_WARNING, TTR("Resources Modification"), vformat(TTR("Could not find wine executable at \"%s\"."), wine_path));
 		return ERR_FILE_NOT_FOUND;
 	}
 
@@ -222,10 +229,14 @@ Error EditorExportPlatformWindows::_rcedit_add_data(const Ref<EditorExportPreset
 
 	String str;
 	Error err = OS::get_singleton()->execute(rcedit_path, args, &str, nullptr, true);
-	ERR_FAIL_COND_V_MSG(err != OK, err, "Could not start rcedit executable, configure rcedit path in the Editor Settings (Export > Windows > Rcedit).");
+	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)."));
+		return err;
+	}
 	print_line("rcedit (" + p_path + "): " + str);
 
 	if (str.find("Fatal error") != -1) {
+		add_message(EXPORT_MESSAGE_WARNING, TTR("Resources Modification"), vformat(TTR("rcedit failed to modify executable:\n%s"), str));
 		return FAILED;
 	}
 
@@ -238,7 +249,7 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p
 #ifdef WINDOWS_ENABLED
 	String signtool_path = EditorSettings::get_singleton()->get("export/windows/signtool");
 	if (!signtool_path.is_empty() && !FileAccess::exists(signtool_path)) {
-		ERR_PRINT("Could not find signtool executable at " + signtool_path + ", aborting.");
+		add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("Could not find signtool executable at \"%s\"."), signtool_path));
 		return ERR_FILE_NOT_FOUND;
 	}
 	if (signtool_path.is_empty()) {
@@ -247,7 +258,7 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p
 #else
 	String signtool_path = EditorSettings::get_singleton()->get("export/windows/osslsigncode");
 	if (!signtool_path.is_empty() && !FileAccess::exists(signtool_path)) {
-		ERR_PRINT("Could not find osslsigncode executable at " + signtool_path + ", aborting.");
+		add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("Could not find osslsigncode executable at \"%s\"."), signtool_path));
 		return ERR_FILE_NOT_FOUND;
 	}
 	if (signtool_path.is_empty()) {
@@ -267,7 +278,7 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p
 			args.push_back("/f");
 			args.push_back(p_preset->get("codesign/identity"));
 		} else {
-			EditorNode::add_io_error("codesign: no identity found");
+			add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("No identity found."));
 			return FAILED;
 		}
 	} else if (id_type == 2) { //Windows certificate store
@@ -275,11 +286,11 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p
 			args.push_back("/sha1");
 			args.push_back(p_preset->get("codesign/identity"));
 		} else {
-			EditorNode::add_io_error("codesign: no identity found");
+			add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("No identity found."));
 			return FAILED;
 		}
 	} else {
-		EditorNode::add_io_error("codesign: invalid identity type");
+		add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Invalid identity type."));
 		return FAILED;
 	}
 #else
@@ -287,7 +298,7 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p
 		args.push_back("-pkcs12");
 		args.push_back(p_preset->get("codesign/identity"));
 	} else {
-		EditorNode::add_io_error("codesign: no identity found");
+		add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("No identity found."));
 		return FAILED;
 	}
 #endif
@@ -319,7 +330,7 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p
 			args.push_back(p_preset->get("codesign/timestamp_server_url"));
 #endif
 		} else {
-			EditorNode::add_io_error("codesign: invalid timestamp server");
+			add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Invalid timestamp server."));
 			return FAILED;
 		}
 	}
@@ -366,7 +377,10 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p
 
 	String str;
 	Error err = OS::get_singleton()->execute(signtool_path, args, &str, nullptr, true);
-	ERR_FAIL_COND_V_MSG(err != OK, err, "Could not start signtool executable, configure signtool path in the Editor Settings (Export > Windows > Signtool).");
+	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)."));
+		return err;
+	}
 
 	print_line("codesign (" + p_path + "): " + str);
 #ifndef WINDOWS_ENABLED
@@ -374,6 +388,7 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p
 #else
 	if (str.find("Failed") != -1) {
 #endif
+		add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("Signtool failed to sign executable:\n%s"), str));
 		return FAILED;
 	}
 
@@ -381,10 +396,16 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p
 	Ref<DirAccess> tmp_dir = DirAccess::create_for_path(p_path.get_base_dir());
 
 	err = tmp_dir->remove(p_path);
-	ERR_FAIL_COND_V(err != OK, err);
+	if (err != OK) {
+		add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("Failed to remove temporary file \"%s\"."), p_path));
+		return err;
+	}
 
 	err = tmp_dir->rename(p_path + "_signed", p_path);
-	ERR_FAIL_COND_V(err != OK, err);
+	if (err != OK) {
+		add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("Failed to rename temporary file \"%s\"."), p_path + "_signed"));
+		return err;
+	}
 #endif
 
 	return OK;
@@ -433,15 +454,17 @@ bool EditorExportPlatformWindows::can_export(const Ref<EditorExportPreset> &p_pr
 	return valid;
 }
 
-Error EditorExportPlatformWindows::fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) const {
+Error EditorExportPlatformWindows::fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) {
 	// Patch the header of the "pck" section in the PE file so that it corresponds to the embedded data
 
 	if (p_embedded_size + p_embedded_start >= 0x100000000) { // Check for total executable size
-		ERR_FAIL_V_MSG(ERR_INVALID_DATA, "Windows executables cannot be >= 4 GiB.");
+		add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding"), TTR("Windows executables cannot be >= 4 GiB."));
+		return ERR_INVALID_DATA;
 	}
 
 	Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ_WRITE);
 	if (f.is_null()) {
+		add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding"), vformat(TTR("Failed to open executable file \"%s\"."), p_path));
 		return ERR_CANT_OPEN;
 	}
 
@@ -453,6 +476,7 @@ Error EditorExportPlatformWindows::fixup_embedded_pck(const String &p_path, int6
 		f->seek(pe_pos);
 		uint32_t magic = f->get_32();
 		if (magic != 0x00004550) {
+			add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding"), TTR("Executable file header corrupted."));
 			return ERR_FILE_CORRUPT;
 		}
 	}
@@ -502,5 +526,9 @@ Error EditorExportPlatformWindows::fixup_embedded_pck(const String &p_path, int6
 		}
 	}
 
-	return found ? OK : ERR_FILE_CORRUPT;
+	if (!found) {
+		add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding"), TTR("Executable \"pck\" section not found."));
+		return ERR_FILE_CORRUPT;
+	}
+	return OK;
 }

+ 1 - 1
platform/windows/export/export_plugin.h

@@ -51,7 +51,7 @@ public:
 	virtual bool get_export_option_visibility(const String &p_option, const HashMap<StringName, Variant> &p_options) const override;
 	virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
 	virtual String get_template_file_name(const String &p_target, const String &p_arch) const override;
-	virtual Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) const override;
+	virtual Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) override;
 };
 
 #endif