258 lines
9.9 KiB
Kotlin
258 lines
9.9 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 = 34
|
|
versionName = "2.0.13-native"
|
|
|
|
val apiBaseUrl = project.findProperty("API_BASE_URL") as String?
|
|
?: "https://www.matchlivetv.it"
|
|
buildConfigField("String", "API_BASE_URL", "\"$apiBaseUrl\"")
|
|
buildConfigField("boolean", "IS_COLLAUDO", "false")
|
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
|
|
|
// Anche in defaultConfig: AGP estrae simboli se le .so non sono già stripped.
|
|
ndk {
|
|
debugSymbolLevel = "SYMBOL_TABLE"
|
|
}
|
|
}
|
|
|
|
flavorDimensions += "env"
|
|
productFlavors {
|
|
create("prod") {
|
|
dimension = "env"
|
|
isDefault = true
|
|
}
|
|
create("collaudo") {
|
|
dimension = "env"
|
|
applicationId = "com.matchlivetv.match_live_tv.collaudo"
|
|
versionNameSuffix = "-collaudo"
|
|
val collaudoApi = project.findProperty("API_BASE_URL") as String?
|
|
?: "https://collaudo.matchlivetv.it"
|
|
buildConfigField("String", "API_BASE_URL", "\"$collaudoApi\"")
|
|
buildConfigField("boolean", "IS_COLLAUDO", "true")
|
|
}
|
|
}
|
|
|
|
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("mergeProdReleaseNativeLibs")
|
|
val packageBundle = tasks.named("packageProdReleaseBundle")
|
|
dependsOn(mergeNative, packageBundle)
|
|
inputs.dir(
|
|
layout.buildDirectory.dir(
|
|
"intermediates/merged_native_libs/prodRelease/mergeProdReleaseNativeLibs/out/lib",
|
|
),
|
|
)
|
|
inputs.file(
|
|
layout.buildDirectory.file(
|
|
"intermediates/intermediary_bundle/prodRelease/packageProdReleaseBundle/intermediary-bundle.aab",
|
|
),
|
|
)
|
|
outputs.file(
|
|
layout.buildDirectory.file(
|
|
"intermediates/intermediary_bundle/prodRelease/packageProdReleaseBundle/intermediary-bundle.aab",
|
|
),
|
|
)
|
|
doLast {
|
|
val libsDir = layout.buildDirectory
|
|
.dir("intermediates/merged_native_libs/prodRelease/mergeProdReleaseNativeLibs/out/lib")
|
|
.get()
|
|
.asFile
|
|
val aabFile = layout.buildDirectory
|
|
.file("intermediates/intermediary_bundle/prodRelease/packageProdReleaseBundle/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 == "signProdReleaseBundle" }.configureEach {
|
|
dependsOn(injectNativeDebugSymbolsInReleaseBundle)
|
|
}
|