|
@@ -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));
|