ZapretKVN-android/app/build.gradle.kts
loop-uh a0511c375a
Some checks failed
Android CI / build (push) Has been cancelled
Baseline Profile: правила запуска и скрипты записи и замеров
- app/src/main/baseline-prof.txt: 29 641 правило (4 344 — код приложения),
  записано на эмуляторе по обходу Главной, Серверов, Маршрутов и Настроек;
  AGP сам кладёт его в assets/dexopt/baseline.prof и переносит через R8.
- scripts/generate-baseline-profile.sh: сборка build type profileGen (release
  без R8, подпись одноразовым ключом), обход UI через uiautomator/input,
  сброс профиля SIGUSR1, profman -> отсортированный HRF.
- scripts/measure-startup-frames.sh: холодный старт и gfxinfo до/после,
  docs/PERFORMANCE.md с методом и честными цифрами (эмулятор, не телефон).
- verify-project.sh проверяет файл профиля, verify-release-candidate.sh
  требует baseline.prof(m) в релизном APK; release build type не менялся.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 23:16:30 +03:00

257 lines
10 KiB
Kotlin
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import java.util.Properties
import javax.inject.Inject
import org.gradle.process.ExecOperations
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.plugin.compose")
}
val coreProperties = Properties().apply {
rootProject.file("core.properties").inputStream().use { load(it) }
}
val coreTag = coreProperties.getProperty("CORE_TAG")
val coreCommit = coreProperties.getProperty("CORE_COMMIT")
val corePatchFile = coreProperties.getProperty("CORE_PATCH_FILE")
val corePatchSha256 = coreProperties.getProperty("CORE_PATCH_SHA256")
val hysteriaCoreTag = coreProperties.getProperty("HYSTERIA_CORE_TAG")
val hysteriaCoreCommit = coreProperties.getProperty("HYSTERIA_CORE_COMMIT")
val libboxAar = layout.projectDirectory.file("libs/libbox.aar").asFile
val appVersionCode = providers.gradleProperty("zapretVersionCode")
.orElse(providers.environmentVariable("ZAPRET_VERSION_CODE"))
.orElse("200099")
.get()
.toInt()
val appVersionName = providers.gradleProperty("zapretVersionName")
.orElse(providers.environmentVariable("ZAPRET_VERSION_NAME"))
.orElse("0.2.0")
.get()
val defaultUpdateChannel = if (
Regex("""^\d+\.\d+\.\d+-beta\.\d+$""").matches(appVersionName)
) {
"Beta"
} else {
"Stable"
}
val debugVersionNameSuffix = providers.gradleProperty("zapretDebugVersionNameSuffix")
.orElse("-debug")
.get()
val updateRepository = providers.gradleProperty("zapretUpdateRepository")
.orElse(providers.environmentVariable("ZAPRET_UPDATE_REPOSITORY"))
.orElse("zapretkvn/ZapretKVN-android")
.get()
.also { require(Regex("[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+").matches(it)) }
val supportedApkAbis = setOf("arm64-v8a", "armeabi-v7a", "x86_64")
val requestedApkAbi = providers.gradleProperty("zapretAbi")
.orElse(providers.gradleProperty("zapretReleaseAbi"))
.orNull
?.also { require(it in supportedApkAbis) { "Unsupported APK ABI: $it" } }
val signingStorePath = providers.environmentVariable("ZAPRET_SIGNING_STORE_FILE").orNull
val signingStorePassword = providers.environmentVariable("ZAPRET_SIGNING_STORE_PASSWORD").orNull
val signingKeyAlias = providers.environmentVariable("ZAPRET_SIGNING_KEY_ALIAS").orNull
val signingKeyPassword = providers.environmentVariable("ZAPRET_SIGNING_KEY_PASSWORD").orNull
val releaseSigningConfigured = listOf(
signingStorePath,
signingStorePassword,
signingKeyAlias,
signingKeyPassword,
).all { !it.isNullOrBlank() }
val releaseSigningValueCount = listOf(
signingStorePath,
signingStorePassword,
signingKeyAlias,
signingKeyPassword,
).count { !it.isNullOrBlank() }
require(releaseSigningValueCount == 0 || releaseSigningValueCount == 4) {
"Release signing requires all four ZAPRET_SIGNING_* environment variables."
}
android {
namespace = "io.github.zapretkvn.android"
compileSdk = 36
ndkVersion = coreProperties.getProperty("ANDROID_NDK_VERSION")
defaultConfig {
applicationId = "io.github.zapretkvn.android"
minSdk = 26
targetSdk = 36
versionCode = appVersionCode
versionName = appVersionName
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
requestedApkAbi?.let { abi ->
ndk {
abiFilters += abi
}
}
buildConfigField("String", "CORE_TAG", "\"$coreTag\"")
buildConfigField("String", "UPDATE_REPOSITORY", "\"$updateRepository\"")
buildConfigField("String", "DEFAULT_UPDATE_CHANNEL", "\"$defaultUpdateChannel\"")
buildConfigField(
"String",
"CORE_COMMIT",
"\"$coreCommit\"",
)
buildConfigField("String", "CORE_PATCH_SHA256", "\"$corePatchSha256\"")
buildConfigField("String", "CORE_UDP_PATCH_SHA256", "\"${coreProperties.getProperty("CORE_UDP_PATCH_SHA256")}\"")
buildConfigField("String", "HYSTERIA_CORE_TAG", "\"$hysteriaCoreTag\"")
buildConfigField("String", "HYSTERIA_CORE_COMMIT", "\"$hysteriaCoreCommit\"")
}
signingConfigs {
if (releaseSigningConfigured) {
create("release") {
storeFile = file(requireNotNull(signingStorePath))
storePassword = signingStorePassword
keyAlias = signingKeyAlias
keyPassword = signingKeyPassword
enableV1Signing = true
enableV2Signing = true
enableV3Signing = true
}
}
}
buildTypes {
debug {
applicationIdSuffix = ".debug"
if (debugVersionNameSuffix.isNotEmpty()) {
versionNameSuffix = debugVersionNameSuffix
}
if (requestedApkAbi == null) {
ndk {
// CI and the default local Android test target use an x86_64 AVD.
// Device tests can select another single ABI with -PzapretAbi.
abiFilters += "x86_64"
}
}
}
release {
signingConfig = signingConfigs.findByName("release")
isMinifyEnabled = true
isShrinkResources = true
if (requestedApkAbi == null) {
ndk {
// A direct assembleRelease remains small and predictable. The release
// workflow passes zapretAbi once per supported architecture.
abiFilters += "arm64-v8a"
}
}
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
)
}
// Только для scripts/generate-baseline-profile.sh: тот же release, но без R8, чтобы
// записанный профиль содержал исходные имена классов (AGP при сборке настоящего
// release сам перенесёт правила baseline-prof.txt через R8). В выпуск не попадает.
create("profileGen") {
initWith(getByName("release"))
matchingFallbacks += "release"
isMinifyEnabled = false
isShrinkResources = false
isDebuggable = false
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
buildFeatures {
buildConfig = true
compose = true
}
}
dependencies {
val composeBom = platform("androidx.compose:compose-bom:2026.06.00")
implementation("androidx.core:core-ktx:1.18.0")
implementation("androidx.activity:activity-compose:1.13.0")
implementation("androidx.datastore:datastore-preferences:1.2.1")
implementation(composeBom)
implementation("androidx.compose.foundation:foundation")
implementation("androidx.compose.material:material-icons-core")
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation(files(libboxAar))
implementation(project(":app-updater"))
implementation(project(":network-bootstrap"))
implementation(project(":wireguard-import"))
implementation(project(":core:model"))
implementation(project(":core:designsystem"))
implementation(project(":core:ui-model"))
implementation(project(":feature:home"))
implementation(project(":feature:servers"))
implementation(project(":feature:routing"))
implementation(project(":feature:settings"))
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.9.4")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.10.0")
implementation("com.journeyapps:zxing-android-embedded:4.3.0")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
testImplementation("junit:junit:4.13.2")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.9.0")
androidTestImplementation("androidx.test.ext:junit:1.3.0")
androidTestImplementation("androidx.test:runner:1.7.0")
// Compose UI test currently carries an older transitive Espresso release.
// Pin the API 37-compatible stable test stack explicitly.
androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0")
androidTestImplementation(composeBom)
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
}
// The fingerprint comparison and the rebuild live in scripts/check-libbox-fingerprint.sh
// and scripts/ensure-libbox.sh, shared with the shell tooling and covered by
// scripts/test-libbox-fingerprint.sh. A silent fallback to a stale AAR is forbidden.
abstract class EnsurePinnedLibboxTask : DefaultTask() {
@get:Inject
abstract val execOperations: ExecOperations
@get:Internal
abstract val repositoryRoot: DirectoryProperty
@TaskAction
fun ensure() {
val root = repositoryRoot.get().asFile
val script = root.resolve("scripts/ensure-libbox.sh")
check(script.isFile) { "Missing $script" }
val result = try {
execOperations.exec {
workingDir(root)
commandLine("bash", script.absolutePath, root.absolutePath)
isIgnoreExitValue = true
}
} catch (failure: Exception) {
throw GradleException("Could not run $script; bash is required to verify libbox.aar", failure)
}
if (result.exitValue != 0) {
throw GradleException(
"app/libs/libbox.aar does not match the pinned core fingerprint " +
"(core.properties + core-patches/series.sha256) and was not rebuilt " +
"automatically. See the messages above and run scripts/build-core.sh.",
)
}
}
}
val ensurePinnedLibbox by tasks.registering(EnsurePinnedLibboxTask::class) {
group = "verification"
description =
"Verifies libbox.aar against the pinned core fingerprint and rebuilds a stale core."
repositoryRoot.set(rootProject.layout.projectDirectory)
outputs.upToDateWhen { false }
}
tasks.named("preBuild").configure {
dependsOn(ensurePinnedLibbox)
}