Files
MatchLiveTv/native/android/app/build.gradle.kts
T

240 lines
9.2 KiB
Kotlin

import java.io.File
import java.util.Properties
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipOutputStream
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("org.jetbrains.kotlin.plugin.compose")
}
val keystorePropertiesFile = rootProject.file("keystore.properties")
val keystoreProperties = Properties()
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(keystorePropertiesFile.inputStream())
}
android {
namespace = "com.matchlivetv.match_live_tv"
compileSdk = 36
defaultConfig {
applicationId = "com.matchlivetv.match_live_tv"
minSdk = 24
targetSdk = 36
versionCode = 32
versionName = "2.0.11-native"
val apiBaseUrl = project.findProperty("API_BASE_URL") as String?
?: "https://www.matchlivetv.it"
buildConfigField("String", "API_BASE_URL", "\"$apiBaseUrl\"")
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
// Anche in defaultConfig: AGP estrae simboli se le .so non sono già stripped.
ndk {
debugSymbolLevel = "SYMBOL_TABLE"
}
}
signingConfigs {
if (keystorePropertiesFile.exists()) {
create("release") {
keyAlias = keystoreProperties["keyAlias"] as String
keyPassword = keystoreProperties["keyPassword"] as String
storeFile = rootProject.file(keystoreProperties["storeFile"] as String)
storePassword = keystoreProperties["storePassword"] as String
}
}
}
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
)
// Le .so AndroidX arrivano già stripped: AGP non genera metadata.
// Il task injectNativeDebugSymbolsInReleaseBundle le incorpora nell'AAB.
ndk {
debugSymbolLevel = "SYMBOL_TABLE"
}
signingConfig = if (keystorePropertiesFile.exists()) {
signingConfigs.getByName("release")
} else {
signingConfigs.getByName("debug")
}
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
buildFeatures {
compose = true
buildConfig = true
}
}
dependencies {
val composeBom = platform("androidx.compose:compose-bom:2024.12.01")
implementation(composeBom)
androidTestImplementation(composeBom)
implementation("androidx.appcompat:appcompat:1.7.0")
implementation("androidx.core:core-ktx:1.15.0")
implementation("androidx.activity:activity-compose:1.9.3")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7")
implementation("androidx.navigation:navigation-compose:2.8.5")
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.material:material-icons-extended")
debugImplementation("androidx.compose.ui:ui-tooling")
implementation("com.squareup.retrofit2:retrofit:2.11.0")
implementation("com.squareup.retrofit2:converter-moshi:2.11.0")
implementation("com.squareup.moshi:moshi-kotlin:1.15.1")
implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
implementation("androidx.datastore:datastore-preferences:1.1.1")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0")
testImplementation("junit:junit:4.13.2")
implementation("io.coil-kt:coil-compose:2.7.0")
implementation("com.github.pedroSG94.RootEncoder:library:2.5.5")
androidTestImplementation("androidx.test.ext:junit:1.2.1")
androidTestImplementation("androidx.test:runner:1.6.2")
androidTestImplementation("androidx.test:rules:1.6.1")
androidTestImplementation("androidx.test:core:1.6.1")
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.3.0")
}
// Play Console: le .so prebuilt (AndroidX) sono già stripped → AGP salta l'estrazione
// (niente BUNDLE-METADATA/com.android.tools.build.debugsymbols). Generiamo i .so.sym
// come fa AGP (llvm-objcopy --strip-debug) e li iniettiamo nell'intermediary AAB.
fun findLlvmObjcopy(): File {
val ndkRoot = File(android.sdkDirectory, "ndk")
val ndkDirs = ndkRoot.listFiles()?.filter { it.isDirectory }?.sortedByDescending { it.name }.orEmpty()
for (ndk in ndkDirs) {
val candidate = ndk.resolve("toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-objcopy")
if (candidate.isFile) return candidate
}
error("llvm-objcopy non trovato sotto ${ndkRoot.absolutePath}")
}
val injectNativeDebugSymbolsInReleaseBundle by tasks.registering {
description = "Embed native-debug-symbols.zip (.so.sym) into the release AAB intermediary"
val mergeNative = tasks.named("mergeReleaseNativeLibs")
val packageBundle = tasks.named("packageReleaseBundle")
dependsOn(mergeNative, packageBundle)
inputs.dir(
layout.buildDirectory.dir(
"intermediates/merged_native_libs/release/mergeReleaseNativeLibs/out/lib",
),
)
inputs.file(
layout.buildDirectory.file(
"intermediates/intermediary_bundle/release/packageReleaseBundle/intermediary-bundle.aab",
),
)
outputs.file(
layout.buildDirectory.file(
"intermediates/intermediary_bundle/release/packageReleaseBundle/intermediary-bundle.aab",
),
)
doLast {
val libsDir = layout.buildDirectory
.dir("intermediates/merged_native_libs/release/mergeReleaseNativeLibs/out/lib")
.get()
.asFile
val aabFile = layout.buildDirectory
.file("intermediates/intermediary_bundle/release/packageReleaseBundle/intermediary-bundle.aab")
.get()
.asFile
require(libsDir.isDirectory) { "Native libs missing: $libsDir" }
require(aabFile.isFile) { "Intermediary AAB missing: $aabFile" }
val objcopy = findLlvmObjcopy()
val workDir = layout.buildDirectory.dir("tmp/native-debug-symbols-inject").get().asFile
workDir.deleteRecursively()
workDir.mkdirs()
val symbolsRoot = workDir.resolve("symbols")
symbolsRoot.mkdirs()
var count = 0
libsDir.walkTopDown().filter { it.isFile && it.extension == "so" }.forEach { so ->
val rel = so.relativeTo(libsDir).invariantSeparatorsPath
val outRel = "$rel.sym"
val outFile = symbolsRoot.resolve(outRel)
outFile.parentFile.mkdirs()
// Allineato ad AGP SYMBOL_TABLE: llvm-objcopy --strip-debug → *.so.sym
val proc = ProcessBuilder(
objcopy.absolutePath,
"--strip-debug",
so.absolutePath,
outFile.absolutePath,
).redirectErrorStream(true).start()
val out = proc.inputStream.bufferedReader().readText()
check(proc.waitFor() == 0) {
"objcopy failed for $rel: $out"
}
count++
}
check(count > 0) { "Nessuna .so da cui generare simboli in $libsDir" }
val symbolsZip = workDir.resolve("native-debug-symbols.zip")
ZipOutputStream(symbolsZip.outputStream().buffered()).use { zos ->
symbolsRoot.walkTopDown().filter { it.isFile }.forEach { f ->
val rel = f.relativeTo(symbolsRoot).invariantSeparatorsPath
zos.putNextEntry(ZipEntry(rel))
f.inputStream().use { it.copyTo(zos) }
zos.closeEntry()
}
}
val metaPath = "BUNDLE-METADATA/com.android.tools.build.debugsymbols/native-debug-symbols.zip"
val outAab = workDir.resolve("intermediary-with-symbols.aab")
ZipFile(aabFile).use { src ->
ZipOutputStream(outAab.outputStream().buffered()).use { dst ->
val entries = src.entries()
while (entries.hasMoreElements()) {
val entry = entries.nextElement()
if (entry.name == metaPath) continue
val newEntry = ZipEntry(entry.name)
newEntry.time = entry.time
// Deflate all entries when rewriting; STORED copy is fragile across ZipFile.
newEntry.method = ZipEntry.DEFLATED
dst.putNextEntry(newEntry)
src.getInputStream(entry).use { it.copyTo(dst) }
dst.closeEntry()
}
dst.putNextEntry(ZipEntry(metaPath))
symbolsZip.inputStream().use { it.copyTo(dst) }
dst.closeEntry()
}
}
outAab.copyTo(aabFile, overwrite = true)
logger.lifecycle(
"Injected $metaPath ($count .so.sym, ${symbolsZip.length()} bytes) into ${aabFile.name} via ${objcopy.name}",
)
}
}
tasks.matching { it.name == "signReleaseBundle" }.configureEach {
dependsOn(injectNativeDebugSymbolsInReleaseBundle)
}