Add 'Export App Bundle' to Android Export Options

This commit is contained in:
Aman Jain 2020-07-23 12:17:02 -04:00 committed by Fredia Huya-Kouadio
parent 654342d3bf
commit 59da1db188
3 changed files with 237 additions and 94 deletions

View file

@ -1591,6 +1591,7 @@ public:
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::BOOL, "custom_template/use_custom_build"), false));
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "custom_template/export_format", PROPERTY_HINT_ENUM, "Export APK,Export AAB"), 0));
Vector<PluginConfig> plugins_configs = get_plugins();
for (int i = 0; i < plugins_configs.size(); i++) {
@ -2050,6 +2051,13 @@ public:
}
}
if (int(p_preset->get("custom_template/export_format")) == 1 && /*AAB*/
!bool(p_preset->get("custom_template/use_custom_build"))) {
valid = false;
err += TTR("\"Export AAB\" is only valid when \"Use Custom Build\" is enabled.");
err += "\n";
}
r_error = err;
return valid;
}
@ -2057,6 +2065,7 @@ public:
virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const {
List<String> list;
list.push_back("apk");
list.push_back("aab");
return list;
}
@ -2365,7 +2374,21 @@ public:
return have_plugins_changed || first_build;
}
Error get_command_line_flags(const Ref<EditorExportPreset> &p_preset, const String &p_path, int p_flags, Vector<uint8_t> &r_command_line_flags) {
String get_apk_expansion_fullpath(const Ref<EditorExportPreset> &p_preset, const String &p_path) {
int version_code = p_preset->get("version/code");
String package_name = p_preset->get("package/unique_name");
String apk_file_name = "main." + itos(version_code) + "." + get_package_name(package_name) + ".obb";
String fullpath = p_path.get_base_dir().plus_file(apk_file_name);
return fullpath;
}
Error save_apk_expansion_file(const Ref<EditorExportPreset> &p_preset, const String &p_path) {
String fullpath = get_apk_expansion_fullpath(p_preset, p_path);
Error err = save_pack(p_preset, fullpath);
return err;
}
void get_command_line_flags(const Ref<EditorExportPreset> &p_preset, const String &p_path, int p_flags, Vector<uint8_t> &r_command_line_flags) {
String cmdline = p_preset->get("command_line/extra_args");
Vector<String> command_line_strings = cmdline.strip_edges().split(" ");
for (int i = 0; i < command_line_strings.size(); i++) {
@ -2379,17 +2402,8 @@ public:
bool apk_expansion = p_preset->get("apk_expansion/enable");
if (apk_expansion) {
int version_code = p_preset->get("version/code");
String package_name = p_preset->get("package/unique_name");
String apk_file_name = "main." + itos(version_code) + "." + get_package_name(package_name) + ".obb";
String fullpath = p_path.get_base_dir().plus_file(apk_file_name);
String fullpath = get_apk_expansion_fullpath(p_preset, p_path);
String apk_expansion_public_key = p_preset->get("apk_expansion/public_key");
Error err = save_pack(p_preset, fullpath);
if (err != OK) {
EditorNode::add_io_error("Could not write expansion package file: " + apk_file_name);
return err;
}
command_line_strings.push_back("--use_apk_expansion");
command_line_strings.push_back("--apk_expansion_md5");
@ -2435,7 +2449,6 @@ public:
copymem(&r_command_line_flags.write[base + 4], command_line_argument.ptr(), length);
}
}
return OK;
}
Error sign_apk(const Ref<EditorExportPreset> &p_preset, bool p_debug, String apk_path, EditorProgress ep) {
@ -2532,11 +2545,16 @@ public:
ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
String src_apk;
Error err;
EditorProgress ep("export", "Exporting for Android", 105, true);
bool use_custom_build = bool(p_preset->get("custom_template/use_custom_build"));
int export_format = int(p_preset->get("custom_template/export_format"));
bool p_give_internet = p_flags & (DEBUG_FLAG_DUMB_CLIENT | DEBUG_FLAG_REMOTE_DEBUG);
bool _signed = p_preset->get("package/signed");
bool apk_expansion = p_preset->get("apk_expansion/enable");
Vector<String> enabled_abis = get_enabled_abis(p_preset);
Ref<Image> main_image;
Ref<Image> foreground;
@ -2544,25 +2562,48 @@ public:
load_icon_refs(p_preset, main_image, foreground, background);
if (use_custom_build) {
//re-generate build.gradle and AndroidManifest.xml
{ //test that installed build version is alright
FileAccessRef f = FileAccess::open("res://android/.build_version", FileAccess::READ);
if (!f) {
EditorNode::get_singleton()->show_warning(TTR("Trying to build from a custom built template, but no version info for it exists. Please reinstall from the 'Project' menu."));
return ERR_UNCONFIGURED;
}
String version = f->get_line().strip_edges();
if (version != VERSION_FULL_CONFIG) {
EditorNode::get_singleton()->show_warning(vformat(TTR("Android build version mismatch:\n Template installed: %s\n Godot Version: %s\nPlease reinstall Android build template from 'Project' menu."), version, VERSION_FULL_CONFIG));
return ERR_UNCONFIGURED;
}
Vector<uint8_t> command_line_flags;
// Write command line flags into the command_line_flags variable.
get_command_line_flags(p_preset, p_path, p_flags, command_line_flags);
if (export_format == 1) {
if (!p_path.ends_with(".aab")) {
EditorNode::get_singleton()->show_warning(TTR("Invalid filename! Android App Bundle requires the *.aab extension."));
return ERR_UNCONFIGURED;
}
if (apk_expansion) {
EditorNode::get_singleton()->show_warning(TTR("APK Expansion not compatible with Android App Bundle."));
return ERR_UNCONFIGURED;
}
}
if (export_format == 0 && !p_path.ends_with(".apk")) {
EditorNode::get_singleton()->show_warning(
TTR("Invalid filename! Android APK requires the *.apk extension."));
return ERR_UNCONFIGURED;
}
if (export_format > 1 || export_format < 0) {
EditorNode::add_io_error("Unsupported export format!\n");
return ERR_UNCONFIGURED; //TODO: is this the right error?
}
if (use_custom_build) {
//test that installed build version is alright
FileAccessRef f = FileAccess::open("res://android/.build_version", FileAccess::READ);
if (!f) {
EditorNode::get_singleton()->show_warning(TTR("Trying to build from a custom built template, but no version info for it exists. Please reinstall from the 'Project' menu."));
return ERR_UNCONFIGURED;
}
String version = f->get_line().strip_edges();
if (version != VERSION_FULL_CONFIG) {
EditorNode::get_singleton()->show_warning(vformat(TTR("Android build version mismatch:\n Template installed: %s\n Godot Version: %s\nPlease reinstall Android build template from 'Project' menu."), version, VERSION_FULL_CONFIG));
return ERR_UNCONFIGURED;
}
String sdk_path = EDITOR_GET("export/android/custom_build_sdk_path");
ERR_FAIL_COND_V_MSG(sdk_path == "", ERR_UNCONFIGURED, "Android SDK path must be configured in Editor Settings at 'export/android/custom_build_sdk_path'.");
// TODO: should we use "package/name" or "application/config/name"?
String project_name = get_project_name(p_preset->get("package/name"));
// instead of calling _fix_resources
Error err = _create_project_name_strings_files(p_preset, project_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");
}
@ -2570,16 +2611,32 @@ public:
_copy_icons_to_gradle_project(p_preset, main_image, foreground, background);
// Write an AndroidManifest.xml file into the Gradle project directory.
_write_tmp_manifest(p_preset, p_give_internet, p_debug);
//build project if custom build is enabled
String sdk_path = EDITOR_GET("export/android/custom_build_sdk_path");
ERR_FAIL_COND_V_MSG(sdk_path == "", ERR_UNCONFIGURED, "Android SDK path must be configured in Editor Settings at 'export/android/custom_build_sdk_path'.");
_update_custom_build_project();
//stores all the project files inside the Gradle project directory. Also includes all ABIs
if (!apk_expansion) {
DirAccess *da_res = DirAccess::create(DirAccess::ACCESS_RESOURCES);
if (da_res->dir_exists("res://android/build/assets")) {
DirAccess *da_assets = DirAccess::open("res://android/build/assets");
da_assets->erase_contents_recursive();
da_res->remove("res://android/build/assets");
}
err = export_project_files(p_preset, rename_and_store_file_in_gradle_project, NULL, ignore_so_file);
if (err != OK) {
EditorNode::add_io_error("Could not export project files to gradle project\n");
return err;
}
} else {
err = save_apk_expansion_file(p_preset, p_path);
if (err != OK) {
EditorNode::add_io_error("Could not write expansion package file!");
return err;
}
}
store_file_at_path("res://android/build/assets/_cl_", command_line_flags);
OS::get_singleton()->set_environment("ANDROID_HOME", sdk_path); //set and overwrite if required
String build_command;
#ifdef WINDOWS_ENABLED
build_command = "gradlew.bat";
#else
@ -2587,12 +2644,12 @@ public:
#endif
String build_path = ProjectSettings::get_singleton()->get_resource_path().plus_file("android/build");
build_command = build_path.plus_file(build_command);
String package_name = get_package_name(p_preset->get("package/unique_name"));
String version_code = itos(p_preset->get("version/code"));
String version_name = p_preset->get("version/name");
String enabled_abi_string = String("|").join(enabled_abis);
Vector<PluginConfig> enabled_plugins = get_enabled_plugins(p_preset);
String local_plugins_binaries = get_plugins_binaries(BINARY_TYPE_LOCAL, enabled_plugins);
@ -2604,10 +2661,20 @@ public:
if (clean_build_required) {
cmdline.push_back("clean");
}
cmdline.push_back("build");
String build_type = p_debug ? "Debug" : "Release";
if (export_format == 1) {
String bundle_build_command = vformat("bundle%s", build_type);
cmdline.push_back(bundle_build_command);
} else if (export_format == 0) {
String apk_build_command = vformat("assemble%s", build_type);
cmdline.push_back(apk_build_command);
}
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_name=" + version_name); // argument to specify the version name.
cmdline.push_back("-Pexport_enabled_abis=" + enabled_abi_string); // argument to specify enabled ABIs.
cmdline.push_back("-Pplugins_local_binaries=" + local_plugins_binaries); // argument to specify the list of plugins local dependencies.
cmdline.push_back("-Pplugins_remote_binaries=" + remote_plugins_binaries); // argument to specify the list of plugins remote dependencies.
cmdline.push_back("-Pplugins_maven_repos=" + custom_maven_repos); // argument to specify the list of custom maven repos for the plugins dependencies.
@ -2625,35 +2692,56 @@ public:
EditorNode::get_singleton()->show_warning(TTR("Building of Android project failed, check output for the error.\nAlternatively visit docs.godotengine.org for Android build documentation."));
return ERR_CANT_CREATE;
}
if (p_debug) {
src_apk = build_path.plus_file("build/outputs/apk/debug/android_debug.apk");
} else {
src_apk = build_path.plus_file("build/outputs/apk/release/android_release.apk");
List<String> copy_args;
String copy_command;
if (export_format == 1) {
copy_command = vformat("copyAndRename%sAab", build_type);
} else if (export_format == 0) {
copy_command = vformat("copyAndRename%sApk", build_type);
}
if (!FileAccess::exists(src_apk)) {
EditorNode::get_singleton()->show_warning(TTR("No build apk generated at: ") + "\n" + src_apk);
copy_args.push_back(copy_command);
copy_args.push_back("-p"); // argument to specify the start directory.
copy_args.push_back(build_path); // start directory.
String export_filename = p_path.get_file();
String export_path = p_path.get_base_dir();
copy_args.push_back("-Pexport_path=file:" + export_path);
copy_args.push_back("-Pexport_filename=" + export_filename);
int copy_result = EditorNode::get_singleton()->execute_and_show_output(TTR("Moving output"), build_command, copy_args);
if (copy_result != 0) {
EditorNode::get_singleton()->show_warning(TTR("Unable to copy and rename export file, check gradle project directory for outputs."));
return ERR_CANT_CREATE;
}
} else {
if (_signed) {
err = sign_apk(p_preset, p_debug, p_path, ep);
if (err != OK) {
return err;
}
}
return OK;
}
// This is the start of the Legacy build system
if (p_debug)
src_apk = p_preset->get("custom_template/debug");
else
src_apk = p_preset->get("custom_template/release");
if (p_debug)
src_apk = p_preset->get("custom_template/debug");
else
src_apk = p_preset->get("custom_template/release");
src_apk = src_apk.strip_edges();
src_apk = src_apk.strip_edges();
if (src_apk == "") {
if (p_debug) {
src_apk = find_export_template("android_debug.apk");
} else {
src_apk = find_export_template("android_release.apk");
}
if (src_apk == "") {
if (p_debug) {
src_apk = find_export_template("android_debug.apk");
} else {
src_apk = find_export_template("android_release.apk");
}
if (src_apk == "") {
EditorNode::add_io_error("Package not found: " + src_apk);
return ERR_FILE_NOT_FOUND;
}
EditorNode::add_io_error("Package not found: " + src_apk);
return ERR_FILE_NOT_FOUND;
}
}
@ -2670,7 +2758,6 @@ public:
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);
return ERR_FILE_NOT_FOUND;
}
@ -2691,19 +2778,13 @@ public:
zipFile unaligned_apk = zipOpen2(tmp_unaligned_path.utf8().get_data(), APPEND_STATUS_CREATE, NULL, &io2);
bool _signed = p_preset->get("package/signed");
String cmdline = p_preset->get("command_line/extra_args");
String version_name = p_preset->get("version/name");
String package_name = p_preset->get("package/unique_name");
bool apk_expansion = p_preset->get("apk_expansion/enable");
String apk_expansion_pkey = p_preset->get("apk_expansion/public_key");
Vector<String> enabled_abis = get_enabled_abis(p_preset);
// Prepare images to be resized for the icons. If some image ends up being uninitialized, the default image from the export template will be used.
Vector<String> invalid_abis(enabled_abis);
while (ret == UNZ_OK) {
@ -2725,28 +2806,26 @@ public:
unzCloseCurrentFile(pkg);
//write
if (!use_custom_build) {
if (file == "AndroidManifest.xml") {
_fix_manifest(p_preset, data, p_give_internet);
}
if (file == "resources.arsc") {
_fix_resources(p_preset, data);
}
for (int i = 0; i < icon_densities_count; ++i) {
if (main_image.is_valid() && !main_image->empty()) {
if (file == launcher_icons[i].export_path) {
_process_launcher_icons(file, main_image, launcher_icons[i].dimensions, data);
}
if (file == "AndroidManifest.xml") {
_fix_manifest(p_preset, data, p_give_internet);
}
if (file == "resources.arsc") {
_fix_resources(p_preset, data);
}
for (int i = 0; i < icon_densities_count; ++i) {
if (main_image.is_valid() && !main_image->empty()) {
if (file == launcher_icons[i].export_path) {
_process_launcher_icons(file, main_image, launcher_icons[i].dimensions, data);
}
if (foreground.is_valid() && !foreground->empty()) {
if (file == launcher_adaptive_icon_foregrounds[i].export_path) {
_process_launcher_icons(file, foreground, launcher_adaptive_icon_foregrounds[i].dimensions, data);
}
}
if (foreground.is_valid() && !foreground->empty()) {
if (file == launcher_adaptive_icon_foregrounds[i].export_path) {
_process_launcher_icons(file, foreground, launcher_adaptive_icon_foregrounds[i].dimensions, data);
}
if (background.is_valid() && !background->empty()) {
if (file == launcher_adaptive_icon_backgrounds[i].export_path) {
_process_launcher_icons(file, background, launcher_adaptive_icon_backgrounds[i].dimensions, data);
}
}
if (background.is_valid() && !background->empty()) {
if (file == launcher_adaptive_icon_backgrounds[i].export_path) {
_process_launcher_icons(file, background, launcher_adaptive_icon_backgrounds[i].dimensions, data);
}
}
}
@ -2805,18 +2884,26 @@ public:
if (ep.step("Adding files...", 1)) {
CLEANUP_AND_RETURN(ERR_SKIP);
}
Error err = OK;
err = OK;
if (p_flags & DEBUG_FLAG_DUMB_CLIENT) {
APKExportData ed;
ed.ep = &ep;
ed.apk = unaligned_apk;
err = export_project_files(p_preset, ignore_apk_file, &ed, save_apk_so);
} else if (!apk_expansion) {
APKExportData ed;
ed.ep = &ep;
ed.apk = unaligned_apk;
err = export_project_files(p_preset, save_apk_file, &ed, save_apk_so);
} else {
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!");
return err;
}
} else {
APKExportData ed;
ed.ep = &ep;
ed.apk = unaligned_apk;
err = export_project_files(p_preset, save_apk_file, &ed, save_apk_so);
}
}
if (err != OK) {
@ -2825,10 +2912,6 @@ public:
CLEANUP_AND_RETURN(ERR_SKIP);
}
Vector<uint8_t> command_line_flags;
// Write command line flags into the command_line_flags variable.
err = get_command_line_flags(p_preset, p_path, p_flags, command_line_flags);
zip_fileinfo zipfi = get_zip_fileinfo();
zipOpenNewFileInZip(unaligned_apk,
"assets/_cl_",

View file

@ -92,6 +92,11 @@ android {
ignoreAssetsPattern "!.svn:!.git:!.ds_store:!*.scc:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"
}
ndk {
String[] export_abi_list = getExportEnabledABIs()
abiFilters export_abi_list
}
// Feel free to modify the application id to your own.
applicationId getExportPackageName()
versionCode getExportVersionCode()
@ -164,5 +169,29 @@ android {
}
}
task copyAndRenameDebugApk(type: Copy) {
from "$buildDir/outputs/apk/debug/android_debug.apk"
into getExportPath()
rename "android_debug.apk", getExportFilename()
}
task copyAndRenameReleaseApk(type: Copy) {
from "$buildDir/outputs/apk/release/android_release.apk"
into getExportPath()
rename "android_release.apk", getExportFilename()
}
task copyAndRenameDebugAab(type: Copy) {
from "$buildDir/outputs/bundle/debug/build-debug.aab"
into getExportPath()
rename "build-debug.aab", getExportFilename()
}
task copyAndRenameReleaseAab(type: Copy) {
from "$buildDir/outputs/bundle/release/build-release.aab"
into getExportPath()
rename "build-release.aab", getExportFilename()
}
//CHUNK_GLOBAL_BEGIN
//CHUNK_GLOBAL_END

View file

@ -46,6 +46,37 @@ ext.getExportVersionName = { ->
final String PLUGIN_VALUE_SEPARATOR_REGEX = "\\|"
// get the list of ABIs the project should be exported to
ext.getExportEnabledABIs = { ->
String enabledABIs = project.hasProperty("export_enabled_abis") ? project.property("export_enabled_abis") : "";
if (enabledABIs == null || enabledABIs.isEmpty()) {
enabledABIs = "armeabi-v7a|arm64-v8a|x86|x86_64|"
}
Set<String> exportAbiFilter = [];
for (String abi_name : enabledABIs.split(PLUGIN_VALUE_SEPARATOR_REGEX)) {
if (!abi_name.trim().isEmpty()){
exportAbiFilter.add(abi_name);
}
}
return exportAbiFilter;
}
ext.getExportPath = {
String exportPath = project.hasProperty("export_path") ? project.property("export_path") : ""
if (exportPath == null || exportPath.isEmpty()) {
exportPath = "."
}
return exportPath
}
ext.getExportFilename = {
String exportFilename = project.hasProperty("export_filename") ? project.property("export_filename") : ""
if (exportFilename == null || exportFilename.isEmpty()) {
exportFilename = "godot_android"
}
return exportFilename
}
/**
* Parse the project properties for the 'plugins_maven_repos' property and return the list
* of maven repos.