From a37ad265dc818d2aeaec97e1559ecae42e5487e2 Mon Sep 17 00:00:00 2001 From: David Snopek Date: Tue, 13 Feb 2024 14:20:23 -0600 Subject: [PATCH] Android: Allow using alternative Gradle build directory --- editor/editor_node.cpp | 83 +++++++++-- editor/editor_node.h | 5 + editor/export/export_template_manager.cpp | 72 +++++++--- editor/export/export_template_manager.h | 12 +- .../EditorExportPlatformAndroid.xml | 12 +- platform/android/export/export_plugin.cpp | 132 +++++++++++------- platform/android/export/export_plugin.h | 11 +- .../android/export/gradle_export_util.cpp | 8 +- platform/android/export/gradle_export_util.h | 3 +- 9 files changed, 244 insertions(+), 94 deletions(-) diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 6ec656d588d7..3162095e1f25 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -170,6 +170,9 @@ static const String META_TEXT_TO_COPY = "text_to_copy"; static const String EDITOR_NODE_CONFIG_SECTION = "EditorNode"; +static const String REMOVE_ANDROID_BUILD_TEMPLATE_MESSAGE = "The Android build template is already installed in this project and it won't be overwritten.\nRemove the \"%s\" directory manually before attempting this operation again."; +static const String INSTALL_ANDROID_BUILD_TEMPLATE_MESSAGE = "This will set up your project for gradle Android builds by installing the source template to \"%s\".\nNote that in order to make gradle builds instead of using pre-built APKs, the \"Use Gradle Build\" option should be enabled in the Android export preset."; + void EditorNode::disambiguate_filenames(const Vector p_full_paths, Vector &r_filenames) { ERR_FAIL_COND_MSG(p_full_paths.size() != r_filenames.size(), vformat("disambiguate_filenames requires two string vectors of same length (%d != %d).", p_full_paths.size(), r_filenames.size())); @@ -965,7 +968,7 @@ void EditorNode::_fs_changed() { String config_error; bool missing_templates; if (export_defer.android_build_template) { - export_template_manager->install_android_template(); + export_template_manager->install_android_template(export_preset); } if (!platform->can_export(export_preset, config_error, missing_templates, export_defer.debug)) { ERR_PRINT(vformat("Cannot export project with preset \"%s\" due to configuration errors:\n%s", preset_name, config_error)); @@ -2515,7 +2518,16 @@ void EditorNode::_edit_current(bool p_skip_foreign) { } void EditorNode::_android_build_source_selected(const String &p_file) { - export_template_manager->install_android_template_from_file(p_file); + export_template_manager->install_android_template_from_file(p_file, android_export_preset); +} + +void EditorNode::_android_export_preset_selected(int p_index) { + if (p_index >= 0) { + android_export_preset = EditorExport::get_singleton()->get_export_preset(choose_android_export_profile->get_item_id(p_index)); + } else { + android_export_preset.unref(); + } + install_android_build_template_message->set_text(vformat(TTR(INSTALL_ANDROID_BUILD_TEMPLATE_MESSAGE), export_template_manager->get_android_build_directory(android_export_preset))); } void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { @@ -2801,14 +2813,45 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { } break; case FILE_INSTALL_ANDROID_SOURCE: { if (p_confirmed) { - export_template_manager->install_android_template(); - } else { - if (DirAccess::exists("res://android/build")) { + if (export_template_manager->is_android_template_installed(android_export_preset)) { + remove_android_build_template->set_text(vformat(TTR(REMOVE_ANDROID_BUILD_TEMPLATE_MESSAGE), export_template_manager->get_android_build_directory(android_export_preset))); remove_android_build_template->popup_centered(); - } else if (export_template_manager->can_install_android_template()) { + } else if (!export_template_manager->can_install_android_template(android_export_preset)) { + gradle_build_manage_templates->popup_centered(); + } else { + export_template_manager->install_android_template(android_export_preset); + } + } else { + bool has_custom_gradle_build = false; + choose_android_export_profile->clear(); + for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) { + Ref export_preset = EditorExport::get_singleton()->get_export_preset(i); + if (export_preset->get_platform()->get_class_name() == "EditorExportPlatformAndroid" && (bool)export_preset->get("gradle_build/use_gradle_build")) { + choose_android_export_profile->add_item(export_preset->get_name(), i); + String gradle_build_directory = export_preset->get("gradle_build/gradle_build_directory"); + String android_source_template = export_preset->get("gradle_build/android_source_template"); + if (!android_source_template.is_empty() || (gradle_build_directory != "" && gradle_build_directory != "res://android")) { + has_custom_gradle_build = true; + } + } + } + _android_export_preset_selected(choose_android_export_profile->get_item_count() >= 1 ? 0 : -1); + + if (choose_android_export_profile->get_item_count() > 1 && has_custom_gradle_build) { + // If there's multiple options and at least one of them uses a custom gradle build then prompt the user to choose. + choose_android_export_profile->show(); install_android_build_template->popup_centered(); } else { - gradle_build_manage_templates->popup_centered(); + choose_android_export_profile->hide(); + + if (export_template_manager->is_android_template_installed(android_export_preset)) { + remove_android_build_template->set_text(vformat(TTR(REMOVE_ANDROID_BUILD_TEMPLATE_MESSAGE), export_template_manager->get_android_build_directory(android_export_preset))); + remove_android_build_template->popup_centered(); + } else if (export_template_manager->can_install_android_template(android_export_preset)) { + install_android_build_template->popup_centered(); + } else { + gradle_build_manage_templates->popup_centered(); + } } } } break; @@ -2821,7 +2864,7 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { OS::get_singleton()->shell_show_in_file_manager(OS::get_singleton()->get_user_data_dir(), true); } break; case FILE_EXPLORE_ANDROID_BUILD_TEMPLATES: { - OS::get_singleton()->shell_show_in_file_manager(ProjectSettings::get_singleton()->get_resource_path().path_join("android"), true); + OS::get_singleton()->shell_show_in_file_manager(ProjectSettings::get_singleton()->globalize_path(export_template_manager->get_android_build_directory(android_export_preset).get_base_dir()), true); } break; case FILE_QUIT: case RUN_PROJECT_MANAGER: @@ -7208,14 +7251,26 @@ EditorNode::EditorNode() { file_android_build_source->connect("file_selected", callable_mp(this, &EditorNode::_android_build_source_selected)); gui_base->add_child(file_android_build_source); - install_android_build_template = memnew(ConfirmationDialog); - install_android_build_template->set_text(TTR("This will set up your project for gradle Android builds by installing the source template to \"res://android/build\".\nYou can then apply modifications and build your own custom APK on export (adding modules, changing the AndroidManifest.xml, etc.).\nNote that in order to make gradle builds instead of using pre-built APKs, the \"Use Gradle Build\" option should be enabled in the Android export preset.")); - install_android_build_template->set_ok_button_text(TTR("Install")); - install_android_build_template->connect("confirmed", callable_mp(this, &EditorNode::_menu_confirm_current)); - gui_base->add_child(install_android_build_template); + { + VBoxContainer *vbox = memnew(VBoxContainer); + install_android_build_template_message = memnew(Label); + install_android_build_template_message->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART); + install_android_build_template_message->set_custom_minimum_size(Size2(300 * EDSCALE, 1)); + vbox->add_child(install_android_build_template_message); + + choose_android_export_profile = memnew(OptionButton); + choose_android_export_profile->connect("item_selected", callable_mp(this, &EditorNode::_android_export_preset_selected)); + vbox->add_child(choose_android_export_profile); + + install_android_build_template = memnew(ConfirmationDialog); + install_android_build_template->set_ok_button_text(TTR("Install")); + install_android_build_template->connect("confirmed", callable_mp(this, &EditorNode::_menu_confirm_current)); + install_android_build_template->add_child(vbox); + install_android_build_template->set_min_size(Vector2(500.0 * EDSCALE, 0)); + gui_base->add_child(install_android_build_template); + } remove_android_build_template = memnew(ConfirmationDialog); - remove_android_build_template->set_text(TTR("The Android build template is already installed in this project and it won't be overwritten.\nRemove the \"res://android/build\" directory manually before attempting this operation again.")); remove_android_build_template->set_ok_button_text(TTR("Show in File Manager")); remove_android_build_template->connect("confirmed", callable_mp(this, &EditorNode::_menu_option).bind(FILE_EXPLORE_ANDROID_BUILD_TEMPLATES)); gui_base->add_child(remove_android_build_template); diff --git a/editor/editor_node.h b/editor/editor_node.h index f1f75ddd8411..78a338c6f388 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -80,6 +80,7 @@ class EditorBuildProfileManager; class EditorCommandPalette; class EditorDockManager; class EditorExport; +class EditorExportPreset; class EditorExtensionManager; class EditorFeatureProfileManager; class EditorFileDialog; @@ -382,6 +383,9 @@ class EditorNode : public Node { ConfirmationDialog *gradle_build_manage_templates = nullptr; ConfirmationDialog *install_android_build_template = nullptr; ConfirmationDialog *remove_android_build_template = nullptr; + Label *install_android_build_template_message = nullptr; + OptionButton *choose_android_export_profile = nullptr; + Ref android_export_preset; PopupMenu *vcs_actions_menu = nullptr; EditorFileDialog *file = nullptr; @@ -527,6 +531,7 @@ class EditorNode : public Node { void _menu_option_confirm(int p_option, bool p_confirmed); void _android_build_source_selected(const String &p_file); + void _android_export_preset_selected(int p_index); void _request_screenshot(); void _screenshot(bool p_use_utc = false); diff --git a/editor/export/export_template_manager.cpp b/editor/export/export_template_manager.cpp index b3951d2c28aa..c3e48820b2bc 100644 --- a/editor/export/export_template_manager.cpp +++ b/editor/export/export_template_manager.cpp @@ -38,6 +38,7 @@ #include "editor/editor_paths.h" #include "editor/editor_settings.h" #include "editor/editor_string_names.h" +#include "editor/export/editor_export.h" #include "editor/progress_dialog.h" #include "editor/themes/editor_scale.h" #include "scene/gui/file_dialog.h" @@ -655,39 +656,78 @@ void ExportTemplateManager::_hide_dialog() { hide(); } -bool ExportTemplateManager::can_install_android_template() { +String ExportTemplateManager::get_android_build_directory(const Ref &p_preset) { + if (p_preset.is_valid()) { + String gradle_build_dir = p_preset->get("gradle_build/gradle_build_directory"); + if (!gradle_build_dir.is_empty()) { + return gradle_build_dir.path_join("build"); + } + } + return "res://android/build"; +} + +String ExportTemplateManager::get_android_source_zip(const Ref &p_preset) { + if (p_preset.is_valid()) { + String android_source_zip = p_preset->get("gradle_build/android_source_template"); + if (!android_source_zip.is_empty()) { + return android_source_zip; + } + } + const String templates_dir = EditorPaths::get_singleton()->get_export_templates_dir().path_join(VERSION_FULL_CONFIG); - return FileAccess::exists(templates_dir.path_join("android_source.zip")); + return templates_dir.path_join("android_source.zip"); } -Error ExportTemplateManager::install_android_template() { - const String &templates_path = EditorPaths::get_singleton()->get_export_templates_dir().path_join(VERSION_FULL_CONFIG); - const String &source_zip = templates_path.path_join("android_source.zip"); +String ExportTemplateManager::get_android_template_identifier(const Ref &p_preset) { + // The template identifier is the Godot version for the default template, and the full path plus md5 hash for custom templates. + if (p_preset.is_valid()) { + String android_source_zip = p_preset->get("gradle_build/android_source_template"); + if (!android_source_zip.is_empty()) { + return android_source_zip + String(" [") + FileAccess::get_md5(android_source_zip) + String("]"); + } + } + return VERSION_FULL_CONFIG; +} + +bool ExportTemplateManager::is_android_template_installed(const Ref &p_preset) { + return DirAccess::exists(get_android_build_directory(p_preset)); +} + +bool ExportTemplateManager::can_install_android_template(const Ref &p_preset) { + return FileAccess::exists(get_android_source_zip(p_preset)); +} + +Error ExportTemplateManager::install_android_template(const Ref &p_preset) { + const String source_zip = get_android_source_zip(p_preset); ERR_FAIL_COND_V(!FileAccess::exists(source_zip), ERR_CANT_OPEN); - return install_android_template_from_file(source_zip); + return install_android_template_from_file(source_zip, p_preset); } -Error ExportTemplateManager::install_android_template_from_file(const String &p_file) { + +Error ExportTemplateManager::install_android_template_from_file(const String &p_file, const Ref &p_preset) { // To support custom Android builds, we install the Java source code and buildsystem // from android_source.zip to the project's res://android folder. Ref da = DirAccess::create(DirAccess::ACCESS_RESOURCES); ERR_FAIL_COND_V(da.is_null(), ERR_CANT_CREATE); - // Make res://android dir (if it does not exist). - da->make_dir("android"); + String build_dir = get_android_build_directory(p_preset); + String parent_dir = build_dir.get_base_dir(); + + // Make parent of the build dir (if it does not exist). + da->make_dir_recursive(parent_dir); { - // Add version, to ensure building won't work if template and Godot version don't match. - Ref f = FileAccess::open("res://android/.build_version", FileAccess::WRITE); + // Add identifier, to ensure building won't work if the current template doesn't match. + Ref f = FileAccess::open(parent_dir.path_join(".build_version"), FileAccess::WRITE); ERR_FAIL_COND_V(f.is_null(), ERR_CANT_CREATE); - f->store_line(VERSION_FULL_CONFIG); + f->store_line(get_android_template_identifier(p_preset)); } // Create the android build directory. - Error err = da->make_dir_recursive("android/build"); + Error err = da->make_dir_recursive(build_dir); ERR_FAIL_COND_V(err != OK, err); { // Add an empty .gdignore file to avoid scan. - Ref f = FileAccess::open("res://android/build/.gdignore", FileAccess::WRITE); + Ref f = FileAccess::open(build_dir.path_join(".gdignore"), FileAccess::WRITE); ERR_FAIL_COND_V(f.is_null(), ERR_CANT_CREATE); f->store_line(""); } @@ -735,11 +775,11 @@ Error ExportTemplateManager::install_android_template_from_file(const String &p_ unzCloseCurrentFile(pkg); if (!dirs_tested.has(base_dir)) { - da->make_dir_recursive(String("android/build").path_join(base_dir)); + da->make_dir_recursive(build_dir.path_join(base_dir)); dirs_tested.insert(base_dir); } - String to_write = String("res://android/build").path_join(path); + String to_write = build_dir.path_join(path); Ref f = FileAccess::open(to_write, FileAccess::WRITE); if (f.is_valid()) { f->store_buffer(uncomp_data.ptr(), uncomp_data.size()); diff --git a/editor/export/export_template_manager.h b/editor/export/export_template_manager.h index 8c26554d13bc..a00d874580a9 100644 --- a/editor/export/export_template_manager.h +++ b/editor/export/export_template_manager.h @@ -33,6 +33,7 @@ #include "scene/gui/dialogs.h" +class EditorExportPreset; class ExportTemplateVersion; class FileDialog; class HTTPRequest; @@ -121,10 +122,15 @@ class ExportTemplateManager : public AcceptDialog { static void _bind_methods(); public: - bool can_install_android_template(); - Error install_android_template(); + static String get_android_build_directory(const Ref &p_preset); + static String get_android_source_zip(const Ref &p_preset); + static String get_android_template_identifier(const Ref &p_preset); - Error install_android_template_from_file(const String &p_file); + bool is_android_template_installed(const Ref &p_preset); + bool can_install_android_template(const Ref &p_preset); + Error install_android_template(const Ref &p_preset); + + Error install_android_template_from_file(const String &p_file, const Ref &p_preset); void popup_manager(); diff --git a/platform/android/doc_classes/EditorExportPlatformAndroid.xml b/platform/android/doc_classes/EditorExportPlatformAndroid.xml index a6f92158f94b..976d64fd497a 100644 --- a/platform/android/doc_classes/EditorExportPlatformAndroid.xml +++ b/platform/android/doc_classes/EditorExportPlatformAndroid.xml @@ -37,14 +37,22 @@ A list of additional command line arguments, exported project will receive when started. - Path to the custom export template. If left empty, default template is used. + Path to an APK file to use as a custom export template for debug exports. If left empty, default template is used. + [b]Note:[/b] This is only used if [member EditorExportPlatformAndroid.gradle_build/use_gradle_build] is disabled. - Path to the custom export template. If left empty, default template is used. + Path to an APK file to use as a custom export template for release exports. If left empty, default template is used. + [b]Note:[/b] This is only used if [member EditorExportPlatformAndroid.gradle_build/use_gradle_build] is disabled. + + + Path to a ZIP file holding the source for the export template used in a Gradle build. If left empty, the default template is used. Export format for Gradle build. + + Path to the Gradle build directory. If left empty, then [code]res://android[/code] will be used. + Minimal Android SDK version for Gradle build. diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 459f5a5983db..bd062401a4e8 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -46,6 +46,7 @@ #include "editor/editor_node.h" #include "editor/editor_paths.h" #include "editor/editor_settings.h" +#include "editor/export/export_template_manager.h" #include "editor/import/resource_importer_texture_settings.h" #include "editor/themes/editor_scale.h" #include "main/splash.gen.h" @@ -208,12 +209,14 @@ static const char *android_perms[] = { nullptr }; +static const char *MISMATCHED_VERSIONS_MESSAGE = "Android build version mismatch:\n| Template installed: %s\n| Requested version: %s\nPlease reinstall Android build template from 'Project' menu."; + static const char *SPLASH_IMAGE_EXPORT_PATH = "res/drawable-nodpi/splash.png"; static const char *LEGACY_BUILD_SPLASH_IMAGE_EXPORT_PATH = "res/drawable-nodpi-v4/splash.png"; static const char *SPLASH_BG_COLOR_PATH = "res/drawable-nodpi/splash_bg_color.png"; static const char *LEGACY_BUILD_SPLASH_BG_COLOR_PATH = "res/drawable-nodpi-v4/splash_bg_color.png"; -static const char *SPLASH_CONFIG_PATH = "res://android/build/res/drawable/splash_drawable.xml"; -static const char *GDEXTENSION_LIBS_PATH = "res://android/build/libs/gdextensionlibs.json"; +static const char *SPLASH_CONFIG_PATH = "res/drawable/splash_drawable.xml"; +static const char *GDEXTENSION_LIBS_PATH = "libs/gdextensionlibs.json"; static const int icon_densities_count = 6; static const char *launcher_icon_option = PNAME("launcher_icons/main_192x192"); @@ -250,8 +253,8 @@ static const LauncherIcon launcher_adaptive_icon_backgrounds[icon_densities_coun static const int EXPORT_FORMAT_APK = 0; static const int EXPORT_FORMAT_AAB = 1; -static const char *APK_ASSETS_DIRECTORY = "res://android/build/assets"; -static const char *AAB_ASSETS_DIRECTORY = "res://android/build/assetPacks/installTime/src/main/assets"; +static const char *APK_ASSETS_DIRECTORY = "assets"; +static const char *AAB_ASSETS_DIRECTORY = "assetPacks/installTime/src/main/assets"; static const int OPENGL_MIN_SDK_VERSION = 21; // Should match the value in 'platform/android/java/app/config.gradle#minSdk' static const int VULKAN_MIN_SDK_VERSION = 24; @@ -474,7 +477,8 @@ String EditorExportPlatformAndroid::get_valid_basename() const { } String EditorExportPlatformAndroid::get_assets_directory(const Ref &p_preset, int p_export_format) const { - return p_export_format == EXPORT_FORMAT_AAB ? AAB_ASSETS_DIRECTORY : APK_ASSETS_DIRECTORY; + String gradle_build_directory = ExportTemplateManager::get_android_build_directory(p_preset); + return gradle_build_directory.path_join(p_export_format == EXPORT_FORMAT_AAB ? AAB_ASSETS_DIRECTORY : APK_ASSETS_DIRECTORY); } bool EditorExportPlatformAndroid::is_package_name_valid(const String &p_package, String *r_error) const { @@ -774,11 +778,10 @@ Error EditorExportPlatformAndroid::copy_gradle_so(void *p_userdata, const Shared } if (abi_index != -1) { exported = true; - String base = "res://android/build/libs"; String type = export_data->debug ? "debug" : "release"; String abi = abis[abi_index].abi; String filename = p_so.path.get_file(); - String dst_path = base.path_join(type).path_join(abi).path_join(filename); + String dst_path = export_data->libs_directory.path_join(type).path_join(abi).path_join(filename); Vector data = FileAccess::get_file_as_bytes(p_so.path); print_verbose("Copying .so file from " + p_so.path + " to " + dst_path); Error err = store_file_at_path(dst_path, data); @@ -867,7 +870,7 @@ void EditorExportPlatformAndroid::_write_tmp_manifest(const Ref(this), p_preset, _has_read_write_storage_permission(perms), p_debug); manifest_text += "\n"; - String manifest_path = vformat("res://android/build/src/%s/AndroidManifest.xml", (p_debug ? "debug" : "release")); + String manifest_path = ExportTemplateManager::get_android_build_directory(p_preset).path_join(vformat("src/%s/AndroidManifest.xml", (p_debug ? "debug" : "release"))); print_verbose("Storing manifest into " + manifest_path + ": " + "\n" + manifest_text); store_string_at_path(manifest_path, manifest_text); @@ -1628,15 +1631,6 @@ void EditorExportPlatformAndroid::load_icon_refs(const Ref & } } -void EditorExportPlatformAndroid::store_image(const LauncherIcon launcher_icon, const Vector &data) { - store_image(launcher_icon.export_path, data); -} - -void EditorExportPlatformAndroid::store_image(const String &export_path, const Vector &data) { - String img_path = export_path.insert(0, "res://android/build/"); - store_file_at_path(img_path, data); -} - void EditorExportPlatformAndroid::_copy_icons_to_gradle_project(const Ref &p_preset, const String &processed_splash_config_xml, const Ref &splash_image, @@ -1644,26 +1638,30 @@ void EditorExportPlatformAndroid::_copy_icons_to_gradle_project(const Ref &main_image, const Ref &foreground, const Ref &background) { + String gradle_build_dir = ExportTemplateManager::get_android_build_directory(p_preset); + // Store the splash configuration if (!processed_splash_config_xml.is_empty()) { print_verbose("Storing processed splash configuration: " + String("\n") + processed_splash_config_xml); - store_string_at_path(SPLASH_CONFIG_PATH, processed_splash_config_xml); + store_string_at_path(gradle_build_dir.path_join(SPLASH_CONFIG_PATH), processed_splash_config_xml); } // Store the splash image if (splash_image.is_valid() && !splash_image->is_empty()) { - print_verbose("Storing splash image in " + String(SPLASH_IMAGE_EXPORT_PATH)); + String splash_export_path = gradle_build_dir.path_join(SPLASH_IMAGE_EXPORT_PATH); + print_verbose("Storing splash image in " + splash_export_path); Vector data; _load_image_data(splash_image, data); - store_image(SPLASH_IMAGE_EXPORT_PATH, data); + store_file_at_path(splash_export_path, data); } // Store the splash bg color image if (splash_bg_color_image.is_valid() && !splash_bg_color_image->is_empty()) { - print_verbose("Storing splash background image in " + String(SPLASH_BG_COLOR_PATH)); + String splash_bg_color_path = gradle_build_dir.path_join(SPLASH_BG_COLOR_PATH); + print_verbose("Storing splash background image in " + splash_bg_color_path); Vector data; _load_image_data(splash_bg_color_image, data); - store_image(SPLASH_BG_COLOR_PATH, data); + store_file_at_path(splash_bg_color_path, data); } // Prepare images to be resized for the icons. If some image ends up being uninitialized, @@ -1674,7 +1672,7 @@ void EditorExportPlatformAndroid::_copy_icons_to_gradle_project(const Ref data; _process_launcher_icons(launcher_icons[i].export_path, main_image, launcher_icons[i].dimensions, data); - store_image(launcher_icons[i], data); + store_file_at_path(gradle_build_dir.path_join(launcher_icons[i].export_path), data); } if (foreground.is_valid() && !foreground->is_empty()) { @@ -1682,7 +1680,7 @@ void EditorExportPlatformAndroid::_copy_icons_to_gradle_project(const Ref data; _process_launcher_icons(launcher_adaptive_icon_foregrounds[i].export_path, foreground, launcher_adaptive_icon_foregrounds[i].dimensions, data); - store_image(launcher_adaptive_icon_foregrounds[i], data); + store_file_at_path(gradle_build_dir.path_join(launcher_adaptive_icon_foregrounds[i].export_path), data); } if (background.is_valid() && !background->is_empty()) { @@ -1690,7 +1688,7 @@ void EditorExportPlatformAndroid::_copy_icons_to_gradle_project(const Ref data; _process_launcher_icons(launcher_adaptive_icon_backgrounds[i].export_path, background, launcher_adaptive_icon_backgrounds[i].dimensions, data); - store_image(launcher_adaptive_icon_backgrounds[i], data); + store_file_at_path(gradle_build_dir.path_join(launcher_adaptive_icon_backgrounds[i].export_path), data); } } } @@ -1798,7 +1796,9 @@ void EditorExportPlatformAndroid::get_export_options(List *r_optio r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "gradle_build/use_gradle_build"), false, false, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "gradle_build/use_gradle_build"), false, true, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "gradle_build/gradle_build_directory", PROPERTY_HINT_PLACEHOLDER_TEXT, "res://android"), "", false, false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "gradle_build/android_source_template", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "gradle_build/export_format", PROPERTY_HINT_ENUM, "Export APK,Export AAB"), EXPORT_FORMAT_APK, false, true)); // Using String instead of int to default to an empty string (no override) with placeholder for instructions (see GH-62465). // This implies doing validation that the string is a proper int. @@ -1876,6 +1876,18 @@ void EditorExportPlatformAndroid::get_export_options(List *r_optio } } +bool EditorExportPlatformAndroid::get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const { + if (p_option == "gradle_build/gradle_build_directory" || p_option == "gradle_build/android_source_template") { + // @todo These are experimental options - keep them hidden for now. + //return (bool)p_preset->get("gradle_build/use_gradle_build"); + return false; + } else if (p_option == "custom_template/debug" || p_option == "custom_template/release") { + // The APK templates are ignored if Gradle build is enabled. + return !p_preset->get("gradle_build/use_gradle_build"); + } + return true; +} + String EditorExportPlatformAndroid::get_name() const { return "Android"; } @@ -2345,7 +2357,7 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Refget("gradle_build/use_gradle_build")) { + String build_version_path = ExportTemplateManager::get_android_build_directory(p_preset).get_base_dir().path_join(".build_version"); + Ref f = FileAccess::open(build_version_path, FileAccess::READ); + if (f.is_valid()) { + String current_version = ExportTemplateManager::get_android_template_identifier(p_preset); + String installed_version = f->get_line().strip_edges(); + if (current_version != installed_version) { + err += vformat(TTR(MISMATCHED_VERSIONS_MESSAGE), installed_version, current_version); + err += "\n"; + } + } + } + String min_sdk_str = p_preset->get("gradle_build/min_sdk"); int min_sdk_int = VULKAN_MIN_SDK_VERSION; if (!min_sdk_str.is_empty()) { // Empty means no override, nothing to do. @@ -2734,34 +2759,37 @@ Error EditorExportPlatformAndroid::sign_apk(const Ref &p_pre return OK; } -void EditorExportPlatformAndroid::_clear_assets_directory() { +void EditorExportPlatformAndroid::_clear_assets_directory(const Ref &p_preset) { Ref da_res = DirAccess::create(DirAccess::ACCESS_RESOURCES); + String gradle_build_directory = ExportTemplateManager::get_android_build_directory(p_preset); // Clear the APK assets directory - if (da_res->dir_exists(APK_ASSETS_DIRECTORY)) { + String apk_assets_directory = gradle_build_directory.path_join(APK_ASSETS_DIRECTORY); + if (da_res->dir_exists(apk_assets_directory)) { print_verbose("Clearing APK assets directory..."); - Ref da_assets = DirAccess::open(APK_ASSETS_DIRECTORY); + Ref da_assets = DirAccess::open(apk_assets_directory); ERR_FAIL_COND(da_assets.is_null()); da_assets->erase_contents_recursive(); - da_res->remove(APK_ASSETS_DIRECTORY); + da_res->remove(apk_assets_directory); } // Clear the AAB assets directory - if (da_res->dir_exists(AAB_ASSETS_DIRECTORY)) { + String aab_assets_directory = gradle_build_directory.path_join(AAB_ASSETS_DIRECTORY); + if (da_res->dir_exists(aab_assets_directory)) { print_verbose("Clearing AAB assets directory..."); - Ref da_assets = DirAccess::open(AAB_ASSETS_DIRECTORY); + Ref da_assets = DirAccess::open(aab_assets_directory); ERR_FAIL_COND(da_assets.is_null()); da_assets->erase_contents_recursive(); - da_res->remove(AAB_ASSETS_DIRECTORY); + da_res->remove(aab_assets_directory); } } -void EditorExportPlatformAndroid::_remove_copied_libs() { +void EditorExportPlatformAndroid::_remove_copied_libs(String p_gdextension_libs_path) { print_verbose("Removing previously installed libraries..."); Error error; - String libs_json = FileAccess::get_file_as_string(GDEXTENSION_LIBS_PATH, &error); + String libs_json = FileAccess::get_file_as_string(p_gdextension_libs_path, &error); if (error || libs_json.is_empty()) { print_verbose("No previously installed libraries found"); return; @@ -2777,7 +2805,7 @@ void EditorExportPlatformAndroid::_remove_copied_libs() { print_verbose("Removing previously installed library " + libs[i]); da->remove(libs[i]); } - da->remove(GDEXTENSION_LIBS_PATH); + da->remove(p_gdextension_libs_path); } String EditorExportPlatformAndroid::join_list(const List &p_parts, const String &p_separator) { @@ -2836,6 +2864,8 @@ String EditorExportPlatformAndroid::_resolve_export_plugin_android_library_path( bool EditorExportPlatformAndroid::_is_clean_build_required(const Ref &p_preset) { bool first_build = last_gradle_build_time == 0; bool have_plugins_changed = false; + String gradle_build_dir = ExportTemplateManager::get_android_build_directory(p_preset); + bool has_build_dir_changed = last_gradle_build_dir != gradle_build_dir; String plugin_names = _get_plugins_names(p_preset); @@ -2855,9 +2885,10 @@ bool EditorExportPlatformAndroid::_is_clean_build_required(const Refget_unix_time(); + last_gradle_build_dir = gradle_build_dir; last_plugin_names = plugin_names; - return have_plugins_changed || first_build; + return have_plugins_changed || has_build_dir_changed || first_build; } Error EditorExportPlatformAndroid::export_project(const Ref &p_preset, bool p_debug, const String &p_path, int p_flags) { @@ -2881,6 +2912,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Refget("gradle_build/use_gradle_build")); + String gradle_build_directory = use_gradle_build ? ExportTemplateManager::get_android_build_directory(p_preset) : ""; bool p_give_internet = p_flags & (DEBUG_FLAG_DUMB_CLIENT | DEBUG_FLAG_REMOTE_DEBUG); bool apk_expansion = p_preset->get("apk_expansion/enable"); Vector enabled_abis = get_enabled_abis(p_preset); @@ -2940,15 +2972,17 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref f = FileAccess::open("res://android/.build_version", FileAccess::READ); + String gradle_base_directory = gradle_build_directory.get_base_dir(); + Ref f = FileAccess::open(gradle_base_directory.path_join(".build_version"), FileAccess::READ); if (f.is_null()) { add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Trying to build from a gradle 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) { - 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)); + String current_version = ExportTemplateManager::get_android_template_identifier(p_preset); + String installed_version = f->get_line().strip_edges(); + print_verbose("- build version: " + installed_version); + if (installed_version != current_version) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR(MISMATCHED_VERSIONS_MESSAGE), installed_version, current_version)); return ERR_UNCONFIGURED; } } @@ -2969,9 +3003,9 @@ Error EditorExportPlatformAndroid::export_project_helper(const Refget("package/name")); - err = _create_project_name_strings_files(p_preset, project_name); //project name localization. + err = _create_project_name_strings_files(p_preset, project_name, gradle_build_directory); //project name localization. if (err != OK) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), 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/*.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); @@ -2979,12 +3013,14 @@ Error EditorExportPlatformAndroid::export_project_helper(const Refget_resource_path().path_join("android/build"); + String build_path = ProjectSettings::get_singleton()->globalize_path(gradle_build_directory); build_command = build_path.path_join(build_command); String package_name = get_package_name(p_preset->get("package/unique_name")); diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h index c282055fba0e..abc462a69118 100644 --- a/platform/android/export/export_plugin.h +++ b/platform/android/export/export_plugin.h @@ -90,6 +90,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { #endif // DISABLE_DEPRECATED String last_plugin_names; uint64_t last_gradle_build_time = 0; + String last_gradle_build_dir; Vector devices; SafeFlag devices_changed; @@ -174,10 +175,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { void load_icon_refs(const Ref &p_preset, Ref &icon, Ref &foreground, Ref &background); - void store_image(const LauncherIcon launcher_icon, const Vector &data); - - void store_image(const String &export_path, const Vector &data); - void _copy_icons_to_gradle_project(const Ref &p_preset, const String &processed_splash_config_xml, const Ref &splash_image, @@ -198,6 +195,8 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { virtual void get_export_options(List *r_options) const override; + virtual bool get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const override; + virtual String get_export_option_warning(const EditorExportPreset *p_preset, const StringName &p_name) const override; virtual String get_name() const override; @@ -248,9 +247,9 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { Error sign_apk(const Ref &p_preset, bool p_debug, const String &export_path, EditorProgress &ep); - void _clear_assets_directory(); + void _clear_assets_directory(const Ref &p_preset); - void _remove_copied_libs(); + void _remove_copied_libs(String p_gdextension_libs_path); static String join_list(const List &p_parts, const String &p_separator); static String join_abis(const Vector &p_parts, const String &p_separator, bool p_use_arch); diff --git a/platform/android/export/gradle_export_util.cpp b/platform/android/export/gradle_export_util.cpp index 09150092359f..9eddef6a4c80 100644 --- a/platform/android/export/gradle_export_util.cpp +++ b/platform/android/export/gradle_export_util.cpp @@ -191,14 +191,14 @@ String _android_xml_escape(const String &p_string) { } // Creates strings.xml files inside the gradle project for different locales. -Error _create_project_name_strings_files(const Ref &p_preset, const String &project_name) { +Error _create_project_name_strings_files(const Ref &p_preset, const String &project_name, const String &p_gradle_build_dir) { print_verbose("Creating strings resources for supported locales for project " + project_name); // Stores the string into the default values directory. String processed_default_xml_string = vformat(godot_project_name_xml_string, _android_xml_escape(project_name)); - store_string_at_path("res://android/build/res/values/godot_project_name_string.xml", processed_default_xml_string); + store_string_at_path(p_gradle_build_dir.path_join("res/values/godot_project_name_string.xml"), processed_default_xml_string); // Searches the Gradle project res/ directory to find all supported locales - Ref da = DirAccess::open("res://android/build/res"); + Ref da = DirAccess::open(p_gradle_build_dir.path_join("res")); if (da.is_null()) { if (OS::get_singleton()->is_stdout_verbose()) { print_error("Unable to open Android resources directory."); @@ -217,7 +217,7 @@ Error _create_project_name_strings_files(const Ref &p_preset continue; } String locale = file.replace("values-", "").replace("-r", "_"); - String locale_directory = "res://android/build/res/" + file + "/godot_project_name_string.xml"; + String locale_directory = p_gradle_build_dir.path_join("res/" + file + "/godot_project_name_string.xml"); if (appnames.has(locale)) { String locale_project_name = appnames[locale]; String processed_xml_string = vformat(godot_project_name_xml_string, _android_xml_escape(locale_project_name)); diff --git a/platform/android/export/gradle_export_util.h b/platform/android/export/gradle_export_util.h index 2498394adda1..9f8e476f73ff 100644 --- a/platform/android/export/gradle_export_util.h +++ b/platform/android/export/gradle_export_util.h @@ -63,6 +63,7 @@ static const int XR_MODE_OPENXR = 1; struct CustomExportData { String assets_directory; + String libs_directory; bool debug; Vector libs; }; @@ -94,7 +95,7 @@ Error store_string_at_path(const String &p_path, const String &p_data); Error rename_and_store_file_in_gradle_project(void *p_userdata, const String &p_path, const Vector &p_data, int p_file, int p_total, const Vector &p_enc_in_filters, const Vector &p_enc_ex_filters, const Vector &p_key); // Creates strings.xml files inside the gradle project for different locales. -Error _create_project_name_strings_files(const Ref &p_preset, const String &project_name); +Error _create_project_name_strings_files(const Ref &p_preset, const String &project_name, const String &p_gradle_build_dir); String bool_to_string(bool v);