Skip to content

Commit

Permalink
Android: Allow using alternative Gradle build directory
Browse files Browse the repository at this point in the history
  • Loading branch information
dsnopek committed Feb 16, 2024
1 parent dfe226b commit a37ad26
Show file tree
Hide file tree
Showing 9 changed files with 244 additions and 94 deletions.
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");
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

0 comments on commit a37ad26

Please sign in to comment.