fix: AGP 9.0 ARM64 Proot release APK 缺 AndroidManifest.xml
packageRelease 任务在 ARM64 Proot 环境下丢失 manifest, 新增 fixReleaseManifest 后处理任务:从 .ap_ 提取 binary AndroidManifest.xml 注入 APK 并用 apksigner 重新签名。 仅 aarch64 环境触发,CI x86_64 不受影响。
This commit is contained in:
parent
ea1cae597d
commit
5ead63a8a3
@ -1,3 +1,5 @@
|
|||||||
|
import java.io.File
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.application)
|
alias(libs.plugins.android.application)
|
||||||
alias(libs.plugins.kotlin.compose)
|
alias(libs.plugins.kotlin.compose)
|
||||||
@ -45,6 +47,10 @@ android {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
androidResources {
|
||||||
|
// AGP 9.0 ARM64 Proot: noCompress workaround (manifest still needs post-process)
|
||||||
|
noCompress += "xml"
|
||||||
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_17
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
targetCompatibility = JavaVersion.VERSION_17
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
@ -59,6 +65,91 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════
|
||||||
|
// AGP 9.0 ARM64 Proot Workaround: packageRelease 丢失
|
||||||
|
// AndroidManifest.xml
|
||||||
|
//
|
||||||
|
// 修复:assembleRelease 后检查 APK 是否含 manifest,
|
||||||
|
// 缺失则注入并重新签名。仅 aarch64 环境触发。
|
||||||
|
// ═══════════════════════════════════════════════════════
|
||||||
|
if (System.getProperty("os.arch") == "aarch64") {
|
||||||
|
tasks.register("fixReleaseManifest") {
|
||||||
|
dependsOn("assembleRelease")
|
||||||
|
doLast {
|
||||||
|
val apk = layout.buildDirectory.file("outputs/apk/release/app-release.apk").get().asFile
|
||||||
|
val manifest = layout.buildDirectory
|
||||||
|
.file("intermediates/linked_resources_binary_format/release/processReleaseResources/linked-resources-binary-format-release.ap_")
|
||||||
|
.get().asFile
|
||||||
|
if (!apk.exists() || !manifest.exists()) return@doLast
|
||||||
|
|
||||||
|
// 检查 APK 是否已包含 manifest
|
||||||
|
val checkResult = providers.exec {
|
||||||
|
commandLine("unzip", "-l", apk.absolutePath)
|
||||||
|
isIgnoreExitValue = true
|
||||||
|
}.standardOutput.asText.get()
|
||||||
|
|
||||||
|
if (!checkResult.contains("AndroidManifest.xml")) {
|
||||||
|
logger.lifecycle("FixManifestTask: injecting AndroidManifest.xml into release APK")
|
||||||
|
// 从 .ap_ 提取 binary AndroidManifest.xml 到临时文件
|
||||||
|
val tmpManifest = File(apk.parentFile, "AndroidManifest.xml")
|
||||||
|
val extractProcess = Runtime.getRuntime().exec(
|
||||||
|
arrayOf("unzip", "-o", manifest.absolutePath, "AndroidManifest.xml", "-d", apk.parentFile!!.absolutePath)
|
||||||
|
)
|
||||||
|
extractProcess.waitFor()
|
||||||
|
logger.lifecycle("FixManifestTask: extract from .ap_ exit code = ${extractProcess.exitValue()}")
|
||||||
|
// 用 zip 命令注入 binary manifest
|
||||||
|
val zipProcess = Runtime.getRuntime().exec(arrayOf("zip", "-j", apk.absolutePath, tmpManifest.absolutePath))
|
||||||
|
zipProcess.waitFor()
|
||||||
|
logger.lifecycle("FixManifestTask: zip exit code = ${zipProcess.exitValue()}")
|
||||||
|
// 找 apksigner 并重新签名
|
||||||
|
val androidHome = System.getenv("ANDROID_HOME") ?: System.getenv("ANDROID_SDK_ROOT")
|
||||||
|
var signerFile: File? = null
|
||||||
|
if (androidHome != null) {
|
||||||
|
val btDir = File(androidHome, "build-tools")
|
||||||
|
val latest = btDir.listFiles()?.filter { it.isDirectory }?.maxByOrNull { it.name }
|
||||||
|
if (latest != null) {
|
||||||
|
val signer = File(latest, "apksigner")
|
||||||
|
if (signer.exists()) signerFile = signer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val sf = signerFile
|
||||||
|
if (sf != null && sf.exists()) {
|
||||||
|
// 从 android extension 拿签名配置
|
||||||
|
val androidExt = project.extensions.findByName("android")
|
||||||
|
val signingConfig = (androidExt as? com.android.build.api.dsl.ApplicationExtension)
|
||||||
|
?.signingConfigs?.findByName("release")
|
||||||
|
val ksFile = signingConfig?.storeFile?.absolutePath ?: ""
|
||||||
|
val ksPass = signingConfig?.storePassword ?: ""
|
||||||
|
val ksAlias = signingConfig?.keyAlias ?: ""
|
||||||
|
val ksKeyPass = signingConfig?.keyPassword ?: ""
|
||||||
|
val signCmd = arrayOf(
|
||||||
|
sf.absolutePath, "sign",
|
||||||
|
"--ks", ksFile,
|
||||||
|
"--ks-pass", "pass:$ksPass",
|
||||||
|
"--ks-key-alias", ksAlias,
|
||||||
|
"--key-pass", "pass:$ksKeyPass",
|
||||||
|
"--v2-signing-enabled", "true",
|
||||||
|
"--v3-signing-enabled", "false",
|
||||||
|
"--min-sdk-version", "31",
|
||||||
|
apk.absolutePath
|
||||||
|
)
|
||||||
|
val signProcess = Runtime.getRuntime().exec(signCmd)
|
||||||
|
val errorText = signProcess.errorStream.bufferedReader().readText()
|
||||||
|
signProcess.waitFor()
|
||||||
|
logger.lifecycle("FixManifestTask: apksigner exit code = ${signProcess.exitValue()}")
|
||||||
|
if (errorText.isNotBlank()) logger.lifecycle("FixManifestTask: apksigner stderr = $errorText")
|
||||||
|
logger.lifecycle("FixManifestTask: APK re-signed successfully")
|
||||||
|
} else {
|
||||||
|
logger.warn("FixManifestTask: apksigner not found, APK not re-signed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
project.tasks.matching { it.name == "assembleRelease" }.configureEach {
|
||||||
|
finalizedBy("fixReleaseManifest")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Force ARM64 AAPT2 in Proot environment (local only; GitHub Actions x86_64 uses default)
|
// Force ARM64 AAPT2 in Proot environment (local only; GitHub Actions x86_64 uses default)
|
||||||
if (System.getProperty("os.arch") == "aarch64") {
|
if (System.getProperty("os.arch") == "aarch64") {
|
||||||
configurations.all {
|
configurations.all {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user