summaryrefslogtreecommitdiffstats
path: root/platform/android/export/export.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'platform/android/export/export.cpp')
-rw-r--r--platform/android/export/export.cpp197
1 files changed, 142 insertions, 55 deletions
diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp
index c45828e194..1795bbe523 100644
--- a/platform/android/export/export.cpp
+++ b/platform/android/export/export.cpp
@@ -31,11 +31,12 @@
#include "export.h"
#include "core/config/project_settings.h"
+#include "core/io/dir_access.h"
+#include "core/io/file_access.h"
#include "core/io/image_loader.h"
+#include "core/io/json.h"
#include "core/io/marshalls.h"
#include "core/io/zip_io.h"
-#include "core/os/dir_access.h"
-#include "core/os/file_access.h"
#include "core/os/os.h"
#include "core/templates/safe_refcount.h"
#include "core/version.h"
@@ -206,6 +207,7 @@ static const char *LEGACY_BUILD_SPLASH_IMAGE_EXPORT_PATH = "res/drawable-nodpi-v
static const char *SPLASH_BG_COLOR_PATH = "res/drawable-nodpi/splash_bg_color.png";
static const char *LEGACY_BUILD_SPLASH_BG_COLOR_PATH = "res/drawable-nodpi-v4/splash_bg_color.png";
static const char *SPLASH_CONFIG_PATH = "res://android/build/res/drawable/splash_drawable.xml";
+static const char *GDNATIVE_LIBS_PATH = "res://android/build/libs/gdnativelibs.json";
const String SPLASH_CONFIG_XML_CONTENT = R"SPLASH(<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
@@ -277,6 +279,11 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
EditorProgress *ep = nullptr;
};
+ struct CustomExportData {
+ bool debug;
+ Vector<String> libs;
+ };
+
Vector<PluginConfigAndroid> plugins;
String last_plugin_names;
uint64_t last_custom_build_time = 0;
@@ -609,9 +616,9 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
zip_fileinfo zipfi;
zipfi.tmz_date.tm_hour = time.hour;
zipfi.tmz_date.tm_mday = date.day;
- zipfi.tmz_date.tm_min = time.min;
+ zipfi.tmz_date.tm_min = time.minute;
zipfi.tmz_date.tm_mon = date.month - 1; // tm_mon is zero indexed
- zipfi.tmz_date.tm_sec = time.sec;
+ zipfi.tmz_date.tm_sec = time.second;
zipfi.tmz_date.tm_year = date.year;
zipfi.dosDate = 0;
zipfi.external_fa = 0;
@@ -757,6 +764,33 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
return OK;
}
+ static Error copy_gradle_so(void *p_userdata, const SharedObject &p_so) {
+ ERR_FAIL_COND_V_MSG(!p_so.path.get_file().begins_with("lib"), FAILED,
+ "Android .so file names must start with \"lib\", but got: " + p_so.path);
+ Vector<String> abis = get_abis();
+ CustomExportData *export_data = (CustomExportData *)p_userdata;
+ bool exported = false;
+ for (int i = 0; i < p_so.tags.size(); ++i) {
+ int abi_index = abis.find(p_so.tags[i]);
+ if (abi_index != -1) {
+ exported = true;
+ String base = "res://android/build/libs";
+ String type = export_data->debug ? "debug" : "release";
+ String abi = abis[abi_index];
+ String filename = p_so.path.get_file();
+ String dst_path = base.plus_file(type).plus_file(abi).plus_file(filename);
+ Vector<uint8_t> data = FileAccess::get_file_as_array(p_so.path);
+ print_verbose("Copying .so file from " + p_so.path + " to " + dst_path);
+ Error err = store_file_at_path(dst_path, data);
+ ERR_FAIL_COND_V_MSG(err, err, "Failed to copy .so file from " + p_so.path + " to " + dst_path);
+ export_data->libs.push_back(dst_path);
+ }
+ }
+ ERR_FAIL_COND_V_MSG(!exported, FAILED,
+ "Cannot determine ABI for library \"" + p_so.path + "\". One of the supported ABIs must be used as a tag: " + String(" ").join(abis));
+ return OK;
+ }
+
void _get_permissions(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, Vector<String> &r_permissions) {
const char **aperms = android_perms;
while (*aperms) {
@@ -858,6 +892,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
int xr_mode_index = p_preset->get("xr_features/xr_mode");
bool backup_allowed = p_preset->get("user_data_backup/allow");
+ bool classify_as_game = p_preset->get("package/classify_as_game");
Vector<String> perms;
// Write permissions into the perms variable.
@@ -955,6 +990,10 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
encode_uint32(backup_allowed, &p_manifest.write[iofs + 16]);
}
+ if (tname == "application" && attrname == "isGame") {
+ encode_uint32(classify_as_game, &p_manifest.write[iofs + 16]);
+ }
+
if (tname == "instrumentation" && attrname == "targetPackage") {
string_table.write[attr_value] = get_package_name(package_name);
}
@@ -1461,7 +1500,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
String project_splash_path = ProjectSettings::get_singleton()->get("application/boot_splash/image");
if (!project_splash_path.is_empty()) {
- splash_image.instance();
+ splash_image.instantiate();
print_verbose("Loading splash image: " + project_splash_path);
const Error err = ImageLoader::load_image(project_splash_path, splash_image);
if (err) {
@@ -1501,7 +1540,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
}
print_verbose("Creating splash background color image.");
- splash_bg_color_image.instance();
+ splash_bg_color_image.instantiate();
splash_bg_color_image->create(splash_image->get_width(), splash_image->get_height(), false, splash_image->get_format());
splash_bg_color_image->fill(bg_color);
@@ -1512,9 +1551,9 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
void load_icon_refs(const Ref<EditorExportPreset> &p_preset, Ref<Image> &icon, Ref<Image> &foreground, Ref<Image> &background) {
String project_icon_path = ProjectSettings::get_singleton()->get("application/config/icon");
- icon.instance();
- foreground.instance();
- background.instance();
+ icon.instantiate();
+ foreground.instantiate();
+ background.instantiate();
// Regular icon: user selection -> project icon -> default.
String path = static_cast<String>(p_preset->get(launcher_icon_option)).strip_edges();
@@ -1673,6 +1712,7 @@ public:
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/unique_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "ext.domain.name"), "org.godotengine.$genname"));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name [default if blank]"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "package/signed"), true));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "package/classify_as_game"), true));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, launcher_icon_option, PROPERTY_HINT_FILE, "*.png"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, launcher_adaptive_icon_foreground_option, PROPERTY_HINT_FILE, "*.png"), ""));
@@ -1777,12 +1817,12 @@ public:
MutexLock lock(device_lock);
- EditorProgress ep("run", "Running on " + devices[p_device].name, 3);
+ EditorProgress ep("run", vformat(TTR("Running on %s"), devices[p_device].name), 3);
String adb = get_adb_path();
// Export_temp APK.
- if (ep.step("Exporting APK...", 0)) {
+ if (ep.step(TTR("Exporting APK..."), 0)) {
return ERR_SKIP;
}
@@ -1817,7 +1857,7 @@ public:
String package_name = p_preset->get("package/unique_name");
if (remove_prev) {
- if (ep.step("Uninstalling...", 1)) {
+ if (ep.step(TTR("Uninstalling..."), 1)) {
CLEANUP_AND_RETURN(ERR_SKIP);
}
@@ -1834,7 +1874,7 @@ public:
}
print_line("Installing to device (please wait...): " + devices[p_device].name);
- if (ep.step("Installing to device, please wait...", 2)) {
+ if (ep.step(TTR("Installing to device, please wait..."), 2)) {
CLEANUP_AND_RETURN(ERR_SKIP);
}
@@ -1849,7 +1889,7 @@ public:
err = OS::get_singleton()->execute(adb, args, &output, &rv, true);
print_verbose(output);
if (err || rv != 0) {
- EditorNode::add_io_error("Could not install to device.");
+ EditorNode::add_io_error(vformat(TTR("Could not install to device: %s"), output));
CLEANUP_AND_RETURN(ERR_CANT_CREATE);
}
@@ -1905,7 +1945,7 @@ public:
}
}
- if (ep.step("Running on device...", 3)) {
+ if (ep.step(TTR("Running on device..."), 3)) {
CLEANUP_AND_RETURN(ERR_SKIP);
}
args.clear();
@@ -1927,7 +1967,7 @@ public:
err = OS::get_singleton()->execute(adb, args, &output, &rv, true);
print_verbose(output);
if (err || rv != 0) {
- EditorNode::add_io_error("Could not execute on device.");
+ EditorNode::add_io_error(TTR("Could not execute on device."));
CLEANUP_AND_RETURN(ERR_CANT_CREATE);
}
@@ -2024,11 +2064,12 @@ public:
err += template_err;
}
} else {
- r_missing_templates = !exists_export_template("android_source.zip", &err);
-
bool installed_android_build_template = FileAccess::exists("res://android/build/build.gradle");
if (!installed_android_build_template) {
+ r_missing_templates = !exists_export_template("android_source.zip", &err);
err += TTR("Android build template not installed in the project. Install it from the Project menu.") + "\n";
+ } else {
+ r_missing_templates = false;
}
valid = installed_android_build_template && !r_missing_templates;
@@ -2285,7 +2326,7 @@ public:
String apksigner = get_apksigner_path();
print_verbose("Starting signing of the " + export_label + " binary using " + apksigner);
if (!FileAccess::exists(apksigner)) {
- EditorNode::add_io_error("'apksigner' could not be found.\nPlease check the command is available in the Android SDK build-tools directory.\nThe resulting " + export_label + " is unsigned.");
+ EditorNode::add_io_error(vformat(TTR("'apksigner' could not be found.\nPlease check the command is available in the Android SDK build-tools directory.\nThe resulting %s is unsigned."), export_label));
return OK;
}
@@ -2303,7 +2344,7 @@ public:
user = EditorSettings::get_singleton()->get("export/android/debug_keystore_user");
}
- if (ep.step("Signing debug " + export_label + "...", 104)) {
+ if (ep.step(vformat(TTR("Signing debug %s..."), export_label), 104)) {
return ERR_SKIP;
}
@@ -2312,13 +2353,13 @@ public:
password = release_password;
user = release_username;
- if (ep.step("Signing release " + export_label + "...", 104)) {
+ if (ep.step(vformat(TTR("Signing release %s..."), export_label), 104)) {
return ERR_SKIP;
}
}
if (!FileAccess::exists(keystore)) {
- EditorNode::add_io_error("Could not find keystore, unable to export.");
+ EditorNode::add_io_error(TTR("Could not find keystore, unable to export."));
return ERR_FILE_CANT_OPEN;
}
@@ -2342,11 +2383,11 @@ public:
OS::get_singleton()->execute(apksigner, args, &output, &retval, true);
print_verbose(output);
if (retval) {
- EditorNode::add_io_error("'apksigner' returned with error #" + itos(retval));
+ EditorNode::add_io_error(vformat(TTR("'apksigner' returned with error #%d"), retval));
return ERR_CANT_CREATE;
}
- if (ep.step("Verifying " + export_label + "...", 105)) {
+ if (ep.step(vformat(TTR("Verifying %s..."), export_label), 105)) {
return ERR_SKIP;
}
@@ -2362,7 +2403,7 @@ public:
OS::get_singleton()->execute(apksigner, args, &output, &retval, true);
print_verbose(output);
if (retval) {
- EditorNode::add_io_error("'apksigner' verification of " + export_label + " failed.");
+ EditorNode::add_io_error(vformat(TTR("'apksigner' verification of %s failed."), export_label));
return ERR_CANT_CREATE;
}
@@ -2380,6 +2421,28 @@ public:
}
}
+ void _remove_copied_libs() {
+ print_verbose("Removing previously installed libraries...");
+ Error error;
+ String libs_json = FileAccess::get_file_as_string(GDNATIVE_LIBS_PATH, &error);
+ if (error || libs_json.is_empty()) {
+ print_verbose("No previously installed libraries found");
+ return;
+ }
+
+ JSON json;
+ error = json.parse(libs_json);
+ ERR_FAIL_COND_MSG(error, "Error parsing \"" + libs_json + "\" on line " + itos(json.get_error_line()) + ": " + json.get_error_message());
+
+ Vector<String> libs = json.get_data();
+ DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
+ for (int i = 0; i < libs.size(); i++) {
+ print_verbose("Removing previously installed library " + libs[i]);
+ da->remove(libs[i]);
+ }
+ da->remove(GDNATIVE_LIBS_PATH);
+ }
+
String join_list(List<String> parts, const String &separator) const {
String ret;
for (int i = 0; i < parts.size(); ++i) {
@@ -2403,7 +2466,7 @@ public:
String src_apk;
Error err;
- EditorProgress ep("export", "Exporting for Android", 105, true);
+ EditorProgress ep("export", TTR("Exporting for Android"), 105, true);
bool use_custom_build = bool(p_preset->get("custom_template/use_custom_build"));
bool p_give_internet = p_flags & (DEBUG_FLAG_DUMB_CLIENT | DEBUG_FLAG_REMOTE_DEBUG);
@@ -2452,7 +2515,7 @@ public:
return ERR_UNCONFIGURED;
}
if (export_format > EXPORT_FORMAT_AAB || export_format < EXPORT_FORMAT_APK) {
- EditorNode::add_io_error("Unsupported export format!\n");
+ EditorNode::add_io_error(TTR("Unsupported export format!\n"));
return ERR_UNCONFIGURED; //TODO: is this the right error?
}
@@ -2482,7 +2545,7 @@ public:
String project_name = get_project_name(p_preset->get("package/name"));
err = _create_project_name_strings_files(p_preset, project_name); //project name localization.
if (err != OK) {
- EditorNode::add_io_error("Unable to overwrite res://android/build/res/*.xml files with project name");
+ EditorNode::add_io_error(TTR("Unable to overwrite res://android/build/res/*.xml files with project name"));
}
// Copies the project icon files into the appropriate Gradle project directory.
_copy_icons_to_gradle_project(p_preset, processed_splash_config_xml, splash_image, splash_bg_color_image, main_image, foreground, background);
@@ -2491,18 +2554,27 @@ public:
//stores all the project files inside the Gradle project directory. Also includes all ABIs
_clear_assets_directory();
+ _remove_copied_libs();
if (!apk_expansion) {
print_verbose("Exporting project files..");
- err = export_project_files(p_preset, rename_and_store_file_in_gradle_project, nullptr, ignore_so_file);
+ CustomExportData user_data;
+ user_data.debug = p_debug;
+ err = export_project_files(p_preset, rename_and_store_file_in_gradle_project, &user_data, copy_gradle_so);
if (err != OK) {
- EditorNode::add_io_error("Could not export project files to gradle project\n");
+ EditorNode::add_io_error(TTR("Could not export project files to gradle project\n"));
return err;
}
+ if (user_data.libs.size() > 0) {
+ FileAccessRef fa = FileAccess::open(GDNATIVE_LIBS_PATH, FileAccess::WRITE);
+ JSON json;
+ fa->store_string(json.stringify(user_data.libs, "\t"));
+ fa->close();
+ }
} else {
print_verbose("Saving apk expansion file..");
err = save_apk_expansion_file(p_preset, p_path);
if (err != OK) {
- EditorNode::add_io_error("Could not write expansion package file!");
+ EditorNode::add_io_error(TTR("Could not write expansion package file!"));
return err;
}
}
@@ -2568,19 +2640,35 @@ public:
// Sensitive additions must be done below the logging statement.
print_verbose("Build Android project using gradle command: " + String("\n") + build_command + " " + join_list(cmdline, String(" ")));
- if (should_sign && !p_debug) {
- // Pass the release keystore info as well
- String release_keystore = p_preset->get("keystore/release");
- String release_username = p_preset->get("keystore/release_user");
- String release_password = p_preset->get("keystore/release_password");
- if (!FileAccess::exists(release_keystore)) {
- EditorNode::add_io_error("Could not find keystore, unable to export.");
- return ERR_FILE_CANT_OPEN;
- }
+ if (should_sign) {
+ if (p_debug) {
+ String debug_keystore = p_preset->get("keystore/debug");
+ String debug_password = p_preset->get("keystore/debug_password");
+ String debug_user = p_preset->get("keystore/debug_user");
+
+ if (debug_keystore.is_empty()) {
+ debug_keystore = EditorSettings::get_singleton()->get("export/android/debug_keystore");
+ debug_password = EditorSettings::get_singleton()->get("export/android/debug_keystore_pass");
+ debug_user = EditorSettings::get_singleton()->get("export/android/debug_keystore_user");
+ }
- cmdline.push_back("-Prelease_keystore_file=" + release_keystore); // argument to specify the release keystore file.
- cmdline.push_back("-Prelease_keystore_alias=" + release_username); // argument to specify the release keystore alias.
- cmdline.push_back("-Prelease_keystore_password=" + release_password); // argument to specity the release keystore password.
+ 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_password=" + debug_password); // argument to specify the debug keystore password.
+ } else {
+ // Pass the release keystore info as well
+ String release_keystore = p_preset->get("keystore/release");
+ String release_username = p_preset->get("keystore/release_user");
+ String release_password = p_preset->get("keystore/release_password");
+ if (!FileAccess::exists(release_keystore)) {
+ EditorNode::add_io_error(TTR("Could not find keystore, unable to export."));
+ return ERR_FILE_CANT_OPEN;
+ }
+
+ cmdline.push_back("-Prelease_keystore_file=" + release_keystore); // argument to specify the release keystore file.
+ cmdline.push_back("-Prelease_keystore_alias=" + release_username); // argument to specify the release keystore alias.
+ cmdline.push_back("-Prelease_keystore_password=" + release_password); // argument to specify the release keystore password.
+ }
}
int result = EditorNode::get_singleton()->execute_and_show_output(TTR("Building Android Project (gradle)"), build_command, cmdline);
@@ -2637,7 +2725,7 @@ public:
src_apk = find_export_template("android_release.apk");
}
if (src_apk == "") {
- EditorNode::add_io_error("Package not found: " + src_apk);
+ EditorNode::add_io_error(vformat(TTR("Package not found: %s"), src_apk));
return ERR_FILE_NOT_FOUND;
}
}
@@ -2649,13 +2737,13 @@ public:
FileAccess *src_f = nullptr;
zlib_filefunc_def io = zipio_create_io_from_file(&src_f);
- if (ep.step("Creating APK...", 0)) {
+ if (ep.step(TTR("Creating APK..."), 0)) {
return ERR_SKIP;
}
unzFile pkg = unzOpen2(src_apk.utf8().get_data(), &io);
if (!pkg) {
- EditorNode::add_io_error("Could not find template APK to export:\n" + src_apk);
+ EditorNode::add_io_error(vformat(TTR("Could not find template APK to export:\n%s"), src_apk));
return ERR_FILE_NOT_FOUND;
}
@@ -2783,12 +2871,11 @@ public:
if (!invalid_abis.is_empty()) {
String unsupported_arch = String(", ").join(invalid_abis);
- EditorNode::add_io_error("Missing libraries in the export template for the selected architectures: " + unsupported_arch + ".\n" +
- "Please build a template with all required libraries, or uncheck the missing architectures in the export preset.");
+ EditorNode::add_io_error(vformat(TTR("Missing libraries in the export template for the selected architectures: %s.\nPlease build a template with all required libraries, or uncheck the missing architectures in the export preset."), unsupported_arch));
CLEANUP_AND_RETURN(ERR_FILE_NOT_FOUND);
}
- if (ep.step("Adding files...", 1)) {
+ if (ep.step(TTR("Adding files..."), 1)) {
CLEANUP_AND_RETURN(ERR_SKIP);
}
err = OK;
@@ -2802,7 +2889,7 @@ public:
if (apk_expansion) {
err = save_apk_expansion_file(p_preset, p_path);
if (err != OK) {
- EditorNode::add_io_error("Could not write expansion package file!");
+ EditorNode::add_io_error(TTR("Could not write expansion package file!"));
return err;
}
} else {
@@ -2815,7 +2902,7 @@ public:
if (err != OK) {
unzClose(pkg);
- EditorNode::add_io_error("Could not export project files");
+ EditorNode::add_io_error(TTR("Could not export project files"));
CLEANUP_AND_RETURN(ERR_SKIP);
}
@@ -2845,13 +2932,13 @@ public:
// If we're not signing the apk, then the next step should be the last.
const int next_step = should_sign ? 103 : 105;
- if (ep.step("Aligning APK...", next_step)) {
+ if (ep.step(TTR("Aligning APK..."), next_step)) {
CLEANUP_AND_RETURN(ERR_SKIP);
}
unzFile tmp_unaligned = unzOpen2(tmp_unaligned_path.utf8().get_data(), &io);
if (!tmp_unaligned) {
- EditorNode::add_io_error("Could not unzip temporary unaligned APK.");
+ EditorNode::add_io_error(TTR("Could not unzip temporary unaligned APK."));
CLEANUP_AND_RETURN(ERR_FILE_NOT_FOUND);
}
@@ -2941,11 +3028,11 @@ public:
EditorExportPlatformAndroid() {
Ref<Image> img = memnew(Image(_android_logo));
- logo.instance();
+ logo.instantiate();
logo->create_from_image(img);
img = Ref<Image>(memnew(Image(_android_run_icon)));
- run_icon.instance();
+ run_icon.instantiate();
run_icon->create_from_image(img);
devices_changed.set();