1
0
mirror of https://github.com/godotengine/godot.git synced 2025-12-02 16:48:55 +00:00

Enable Gradle builds on the Android editor via a dedicated build app

Co-authored-by: Logan Lang <devloglogan@gmail.com>
This commit is contained in:
David Snopek
2025-03-13 08:04:15 -05:00
parent 3a97723ff2
commit 5593a0b2b2
16 changed files with 928 additions and 19 deletions

View File

@@ -8528,8 +8528,8 @@ EditorNode::EditorNode() {
project_menu->add_separator(); project_menu->add_separator();
project_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/export", TTRC("Export..."), Key::NONE, TTRC("Export")), PROJECT_EXPORT); project_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/export", TTRC("Export..."), Key::NONE, TTRC("Export")), PROJECT_EXPORT);
project_menu->add_item(TTRC("Pack Project as ZIP..."), PROJECT_PACK_AS_ZIP); project_menu->add_item(TTRC("Pack Project as ZIP..."), PROJECT_PACK_AS_ZIP);
#ifndef ANDROID_ENABLED
project_menu->add_item(TTRC("Install Android Build Template..."), PROJECT_INSTALL_ANDROID_SOURCE); project_menu->add_item(TTRC("Install Android Build Template..."), PROJECT_INSTALL_ANDROID_SOURCE);
#ifndef ANDROID_ENABLED
project_menu->add_item(TTRC("Open User Data Folder"), PROJECT_OPEN_USER_DATA_FOLDER); project_menu->add_item(TTRC("Open User Data Folder"), PROJECT_OPEN_USER_DATA_FOLDER);
#endif #endif

View File

@@ -0,0 +1,192 @@
/**************************************************************************/
/* android_editor_gradle_runner.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. */
/**************************************************************************/
#ifdef ANDROID_ENABLED
#include "android_editor_gradle_runner.h"
#include "editor/editor_interface.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/rich_text_label.h"
#include "../java_godot_wrapper.h"
#include "../os_android.h"
void AndroidEditorGradleRunner::run_gradle(const String &p_project_path, const String &p_build_path, const List<String> &p_gradle_build_args, const List<String> &p_gradle_copy_args) {
project_path = p_project_path;
build_path = p_build_path;
gradle_build_args = p_gradle_build_args;
gradle_copy_args = p_gradle_copy_args;
if (output_dialog == nullptr) {
output_label = memnew(RichTextLabel);
output_label->set_selection_enabled(true);
output_label->set_context_menu_enabled(true);
output_label->set_scroll_follow(true);
output_dialog = memnew(ConfirmationDialog);
output_dialog->set_unparent_when_invisible(true);
output_dialog->set_title(TTR("Building Android Project (gradle)"));
output_dialog->add_child(output_label);
output_dialog->connect("canceled", callable_mp(this, &AndroidEditorGradleRunner::_android_gradle_build_cancel));
}
output_label->clear();
output_dialog->get_ok_button()->set_disabled(true);
EditorInterface::get_singleton()->popup_dialog_centered_ratio(output_dialog);
state = STATE_BUILDING;
_android_gradle_build_connect();
}
void AndroidEditorGradleRunner::_android_gradle_build_connect() {
_android_gradle_build_output(0, TTR("> Connecting to Gradle Build Environment..."));
GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java();
if (!godot_java->build_env_connect(callable_mp(this, &AndroidEditorGradleRunner::_android_gradle_build_build))) {
_android_gradle_build_failed(TTR("Unable to connect to Gradle Build Environment service"));
}
}
void AndroidEditorGradleRunner::_android_gradle_build_disconnect() {
GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java();
godot_java->build_env_disconnect();
}
void AndroidEditorGradleRunner::_android_gradle_build_output(int p_type, const String &p_line) {
if (p_type == 0) {
print_line(p_line);
output_label->append_text("[color=green]" + p_line + "[/color]\n");
} else if (p_type == 1) {
print_line(p_line);
output_label->add_text(p_line + "\n");
} else {
print_error(p_line);
output_label->append_text("[color=red]" + p_line + "[/color]\n");
}
}
void AndroidEditorGradleRunner::_android_gradle_build_build() {
_android_gradle_build_output(0, TTR("> Starting Gradle build..."));
GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java();
job_id = godot_java->build_env_execute(
"gradle",
gradle_build_args,
project_path,
build_path,
callable_mp(this, &AndroidEditorGradleRunner::_android_gradle_build_output),
callable_mp(this, &AndroidEditorGradleRunner::_android_gradle_build_build_callback));
if (job_id < 0) {
_android_gradle_build_failed(TTR("Failed to execute Gradle command"));
}
}
void AndroidEditorGradleRunner::_android_gradle_build_build_callback(int p_exit_code) {
job_id = -1;
if (p_exit_code != 0) {
_android_gradle_build_failed();
return;
}
_android_gradle_build_copy();
}
void AndroidEditorGradleRunner::_android_gradle_build_copy() {
_android_gradle_build_output(0, TTR("> Copying Gradle artifacts..."));
GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java();
job_id = godot_java->build_env_execute(
"gradle",
gradle_copy_args,
project_path,
build_path,
callable_mp(this, &AndroidEditorGradleRunner::_android_gradle_build_output),
callable_mp(this, &AndroidEditorGradleRunner::_android_gradle_build_copy_callback));
if (job_id < 0) {
_android_gradle_build_failed(TTR("Failed to execute Gradle command"));
}
}
void AndroidEditorGradleRunner::_android_gradle_build_copy_callback(int p_exit_code) {
job_id = -1;
if (p_exit_code != 0) {
_android_gradle_build_failed();
} else {
_android_gradle_build_clean_project(true);
}
}
void AndroidEditorGradleRunner::_android_gradle_build_clean_project(bool p_was_successful) {
if (state != STATE_CLEANING) {
state = STATE_CLEANING;
if (p_was_successful) {
output_dialog->hide();
} else {
output_dialog->get_ok_button()->set_disabled(false);
}
GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java();
godot_java->build_env_clean_project(
project_path,
build_path,
callable_mp(this, &AndroidEditorGradleRunner::_android_gradle_build_clean_project_callback));
}
}
void AndroidEditorGradleRunner::_android_gradle_build_clean_project_callback() {
// Ensure we haven't switched back to STATE_BUILDING in the meantime.
if (state == STATE_CLEANING) {
_android_gradle_build_disconnect();
state = STATE_IDLE;
}
}
void AndroidEditorGradleRunner::_android_gradle_build_failed(const String &p_msg) {
job_id = -1;
if (p_msg != "") {
_android_gradle_build_output(1, p_msg);
}
_android_gradle_build_clean_project(false);
}
void AndroidEditorGradleRunner::_android_gradle_build_cancel() {
if (job_id > 0) {
GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java();
godot_java->build_env_cancel(job_id);
_android_gradle_build_clean_project(false);
}
}
#endif // ANDROID_ENABLED

View File

@@ -0,0 +1,76 @@
/**************************************************************************/
/* android_editor_gradle_runner.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
#ifdef ANDROID_ENABLED
#include "core/object/object.h"
class ConfirmationDialog;
class RichTextLabel;
class AndroidEditorGradleRunner : public Object {
GDCLASS(AndroidEditorGradleRunner, Object);
RichTextLabel *output_label = nullptr;
ConfirmationDialog *output_dialog = nullptr;
enum State {
STATE_IDLE,
STATE_BUILDING,
STATE_CLEANING,
};
State state = STATE_IDLE;
String project_path;
String build_path;
List<String> gradle_build_args;
List<String> gradle_copy_args;
int64_t job_id;
void _android_gradle_build_connect();
void _android_gradle_build_disconnect();
void _android_gradle_build_output(int p_type, const String &p_line);
void _android_gradle_build_build();
void _android_gradle_build_build_callback(int p_exit_code);
void _android_gradle_build_copy();
void _android_gradle_build_copy_callback(int p_exit_code);
void _android_gradle_build_clean_project(bool p_was_successful);
void _android_gradle_build_clean_project_callback();
void _android_gradle_build_failed(const String &p_msg = String());
void _android_gradle_build_cancel();
public:
void run_gradle(const String &p_project_path, const String &p_build_path, const List<String> &p_gradle_build_args, const List<String> &p_gradle_copy_args);
};
#endif // ANDROID_ENABLED

View File

@@ -60,7 +60,9 @@
#endif #endif
#ifdef ANDROID_ENABLED #ifdef ANDROID_ENABLED
#include "../java_godot_wrapper.h"
#include "../os_android.h" #include "../os_android.h"
#include "android_editor_gradle_runner.h"
#endif #endif
static const char *ANDROID_PERMS[] = { static const char *ANDROID_PERMS[] = {
@@ -2015,6 +2017,11 @@ String EditorExportPlatformAndroid::get_export_option_warning(const EditorExport
if (!enabled_deprecated_plugins_names.is_empty() && !gradle_build_enabled) { if (!enabled_deprecated_plugins_names.is_empty() && !gradle_build_enabled) {
return TTR("\"Use Gradle Build\" must be enabled to use the plugins."); return TTR("\"Use Gradle Build\" must be enabled to use the plugins.");
} }
#ifdef ANDROID_ENABLED
if (gradle_build_enabled) {
return TTR("Support for \"Use Gradle Build\" on Android is currently experimental.");
}
#endif // ANDROID_ENABLED
} else if (p_name == "gradle_build/compress_native_libraries") { } else if (p_name == "gradle_build/compress_native_libraries") {
bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build"); bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build");
if (bool(p_preset->get("gradle_build/compress_native_libraries")) && !gradle_build_enabled) { if (bool(p_preset->get("gradle_build/compress_native_libraries")) && !gradle_build_enabled) {
@@ -2100,7 +2107,7 @@ void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_optio
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "gradle_build/use_gradle_build"), false, true, true)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "gradle_build/use_gradle_build"), false, true, false));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "gradle_build/gradle_build_directory", PROPERTY_HINT_PLACEHOLDER_TEXT, "res://android"), "", false, false)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "gradle_build/gradle_build_directory", PROPERTY_HINT_PLACEHOLDER_TEXT, "res://android"), "", false, false));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "gradle_build/android_source_template", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "gradle_build/android_source_template", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "gradle_build/compress_native_libraries"), false, false, true)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "gradle_build/compress_native_libraries"), false, false, true));
@@ -2889,10 +2896,6 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito
err += template_err; err += template_err;
} }
} else { } else {
#ifdef ANDROID_ENABLED
err += TTR("Gradle build is not supported for the Android editor.") + "\n";
valid = false;
#else
// Validate the custom gradle android source template. // Validate the custom gradle android source template.
bool android_source_template_valid = false; bool android_source_template_valid = false;
const String android_source_template = p_preset->get("gradle_build/android_source_template"); const String android_source_template = p_preset->get("gradle_build/android_source_template");
@@ -2915,7 +2918,6 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito
} }
valid = installed_android_build_template && !r_missing_templates; valid = installed_android_build_template && !r_missing_templates;
#endif
} }
// Validate the rest of the export configuration. // Validate the rest of the export configuration.
@@ -3665,6 +3667,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
} }
} }
const String assets_directory = get_assets_directory(p_preset, export_format); const String assets_directory = get_assets_directory(p_preset, export_format);
#ifndef ANDROID_ENABLED
String java_sdk_path = EDITOR_GET("export/android/java_sdk_path"); String java_sdk_path = EDITOR_GET("export/android/java_sdk_path");
if (java_sdk_path.is_empty()) { if (java_sdk_path.is_empty()) {
add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Java SDK path must be configured in Editor Settings at 'export/android/java_sdk_path'.")); add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Java SDK path must be configured in Editor Settings at 'export/android/java_sdk_path'."));
@@ -3678,6 +3681,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
return ERR_UNCONFIGURED; return ERR_UNCONFIGURED;
} }
print_verbose("Android sdk path: " + sdk_path); print_verbose("Android sdk path: " + sdk_path);
#endif
// TODO: should we use "package/name" or "application/config/name"? // TODO: should we use "package/name" or "application/config/name"?
String project_name = get_project_name(p_preset, p_preset->get("package/name")); String project_name = get_project_name(p_preset, p_preset->get("package/name"));
@@ -3738,14 +3742,17 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
return err; return err;
} }
} }
print_verbose("Storing command line flags..."); print_verbose("Storing command line flags...");
store_file_at_path(assets_directory + "/_cl_", command_line_flags); store_file_at_path(assets_directory + "/_cl_", command_line_flags);
#ifndef ANDROID_ENABLED
print_verbose("Updating JAVA_HOME environment to " + java_sdk_path); print_verbose("Updating JAVA_HOME environment to " + java_sdk_path);
OS::get_singleton()->set_environment("JAVA_HOME", java_sdk_path); OS::get_singleton()->set_environment("JAVA_HOME", java_sdk_path);
print_verbose("Updating ANDROID_HOME environment to " + sdk_path); print_verbose("Updating ANDROID_HOME environment to " + sdk_path);
OS::get_singleton()->set_environment("ANDROID_HOME", sdk_path); OS::get_singleton()->set_environment("ANDROID_HOME", sdk_path);
#endif
String build_command; String build_command;
#ifdef WINDOWS_ENABLED #ifdef WINDOWS_ENABLED
@@ -3832,8 +3839,10 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
String addons_directory = ProjectSettings::get_singleton()->globalize_path("res://addons"); String addons_directory = ProjectSettings::get_singleton()->globalize_path("res://addons");
#ifndef ANDROID_ENABLED
cmdline.push_back("-p"); // argument to specify the start directory. cmdline.push_back("-p"); // argument to specify the start directory.
cmdline.push_back(build_path); // start directory. cmdline.push_back(build_path); // start directory.
#endif
cmdline.push_back("-Paddons_directory=" + addons_directory); // path to the addon directory as it may contain jar or aar dependencies cmdline.push_back("-Paddons_directory=" + addons_directory); // path to the addon directory as it may contain jar or aar dependencies
cmdline.push_back("-Pexport_package_name=" + package_name); // argument to specify the package name. cmdline.push_back("-Pexport_package_name=" + package_name); // argument to specify the package name.
cmdline.push_back("-Pexport_version_code=" + version_code); // argument to specify the version code. cmdline.push_back("-Pexport_version_code=" + version_code); // argument to specify the version code.
@@ -3872,6 +3881,25 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Could not find debug keystore, unable to export.")); add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Could not find debug keystore, unable to export."));
return ERR_FILE_CANT_OPEN; return ERR_FILE_CANT_OPEN;
} }
#ifdef ANDROID_ENABLED
if (debug_keystore.begins_with("assets://")) {
// The Gradle build environment app can't access the Godot
// editor's assets, so we need to copy this to temp file.
Error err;
PackedByteArray keystore_data = FileAccess::get_file_as_bytes(debug_keystore, &err);
if (err == OK) {
String temp_dir = build_path + "/.android";
String temp_filename = temp_dir + "/debug.keystore";
DirAccess::make_dir_recursive_absolute(temp_dir);
Ref<FileAccess> temp_file = FileAccess::open(temp_filename, FileAccess::WRITE);
if (temp_file.is_valid()) {
temp_file->store_buffer(keystore_data);
debug_keystore = temp_filename;
}
}
}
#endif
cmdline.push_back("-Pdebug_keystore_file=" + debug_keystore); // argument to specify the debug keystore file. cmdline.push_back("-Pdebug_keystore_file=" + debug_keystore); // argument to specify the debug keystore file.
cmdline.push_back("-Pdebug_keystore_alias=" + debug_user); // argument to specify the debug keystore alias. cmdline.push_back("-Pdebug_keystore_alias=" + debug_user); // argument to specify the debug keystore alias.
@@ -3895,21 +3923,14 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
} }
} }
String build_project_output;
int result = EditorNode::get_singleton()->execute_and_show_output(TTR("Building Android Project (gradle)"), build_command, cmdline, true, false, &build_project_output);
if (result != 0) {
add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Building of Android project failed, check output for the error:") + "\n\n" + build_project_output);
return ERR_CANT_CREATE;
} else {
print_verbose(build_project_output);
}
List<String> copy_args; List<String> copy_args;
String copy_command = "copyAndRenameBinary"; String copy_command = "copyAndRenameBinary";
copy_args.push_back(copy_command); copy_args.push_back(copy_command);
#ifndef ANDROID_ENABLED
copy_args.push_back("-p"); // argument to specify the start directory. copy_args.push_back("-p"); // argument to specify the start directory.
copy_args.push_back(build_path); // start directory. copy_args.push_back(build_path); // start directory.
#endif
copy_args.push_back("-Pexport_edition=" + edition.to_lower()); copy_args.push_back("-Pexport_edition=" + edition.to_lower());
@@ -3928,6 +3949,23 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
copy_args.push_back("-Pexport_path=file:" + export_path); copy_args.push_back("-Pexport_path=file:" + export_path);
copy_args.push_back("-Pexport_filename=" + export_filename); copy_args.push_back("-Pexport_filename=" + export_filename);
#ifdef ANDROID_ENABLED
String project_path = ProjectSettings::get_singleton()->globalize_path("res://");
android_editor_gradle_runner->run_gradle(
project_path,
build_path.substr(project_path.length()),
cmdline,
copy_args);
#else
String build_project_output;
int result = EditorNode::get_singleton()->execute_and_show_output(TTR("Building Android Project (gradle)"), build_command, cmdline, true, false, &build_project_output);
if (result != 0) {
add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Building of Android project failed, check output for the error:") + "\n\n" + build_project_output);
return ERR_CANT_CREATE;
} else {
print_verbose(build_project_output);
}
print_verbose("Copying Android binary using gradle command: " + String("\n") + build_command + " " + join_list(copy_args, String(" "))); print_verbose("Copying Android binary using gradle command: " + String("\n") + build_command + " " + join_list(copy_args, String(" ")));
String copy_binary_output; String copy_binary_output;
int copy_result = EditorNode::get_singleton()->execute_and_show_output(TTR("Moving output"), build_command, copy_args, true, false, &copy_binary_output); int copy_result = EditorNode::get_singleton()->execute_and_show_output(TTR("Moving output"), build_command, copy_args, true, false, &copy_binary_output);
@@ -3939,6 +3977,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
} }
print_verbose("Successfully completed Android gradle build."); print_verbose("Successfully completed Android gradle build.");
#endif
return OK; return OK;
} }
// This is the start of the Legacy build system // This is the start of the Legacy build system
@@ -4338,6 +4377,8 @@ void EditorExportPlatformAndroid::initialize() {
_create_editor_debug_keystore_if_needed(); _create_editor_debug_keystore_if_needed();
_update_preset_status(); _update_preset_status();
check_for_changes_thread.start(_check_for_changes_poll_thread, this); check_for_changes_thread.start(_check_for_changes_poll_thread, this);
#else
android_editor_gradle_runner = memnew(AndroidEditorGradleRunner);
#endif #endif
use_scrcpy = EditorSettings::get_singleton()->get_project_metadata("android", "use_scrcpy", false); use_scrcpy = EditorSettings::get_singleton()->get_project_metadata("android", "use_scrcpy", false);
} }
@@ -4349,5 +4390,9 @@ EditorExportPlatformAndroid::~EditorExportPlatformAndroid() {
if (check_for_changes_thread.is_started()) { if (check_for_changes_thread.is_started()) {
check_for_changes_thread.wait_to_finish(); check_for_changes_thread.wait_to_finish();
} }
#else
if (android_editor_gradle_runner) {
memdelete(android_editor_gradle_runner);
}
#endif #endif
} }

View File

@@ -60,6 +60,8 @@ struct LauncherIcon {
int dimensions = 0; int dimensions = 0;
}; };
class AndroidEditorGradleRunner;
class EditorExportPlatformAndroid : public EditorExportPlatform { class EditorExportPlatformAndroid : public EditorExportPlatform {
GDCLASS(EditorExportPlatformAndroid, EditorExportPlatform); GDCLASS(EditorExportPlatformAndroid, EditorExportPlatform);
@@ -106,6 +108,8 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
static void _check_for_changes_poll_thread(void *ud); static void _check_for_changes_poll_thread(void *ud);
void _update_preset_status(); void _update_preset_status();
#else
AndroidEditorGradleRunner *android_editor_gradle_runner = nullptr;
#endif #endif
String get_project_name(const Ref<EditorExportPreset> &p_preset, const String &p_name) const; String get_project_name(const Ref<EditorExportPreset> &p_preset, const String &p_name) const;

View File

@@ -3,6 +3,10 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:installLocation="auto"> android:installLocation="auto">
<queries>
<package android:name="org.godotengine.godot_gradle_build_environment" />
</queries>
<supports-screens <supports-screens
android:largeScreens="true" android:largeScreens="true"
android:normalScreens="true" android:normalScreens="true"

View File

@@ -53,11 +53,12 @@ import androidx.core.content.edit
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.window.layout.WindowMetricsCalculator import androidx.window.layout.WindowMetricsCalculator
import org.godotengine.editor.buildprovider.GradleBuildProvider
import org.godotengine.editor.embed.EmbeddedGodotGame import org.godotengine.editor.embed.EmbeddedGodotGame
import org.godotengine.editor.embed.GameMenuFragment import org.godotengine.editor.embed.GameMenuFragment
import org.godotengine.editor.utils.signApk import org.godotengine.editor.utils.signApk
import org.godotengine.editor.utils.verifyApk import org.godotengine.editor.utils.verifyApk
import org.godotengine.godot.BuildConfig import org.godotengine.godot.BuildProvider
import org.godotengine.godot.Godot import org.godotengine.godot.Godot
import org.godotengine.godot.GodotActivity import org.godotengine.godot.GodotActivity
import org.godotengine.godot.GodotLib import org.godotengine.godot.GodotLib
@@ -171,6 +172,7 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
} }
} }
internal val gradleBuildProvider: GradleBuildProvider = GradleBuildProvider(this, this)
internal val editorMessageDispatcher = EditorMessageDispatcher(this) internal val editorMessageDispatcher = EditorMessageDispatcher(this)
private val editorLoadingIndicator: View? by lazy { findViewById(R.id.editor_loading_indicator) } private val editorLoadingIndicator: View? by lazy { findViewById(R.id.editor_loading_indicator) }
@@ -262,6 +264,11 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
setupGameMenuBar() setupGameMenuBar()
} }
override fun onDestroy() {
gradleBuildProvider.buildEnvDisconnect()
super.onDestroy()
}
override fun onNewIntent(newIntent: Intent) { override fun onNewIntent(newIntent: Intent) {
if (newIntent.hasCategory(HYBRID_APP_PANEL_CATEGORY) || newIntent.hasCategory(HYBRID_APP_IMMERSIVE_CATEGORY)) { if (newIntent.hasCategory(HYBRID_APP_PANEL_CATEGORY) || newIntent.hasCategory(HYBRID_APP_IMMERSIVE_CATEGORY)) {
val params = retrieveCommandLineParamsFromLaunchIntent(newIntent) val params = retrieveCommandLineParamsFromLaunchIntent(newIntent)
@@ -968,4 +975,8 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
} }
override fun isGameEmbeddingSupported() = !isNativeXRDevice(applicationContext) override fun isGameEmbeddingSupported() = !isNativeXRDevice(applicationContext)
override fun getBuildProvider(): BuildProvider? {
return gradleBuildProvider
}
} }

View File

@@ -0,0 +1,225 @@
/**************************************************************************/
/* GradleBuildEnvironmentClient.kt */
/**************************************************************************/
/* 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. */
/**************************************************************************/
package org.godotengine.editor.buildprovider
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Bundle
import android.os.Handler
import android.os.IBinder
import android.os.Message
import android.os.Messenger
import android.os.RemoteException
import android.util.Log
import kotlin.collections.set
private const val MSG_EXECUTE_GRADLE = 1
private const val MSG_COMMAND_RESULT = 2
private const val MSG_COMMAND_OUTPUT = 3
private const val MSG_CANCEL_COMMAND = 4
private const val MSG_CLEAN_PROJECT = 5
internal class GradleBuildEnvironmentClient(private val context: Context) {
companion object {
private val TAG = GradleBuildEnvironmentClient::class.java.simpleName
}
private var bound: Boolean = false
private var outgoingMessenger: Messenger? = null
private val connection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
outgoingMessenger = Messenger(service)
bound = true
Log.i(TAG, "Service connected")
for (callable in connectionCallbacks) {
callable()
}
connectionCallbacks.clear()
connecting = false
}
override fun onServiceDisconnected(name: ComponentName?) {
outgoingMessenger = null
bound = false
Log.i(TAG, "Service disconnected")
}
}
private inner class IncomingHandler: Handler() {
override fun handleMessage(msg: Message) {
when (msg.what) {
MSG_COMMAND_RESULT -> {
this@GradleBuildEnvironmentClient.receiveCommandResult(msg)
}
MSG_COMMAND_OUTPUT -> {
this@GradleBuildEnvironmentClient.receiveCommandOutput(msg)
}
else -> super.handleMessage(msg)
}
}
}
private val incomingMessenger = Messenger(IncomingHandler())
private val connectionCallbacks = mutableListOf<() -> Unit>()
private var connecting = false
private var executionId = 1000
private class ExecutionInfo(val outputCallback: (Int, String) -> Unit, val resultCallback: (Int) -> Unit)
private val executionMap = HashMap<Int, ExecutionInfo>()
fun connect(callback: () -> Unit): Boolean {
if (bound) {
callback()
return true;
}
connectionCallbacks.add(callback)
if (connecting) {
return true;
}
connecting = true;
val intent = Intent("org.godotengine.action.BUILD_PROVIDER").apply {
setPackage("org.godotengine.godot_gradle_build_environment")
}
val info = context.packageManager.resolveService(intent, 0)
if (info == null) {
connecting = false;
Log.e(TAG, "Unable to resolve service")
return false
}
val result = context.bindService(intent, connection, Context.BIND_AUTO_CREATE)
if (!result) {
Log.e(TAG, "Unable to bind to service")
connecting = false;
}
return result;
}
fun disconnect() {
if (bound) {
context.unbindService(connection)
bound = false
}
}
private fun getNextExecutionId(outputCallback: (Int, String) -> Unit, resultCallback: (Int) -> Unit): Int {
val id = executionId++
executionMap[id] = ExecutionInfo(outputCallback, resultCallback)
return id
}
fun execute(arguments: Array<String>, projectPath: String, gradleBuildDir: String, outputCallback: (Int, String) -> Unit, resultCallback: (Int) -> Unit): Int {
if (outgoingMessenger == null) {
return -1
}
val msg: Message = Message.obtain(null, MSG_EXECUTE_GRADLE, getNextExecutionId(outputCallback, resultCallback),0)
msg.replyTo = incomingMessenger
val data = Bundle()
data.putStringArrayList("arguments", ArrayList(arguments.toList()))
data.putString("project_path", projectPath)
data.putString("gradle_build_directory", gradleBuildDir)
msg.data = data
try {
outgoingMessenger?.send(msg)
} catch (e: RemoteException) {
Log.e(TAG, "Unable to execute Gradle command: gradlew ${arguments.joinToString(" ")}", e)
e.printStackTrace()
executionMap.remove(msg.arg1)
resultCallback(255)
return -1
}
return msg.arg1
}
private fun receiveCommandResult(msg: Message) {
val executionInfo = executionMap.remove(msg.arg1)
executionInfo?.resultCallback?.invoke(msg.arg2)
}
private fun receiveCommandOutput(msg: Message) {
val data = msg.data
val line = data.getString("line")
if (line != null) {
val executionInfo = executionMap.get(msg.arg1)
executionInfo?.outputCallback?.invoke(msg.arg2, line)
}
}
fun cancel(jobId: Int) {
if (outgoingMessenger == null) {
return
}
val msg: Message = Message.obtain(null, MSG_CANCEL_COMMAND, jobId, 0)
try {
outgoingMessenger?.send(msg)
} catch (e: RemoteException) {
Log.e(TAG, "Unable to cancel Gradle command: ${jobId}", e)
e.printStackTrace()
}
}
fun cleanProject(projectPath: String, gradleBuildDir: String, resultCallback: (Int) -> Unit) {
if (outgoingMessenger == null) {
return
}
val emptyOutputCallback: (Int, String) -> Unit = { outputType, line -> }
val msg: Message = Message.obtain(null, MSG_CLEAN_PROJECT, getNextExecutionId(emptyOutputCallback, resultCallback), 0)
msg.replyTo = incomingMessenger
val data = Bundle()
data.putString("project_path", projectPath)
data.putString("gradle_build_directory", gradleBuildDir)
msg.data = data
try {
outgoingMessenger?.send(msg)
} catch (e: RemoteException) {
Log.e(TAG, "Unable to clean Gradle project", e)
executionMap.remove(msg.arg1)
resultCallback(0)
e.printStackTrace()
}
}
}

View File

@@ -0,0 +1,95 @@
/**************************************************************************/
/* GradleBuildProvider.kt */
/**************************************************************************/
/* 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. */
/**************************************************************************/
package org.godotengine.editor.buildprovider
import android.content.Context
import org.godotengine.godot.BuildProvider
import org.godotengine.godot.GodotHost
import org.godotengine.godot.variant.Callable
internal class GradleBuildProvider(
val context: Context,
val host: GodotHost,
) : BuildProvider {
val gradleBuildEnvironmentClient = GradleBuildEnvironmentClient(context)
val godot get() = host.godot
override fun buildEnvConnect(callback: Callable): Boolean {
return gradleBuildEnvironmentClient.connect {
godot?.runOnRenderThread {
callback.call()
}
}
}
override fun buildEnvDisconnect() {
gradleBuildEnvironmentClient.disconnect()
}
override fun buildEnvExecute(
buildTool: String,
arguments: Array<String>,
projectPath: String,
buildDir: String,
outputCallback: Callable,
resultCallback: Callable
): Int {
if (buildTool != "gradle") {
return -1;
}
val outputCb: (Int, String) -> Unit = { outputType, line ->
godot?.runOnRenderThread {
outputCallback.call(outputType, line)
}
}
val resultCb: (Int) -> Unit = { exitCode ->
godot?.runOnRenderThread {
resultCallback.call(exitCode)
}
}
return gradleBuildEnvironmentClient.execute(arguments, projectPath, buildDir, outputCb, resultCb)
}
override fun buildEnvCancel(jobId: Int) {
gradleBuildEnvironmentClient.cancel(jobId)
}
override fun buildEnvCleanProject(projectPath: String, buildDir: String, callback: Callable) {
val cb: (Int) -> Unit = { exitCode ->
godot?.runOnRenderThread {
callback.call()
}
}
gradleBuildEnvironmentClient.cleanProject(projectPath, buildDir, cb)
}
}

View File

@@ -0,0 +1,81 @@
/**************************************************************************/
/* BuildProvider.java */
/**************************************************************************/
/* 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. */
/**************************************************************************/
package org.godotengine.godot;
import org.godotengine.godot.variant.Callable;
import androidx.annotation.NonNull;
/**
* Provides an environment for executing build commands.
*/
public interface BuildProvider {
/**
* Connects to the build environment.
*
* @param callback The callback to call when connected
* @return Whether or not connecting is possible
*/
boolean buildEnvConnect(@NonNull Callable callback);
/**
* Disconnects from the build environment.
*/
void buildEnvDisconnect();
/**
* Executes a command via the build environment.
*
* @param buildTool The build tool to execute (for example, "gradle")
* @param arguments The argument for the command
* @param projectPath The working directory to use when executing the command
* @param buildDir The build directory within the project
* @param outputCallback The callback to call for each line of output from the command
* @param resultCallback The callback to call when the command is finished running
* @return A positive job id, if successful; otherwise, a negative number
*/
int buildEnvExecute(String buildTool, @NonNull String[] arguments, @NonNull String projectPath, @NonNull String buildDir, @NonNull Callable outputCallback, @NonNull Callable resultCallback);
/**
* Cancels a command executed via the build environment.
*
* @param jobId The job id returned from buildEnvExecute()
*/
void buildEnvCancel(int jobId);
/**
* Requests that a project be cleaned up via the build environment.
*
* @param projectPath The working directory to use when executing the command
* @param buildDir The build directory within the project
*/
void buildEnvCleanProject(@NonNull String projectPath, @NonNull String buildDir, @NonNull Callable callback);
}

View File

@@ -75,6 +75,7 @@ import org.godotengine.godot.utils.benchmarkFile
import org.godotengine.godot.utils.dumpBenchmark import org.godotengine.godot.utils.dumpBenchmark
import org.godotengine.godot.utils.endBenchmarkMeasure import org.godotengine.godot.utils.endBenchmarkMeasure
import org.godotengine.godot.utils.useBenchmark import org.godotengine.godot.utils.useBenchmark
import org.godotengine.godot.variant.Callable as GodotCallable
import org.godotengine.godot.xr.XRMode import org.godotengine.godot.xr.XRMode
import java.io.File import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
@@ -1304,4 +1305,64 @@ class Godot private constructor(val context: Context) {
private fun nativeOnEditorWorkspaceSelected(workspace: String) { private fun nativeOnEditorWorkspaceSelected(workspace: String) {
primaryHost?.onEditorWorkspaceSelected(workspace) primaryHost?.onEditorWorkspaceSelected(workspace)
} }
@Keep
private fun nativeBuildEnvConnect(callback: GodotCallable): Boolean {
try {
val buildProvider = primaryHost?.getBuildProvider()
return buildProvider?.buildEnvConnect(callback) ?: false
} catch (e: Exception) {
Log.e(TAG, "Unable to connect to build environment", e)
return false
}
}
@Keep
private fun nativeBuildEnvDisconnect() {
try {
val buildProvider = primaryHost?.getBuildProvider()
buildProvider?.buildEnvDisconnect()
} catch (e: Exception) {
Log.e(TAG, "Unable to disconnect from build environment", e)
}
}
@Keep
private fun nativeBuildEnvExecute(buildTool: String, arguments: Array<String>, projectPath: String, buildDir: String, outputCallback: GodotCallable, resultCallback: GodotCallable): Int {
try {
val buildProvider = primaryHost?.getBuildProvider()
return buildProvider?.buildEnvExecute(
buildTool,
arguments,
projectPath,
buildDir,
outputCallback,
resultCallback
) ?: -1
} catch (e: Exception) {
Log.e(TAG, "Unable to execute Gradle command in build environment", e);
return -1
}
}
@Keep
private fun nativeBuildEnvCancel(jobId: Int) {
try {
val buildProvider = primaryHost?.getBuildProvider()
buildProvider?.buildEnvCancel(jobId)
} catch (e: Exception) {
Log.e(TAG, "Unable to cancel command in build environment", e)
}
}
@Keep
private fun nativeBuildEnvCleanProject(projectPath: String, buildDir: String, callback: GodotCallable) {
try {
val buildProvider = primaryHost?.getBuildProvider()
buildProvider?.buildEnvCleanProject(projectPath, buildDir, callback)
} catch(e: Exception) {
Log.e(TAG, "Unable to clean project in build environment", e)
}
}
} }

View File

@@ -40,7 +40,6 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Messenger; import android.os.Messenger;
import android.text.TextUtils; import android.text.TextUtils;
@@ -496,4 +495,12 @@ public class GodotFragment extends Fragment implements IDownloaderClient, GodotH
parentHost.onEditorWorkspaceSelected(workspace); parentHost.onEditorWorkspaceSelected(workspace);
} }
} }
@Override
public BuildProvider getBuildProvider() {
if (parentHost != null) {
return parentHost.getBuildProvider();
}
return null;
}
} }

View File

@@ -166,4 +166,13 @@ public interface GodotHost {
activity.runOnUiThread(action); activity.runOnUiThread(action);
} }
} }
/**
* Gets the build provider, if available.
*
* @return the build provider, if available; otherwise, null.
*/
default @Nullable BuildProvider getBuildProvider() {
return null;
}
} }

View File

@@ -71,7 +71,7 @@ class Callable private constructor(private val nativeCallablePointer: Long) {
/** /**
* Calls the method represented by this [Callable]. Arguments can be passed and should match the method's signature. * Calls the method represented by this [Callable]. Arguments can be passed and should match the method's signature.
*/ */
internal fun call(vararg params: Any): Any? { fun call(vararg params: Any): Any? {
if (nativeCallablePointer == 0L) { if (nativeCallablePointer == 0L) {
return null return null
} }

View File

@@ -30,6 +30,8 @@
#include "java_godot_wrapper.h" #include "java_godot_wrapper.h"
#include "jni_utils.h"
// JNIEnv is only valid within the thread it belongs to, in a multi threading environment // JNIEnv is only valid within the thread it belongs to, in a multi threading environment
// we can't cache it. // we can't cache it.
// For Godot we call most access methods from our thread and we thus get a valid JNIEnv // For Godot we call most access methods from our thread and we thus get a valid JNIEnv
@@ -88,6 +90,11 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_godot_instance) {
_set_window_color = p_env->GetMethodID(godot_class, "setWindowColor", "(Ljava/lang/String;)V"); _set_window_color = p_env->GetMethodID(godot_class, "setWindowColor", "(Ljava/lang/String;)V");
_on_editor_workspace_selected = p_env->GetMethodID(godot_class, "nativeOnEditorWorkspaceSelected", "(Ljava/lang/String;)V"); _on_editor_workspace_selected = p_env->GetMethodID(godot_class, "nativeOnEditorWorkspaceSelected", "(Ljava/lang/String;)V");
_get_activity = p_env->GetMethodID(godot_class, "getActivity", "()Landroid/app/Activity;"); _get_activity = p_env->GetMethodID(godot_class, "getActivity", "()Landroid/app/Activity;");
_build_env_connect = p_env->GetMethodID(godot_class, "nativeBuildEnvConnect", "(Lorg/godotengine/godot/variant/Callable;)Z");
_build_env_disconnect = p_env->GetMethodID(godot_class, "nativeBuildEnvDisconnect", "()V");
_build_env_execute = p_env->GetMethodID(godot_class, "nativeBuildEnvExecute", "(Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/godotengine/godot/variant/Callable;Lorg/godotengine/godot/variant/Callable;)I");
_build_env_cancel = p_env->GetMethodID(godot_class, "nativeBuildEnvCancel", "(I)V");
_build_env_clean_project = p_env->GetMethodID(godot_class, "nativeBuildEnvCleanProject", "(Ljava/lang/String;Ljava/lang/String;Lorg/godotengine/godot/variant/Callable;)V");
} }
GodotJavaWrapper::~GodotJavaWrapper() { GodotJavaWrapper::~GodotJavaWrapper() {
@@ -607,3 +614,84 @@ void GodotJavaWrapper::on_editor_workspace_selected(const String &p_workspace) {
env->CallVoidMethod(godot_instance, _on_editor_workspace_selected, j_workspace); env->CallVoidMethod(godot_instance, _on_editor_workspace_selected, j_workspace);
} }
} }
bool GodotJavaWrapper::build_env_connect(const Callable &p_callback) {
if (_build_env_connect) {
JNIEnv *env = get_jni_env();
ERR_FAIL_NULL_V(env, false);
jobject j_callback = callable_to_jcallable(env, p_callback);
jboolean result = env->CallBooleanMethod(godot_instance, _build_env_connect, j_callback);
env->DeleteLocalRef(j_callback);
return result;
}
return false;
}
void GodotJavaWrapper::build_env_disconnect() {
if (_build_env_disconnect) {
JNIEnv *env = get_jni_env();
ERR_FAIL_NULL(env);
env->CallVoidMethod(godot_instance, _build_env_disconnect);
}
}
int GodotJavaWrapper::build_env_execute(const String &p_build_tool, const List<String> &p_arguments, const String &p_project_path, const String &p_gradle_build_directory, const Callable &p_output_callback, const Callable &p_result_callback) {
if (_build_env_execute) {
JNIEnv *env = get_jni_env();
ERR_FAIL_NULL_V(env, -1);
jstring j_build_tool = env->NewStringUTF(p_build_tool.utf8().get_data());
jobjectArray j_args = env->NewObjectArray(p_arguments.size(), env->FindClass("java/lang/String"), nullptr);
for (int i = 0; i < p_arguments.size(); i++) {
jstring j_arg = env->NewStringUTF(p_arguments.get(i).utf8().get_data());
env->SetObjectArrayElement(j_args, i, j_arg);
env->DeleteLocalRef(j_arg);
}
jstring j_project_path = env->NewStringUTF(p_project_path.utf8().get_data());
jstring j_gradle_build_directory = env->NewStringUTF(p_gradle_build_directory.utf8().get_data());
jobject j_output_callback = callable_to_jcallable(env, p_output_callback);
jobject j_result_callback = callable_to_jcallable(env, p_result_callback);
jint result = env->CallIntMethod(godot_instance, _build_env_execute, j_build_tool, j_args, j_project_path, j_gradle_build_directory, j_output_callback, j_result_callback);
env->DeleteLocalRef(j_build_tool);
env->DeleteLocalRef(j_args);
env->DeleteLocalRef(j_project_path);
env->DeleteLocalRef(j_gradle_build_directory);
env->DeleteLocalRef(j_output_callback);
env->DeleteLocalRef(j_result_callback);
return result;
}
return -1;
}
void GodotJavaWrapper::build_env_cancel(int p_job_id) {
if (_build_env_cancel) {
JNIEnv *env = get_jni_env();
ERR_FAIL_NULL(env);
env->CallVoidMethod(godot_instance, _build_env_cancel, p_job_id);
}
}
void GodotJavaWrapper::build_env_clean_project(const String &p_project_path, const String &p_gradle_build_directory, const Callable &p_callback) {
if (_build_env_clean_project) {
JNIEnv *env = get_jni_env();
ERR_FAIL_NULL(env);
jstring j_project_path = env->NewStringUTF(p_project_path.utf8().get_data());
jstring j_gradle_build_directory = env->NewStringUTF(p_gradle_build_directory.utf8().get_data());
jobject j_callback = callable_to_jcallable(env, p_callback);
env->CallVoidMethod(godot_instance, _build_env_clean_project, j_project_path, j_gradle_build_directory, j_callback);
env->DeleteLocalRef(j_project_path);
env->DeleteLocalRef(j_gradle_build_directory);
env->DeleteLocalRef(j_callback);
}
}

View File

@@ -84,6 +84,11 @@ private:
jmethodID _set_window_color = nullptr; jmethodID _set_window_color = nullptr;
jmethodID _on_editor_workspace_selected = nullptr; jmethodID _on_editor_workspace_selected = nullptr;
jmethodID _get_activity = nullptr; jmethodID _get_activity = nullptr;
jmethodID _build_env_connect = nullptr;
jmethodID _build_env_disconnect = nullptr;
jmethodID _build_env_execute = nullptr;
jmethodID _build_env_cancel = nullptr;
jmethodID _build_env_clean_project = nullptr;
public: public:
GodotJavaWrapper(JNIEnv *p_env, jobject p_godot_instance); GodotJavaWrapper(JNIEnv *p_env, jobject p_godot_instance);
@@ -141,4 +146,10 @@ public:
void set_window_color(const Color &p_color); void set_window_color(const Color &p_color);
void on_editor_workspace_selected(const String &p_workspace); void on_editor_workspace_selected(const String &p_workspace);
bool build_env_connect(const Callable &p_callback);
void build_env_disconnect();
int build_env_execute(const String &p_build_tool, const List<String> &p_arguments, const String &p_project_path, const String &p_gradle_build_directory, const Callable &p_output_callback, const Callable &p_result_callback);
void build_env_cancel(int p_job_id);
void build_env_clean_project(const String &p_project_path, const String &p_gradle_build_directory, const Callable &p_callback);
}; };