diff options
Diffstat (limited to 'platform/android/export/export.cpp')
| -rw-r--r-- | platform/android/export/export.cpp | 197 |
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(); |
