diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index b932c76a08d9..ca1eb48823e7 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -209,6 +209,8 @@ #ifdef ANDROID_ENABLED #include "editor/gui/touch_actions_panel.h" +#else +#include "editor/export/android_sdk_manager.h" #endif // ANDROID_ENABLED #include "modules/modules_enabled.gen.h" // For gdscript, mono. @@ -3397,6 +3399,37 @@ void EditorNode::_android_remove_build_templates(bool p_prompt_for_removal) { remove_android_build_template->popup_centered_clamped(Size2(600, 200) * EDSCALE, 0.7); } +void EditorNode::_setup_android_build(bool p_confirmed) { + if (p_confirmed) { + setup_android_build_template(android_export_preset, p_confirmed); + } 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 { + choose_android_export_profile->hide(); + + setup_android_build_template(android_export_preset, p_confirmed); + } + } +} + Error EditorNode::setup_android_build_template(const Ref &p_preset, bool p_confirmed) { android_export_preset = p_preset; if (export_template_manager->is_android_template_installed(p_preset)) { @@ -3775,35 +3808,17 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { FindInFiles::get_singleton()->open_dock("", PROJECT_REPLACE_IN_FILES == p_option); } break; - case PROJECT_INSTALL_ANDROID_SOURCE: { - if (p_confirmed) { - setup_android_build_template(android_export_preset, p_confirmed); + case PROJECT_SETUP_ANDROID_BUILD: { +#ifndef ANDROID_ENABLED + if (!(AndroidSDKManager::is_android_sdk_setup() && AndroidSDKManager::is_java_sdk_setup())) { + Callable setup_android_build_callable = callable_mp(this, &EditorNode::_setup_android_build).bind(p_confirmed); + android_sdk_manager->run_setup(setup_android_build_callable, setup_android_build_callable); } 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 { - choose_android_export_profile->hide(); - - setup_android_build_template(android_export_preset, p_confirmed); - } +#endif + _setup_android_build(p_confirmed); +#ifndef ANDROID_ENABLED } +#endif } break; case PROJECT_OPEN_USER_DATA_FOLDER: { // Ensure_user_data_dir() to prevent the edge case: "Open User Data Folder" won't work after the project was renamed in ProjectSettingsEditor unless the project is saved. @@ -8189,7 +8204,7 @@ void EditorNode::_build_project_menu(bool p_dark_mode) { project_menu->add_separator(); project_menu->add_icon_shortcut(get_editor_theme_native_menu_icon(SNAME("ResourcePreloader"), menu_type == MENU_TYPE_GLOBAL, p_dark_mode), ED_GET_SHORTCUT("editor/export"), PROJECT_EXPORT); project_menu->add_item(TTRC("Pack Project as ZIP..."), PROJECT_PACK_AS_ZIP); - project_menu->add_item(TTRC("Install Android Build Template..."), PROJECT_INSTALL_ANDROID_SOURCE); + project_menu->add_item(TTRC("Setup Android Build..."), PROJECT_SETUP_ANDROID_BUILD); #ifndef ANDROID_ENABLED project_menu->add_item(TTRC("Open User Data Folder"), PROJECT_OPEN_USER_DATA_FOLDER); #endif @@ -9069,6 +9084,11 @@ EditorNode::EditorNode() { gui_base->add_child(fbx_importer_manager); #endif +#ifndef ANDROID_ENABLED + android_sdk_manager = memnew(AndroidSDKManager); + gui_base->add_child(android_sdk_manager); +#endif + warning = memnew(AcceptDialog); warning->set_unparent_when_invisible(true); warning->add_button(TTRC("Copy Text"), true, "copy"); diff --git a/editor/editor_node.h b/editor/editor_node.h index 7ad2b3cab5bd..1a67dec89832 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -42,6 +42,9 @@ typedef void (*EditorNodeInitCallback)(); typedef void (*EditorPluginInitializeCallback)(); typedef bool (*EditorBuildCallback)(); +#ifndef ANDROID_ENABLED +class AndroidSDKManager; +#endif class AcceptDialog; class BoxContainer; class ColorPicker; @@ -173,7 +176,7 @@ class EditorNode : public Node { PROJECT_VERSION_CONTROL, PROJECT_EXPORT, PROJECT_PACK_AS_ZIP, - PROJECT_INSTALL_ANDROID_SOURCE, + PROJECT_SETUP_ANDROID_BUILD, PROJECT_OPEN_USER_DATA_FOLDER, PROJECT_RELOAD_CURRENT_PROJECT, PROJECT_QUIT_TO_PROJECT_MANAGER, @@ -500,6 +503,10 @@ class EditorNode : public Node { bool unfocused_low_processor_usage_mode_enabled = true; +#ifndef ANDROID_ENABLED + AndroidSDKManager *android_sdk_manager = nullptr; +#endif + static EditorBuildCallback build_callbacks[MAX_BUILD_CALLBACKS]; static EditorPluginInitializeCallback plugin_init_callbacks[MAX_INIT_CALLBACKS]; static int build_callback_count; @@ -553,6 +560,7 @@ class EditorNode : public Node { void _android_install_build_template(); void _android_explore_build_templates(); void _android_remove_build_templates(bool p_prompt_for_removal); + void _setup_android_build(bool p_confirmed); void _request_screenshot(); void _screenshot(bool p_use_utc = false); diff --git a/editor/export/android_sdk_manager.cpp b/editor/export/android_sdk_manager.cpp new file mode 100644 index 000000000000..b155f4dbfae1 --- /dev/null +++ b/editor/export/android_sdk_manager.cpp @@ -0,0 +1,905 @@ +/**************************************************************************/ +/* android_sdk_manager.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "android_sdk_manager.h" + +#include "core/io/dir_access.h" +#include "core/io/file_access.h" +#include "core/io/zip_io.h" +#include "core/object/callable_mp.h" +#include "core/os/os.h" +#include "editor/editor_node.h" +#include "editor/file_system/editor_paths.h" +#include "editor/settings/editor_settings.h" +#include "scene/gui/label.h" +#include "scene/gui/progress_bar.h" +#include "scene/gui/rich_text_label.h" + +#include "modules/zip/zip_reader.h" + +static const char *ANDROID_CLI_URL_LINUX = "https://dl.google.com/android/cli/latest/linux_x86_64/android"; +static const char *ANDROID_CLI_URL_MAC_X86 = "https://dl.google.com/android/cli/latest/darwin_x86_64/android"; +static const char *ANDROID_CLI_URL_MAC_ARM = "https://dl.google.com/android/cli/latest/darwin_arm64/android"; +static const char *ANDROID_CLI_URL_WIN = "https://dl.google.com/android/cli/latest/windows_x86_64/android.exe"; + +// Android SDK packages. +static const char *ANDROID_SDK_PACKAGES[] = { + "platform-tools", + "build-tools/36.1.0", // Should match the value in 'platform/android/java/app/config.gradle#buildTools'. + "platforms/android-36", + "ndk/29.0.14206865", // Should match the value in 'platform/android/java/app/config.gradle#ndkVersion'. + "cmake/3.22.1", + "cmdline-tools/latest", + nullptr +}; + +static const uint64_t INSTALL_ANDROID_SDK_POLL_TIME = 3000000; + +void AndroidSDKManager::_bind_methods() { + ADD_SIGNAL(MethodInfo("java_sdk_installed")); + ADD_SIGNAL(MethodInfo("android_sdk_installed")); +} + +void AndroidSDKManager::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + connect(SNAME("canceled"), callable_mp(this, &AndroidSDKManager::_cancel_setup)); + } break; + + case NOTIFICATION_EXIT_TREE: { + disconnect("canceled", callable_mp(this, &AndroidSDKManager::_cancel_setup)); + } break; + + case NOTIFICATION_PROCESS: { + switch (current_setup_status) { + case DOWNLOADING_ANDROID_SDK: + case DOWNLOADING_JAVA_SDK: { + int downloaded_bytes = downloader->get_downloaded_bytes(); + if (downloaded_bytes > 0) { + setup_progress_bar->set_indeterminate(false); + setup_progress_bar->set_max(downloader->get_body_size()); + setup_progress_bar->set_value(downloaded_bytes); + } + } break; + + case INSTALLING_ANDROID_SDK: { + if (!setup_process_data.is_empty()) { + int setup_android_pid = setup_process_data["pid"]; + if (OS::get_singleton()->is_process_running(setup_android_pid)) { + Ref fa_out = setup_process_data["stdio"]; + Ref fa_err = setup_process_data["stderr"]; + if (fa_out->is_open() && fa_err->is_open() && OS::get_singleton()->get_ticks_usec() - install_android_sdk_time > INSTALL_ANDROID_SDK_POLL_TIME) { + install_android_sdk_time = OS::get_singleton()->get_ticks_usec(); + PackedByteArray buf; + + String output; + buf.resize(fa_out->get_length()); + uint64_t buf_size = fa_out->get_buffer(buf.ptrw(), buf.size()); + output.append_utf8((const char *)buf.ptr(), buf_size); + + String err_output; + buf.resize(fa_err->get_length()); + buf_size = fa_err->get_buffer(buf.ptrw(), buf.size()); + err_output.append_utf8((const char *)buf.ptr(), buf_size); + + if (!output.is_empty()) { + print_verbose(output); + execute_outputs->add_text(output); + } + + if (!err_output.is_empty()) { + print_verbose(err_output); + execute_outputs->add_text(err_output); + } + } + } else { + int exit_code = OS::get_singleton()->get_process_exit_code(setup_android_pid); + print_verbose("Exit code: " + itos(exit_code)); + execute_outputs->add_text("\nExit code: " + itos(exit_code)); + _android_sdk_installed(exit_code); + } + } + } break; + + default: { + } break; + } + } break; + } +} + +void AndroidSDKManager::_cancel_setup() { + switch (current_setup_status) { + case IDLE: { + } break; + + case PROMPTING_ANDROID_ONLINE_ACCESS: { + print_verbose("Canceling online mode request prompt for Android SDK setup."); + } break; + + case PROMPTING_ANDROID_SDK_SETUP: { + print_verbose("Canceling Android SDK setup prompt."); + } break; + + case PROMPTING_JAVA_ONLINE_ACCESS: { + print_verbose("Canceling online mode request prompt for Java SDK setup."); + } break; + + case PROMPTING_JAVA_SDK_SETUP: { + print_verbose("Canceling Java SDK setup prompt."); + } break; + + case DOWNLOADING_ANDROID_SDK: { + print_verbose("Canceling Android SDK download."); + downloader->cancel_request(); + } break; + + case DOWNLOADING_JAVA_SDK: { + print_verbose("Canceling Java SDK download."); + downloader->cancel_request(); + } break; + + case INSTALLING_ANDROID_SDK: { + print_verbose("Canceling Android SDK installation."); + int setup_android_pid = setup_process_data["pid"]; + if (setup_android_pid > 0 && OS::get_singleton()->is_process_running(setup_android_pid)) { + OS::get_singleton()->kill(setup_android_pid); + } + } break; + + case INSTALLING_JAVA_SDK: { + print_verbose("Canceling Java SDK installation."); + } break; + } + + _hide_setup_dialog(current_setup_status); + + if (on_setup_cancelled.is_valid()) { + on_setup_cancelled.call_deferred(); + } + + on_setup_completed = Callable(); + on_setup_cancelled = Callable(); +} + +void AndroidSDKManager::_show_setup_dialog(SetupStatus p_status) { + if (current_setup_status != IDLE) { + _hide_setup_dialog(current_setup_status); + } + current_setup_status = p_status; + + String label; + bool outputs_visible = false; + bool ok_button_visible = false; + bool show_progress_bar = false; + switch (p_status) { + case IDLE: { + label = ""; + } break; + case PROMPTING_ANDROID_ONLINE_ACCESS: { + label = TTRC("Enable online mode for Android SDK setup?"); + ok_button_visible = true; + connect(SNAME("confirmed"), callable_mp(this, &AndroidSDKManager::_force_online_mode)); + } break; + case PROMPTING_ANDROID_SDK_SETUP: { + label = TTRC("Setup Android SDK?"); + ok_button_visible = true; + connect(SNAME("confirmed"), callable_mp(this, &AndroidSDKManager::_setup_android_sdk)); + } break; + case PROMPTING_JAVA_ONLINE_ACCESS: { + label = TTRC("Enable online mode for Java SDK setup?"); + ok_button_visible = true; + connect(SNAME("confirmed"), callable_mp(this, &AndroidSDKManager::_force_online_mode)); + } break; + case PROMPTING_JAVA_SDK_SETUP: { + label = TTRC("Setup Java SDK?"); + ok_button_visible = true; + connect(SNAME("confirmed"), callable_mp(this, &AndroidSDKManager::_setup_java_sdk)); + } break; + case DOWNLOADING_ANDROID_SDK: { + label = TTRC("Downloading Android SDK..."); + show_progress_bar = true; + } break; + case DOWNLOADING_JAVA_SDK: { + label = TTRC("Downloading Java SDK..."); + show_progress_bar = true; + } break; + case INSTALLING_ANDROID_SDK: { + label = TTRC("Installing Android SDK..."); + outputs_visible = true; + show_progress_bar = true; + } break; + case INSTALLING_JAVA_SDK: { + label = TTRC("Installing Java SDK..."); + show_progress_bar = true; + } break; + } + + get_ok_button()->set_visible(ok_button_visible); + execute_outputs->clear(); + execute_outputs->set_visible(outputs_visible); + setup_label->set_text(label); + setup_progress_bar->set_indeterminate(true); + setup_progress_bar->set_visible(show_progress_bar); + reset_size(); + popup_centered(); + + set_process(true); +} + +void AndroidSDKManager::_hide_setup_dialog(SetupStatus p_status) { + if (p_status != current_setup_status) { + return; + } + + switch (p_status) { + case PROMPTING_ANDROID_ONLINE_ACCESS: { + disconnect(SNAME("confirmed"), callable_mp(this, &AndroidSDKManager::_force_online_mode)); + } break; + case PROMPTING_ANDROID_SDK_SETUP: { + disconnect(SNAME("confirmed"), callable_mp(this, &AndroidSDKManager::_setup_android_sdk)); + } break; + case PROMPTING_JAVA_ONLINE_ACCESS: { + disconnect(SNAME("confirmed"), callable_mp(this, &AndroidSDKManager::_force_online_mode)); + } break; + case PROMPTING_JAVA_SDK_SETUP: { + disconnect(SNAME("confirmed"), callable_mp(this, &AndroidSDKManager::_setup_java_sdk)); + } break; + default: { + } break; + } + + current_setup_status = IDLE; + set_process(false); + + hide(); + + execute_outputs->clear(); + execute_outputs->set_visible(false); + setup_label->set_text(""); + setup_progress_bar->set_visible(false); + setup_progress_bar->set_indeterminate(true); + get_ok_button()->hide(); +} + +void AndroidSDKManager::run_setup(const Callable &p_on_setup_completed, const Callable &p_on_setup_cancelled) { + on_setup_cancelled = p_on_setup_cancelled; + if (!is_java_sdk_setup()) { + on_setup_completed = callable_mp(this, &AndroidSDKManager::run_setup).bind(p_on_setup_completed, p_on_setup_cancelled); + _show_setup_dialog(PROMPTING_JAVA_SDK_SETUP); + } else if (!is_android_sdk_setup()) { + on_setup_completed = callable_mp(this, &AndroidSDKManager::run_setup).bind(p_on_setup_completed, p_on_setup_cancelled); + _show_setup_dialog(PROMPTING_ANDROID_SDK_SETUP); + } else { + on_setup_completed = Callable(); + on_setup_cancelled = Callable(); + if (p_on_setup_completed.is_valid()) { + p_on_setup_completed.call_deferred(); + } + } +} + +void AndroidSDKManager::_install_android_sdk_packages() { + String cli_bin = get_android_cli_path(); + if (!FileAccess::exists(cli_bin)) { + ERR_PRINT("Unable to find android-cli binary"); + return; + } + + String default_android_sdk_path = EditorPaths::get_singleton()->get_default_android_sdk_path(); + + List cli_args; + cli_args.push_back("--sdk=" + default_android_sdk_path); + cli_args.push_back("sdk"); + cli_args.push_back("install"); + + const char **packages = ANDROID_SDK_PACKAGES; + while (*packages) { + String package = String(*packages); + print_verbose("Requesting Android SDK package " + package); + cli_args.push_back(package); + packages++; + } + + print_verbose("Installing Android SDK packages to " + default_android_sdk_path); + setup_process_data = OS::get_singleton()->execute_with_pipe(cli_bin, cli_args, false); + if (!setup_process_data.has("pid") || setup_process_data["pid"].operator int() <= 0) { + ERR_PRINT("Installation of Android SDK packages failed."); + _cancel_setup(); + } else { + install_android_sdk_time = OS::get_singleton()->get_ticks_usec(); + _show_setup_dialog(INSTALLING_ANDROID_SDK); + } +} + +void AndroidSDKManager::_android_sdk_installed(int p_exit_code) { + if (p_exit_code != 0) { + ERR_PRINT("Installation of Android SDK packages failed. Check output for the error."); + execute_outputs->add_text(TTR("Installation of Android SDK packages failed. Check output for the error.")); + } else { + // Update the editor settings. + EditorSettings::get_singleton()->set_setting("export/android/android_sdk_path", EditorPaths::get_singleton()->get_default_android_sdk_path()); + EditorSettings::get_singleton()->save(); + + _hide_setup_dialog(current_setup_status); + emit_signal(SNAME("android_sdk_installed")); + + // Run the next setup step.. + if (on_setup_completed.is_valid()) { + on_setup_completed.call_deferred(); + } + } +} + +void AndroidSDKManager::_setup_android_sdk() { + if (is_android_sdk_setup()) { + print_verbose("Android SDK is already setup!"); + return; + } + + if (_is_android_sdk_setup(EditorPaths::get_singleton()->get_default_android_sdk_path())) { + // Check if the Android SDK is already set up in the default path. If it's, we just update the editor settings + // to point to the default Android SDK path. + _android_sdk_installed(0); + } else { + if (_is_online()) { + String cli_bin = get_android_cli_path(); + if (!FileAccess::exists(cli_bin)) { + _download_android_cli(); + } else { + _install_android_sdk_packages(); + } + } else { + _show_setup_dialog(PROMPTING_ANDROID_ONLINE_ACCESS); + } + } +} + +void AndroidSDKManager::_setup_java_sdk() { + if (is_java_sdk_setup()) { + print_verbose("Java SDK is already setup!"); + return; + } + + if (_is_java_sdk_setup(EditorPaths::get_singleton()->get_default_java_sdk_path())) { + // Check if the Java SDK is already set up in the default path. If it's, we just update the editor settings + // to point to the default Java SDK path. + _java_sdk_installed(); + } else { + if (_is_online()) { + _download_java_sdk(); + } else { + _show_setup_dialog(PROMPTING_JAVA_ONLINE_ACCESS); + } + } +} + +void AndroidSDKManager::_download_java_sdk() { + String os_str; + if (OS::get_singleton()->has_feature("windows")) { + os_str = "windows"; + } else if (OS::get_singleton()->has_feature("macos")) { + os_str = "mac"; + } else if (OS::get_singleton()->has_feature("linux")) { + os_str = "linux"; + } + + if (os_str.is_empty()) { + ERR_PRINT("Unsupported OS for Java SDK download."); + return; + } + + String arch_str = "x64"; + if (Engine::get_singleton()->get_architecture_name() == "arm64") { + arch_str = "aarch64"; + } + + String url = vformat("https://api.adoptium.net/v3/binary/latest/%d/ga/%s/%s/jdk/hotspot/normal/eclipse", DEFAULT_JAVA_VERSION, os_str, arch_str); + + Error err = downloader->request(url); + if (err != OK) { + ERR_PRINT("Failed to start Java SDK download."); + _cancel_setup(); + } else { + print_verbose("Downloading Java SDK from " + url); + _show_setup_dialog(DOWNLOADING_JAVA_SDK); + } +} + +void AndroidSDKManager::_on_download_completed(int p_result, int p_code, const PackedStringArray &p_headers, const PackedByteArray &p_body) { + if (p_result != HTTPRequest::RESULT_SUCCESS || p_code != HTTPClient::RESPONSE_OK) { + ERR_PRINT(vformat("Download failed. Result: %d\nResponse code: %d", p_result, p_code)); + _cancel_setup(); + } else { + if (current_setup_status == DOWNLOADING_JAVA_SDK) { + _java_sdk_downloaded(p_body); + } else if (current_setup_status == DOWNLOADING_ANDROID_SDK) { + _android_cli_downloaded(p_body); + } + } +} + +void AndroidSDKManager::_java_sdk_downloaded(const PackedByteArray &p_body) { + _show_setup_dialog(INSTALLING_JAVA_SDK); + + String cache_dir = EditorPaths::get_singleton()->get_cache_dir(); + String archive_ext = ".tar.gz"; +#ifdef WINDOWS_ENABLED + archive_ext = ".zip"; +#endif + String archive_path = cache_dir.path_join("java_sdk_temp" + archive_ext); + + Ref f = FileAccess::open(archive_path, FileAccess::WRITE); + if (f.is_null()) { + ERR_PRINT("Failed to save Java SDK archive."); + return; + } + print_verbose("Storing Java SDK archive to " + archive_path); + f->store_buffer(p_body.ptr(), p_body.size()); + f.unref(); + + String default_java_sdk_path = EditorPaths::get_singleton()->get_default_java_sdk_path(); + Error err = _extract_java_sdk(archive_path, default_java_sdk_path); + if (err != OK) { + ERR_PRINT("Unable to extract Java SDK."); + _cancel_setup(); + return; + } + + // Remove temporary archive. + print_verbose("Deleting Java SDK archive..."); + DirAccess::remove_file_or_error(archive_path); + + _java_sdk_installed(); +} + +void AndroidSDKManager::_java_sdk_installed() { + // Update the editor settings. + EditorSettings::get_singleton()->set_setting("export/android/java_sdk_path", EditorPaths::get_singleton()->get_default_java_sdk_path()); + EditorSettings::get_singleton()->save(); + + _hide_setup_dialog(current_setup_status); + emit_signal(SNAME("java_sdk_installed")); + + // Run the next setup step.. + if (on_setup_completed.is_valid()) { + on_setup_completed.call_deferred(); + } +} + +Error AndroidSDKManager::_extract_java_sdk(const String &p_file, const String &p_target_path) { + print_verbose(vformat("Extracting Java SDK from %s to %s", p_file, p_target_path)); + if (OS::get_singleton()->has_feature("windows")) { + Ref reader; + reader.instantiate(); + Error err = reader->open(p_file); + if (err != OK) { + ERR_PRINT(vformat("Can't open the Java SDK zip file %s: %s", p_file, error_names[err])); + return err; + } + + Ref da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + PackedStringArray reader_files = reader->get_files(); + for (const String &file : reader_files) { + String file_path = file; + if (file_path.ends_with("/")) { + continue; + } + + // Temurin archives usually have a root directory like `jdk-17.0.x+y`. + // We want to extract its contents directly into target_path. + int first_slash = file_path.find("/"); + if (first_slash != -1) { + file_path = file_path.substr(first_slash + 1); + } + + if (file_path.is_empty()) { + continue; + } + + String output_file = p_target_path.path_join(file_path); + err = da->make_dir_recursive(output_file.get_base_dir()); + if (err != OK) { + ERR_PRINT(vformat("Failed to create directory %s.", output_file.get_base_dir())); + return err; + } + + PackedByteArray buffer = reader->read_file(file, true); + Ref f = FileAccess::open(output_file, FileAccess::WRITE); + if (f.is_valid()) { + f->store_buffer(buffer); + f.unref(); + } + } + return OK; + } else { + // Use `tar` for extraction on Linux and macOS. + Ref da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + if (!da->dir_exists(p_target_path)) { + da->make_dir_recursive(p_target_path); + } + + // We strip the root folder. + uint64_t strip_components = 1; + if (OS::get_singleton()->has_feature("macos")) { + // The actual content is 3 components deep for macOS archives (e.g. 'root/Contents/Home/'). + strip_components = 3; + } + + List args; + args.push_back("-xzf"); + args.push_back(p_file); + args.push_back("-C"); + args.push_back(p_target_path); + args.push_back("--strip-components=" + String::num_uint64(strip_components)); + + int retval; + Error err = OS::get_singleton()->execute("tar", args, nullptr, &retval, false); + if (err != OK || retval != 0) { + ERR_PRINT("Failed to extract Java SDK using tar."); + return FAILED; + } + return err; + } +} + +void AndroidSDKManager::_download_android_cli() { + String url; + if (OS::get_singleton()->has_feature("windows")) { + url = ANDROID_CLI_URL_WIN; + } else if (OS::get_singleton()->has_feature("macos")) { + if (Engine::get_singleton()->get_architecture_name() == "arm64") { + url = ANDROID_CLI_URL_MAC_ARM; + } else { + url = ANDROID_CLI_URL_MAC_X86; + } + } else if (OS::get_singleton()->has_feature("linux")) { + url = ANDROID_CLI_URL_LINUX; + } + + if (url.is_empty()) { + ERR_PRINT("Unsupported OS for android-cli download."); + return; + } + + Error err = downloader->request(url); + if (err != OK) { + ERR_PRINT("Failed to start android-cli download."); + _cancel_setup(); + } else { + print_verbose("Downloading android-cli from " + url); + _show_setup_dialog(DOWNLOADING_ANDROID_SDK); + } +} + +void AndroidSDKManager::_android_cli_downloaded(const PackedByteArray &p_body) { + String cli_path = EditorPaths::get_singleton()->get_cache_dir().path_join("android-cli"); + Ref da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + if (!da->dir_exists(cli_path)) { + da->make_dir_recursive(cli_path); + } + + String exe_ext; +#ifdef WINDOWS_ENABLED + exe_ext = ".exe"; +#endif + String cli_bin = cli_path.path_join("android" + exe_ext); + + Ref f = FileAccess::open(cli_bin, FileAccess::WRITE); + if (f.is_null()) { + ERR_PRINT("Failed to save android-cli binary."); + } else { + print_verbose("Storing android-cli to " + cli_bin); + f->store_buffer(p_body.ptr(), p_body.size()); + f.unref(); + +#ifndef WINDOWS_ENABLED + FileAccess::set_unix_permissions(cli_bin, BitField(0755)); +#endif + + _install_android_sdk_packages(); + } +} + +bool AndroidSDKManager::_is_online() { + return (int)EDITOR_GET("network/connection/network_mode") == EditorSettings::NETWORK_ONLINE; +} + +void AndroidSDKManager::_force_online_mode() { + EditorSettings::get_singleton()->set_setting("network/connection/network_mode", EditorSettings::NETWORK_ONLINE); + EditorSettings::get_singleton()->notify_changes(); + EditorSettings::get_singleton()->save(); + + if (current_setup_status == PROMPTING_ANDROID_ONLINE_ACCESS) { + _setup_android_sdk(); + } else if (current_setup_status == PROMPTING_JAVA_ONLINE_ACCESS) { + _setup_java_sdk(); + } +} + +bool AndroidSDKManager::is_java_sdk_setup(String *r_error) { + return _is_java_sdk_setup(EDITOR_GET("export/android/java_sdk_path"), r_error); +} + +bool AndroidSDKManager::_is_java_sdk_setup(const String &p_java_sdk_path, String *r_error) { + // Check the Java SDK is set up. + if (p_java_sdk_path.is_empty()) { + if (r_error) { + *r_error += TTR("Invalid Java SDK path in Editor Settings.") + "\n"; + } + return false; + } else { + // Validate the given path by checking that `java` is present under the `bin` directory. + Error errn; + // Check for the bin directory. + Ref da = DirAccess::open(p_java_sdk_path.path_join("bin"), &errn); + if (errn != OK) { + if (r_error) { + *r_error += TTR("Invalid Java SDK setup.") + " "; + *r_error += "\n"; + } + return false; + } else { + // Check for the `java` command. + String java_path = _get_java_path(p_java_sdk_path); + if (!FileAccess::exists(java_path)) { + if (r_error) { + *r_error += TTR("Unable to find 'java' command using the Java SDK path. Please check the Java SDK directory specified in Editor Settings.") + "\n"; + } + return false; + } + } + } + return true; +} + +bool AndroidSDKManager::is_android_sdk_setup(String *r_error) { + return _is_android_sdk_setup(EDITOR_GET("export/android/android_sdk_path"), r_error); +} + +bool AndroidSDKManager::_is_android_sdk_setup(const String &p_android_sdk_path, String *r_error) { + // Check the Android SDK is set up. + if (p_android_sdk_path.is_empty()) { + if (r_error) { + *r_error += TTR("Invalid Android SDK path in Editor Settings.") + "\n"; + } + return false; + } else { + Error errn; + + // Check the Android SDK packages are installed. + const char **packages = ANDROID_SDK_PACKAGES; + while (*packages) { + String package = String(*packages); + Ref da = DirAccess::open(p_android_sdk_path.path_join(package), &errn); + if (errn != OK) { + if (r_error) { + *r_error += TTR("Unable to find Android SDK package '") + package + "'.\n"; + } + return false; + } + + packages++; + } + + // Validate that adb is available. + String adb_path = _get_adb_path(p_android_sdk_path); + if (!FileAccess::exists(adb_path)) { + if (r_error) { + *r_error += TTR("Unable to find Android SDK platform-tools' adb command. Please check in the Android SDK directory specified in Editor Settings.") + "\n"; + } + return false; + } + } + + return true; +} + +String AndroidSDKManager::get_android_cli_path() { + String cli_path = EditorPaths::get_singleton()->get_cache_dir().path_join("android-cli"); + String exe_ext; + if (OS::get_singleton()->get_name() == "Windows") { + exe_ext = ".exe"; + } + return cli_path.path_join("android" + exe_ext); +} + +String AndroidSDKManager::_get_java_path(const String &p_java_sdk_path) { + String exe_ext; + if (OS::get_singleton()->get_name() == "Windows") { + exe_ext = ".exe"; + } + return p_java_sdk_path.path_join("bin/java" + exe_ext); +} + +String AndroidSDKManager::get_java_path() { + return _get_java_path(EDITOR_GET("export/android/java_sdk_path")); +} + +String AndroidSDKManager::_get_adb_path(const String &p_android_sdk_path) { + String exe_ext; + if (OS::get_singleton()->get_name() == "Windows") { + exe_ext = ".exe"; + } + return p_android_sdk_path.path_join("platform-tools/adb" + exe_ext); +} + +String AndroidSDKManager::get_adb_path() { + return _get_adb_path(EDITOR_GET("export/android/android_sdk_path")); +} + +String AndroidSDKManager::get_apksigner_path(int p_target_sdk, bool p_check_executes) { + if (p_target_sdk == -1) { + p_target_sdk = DEFAULT_TARGET_SDK_VERSION; + } + String exe_ext; + if (OS::get_singleton()->get_name() == "Windows") { + exe_ext = ".bat"; + } + String apksigner_command_name = "apksigner" + exe_ext; + String sdk_path = EDITOR_GET("export/android/android_sdk_path"); + String apksigner_path; + + Error errn; + String build_tools_dir = sdk_path.path_join("build-tools"); + Ref da = DirAccess::open(build_tools_dir, &errn); + if (errn != OK) { + print_error("Unable to open Android 'build-tools' directory."); + return apksigner_path; + } + + // There are additional versions directories we need to go through. + Vector dir_list = da->get_directories(); + + // We need to use the version of build_tools that matches the Target SDK + // If somehow we can't find that, we see if a version between 28 and the default target SDK exists. + // We need to avoid versions <= 27 because they fail on Java versions >9 + // If we can't find that, we just use the first valid version. + Vector ideal_versions; + Vector other_versions; + Vector versions; + bool found_target_sdk = false; + // We only allow for versions <= 27 if specifically set + int min_version = p_target_sdk <= 27 ? p_target_sdk : 28; + for (String sub_dir : dir_list) { + if (!sub_dir.begins_with(".")) { + Vector ver_numbers = sub_dir.split("."); + // Dir not a version number, will use as last resort + if (!ver_numbers.size() || !ver_numbers[0].is_valid_int()) { + other_versions.push_back(sub_dir); + continue; + } + int ver_number = ver_numbers[0].to_int(); + if (ver_number == p_target_sdk) { + found_target_sdk = true; + //ensure this is in front of the ones we check + versions.push_back(sub_dir); + } else { + if (ver_number >= min_version && ver_number <= DEFAULT_TARGET_SDK_VERSION) { + ideal_versions.push_back(sub_dir); + } else { + other_versions.push_back(sub_dir); + } + } + } + } + // we will check ideal versions first, then other versions. + versions.append_array(ideal_versions); + versions.append_array(other_versions); + + if (!versions.size()) { + print_error("Unable to find the 'apksigner' tool."); + return apksigner_path; + } + + int i; + bool failed = false; + String version_to_use; + + String java_sdk_path = EDITOR_GET("export/android/java_sdk_path"); + if (!java_sdk_path.is_empty()) { + OS::get_singleton()->set_environment("JAVA_HOME", java_sdk_path); + +#ifdef UNIX_ENABLED + String env_path = OS::get_singleton()->get_environment("PATH"); + if (!env_path.contains(java_sdk_path)) { + OS::get_singleton()->set_environment("PATH", java_sdk_path + "/bin:" + env_path); + } +#endif + } + + List args; + args.push_back("--version"); + String output; + int retval; + Error err; + for (i = 0; i < versions.size(); i++) { + // Check if the tool is here. + apksigner_path = build_tools_dir.path_join(versions[i]).path_join(apksigner_command_name); + if (FileAccess::exists(apksigner_path)) { + version_to_use = versions[i]; + // If we aren't exporting, just break here. + if (!p_check_executes) { + break; + } + // we only check to see if it executes on export because it is slow to load + err = OS::get_singleton()->execute(apksigner_path, args, &output, &retval, false); + if (err || retval) { + failed = true; + } else { + break; + } + } + } + if (i == versions.size()) { + if (failed) { + print_error("All located 'apksigner' tools in " + build_tools_dir + " failed to execute"); + return ""; + } else { + print_error("Unable to find the 'apksigner' tool."); + return ""; + } + } + if (!found_target_sdk) { + print_line("Could not find version of build tools that matches Target SDK, using " + version_to_use); + } else if (failed && found_target_sdk) { + print_line("Version of build tools that matches Target SDK failed to execute, using " + version_to_use); + } + + return apksigner_path; +} + +AndroidSDKManager::AndroidSDKManager() { + set_title(TTRC("Android SDK Manager")); + add_cancel_button(); + get_ok_button()->hide(); + + VBoxContainer *container = memnew(VBoxContainer); + add_child(container); + + setup_label = memnew(Label); + container->add_child(setup_label); + + setup_progress_bar = memnew(ProgressBar); + setup_progress_bar->set_indeterminate(true); + container->add_child(setup_progress_bar); + + execute_outputs = memnew(RichTextLabel); + execute_outputs->set_selection_enabled(true); + execute_outputs->set_context_menu_enabled(true); + execute_outputs->set_scroll_follow(true); + execute_outputs->set_custom_minimum_size(Size2i(300, 200) * EDSCALE); + execute_outputs->set_v_size_flags(Control::SIZE_EXPAND_FILL); + container->add_child(execute_outputs); + + downloader = memnew(HTTPRequest); + add_child(downloader); + downloader->connect(SNAME("request_completed"), callable_mp(this, &AndroidSDKManager::_on_download_completed)); +} diff --git a/editor/export/android_sdk_manager.h b/editor/export/android_sdk_manager.h new file mode 100644 index 000000000000..156438bb02e7 --- /dev/null +++ b/editor/export/android_sdk_manager.h @@ -0,0 +1,116 @@ +/**************************************************************************/ +/* android_sdk_manager.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#pragma once + +#include "scene/gui/dialogs.h" +#include "scene/main/http_request.h" + +class Label; +class ProgressBar; +class RichTextLabel; + +class AndroidSDKManager : public AcceptDialog { + GDCLASS(AndroidSDKManager, AcceptDialog) + + enum SetupStatus { + IDLE, + PROMPTING_ANDROID_ONLINE_ACCESS, + PROMPTING_ANDROID_SDK_SETUP, + PROMPTING_JAVA_ONLINE_ACCESS, + PROMPTING_JAVA_SDK_SETUP, + DOWNLOADING_ANDROID_SDK, + DOWNLOADING_JAVA_SDK, + INSTALLING_ANDROID_SDK, + INSTALLING_JAVA_SDK, + }; + + void _cancel_setup(); + void _on_download_completed(int p_result, int p_code, const PackedStringArray &p_headers, const PackedByteArray &p_body); + + void _setup_android_sdk(); + void _download_android_cli(); + void _android_cli_downloaded(const PackedByteArray &p_body); + void _install_android_sdk_packages(); + void _android_sdk_installed(int p_exit_code); + + void _setup_java_sdk(); + void _download_java_sdk(); + void _java_sdk_downloaded(const PackedByteArray &p_body); + Error _extract_java_sdk(const String &p_file, const String &p_target_path); + void _java_sdk_installed(); + + void _show_setup_dialog(SetupStatus p_status); + void _hide_setup_dialog(SetupStatus p_status); + + bool _is_online(); + void _force_online_mode(); + + static bool _is_android_sdk_setup(const String &p_android_sdk_path, String *r_error = nullptr); + static bool _is_java_sdk_setup(const String &p_java_sdk_path, String *r_error = nullptr); + + static String _get_java_path(const String &p_java_sdk_path); + static String _get_adb_path(const String &p_android_sdk_path); + + static String get_android_cli_path(); + + Callable on_setup_completed; + Callable on_setup_cancelled; + Dictionary setup_process_data; + uint64_t install_android_sdk_time; + Label *setup_label = nullptr; + RichTextLabel *execute_outputs = nullptr; + ProgressBar *setup_progress_bar = nullptr; + HTTPRequest *downloader = nullptr; + SetupStatus current_setup_status = IDLE; + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + constexpr static const int DEFAULT_MIN_SDK_VERSION = 24; // Should match the value in 'platform/android/java/app/config.gradle#minSdk'. + constexpr static const int VULKAN_MIN_SDK_VERSION = 29; // Minimum recommended sdk version for Vulkan 1.1 support. See https://developer.android.com/games/develop/vulkan/native-engine-support#recommendations. + constexpr static const int DEFAULT_TARGET_SDK_VERSION = 36; // Should match the value in 'platform/android/java/app/config.gradle#targetSdk'. + constexpr static const int DEFAULT_JAVA_VERSION = 17; // Should match the value in 'platform/android/java/app/config.gradle#javaVersion'. + + void run_setup(const Callable &p_on_setup_completed = Callable(), const Callable &p_on_setup_cancelled = Callable()); + + /// Returns true if the Android SDK is set up. + static bool is_android_sdk_setup(String *r_error = nullptr); + /// Returns true if the Java SDK is set up. + static bool is_java_sdk_setup(String *r_error = nullptr); + + static String get_java_path(); + static String get_adb_path(); + static String get_apksigner_path(int p_target_sdk = -1, bool p_check_executes = false); + + AndroidSDKManager(); +}; diff --git a/editor/export/export_template_manager.cpp b/editor/export/export_template_manager.cpp index ad6afe30343f..bf53c9854986 100644 --- a/editor/export/export_template_manager.cpp +++ b/editor/export/export_template_manager.cpp @@ -1377,7 +1377,7 @@ void ExportTemplateManager::_notification(int p_what) { } progress += indeterminate_count; EditorNode::get_bottom_panel()->get_progress_indicator()->set_value(progress / download_count); - } + } break; } } diff --git a/editor/export/export_template_manager.h b/editor/export/export_template_manager.h index 40f96db7c55e..d31f90ff94a4 100644 --- a/editor/export/export_template_manager.h +++ b/editor/export/export_template_manager.h @@ -238,7 +238,6 @@ class ExportTemplateManager : public AcceptDialog { void _update_version_list(); void _update_template_tree(); void _fill_template_tree(Tree *p_tree, const HashMap> &p_installed_template_files, bool p_is_current_version); - void _update_template_tree_with_folding(); void _update_install_button(); bool _can_download_templates(); diff --git a/editor/export/project_export.cpp b/editor/export/project_export.cpp index 82622936ed4b..72f244710b5d 100644 --- a/editor/export/project_export.cpp +++ b/editor/export/project_export.cpp @@ -61,6 +61,10 @@ #include "scene/gui/tree.h" #include "servers/display/display_server.h" +#ifndef ANDROID_ENABLED +#include "editor/export/android_sdk_manager.h" +#endif + #include void ProjectExportTextureFormatError::_on_fix_texture_format_pressed() { @@ -385,6 +389,21 @@ void ProjectExportDialog::_edit_preset(int p_index) { export_templates_error->hide(); } +#ifndef ANDROID_ENABLED + if (current->get_platform()->is_class("EditorExportPlatformAndroid") && !(AndroidSDKManager::is_android_sdk_setup() && AndroidSDKManager::is_java_sdk_setup())) { + if (!AndroidSDKManager::is_java_sdk_setup() && !AndroidSDKManager::is_android_sdk_setup()) { + setup_android_java_sdk->set_text(TTRC("Setup Android Build")); + } else if (!AndroidSDKManager::is_java_sdk_setup()) { + setup_android_java_sdk->set_text(TTRC("Setup Java SDK")); + } else if (!AndroidSDKManager::is_android_sdk_setup()) { + setup_android_java_sdk->set_text(TTRC("Setup Android SDK")); + } + android_sdk_error_container->show(); + } else { + android_sdk_error_container->hide(); + } +#endif + export_warning->hide(); export_button->set_disabled(true); } else { @@ -405,6 +424,9 @@ void ProjectExportDialog::_edit_preset(int p_index) { export_error->hide(); export_templates_error->hide(); +#ifndef ANDROID_ENABLED + android_sdk_error_container->hide(); +#endif export_button->set_disabled(false); } @@ -547,6 +569,14 @@ void ProjectExportDialog::_advanced_options_pressed() { _update_presets(); } +#ifndef ANDROID_ENABLED +void ProjectExportDialog::_update_android_sdk_error_container() { + if (android_sdk_error_container->is_visible() && (AndroidSDKManager::is_android_sdk_setup() || AndroidSDKManager::is_java_sdk_setup())) { + _update_current_preset(); + } +} +#endif + void ProjectExportDialog::_options_filter_changed(const String &p_filter) { const bool search_active = !p_filter.is_empty(); advanced_options->set_disabled(search_active); @@ -2090,6 +2120,23 @@ ProjectExportDialog::ProjectExportDialog() { export_templates_error->add_child(download_templates); download_templates->connect(SceneStringName(pressed), callable_mp(this, &ProjectExportDialog::_open_export_template_manager)); +#ifndef ANDROID_ENABLED + // Android SDK errors bottom section. + android_sdk_error_container = memnew(HBoxContainer); + main_vb->add_child(android_sdk_error_container); + android_sdk_error_container->hide(); + + AndroidSDKManager *android_sdk_manager = memnew(AndroidSDKManager); + add_child(android_sdk_manager); + android_sdk_manager->connect("java_sdk_installed", callable_mp(this, &ProjectExportDialog::_update_android_sdk_error_container)); + android_sdk_manager->connect("android_sdk_installed", callable_mp(this, &ProjectExportDialog::_update_android_sdk_error_container)); + + setup_android_java_sdk = memnew(LinkButton); + setup_android_java_sdk->set_v_size_flags(Control::SIZE_SHRINK_CENTER); + android_sdk_error_container->add_child(setup_android_java_sdk); + setup_android_java_sdk->connect(SceneStringName(pressed), callable_mp(android_sdk_manager, &AndroidSDKManager::run_setup).bind(Callable(), Callable())); +#endif + // Export project file dialog. export_project = memnew(EditorFileDialog); diff --git a/editor/export/project_export.h b/editor/export/project_export.h index b5593e95ccae..2a54b755d4fc 100644 --- a/editor/export/project_export.h +++ b/editor/export/project_export.h @@ -138,6 +138,12 @@ class ProjectExportDialog : public ConfirmationDialog { Label *export_warning = nullptr; HBoxContainer *export_templates_error = nullptr; +#ifndef ANDROID_ENABLED + HBoxContainer *android_sdk_error_container = nullptr; + LinkButton *setup_android_java_sdk = nullptr; + void _update_android_sdk_error_container(); +#endif + String default_filename; bool exporting = false; diff --git a/editor/file_system/editor_paths.cpp b/editor/file_system/editor_paths.cpp index 2738dd04fb7e..31fbed3270e2 100644 --- a/editor/file_system/editor_paths.cpp +++ b/editor/file_system/editor_paths.cpp @@ -84,6 +84,22 @@ String EditorPaths::get_debug_keystore_path() const { #endif } +String EditorPaths::get_default_android_sdk_path() const { +#ifdef WINDOWS_ENABLED + return OS::get_singleton()->get_environment("LOCALAPPDATA").path_join("Android/Sdk"); +#elif LINUXBSD_ENABLED + return OS::get_singleton()->get_environment("HOME").path_join("Android/Sdk"); +#elif MACOS_ENABLED + return OS::get_singleton()->get_environment("HOME").path_join("Library/Android/sdk"); +#else + return get_cache_dir().path_join("android-sdk"); +#endif +} + +String EditorPaths::get_default_java_sdk_path() const { + return get_cache_dir().path_join("java-sdk"); +} + // This returns paths like "res://.godot/editor". String EditorPaths::get_project_settings_dir() const { return get_project_data_dir().path_join("editor"); diff --git a/editor/file_system/editor_paths.h b/editor/file_system/editor_paths.h index 4dcf37b579c7..b0d38d8cadfd 100644 --- a/editor/file_system/editor_paths.h +++ b/editor/file_system/editor_paths.h @@ -64,6 +64,8 @@ class EditorPaths : public Object { String get_project_data_dir() const; String get_export_templates_dir() const; String get_debug_keystore_path() const; + String get_default_java_sdk_path() const; + String get_default_android_sdk_path() const; String get_project_settings_dir() const; String get_text_editor_themes_dir() const; String get_script_templates_dir() const; diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp index bc4aca776d16..8c82a2547357 100644 --- a/platform/android/export/export.cpp +++ b/platform/android/export/export.cpp @@ -38,8 +38,6 @@ #include "editor/file_system/editor_paths.h" #include "editor/settings/editor_settings.h" -String get_default_android_sdk_path(); - void register_android_exporter_types() { GDREGISTER_VIRTUAL_CLASS(EditorExportPlatformAndroid); } @@ -56,10 +54,10 @@ void register_android_exporter() { #ifdef ANDROID_ENABLED EDITOR_DEF_BASIC("export/android/install_exported_apk", !OS::get_singleton()->has_feature("horizonos")); #else - EDITOR_DEF_BASIC("export/android/java_sdk_path", OS::get_singleton()->get_environment("JAVA_HOME")); + EDITOR_DEF("export/android/java_sdk_path", OS::get_singleton()->has_environment("JAVA_HOME") ? OS::get_singleton()->get_environment("JAVA_HOME") : EditorPaths::get_singleton()->get_default_java_sdk_path()); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/java_sdk_path", PROPERTY_HINT_GLOBAL_DIR)); - EDITOR_DEF_BASIC("export/android/android_sdk_path", OS::get_singleton()->has_environment("ANDROID_HOME") ? OS::get_singleton()->get_environment("ANDROID_HOME") : get_default_android_sdk_path()); + EDITOR_DEF("export/android/android_sdk_path", OS::get_singleton()->has_environment("ANDROID_HOME") ? OS::get_singleton()->get_environment("ANDROID_HOME") : EditorPaths::get_singleton()->get_default_android_sdk_path()); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/android_sdk_path", PROPERTY_HINT_GLOBAL_DIR)); EDITOR_DEF_BASIC("export/android/scrcpy/path", ""); @@ -88,15 +86,3 @@ void register_android_exporter() { Ref exporter = Ref(memnew(EditorExportPlatformAndroid)); EditorExport::get_singleton()->add_export_platform(exporter); } - -inline String get_default_android_sdk_path() { -#ifdef WINDOWS_ENABLED - return OS::get_singleton()->get_environment("LOCALAPPDATA").path_join("Android/Sdk"); -#elif LINUXBSD_ENABLED - return OS::get_singleton()->get_environment("HOME").path_join("Android/Sdk"); -#elif MACOS_ENABLED - return OS::get_singleton()->get_environment("HOME").path_join("Library/Android/sdk"); -#else - return String(); -#endif -} diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index fb29a77c9273..793cd38f5ecd 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -44,6 +44,7 @@ #include "core/string/translation_server.h" #include "core/version.h" #include "editor/editor_node.h" +#include "editor/export/android_sdk_manager.h" #include "editor/export/editor_export.h" #include "editor/export/editor_export_plugin.h" #include "editor/export/export_template_manager.h" @@ -298,10 +299,6 @@ static const int EXPORT_FORMAT_AAB = 1; static const char *APK_ASSETS_DIRECTORY = "src/main/assets"; static const char *AAB_ASSETS_DIRECTORY = "assetPackInstallTime/src/main/assets"; -static const int DEFAULT_MIN_SDK_VERSION = 24; // Should match the value in 'platform/android/java/app/config.gradle#minSdk' -static const int VULKAN_MIN_SDK_VERSION = 29; // Minimum recommended sdk version for Vulkan 1.1 support. See https://developer.android.com/games/develop/vulkan/native-engine-support#recommendations -static const int DEFAULT_TARGET_SDK_VERSION = 36; // Should match the value in 'platform/android/java/app/config.gradle#targetSdk' - #ifndef ANDROID_ENABLED void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) { if (!EditorSettings::get_singleton()) { @@ -341,7 +338,7 @@ void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) { #endif // DISABLE_DEPRECATED // Check for devices updates - String adb = get_adb_path(); + String adb = AndroidSDKManager::get_adb_path(); // adb.exe was locking the editor_doc_cache file on startup. Adding a check for is_editor_ready provides just enough time // to regenerate the doc cache. if (ea->has_runnable_preset.is_set() && FileAccess::exists(adb) && EditorNode::get_singleton()->is_editor_ready()) { @@ -466,7 +463,7 @@ void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) { } if (ea->has_runnable_preset.is_set() && EDITOR_GET("export/android/shutdown_adb_on_exit")) { - String adb = get_adb_path(); + String adb = AndroidSDKManager::get_adb_path(); if (!FileAccess::exists(adb)) { return; //adb not configured } @@ -2122,17 +2119,17 @@ String EditorExportPlatformAndroid::get_export_option_warning(const EditorExport return vformat(TTR("\"Min SDK\" should be a valid integer, but got \"%s\" which is invalid."), min_sdk_str); } else { int min_sdk_int = min_sdk_str.to_int(); - if (min_sdk_int < DEFAULT_MIN_SDK_VERSION) { - return vformat(TTR("\"Min SDK\" cannot be lower than %d, which is the version needed by the Godot library."), DEFAULT_MIN_SDK_VERSION); + if (min_sdk_int < AndroidSDKManager::DEFAULT_MIN_SDK_VERSION) { + return vformat(TTR("\"Min SDK\" cannot be lower than %d, which is the version needed by the Godot library."), AndroidSDKManager::DEFAULT_MIN_SDK_VERSION); } } } } else if (p_name == "gradle_build/target_sdk") { String target_sdk_str = p_preset->get("gradle_build/target_sdk"); - int target_sdk_int = DEFAULT_TARGET_SDK_VERSION; + int target_sdk_int = AndroidSDKManager::DEFAULT_TARGET_SDK_VERSION; String min_sdk_str = p_preset->get("gradle_build/min_sdk"); - int min_sdk_int = _uses_vulkan(Ref(p_preset)) ? VULKAN_MIN_SDK_VERSION : DEFAULT_MIN_SDK_VERSION; + int min_sdk_int = _uses_vulkan(Ref(p_preset)) ? AndroidSDKManager::VULKAN_MIN_SDK_VERSION : AndroidSDKManager::DEFAULT_MIN_SDK_VERSION; if (min_sdk_str.is_valid_int()) { min_sdk_int = min_sdk_str.to_int(); } @@ -2193,9 +2190,9 @@ void EditorExportPlatformAndroid::get_export_options(List *r_optio 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. - const String min_sdk_placeholder = _uses_vulkan(nullptr) ? vformat("%d (Vulkan default)", VULKAN_MIN_SDK_VERSION) : vformat("%d (default)", DEFAULT_MIN_SDK_VERSION); + const String min_sdk_placeholder = _uses_vulkan(nullptr) ? vformat("%d (Vulkan default)", AndroidSDKManager::VULKAN_MIN_SDK_VERSION) : vformat("%d (default)", AndroidSDKManager::DEFAULT_MIN_SDK_VERSION); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "gradle_build/min_sdk", PROPERTY_HINT_PLACEHOLDER_TEXT, min_sdk_placeholder), "", false, true)); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "gradle_build/target_sdk", PROPERTY_HINT_PLACEHOLDER_TEXT, vformat("%d (default)", DEFAULT_TARGET_SDK_VERSION)), "", false, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "gradle_build/target_sdk", PROPERTY_HINT_PLACEHOLDER_TEXT, vformat("%d (default)", AndroidSDKManager::DEFAULT_TARGET_SDK_VERSION)), "", false, true)); r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "gradle_build/custom_theme_attributes", PROPERTY_HINT_DICTIONARY_TYPE, "String;String"), Dictionary())); @@ -2428,7 +2425,7 @@ Error EditorExportPlatformAndroid::run(const Ref &p_preset, EditorProgress ep("run", vformat(TTR("Running on %s"), devices[p_device - 1].name), 3); - String adb = get_adb_path(); + String adb = AndroidSDKManager::get_adb_path(); // Export_temp APK. if (ep.step(TTR("Exporting APK..."), 0)) { @@ -2677,15 +2674,6 @@ Ref EditorExportPlatformAndroid::get_run_icon() const { return run_icon; } -String EditorExportPlatformAndroid::get_java_path() { - String exe_ext; - if (OS::get_singleton()->get_name() == "Windows") { - exe_ext = ".exe"; - } - String java_sdk_path = EDITOR_GET("export/android/java_sdk_path"); - return java_sdk_path.path_join("bin/java" + exe_ext); -} - String EditorExportPlatformAndroid::get_keytool_path() { String exe_ext; if (OS::get_singleton()->get_name() == "Windows") { @@ -2695,136 +2683,6 @@ String EditorExportPlatformAndroid::get_keytool_path() { return java_sdk_path.path_join("bin/keytool" + exe_ext); } -String EditorExportPlatformAndroid::get_adb_path() { - String exe_ext; - if (OS::get_singleton()->get_name() == "Windows") { - exe_ext = ".exe"; - } - String sdk_path = EDITOR_GET("export/android/android_sdk_path"); - return sdk_path.path_join("platform-tools/adb" + exe_ext); -} - -String EditorExportPlatformAndroid::get_apksigner_path(int p_target_sdk, bool p_check_executes) { - if (p_target_sdk == -1) { - p_target_sdk = DEFAULT_TARGET_SDK_VERSION; - } - String exe_ext; - if (OS::get_singleton()->get_name() == "Windows") { - exe_ext = ".bat"; - } - String apksigner_command_name = "apksigner" + exe_ext; - String sdk_path = EDITOR_GET("export/android/android_sdk_path"); - String apksigner_path; - - Error errn; - String build_tools_dir = sdk_path.path_join("build-tools"); - Ref da = DirAccess::open(build_tools_dir, &errn); - if (errn != OK) { - print_error("Unable to open Android 'build-tools' directory."); - return apksigner_path; - } - - // There are additional versions directories we need to go through. - Vector dir_list = da->get_directories(); - - // We need to use the version of build_tools that matches the Target SDK - // If somehow we can't find that, we see if a version between 28 and the default target SDK exists. - // We need to avoid versions <= 27 because they fail on Java versions >9 - // If we can't find that, we just use the first valid version. - Vector ideal_versions; - Vector other_versions; - Vector versions; - bool found_target_sdk = false; - // We only allow for versions <= 27 if specifically set - int min_version = p_target_sdk <= 27 ? p_target_sdk : 28; - for (String sub_dir : dir_list) { - if (!sub_dir.begins_with(".")) { - Vector ver_numbers = sub_dir.split("."); - // Dir not a version number, will use as last resort - if (!ver_numbers.size() || !ver_numbers[0].is_valid_int()) { - other_versions.push_back(sub_dir); - continue; - } - int ver_number = ver_numbers[0].to_int(); - if (ver_number == p_target_sdk) { - found_target_sdk = true; - //ensure this is in front of the ones we check - versions.push_back(sub_dir); - } else { - if (ver_number >= min_version && ver_number <= DEFAULT_TARGET_SDK_VERSION) { - ideal_versions.push_back(sub_dir); - } else { - other_versions.push_back(sub_dir); - } - } - } - } - // we will check ideal versions first, then other versions. - versions.append_array(ideal_versions); - versions.append_array(other_versions); - - if (!versions.size()) { - print_error("Unable to find the 'apksigner' tool."); - return apksigner_path; - } - - int i; - bool failed = false; - String version_to_use; - - String java_sdk_path = EDITOR_GET("export/android/java_sdk_path"); - if (!java_sdk_path.is_empty()) { - OS::get_singleton()->set_environment("JAVA_HOME", java_sdk_path); - -#ifdef UNIX_ENABLED - String env_path = OS::get_singleton()->get_environment("PATH"); - if (!env_path.contains(java_sdk_path)) { - OS::get_singleton()->set_environment("PATH", java_sdk_path + "/bin:" + env_path); - } -#endif - } - - List args; - args.push_back("--version"); - String output; - int retval; - Error err; - for (i = 0; i < versions.size(); i++) { - // Check if the tool is here. - apksigner_path = build_tools_dir.path_join(versions[i]).path_join(apksigner_command_name); - if (FileAccess::exists(apksigner_path)) { - version_to_use = versions[i]; - // If we aren't exporting, just break here. - if (!p_check_executes) { - break; - } - // we only check to see if it executes on export because it is slow to load - err = OS::get_singleton()->execute(apksigner_path, args, &output, &retval, false); - if (err || retval) { - failed = true; - } else { - break; - } - } - } - if (i == versions.size()) { - if (failed) { - print_error("All located 'apksigner' tools in " + build_tools_dir + " failed to execute"); - return ""; - } else { - print_error("Unable to find the 'apksigner' tool."); - return ""; - } - } - if (!found_target_sdk) { - print_line("Could not find version of build tools that matches Target SDK, using " + version_to_use); - } else if (failed && found_target_sdk) { - print_line("Version of build tools that matches Target SDK failed to execute, using " + version_to_use); - } - - return apksigner_path; -} - static bool has_valid_keystore_credentials(String &r_error_str, const String &p_keystore, const String &p_username, const String &p_password, const String &p_type) { String output; List args; @@ -3050,77 +2908,25 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref da = DirAccess::open(java_sdk_path.path_join("bin"), &errn); - if (errn != OK) { - err += TTR("Invalid Java SDK path in Editor Settings.") + " "; - err += TTR("Missing 'bin' directory!"); - err += "\n"; - valid = false; - } else { - // Check for the `java` command. - String java_path = get_java_path(); - if (!FileAccess::exists(java_path)) { - err += TTR("Unable to find 'java' command using the Java SDK path.") + " "; - err += TTR("Please check the Java SDK directory specified in Editor Settings."); - err += "\n"; - valid = false; - } - } } - - String sdk_path = EDITOR_GET("export/android/android_sdk_path"); - if (sdk_path.is_empty()) { - err += TTR("A valid Android SDK path is required in Editor Settings.") + "\n"; - valid = false; - } else { - Error errn; - // Check for the platform-tools directory. - Ref da = DirAccess::open(sdk_path.path_join("platform-tools"), &errn); - if (errn != OK) { - err += TTR("Invalid Android SDK path in Editor Settings.") + " "; - err += TTR("Missing 'platform-tools' directory!"); - err += "\n"; - valid = false; - } - - // Validate that adb is available. - String adb_path = get_adb_path(); - if (!FileAccess::exists(adb_path)) { - err += TTR("Unable to find Android SDK platform-tools' adb command.") + " "; - err += TTR("Please check in the Android SDK directory specified in Editor Settings."); - err += "\n"; - valid = false; - } - - // Check for the build-tools directory. - Ref build_tools_da = DirAccess::open(sdk_path.path_join("build-tools"), &errn); - if (errn != OK) { - err += TTR("Invalid Android SDK path in Editor Settings.") + " "; - err += TTR("Missing 'build-tools' directory!"); - err += "\n"; - valid = false; - } - + if (AndroidSDKManager::is_android_sdk_setup(&err)) { + // Validate that apksigner is available. String target_sdk_version = p_preset->get("gradle_build/target_sdk"); if (!target_sdk_version.is_valid_int()) { - target_sdk_version = itos(DEFAULT_TARGET_SDK_VERSION); + target_sdk_version = itos(AndroidSDKManager::DEFAULT_TARGET_SDK_VERSION); } - // Validate that apksigner is available. - String apksigner_path = get_apksigner_path(target_sdk_version.to_int()); + + String apksigner_path = AndroidSDKManager::get_apksigner_path(target_sdk_version.to_int()); if (!FileAccess::exists(apksigner_path)) { err += TTR("Unable to find Android SDK build-tools' apksigner command.") + " "; err += TTR("Please check in the Android SDK directory specified in Editor Settings."); err += "\n"; valid = false; } + } else { + valid = false; } #endif @@ -3166,13 +2972,13 @@ bool EditorExportPlatformAndroid::has_valid_project_configuration(const Refget("gradle_build/target_sdk"); - int target_sdk_int = DEFAULT_TARGET_SDK_VERSION; + int target_sdk_int = AndroidSDKManager::DEFAULT_TARGET_SDK_VERSION; if (!target_sdk_str.is_empty()) { // Empty means no override, nothing to do. if (target_sdk_str.is_valid_int()) { target_sdk_int = target_sdk_str.to_int(); - if (target_sdk_int > DEFAULT_TARGET_SDK_VERSION) { + if (target_sdk_int > AndroidSDKManager::DEFAULT_TARGET_SDK_VERSION) { // Warning only, so don't override `valid`. - err += vformat(TTR("\"Target SDK\" %d is higher than the default version %d. This may work, but wasn't tested and may be unstable."), target_sdk_int, DEFAULT_TARGET_SDK_VERSION); + err += vformat(TTR("\"Target SDK\" %d is higher than the default version %d. This may work, but wasn't tested and may be unstable."), target_sdk_int, AndroidSDKManager::DEFAULT_TARGET_SDK_VERSION); err += "\n"; } } @@ -3187,16 +2993,16 @@ bool EditorExportPlatformAndroid::has_valid_project_configuration(const Refget("gradle_build/min_sdk"); - int min_sdk_int = VULKAN_MIN_SDK_VERSION; + int min_sdk_int = AndroidSDKManager::VULKAN_MIN_SDK_VERSION; if (!min_sdk_str.is_empty()) { // Empty means no override, nothing to do. if (min_sdk_str.is_valid_int()) { min_sdk_int = min_sdk_str.to_int(); } } bool fallback_to_opengl3 = GLOBAL_GET("rendering/rendering_device/fallback_to_opengl3"); - if (min_sdk_int < VULKAN_MIN_SDK_VERSION && !fallback_to_opengl3) { + if (min_sdk_int < AndroidSDKManager::VULKAN_MIN_SDK_VERSION && !fallback_to_opengl3) { // Warning only, so don't override `valid`. - err += vformat(TTR("\"Min SDK\" should be greater or equal to %d for the \"%s\" renderer."), VULKAN_MIN_SDK_VERSION, current_renderer); + err += vformat(TTR("\"Min SDK\" should be greater or equal to %d for the \"%s\" renderer."), AndroidSDKManager::VULKAN_MIN_SDK_VERSION, current_renderer); err += "\n"; } } @@ -3358,10 +3164,10 @@ Error EditorExportPlatformAndroid::sign_apk(const Ref &p_pre #else String target_sdk_version = p_preset->get("gradle_build/target_sdk"); if (!target_sdk_version.is_valid_int()) { - target_sdk_version = itos(DEFAULT_TARGET_SDK_VERSION); + target_sdk_version = itos(AndroidSDKManager::DEFAULT_TARGET_SDK_VERSION); } - String apksigner = get_apksigner_path(target_sdk_version.to_int(), true); + String apksigner = AndroidSDKManager::get_apksigner_path(target_sdk_version.to_int(), true); print_verbose("Starting signing of the APK binary using " + apksigner); if (apksigner == "") { add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("All 'apksigner' tools located in Android SDK 'build-tools' directory failed to execute. Please check that you have the correct version installed for your target sdk version. The resulting APK is unsigned.")); @@ -3849,11 +3655,11 @@ Error EditorExportPlatformAndroid::export_project_helper(const Refget_version("version/name"); String min_sdk_version = p_preset->get("gradle_build/min_sdk"); if (!min_sdk_version.is_valid_int()) { - min_sdk_version = _uses_vulkan(p_preset) ? itos(VULKAN_MIN_SDK_VERSION) : itos(DEFAULT_MIN_SDK_VERSION); + min_sdk_version = _uses_vulkan(p_preset) ? itos(AndroidSDKManager::VULKAN_MIN_SDK_VERSION) : itos(AndroidSDKManager::DEFAULT_MIN_SDK_VERSION); } String target_sdk_version = p_preset->get("gradle_build/target_sdk"); if (!target_sdk_version.is_valid_int()) { - target_sdk_version = itos(DEFAULT_TARGET_SDK_VERSION); + target_sdk_version = itos(AndroidSDKManager::DEFAULT_TARGET_SDK_VERSION); } String enabled_abi_string = join_abis(enabled_abis, "|", false); String sign_flag = bool_to_string(should_sign); diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h index c91c7aa04fc0..69324a769ee5 100644 --- a/platform/android/export/export_plugin.h +++ b/platform/android/export/export_plugin.h @@ -247,12 +247,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { virtual Ref get_run_icon() const override; - static String get_adb_path(); - - static String get_apksigner_path(int p_target_sdk = -1, bool p_check_executes = false); - - static String get_java_path(); - static String get_keytool_path(); virtual bool has_valid_export_configuration(const Ref &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override; diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle index 872f6bbbefa2..4cb465ec553c 100644 --- a/platform/android/java/app/config.gradle +++ b/platform/android/java/app/config.gradle @@ -2,17 +2,21 @@ ext.versions = [ androidGradlePlugin: '8.6.1', compileSdk : 36, // Also update: - // - 'platform/android/export/export_plugin.cpp#DEFAULT_MIN_SDK_VERSION' + // - 'editor/export/android_sdk_manager.h#DEFAULT_MIN_SDK_VERSION' // - 'platform/android/detect.py#get_min_target_api()' minSdk : 24, - // Also update 'platform/android/export/export_plugin.cpp#DEFAULT_TARGET_SDK_VERSION' + // Also update 'editor/export/android_sdk_manager.h#DEFAULT_TARGET_SDK_VERSION'. targetSdk : 36, + // Also update 'editor/export/android_sdk_manager.cpp#ANDROID_SDK_PACKAGES[build-tools]'. buildTools : '36.1.0', kotlinVersion : '2.1.21', fragmentVersion : '1.8.6', nexusPublishVersion: '1.3.0', + // Also update 'editor/export/android_sdk_manager.h#DEFAULT_JAVA_VERSION'. javaVersion : JavaVersion.VERSION_17, - // Also update 'platform/android/detect.py#get_ndk_version()' when this is updated. + // Also update: + // - 'editor/export/android_sdk_manager.cpp#ANDROID_SDK_PACKAGES[ndk]' + // - 'platform/android/detect.py#get_ndk_version()' ndkVersion : '29.0.14206865', splashscreenVersion: '1.0.1', // 'openxrLoaderVersion' should be set to XR_CURRENT_API_VERSION, see 'thirdparty/openxr'