Browse Source

Merge pull request #44645 from m4gr3d/update_apk_signing_logic

Update the logic to sign prebuilt Godot Android apks
Rémi Verschelde 4 years ago
parent
commit
3433780c49
1 changed files with 130 additions and 80 deletions
  1. 130 80
      platform/android/export/export.cpp

+ 130 - 80
platform/android/export/export.cpp

@@ -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;
@@ -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
 			}
@@ -1752,7 +1752,7 @@ public:
 
 		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)) {
@@ -1899,6 +1899,55 @@ public:
 		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;
@@ -1934,25 +1983,16 @@ public:
 			}
 		} 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)) {
@@ -1970,25 +2010,45 @@ public:
 			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;
 			}
 		}
@@ -2171,16 +2231,16 @@ public:
 		}
 	}
 
-	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;
 		}
 
@@ -2198,7 +2258,7 @@ public:
 				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;
 			}
 
@@ -2207,7 +2267,7 @@ public:
 			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;
 			}
 		}
@@ -2218,43 +2278,34 @@ public:
 		}
 
 		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;
@@ -2337,8 +2388,8 @@ public:
 					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"));
@@ -2685,18 +2736,13 @@ public:
 			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);
 		}
 
@@ -2770,6 +2816,15 @@ public:
 		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);
 	}
 
@@ -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));