Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update the logic to sign prebuilt Godot Android apks #44645

Merged
merged 1 commit into from
Jan 1, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
210 changes: 130 additions & 80 deletions platform/android/export/export.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
}

// Check for devices updates
String adb = EditorSettings::get_singleton()->get("export/android/adb");
String adb = get_adb_path();
if (FileAccess::exists(adb)) {
String devices;
List<String> args;
Expand Down Expand Up @@ -425,7 +425,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
}

if (EditorSettings::get_singleton()->get("export/android/shutdown_adb_on_exit")) {
String adb = EditorSettings::get_singleton()->get("export/android/adb");
String adb = get_adb_path();
if (!FileAccess::exists(adb)) {
return; //adb not configured
}
Expand Down Expand Up @@ -1752,7 +1752,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {

EditorProgress ep("run", "Running on " + devices[p_device].name, 3);

String adb = EditorSettings::get_singleton()->get("export/android/adb");
String adb = get_adb_path();

// Export_temp APK.
if (ep.step("Exporting APK...", 0)) {
Expand Down Expand Up @@ -1899,6 +1899,55 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
return run_icon;
}

static String get_adb_path() {
String exe_ext = "";
if (OS::get_singleton()->get_name() == "Windows") {
exe_ext = ".exe";
}
String sdk_path = EditorSettings::get_singleton()->get("export/android/android_sdk_path");
return sdk_path.plus_file("platform-tools/adb" + exe_ext);
}

static String get_apksigner_path() {
String exe_ext = "";
if (OS::get_singleton()->get_name() == "Windows") {
exe_ext = ".bat";
}
String apksigner_command_name = "apksigner" + exe_ext;
String sdk_path = EditorSettings::get_singleton()->get("export/android/android_sdk_path");
String apksigner_path = "";

Error errn;
String build_tools_dir = sdk_path.plus_file("build-tools");
DirAccessRef da = DirAccess::open(build_tools_dir, &errn);
if (errn != OK) {
print_error("Unable to open Android 'build-tools' directory.");
return apksigner_path;
}

// There are additional versions directories we need to go through.
da->list_dir_begin();
String sub_dir = da->get_next();
while (!sub_dir.is_empty()) {
if (!sub_dir.begins_with(".") && da->current_is_dir()) {
// Check if the tool is here.
String tool_path = build_tools_dir.plus_file(sub_dir).plus_file(apksigner_command_name);
if (FileAccess::exists(tool_path)) {
apksigner_path = tool_path;
break;
}
}
sub_dir = da->get_next();
}
da->list_dir_end();

if (apksigner_path.is_empty()) {
EditorNode::get_singleton()->show_warning(TTR("Unable to find the 'apksigner' tool."));
}

return apksigner_path;
}

virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override {
String err;
bool valid = false;
Expand Down Expand Up @@ -1934,25 +1983,16 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
}
} else {
valid = exists_export_template("android_source.zip", &err);

if (!FileAccess::exists("res://android/build/build.gradle")) {
err += TTR("Android build template not installed in the project. Install it from the Project menu.") + "\n";
valid = false;
}
}
r_missing_templates = !valid;

// Validate the rest of the configuration.

String adb = EditorSettings::get_singleton()->get("export/android/adb");

if (!FileAccess::exists(adb)) {
valid = false;
err += TTR("ADB executable not configured in the Editor Settings.") + "\n";
}

String js = EditorSettings::get_singleton()->get("export/android/jarsigner");

if (!FileAccess::exists(js)) {
valid = false;
err += TTR("OpenJDK jarsigner not configured in the Editor Settings.") + "\n";
}

String dk = p_preset->get("keystore/debug");

if (!FileAccess::exists(dk)) {
Expand All @@ -1970,25 +2010,45 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
err += TTR("Release keystore incorrectly configured in the export preset.") + "\n";
}

if (bool(p_preset->get("custom_template/use_custom_build"))) {
String sdk_path = EditorSettings::get_singleton()->get("export/android/custom_build_sdk_path");
if (sdk_path == "") {
err += TTR("Custom build requires a valid Android SDK path in Editor Settings.") + "\n";
String sdk_path = EditorSettings::get_singleton()->get("export/android/android_sdk_path");
if (sdk_path == "") {
err += TTR("A valid Android SDK path is required in Editor Settings.") + "\n";
valid = false;
} else {
Error errn;
// Check for the platform-tools directory.
DirAccessRef da = DirAccess::open(sdk_path.plus_file("platform-tools"), &errn);
if (errn != OK) {
err += TTR("Invalid Android SDK path in Editor Settings.");
err += TTR("Missing 'platform-tools' directory!");
err += "\n";
valid = false;
}

// Validate that adb is available
String adb_path = get_adb_path();
if (!FileAccess::exists(adb_path)) {
err += TTR("Unable to find Android SDK platform-tools' adb command.");
err += TTR("Please check in the Android SDK directory specified in Editor Settings.");
err += "\n";
valid = false;
} else {
Error errn;
// Check for the platform-tools directory.
DirAccessRef da = DirAccess::open(sdk_path.plus_file("platform-tools"), &errn);
if (errn != OK) {
err += TTR("Invalid Android SDK path for custom build in Editor Settings.");
err += TTR("Missing 'platform-tools' directory!");
err += "\n";
valid = false;
}
}

if (!FileAccess::exists("res://android/build/build.gradle")) {
err += TTR("Android build template not installed in the project. Install it from the Project menu.") + "\n";
// Check for the build-tools directory.
DirAccessRef build_tools_da = DirAccess::open(sdk_path.plus_file("build-tools"), &errn);
if (errn != OK) {
err += TTR("Invalid Android SDK path in Editor Settings.");
err += TTR("Missing 'build-tools' directory!");
err += "\n";
valid = false;
}

// Validate that apksigner is available
String apksigner_path = get_apksigner_path();
if (!FileAccess::exists(apksigner_path)) {
err += TTR("Unable to find Android SDK build-tools' apksigner command.");
err += TTR("Please check in the Android SDK directory specified in Editor Settings.");
err += "\n";
valid = false;
}
}
Expand Down Expand Up @@ -2171,16 +2231,16 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
}
}

Error sign_apk(const Ref<EditorExportPreset> &p_preset, bool p_debug, String export_path, EditorProgress ep) {
Error sign_apk(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &export_path, EditorProgress &ep) {
int export_format = int(p_preset->get("custom_template/export_format"));
String export_label = export_format == EXPORT_FORMAT_AAB ? "AAB" : "APK";
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");

String jarsigner = EditorSettings::get_singleton()->get("export/android/jarsigner");
if (!FileAccess::exists(jarsigner)) {
EditorNode::add_io_error("'jarsigner' could not be found.\nPlease supply a path in the Editor Settings.\nThe resulting " + export_label + " is unsigned.");
String apksigner = get_apksigner_path();
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.");
return OK;
}

Expand All @@ -2198,7 +2258,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
user = EditorSettings::get_singleton()->get("export/android/debug_keystore_user");
}

if (ep.step("Signing debug " + export_label + "...", 103)) {
if (ep.step("Signing debug " + export_label + "...", 104)) {
return ERR_SKIP;
}

Expand All @@ -2207,7 +2267,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
password = release_password;
user = release_username;

if (ep.step("Signing release " + export_label + "...", 103)) {
if (ep.step("Signing release " + export_label + "...", 104)) {
return ERR_SKIP;
}
}
Expand All @@ -2218,43 +2278,34 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
}

List<String> args;
args.push_back("-digestalg");
args.push_back("SHA-256");
args.push_back("-sigalg");
args.push_back("SHA256withRSA");
String tsa_url = EditorSettings::get_singleton()->get("export/android/timestamping_authority_url");
if (tsa_url != "") {
args.push_back("-tsa");
args.push_back(tsa_url);
}
args.push_back("-verbose");
args.push_back("-keystore");
args.push_back("sign");
args.push_back("--verbose");
args.push_back("--ks");
args.push_back(keystore);
args.push_back("-storepass");
args.push_back(password);
args.push_back(export_path);
args.push_back("--ks-pass");
args.push_back("pass:" + password);
args.push_back("--ks-key-alias");
args.push_back(user);
args.push_back(export_path);
int retval;
OS::get_singleton()->execute(jarsigner, args, true, NULL, NULL, &retval);
OS::get_singleton()->execute(apksigner, args, true, NULL, NULL, &retval);
if (retval) {
EditorNode::add_io_error("'jarsigner' returned with error #" + itos(retval));
EditorNode::add_io_error("'apksigner' returned with error #" + itos(retval));
return ERR_CANT_CREATE;
}

if (ep.step("Verifying " + export_label + "...", 104)) {
if (ep.step("Verifying " + export_label + "...", 105)) {
return ERR_SKIP;
}

args.clear();
args.push_back("-verify");
args.push_back("-keystore");
args.push_back(keystore);
args.push_back("verify");
args.push_back("--verbose");
args.push_back(export_path);
args.push_back("-verbose");

OS::get_singleton()->execute(jarsigner, args, true, NULL, NULL, &retval);
OS::get_singleton()->execute(apksigner, args, true, NULL, NULL, &retval);
if (retval) {
EditorNode::add_io_error("'jarsigner' verification of " + export_label + " failed. Make sure to use a jarsigner from OpenJDK 8.");
EditorNode::add_io_error("'apksigner' verification of " + export_label + " failed.");
return ERR_CANT_CREATE;
}
return OK;
Expand Down Expand Up @@ -2337,8 +2388,8 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
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'.");
String sdk_path = EDITOR_GET("export/android/android_sdk_path");
ERR_FAIL_COND_V_MSG(sdk_path == "", ERR_UNCONFIGURED, "Android SDK path must be configured in Editor Settings at 'export/android/android_sdk_path'.");

// TODO: should we use "package/name" or "application/config/name"?
String project_name = get_project_name(p_preset->get("package/name"));
Expand Down Expand Up @@ -2685,18 +2736,13 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
CLEANUP_AND_RETURN(err);
}

if (should_sign) {
err = sign_apk(p_preset, p_debug, tmp_unaligned_path, ep);
if (err != OK) {
CLEANUP_AND_RETURN(err);
}
}

// Let's zip-align (must be done after signing)
// Let's zip-align (must be done before signing)

static const int ZIP_ALIGNMENT = 4;

if (ep.step("Aligning APK...", 105)) {
// 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)) {
CLEANUP_AND_RETURN(ERR_SKIP);
}

Expand Down Expand Up @@ -2770,6 +2816,15 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
zipClose(final_apk, nullptr);
unzClose(tmp_unaligned);

if (should_sign) {
// Signing must be done last as any additional modifications to the
// file will invalidate the signature.
err = sign_apk(p_preset, p_debug, p_path, ep);
if (err != OK) {
CLEANUP_AND_RETURN(err);
}
}

CLEANUP_AND_RETURN(OK);
}

Expand Down Expand Up @@ -2809,19 +2864,14 @@ void register_android_exporter() {
exe_ext = "*.exe";
}

EDITOR_DEF("export/android/adb", "");
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/adb", PROPERTY_HINT_GLOBAL_FILE, exe_ext));
EDITOR_DEF("export/android/jarsigner", "");
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/jarsigner", PROPERTY_HINT_GLOBAL_FILE, exe_ext));
EDITOR_DEF("export/android/android_sdk_path", "");
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/android_sdk_path", PROPERTY_HINT_GLOBAL_DIR));
EDITOR_DEF("export/android/debug_keystore", "");
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/debug_keystore", PROPERTY_HINT_GLOBAL_FILE, "*.keystore,*.jks"));
EDITOR_DEF("export/android/debug_keystore_user", "androiddebugkey");
EDITOR_DEF("export/android/debug_keystore_pass", "android");
EDITOR_DEF("export/android/force_system_user", false);
EDITOR_DEF("export/android/custom_build_sdk_path", "");
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/custom_build_sdk_path", PROPERTY_HINT_GLOBAL_DIR));

EDITOR_DEF("export/android/timestamping_authority_url", "");
EDITOR_DEF("export/android/shutdown_adb_on_exit", true);

Ref<EditorExportPlatformAndroid> exporter = Ref<EditorExportPlatformAndroid>(memnew(EditorExportPlatformAndroid));
Expand Down