Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Android: Allow using alternative Gradle build directory #88297

Merged
merged 1 commit into from
Feb 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 69 additions & 14 deletions editor/editor_node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> p_full_paths, Vector<String> &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()));

Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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<EditorExportPreset> 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;
Expand All @@ -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:
Expand Down Expand Up @@ -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);
Expand Down
5 changes: 5 additions & 0 deletions editor/editor_node.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ class EditorBuildProfileManager;
class EditorCommandPalette;
class EditorDockManager;
class EditorExport;
class EditorExportPreset;
class EditorExtensionManager;
class EditorFeatureProfileManager;
class EditorFileDialog;
Expand Down Expand Up @@ -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<EditorExportPreset> android_export_preset;

PopupMenu *vcs_actions_menu = nullptr;
EditorFileDialog *file = nullptr;
Expand Down Expand Up @@ -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);
Expand Down
72 changes: 56 additions & 16 deletions editor/export/export_template_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -655,39 +656,78 @@ void ExportTemplateManager::_hide_dialog() {
hide();
}

bool ExportTemplateManager::can_install_android_template() {
String ExportTemplateManager::get_android_build_directory(const Ref<EditorExportPreset> &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<EditorExportPreset> &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<EditorExportPreset> &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<EditorExportPreset> &p_preset) {
return DirAccess::exists(get_android_build_directory(p_preset));
}

bool ExportTemplateManager::can_install_android_template(const Ref<EditorExportPreset> &p_preset) {
return FileAccess::exists(get_android_source_zip(p_preset));
}

Error ExportTemplateManager::install_android_template(const Ref<EditorExportPreset> &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<EditorExportPreset> &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<DirAccess> 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");
dsnopek marked this conversation as resolved.
Show resolved Hide resolved
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<FileAccess> 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<FileAccess> 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<FileAccess> f = FileAccess::open("res://android/build/.gdignore", FileAccess::WRITE);
Ref<FileAccess> f = FileAccess::open(build_dir.path_join(".gdignore"), FileAccess::WRITE);
ERR_FAIL_COND_V(f.is_null(), ERR_CANT_CREATE);
f->store_line("");
}
Expand Down Expand Up @@ -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<FileAccess> f = FileAccess::open(to_write, FileAccess::WRITE);
if (f.is_valid()) {
f->store_buffer(uncomp_data.ptr(), uncomp_data.size());
Expand Down
12 changes: 9 additions & 3 deletions editor/export/export_template_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

#include "scene/gui/dialogs.h"

class EditorExportPreset;
class ExportTemplateVersion;
class FileDialog;
class HTTPRequest;
Expand Down Expand Up @@ -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<EditorExportPreset> &p_preset);
static String get_android_source_zip(const Ref<EditorExportPreset> &p_preset);
static String get_android_template_identifier(const Ref<EditorExportPreset> &p_preset);

Error install_android_template_from_file(const String &p_file);
bool is_android_template_installed(const Ref<EditorExportPreset> &p_preset);
bool can_install_android_template(const Ref<EditorExportPreset> &p_preset);
Error install_android_template(const Ref<EditorExportPreset> &p_preset);

Error install_android_template_from_file(const String &p_file, const Ref<EditorExportPreset> &p_preset);

void popup_manager();

Expand Down
12 changes: 10 additions & 2 deletions platform/android/doc_classes/EditorExportPlatformAndroid.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,22 @@
A list of additional command line arguments, exported project will receive when started.
</member>
<member name="custom_template/debug" type="String" setter="" getter="">
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.
</member>
<member name="custom_template/release" type="String" setter="" getter="">
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.
</member>
<member name="gradle_build/android_source_template" type="String" setter="" getter="">
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.
</member>
<member name="gradle_build/export_format" type="int" setter="" getter="">
Export format for Gradle build.
</member>
<member name="gradle_build/gradle_build_directory" type="String" setter="" getter="">
Path to the Gradle build directory. If left empty, then [code]res://android[/code] will be used.
</member>
<member name="gradle_build/min_sdk" type="String" setter="" getter="">
Minimal Android SDK version for Gradle build.
</member>
Expand Down
Loading
Loading