build: add Android release APK packaging script
This commit is contained in:
@@ -33,6 +33,24 @@ val releaseStorePassword = resolveSigningProp("CHANORA_RELEASE_STORE_PASSWORD")
|
||||
val releaseKeyAlias = resolveSigningProp("CHANORA_RELEASE_KEY_ALIAS")
|
||||
val releaseKeyPassword = resolveSigningProp("CHANORA_RELEASE_KEY_PASSWORD")
|
||||
val hasReleaseSigning = listOf(releaseStoreFile, releaseStorePassword, releaseKeyAlias, releaseKeyPassword).all { !it.isNullOrBlank() }
|
||||
val splitPerAbiRequested = gradle.startParameter.taskNames.any { taskName ->
|
||||
taskName.contains("split-per-abi", ignoreCase = true)
|
||||
}
|
||||
val configuredAbiSet = listOf("arm64-v8a", "x86_64")
|
||||
val flutterTargetPlatformArg = gradle.startParameter.projectProperties["target-platform"]
|
||||
?.split(',')
|
||||
?.map { it.trim() }
|
||||
?.firstOrNull()
|
||||
val singleAbiFromFlutterTargetPlatform = when (flutterTargetPlatformArg) {
|
||||
"android-arm64" -> "arm64-v8a"
|
||||
"android-x64" -> "x86_64"
|
||||
else -> null
|
||||
}
|
||||
val effectivePackagingAbis: List<String> = when {
|
||||
singleAbiFromFlutterTargetPlatform != null -> listOf(singleAbiFromFlutterTargetPlatform)
|
||||
splitPerAbiRequested -> configuredAbiSet
|
||||
else -> configuredAbiSet
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "app.chanora.chanora_flutter"
|
||||
@@ -77,7 +95,7 @@ android {
|
||||
// ANDROID_PLATFORM as -D variables to the child cmake
|
||||
// invocation). See Cargo.toml [patch.crates-io] block and
|
||||
// docs/governance/product-decision-register.md DEC-032.
|
||||
abiFilters += listOf("arm64-v8a", "x86_64")
|
||||
abiFilters += effectivePackagingAbis
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,6 +155,16 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
packaging {
|
||||
jniLibs {
|
||||
if (singleAbiFromFlutterTargetPlatform != null) {
|
||||
val excludedAbis = configuredAbiSet.filter { it != singleAbiFromFlutterTargetPlatform } +
|
||||
listOf("armeabi-v7a", "x86")
|
||||
excludes += excludedAbis.map { abi -> "lib/$abi/**" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SDD-109 item 2: Android App Bundle (.aab) split configuration.
|
||||
// Combined with the SDD-073 abiFilters set, this yields two
|
||||
// native splits (arm64-v8a, x86_64), per-language resource
|
||||
@@ -155,7 +183,7 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
// ONNX Runtime native library for Silero / TEN VAD.
|
||||
// ONNX Runtime native library for Silero VAD.
|
||||
// The ort crate (Rust) loads libonnxruntime.so via dlopen at runtime
|
||||
// (`load-dynamic` feature). The AAR ships the .so for arm64-v8a,
|
||||
// armeabi-v7a, x86_64, x86. AGP merges these into the APK/AAB.
|
||||
@@ -229,7 +257,11 @@ val abiToRustTriple: Map<String, String> = mapOf(
|
||||
// `android.defaultConfig.ndk.abiFilters` declaration owned by SDD-073 item 4
|
||||
// so that the build-automation list and the AGP packaging list are guaranteed
|
||||
// in sync. Any unsupported ABI fails fast.
|
||||
val configuredAbis: List<String> = android.defaultConfig.ndk.abiFilters.toList()
|
||||
val configuredAbis: List<String> = when {
|
||||
singleAbiFromFlutterTargetPlatform != null -> listOf(singleAbiFromFlutterTargetPlatform)
|
||||
splitPerAbiRequested -> configuredAbiSet
|
||||
else -> android.defaultConfig.ndk.abiFilters.toList()
|
||||
}
|
||||
configuredAbis.forEach { abi ->
|
||||
require(abiToRustTriple.containsKey(abi)) {
|
||||
"[SDD-118] ABI '$abi' is not supported by SDD-118; see SDD-073 item 4. " +
|
||||
@@ -458,7 +490,7 @@ val abiToNdkSysrootTriple: Map<String, String> = mapOf(
|
||||
"arm64-v8a" to "aarch64-linux-android",
|
||||
"x86_64" to "x86_64-linux-android",
|
||||
)
|
||||
fun registerJniLibsCopyTask(profile: String): TaskProvider<Copy> {
|
||||
fun registerJniLibsCopyTask(profile: String): TaskProvider<Task> {
|
||||
val capitalized = profile.replaceFirstChar { it.uppercase() }
|
||||
val buildTask = if (profile == "release") buildRustBridgeRelease else buildRustBridgeDebug
|
||||
|
||||
@@ -468,16 +500,30 @@ fun registerJniLibsCopyTask(profile: String): TaskProvider<Copy> {
|
||||
.takeUnless { it.isNullOrBlank() }
|
||||
?: android.ndkDirectory.absolutePath
|
||||
|
||||
return tasks.register<Copy>("copyRustBridgeJniLibs$capitalized") {
|
||||
return tasks.register("copyRustBridgeJniLibs$capitalized") {
|
||||
group = "chanora_bridge"
|
||||
description = "SDD-118 item 6 (extended): stage per-ABI libchanora_bridge.so + libc++_shared.so (profile=$profile) into src/main/jniLibs/."
|
||||
description = "SDD-118 item 6 (extended): strip and stage per-ABI libchanora_bridge.so + libc++_shared.so (profile=$profile) into src/main/jniLibs/."
|
||||
dependsOn(buildTask)
|
||||
duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
||||
|
||||
val jniLibsDir = layout.projectDirectory.dir("src/main/jniLibs").asFile
|
||||
val llvmStrip = file(
|
||||
"$effectiveNdkPath/toolchains/llvm/prebuilt/$ndkHostTag/bin/llvm-strip"
|
||||
)
|
||||
doFirst {
|
||||
jniLibsDir
|
||||
.listFiles()
|
||||
?.filter { it.isDirectory && !configuredAbis.contains(it.name) }
|
||||
?.forEach { staleDir ->
|
||||
staleDir.deleteRecursively()
|
||||
}
|
||||
}
|
||||
configuredAbis.forEach { abi ->
|
||||
val triple = abiToRustTriple.getValue(abi)
|
||||
val sysrootTriple = abiToNdkSysrootTriple.getValue(abi)
|
||||
val bridgeSo = cargoWorkspaceRoot.resolve("target/$triple/$profile/libchanora_bridge.so")
|
||||
val strippedBridgeSo = layout.buildDirectory
|
||||
.file("intermediates/stripped_rust_jni/$profile/$abi/libchanora_bridge.so")
|
||||
.get()
|
||||
.asFile
|
||||
// SDD-118 item 6 (extended): also stage libc++_shared.so from the
|
||||
// NDK sysroot. libchanora_bridge.so is dynamically linked against
|
||||
// the NDK's shared C++ runtime (via audiopus_sys / opus-cpp and
|
||||
@@ -487,28 +533,37 @@ fun registerJniLibsCopyTask(profile: String): TaskProvider<Copy> {
|
||||
val cxxSharedSo = file(
|
||||
"$effectiveNdkPath/toolchains/llvm/prebuilt/$ndkHostTag/sysroot/usr/lib/$sysrootTriple/libc++_shared.so"
|
||||
)
|
||||
from(bridgeSo) {
|
||||
into(abi)
|
||||
}
|
||||
from(cxxSharedSo) {
|
||||
into(abi)
|
||||
}
|
||||
// SDD-118 item 8: input tracking for both staged sources so
|
||||
// Gradle correctly invalidates when either changes (e.g. NDK
|
||||
// version bump replacing libc++_shared.so).
|
||||
inputs.file(bridgeSo).withPropertyName("bridgeSo_$abi")
|
||||
inputs.file(cxxSharedSo).withPropertyName("cxxSharedSo_$abi")
|
||||
}
|
||||
into(layout.projectDirectory.dir("src/main/jniLibs"))
|
||||
inputs.file(llvmStrip).withPropertyName("llvmStrip")
|
||||
outputs.file(strippedBridgeSo)
|
||||
outputs.file(File(jniLibsDir, "$abi/libchanora_bridge.so"))
|
||||
outputs.file(File(jniLibsDir, "$abi/libc++_shared.so"))
|
||||
|
||||
// SDD-118 item 8: declare staged outputs so Gradle can track them.
|
||||
configuredAbis.forEach { abi ->
|
||||
outputs.file(
|
||||
layout.projectDirectory.file("src/main/jniLibs/$abi/libchanora_bridge.so")
|
||||
)
|
||||
outputs.file(
|
||||
layout.projectDirectory.file("src/main/jniLibs/$abi/libc++_shared.so")
|
||||
)
|
||||
doLast {
|
||||
val abiDir = File(jniLibsDir, abi)
|
||||
abiDir.mkdirs()
|
||||
strippedBridgeSo.parentFile.mkdirs()
|
||||
bridgeSo.copyTo(strippedBridgeSo, overwrite = true)
|
||||
project.exec {
|
||||
commandLine(
|
||||
llvmStrip.absolutePath,
|
||||
"--strip-debug",
|
||||
strippedBridgeSo.absolutePath,
|
||||
)
|
||||
}
|
||||
strippedBridgeSo.copyTo(
|
||||
File(abiDir, "libchanora_bridge.so"),
|
||||
overwrite = true,
|
||||
)
|
||||
cxxSharedSo.copyTo(
|
||||
File(abiDir, "libc++_shared.so"),
|
||||
overwrite = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user