build: add reproducible Android release verification
Pin Gradle inputs and dependency metadata, then enforce analyze, lint, build, R8, immutability, and focused regression gates in CI.
This commit is contained in:
1 parent
9ac236a981
commit
915f8260d5
10 files changed
+9498
-17
No files matched your search
@@ -1,12 +1,18 @@
|
||||
import java.util.Properties
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.security.MessageDigest
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("dev.flutter.flutter-gradle-plugin")
|
||||
}
|
||||
|
||||
fun sha256Hex(file: File): String {
|
||||
val digest = MessageDigest.getInstance("SHA-256").digest(file.readBytes())
|
||||
return digest.joinToString("") { "%02x".format(it.toInt() and 0xff) }
|
||||
}
|
||||
|
||||
val projectRootDir = rootProject.projectDir.parentFile
|
||||
val localPropertiesFile = rootProject.file("local.properties")
|
||||
val localProperties = Properties().apply {
|
||||
@@ -140,10 +146,6 @@ android {
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
packaging {
|
||||
resources.excludes.add("META-INF/**")
|
||||
}
|
||||
|
||||
val keystorePropertiesFile = rootProject.file("key.properties")
|
||||
val keystoreProperties = Properties()
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
@@ -156,6 +158,68 @@ android {
|
||||
admobProperties.load(FileInputStream(admobPropertiesFile))
|
||||
}
|
||||
|
||||
// Explicit opt-in used only by GitHub Actions to exercise the real
|
||||
// release/R8 graph. Production release signing and config pinning remain
|
||||
// fail-closed outside that CI smoke path.
|
||||
val ciReleaseSmoke =
|
||||
System.getenv("GITHUB_ACTIONS") == "true" &&
|
||||
System.getenv("REVOLT_CI_RELEASE_SMOKE") == "true"
|
||||
|
||||
val appConfigFile = rootProject.file("../lib/logic/app_config.dart")
|
||||
val verifyReleaseAppConfig = tasks.register("verifyReleaseAppConfig") {
|
||||
group = "verification"
|
||||
description = "Verify the production Dart app config against REVOLT_APP_CONFIG_SHA256."
|
||||
doLast {
|
||||
if (ciReleaseSmoke) {
|
||||
logger.lifecycle("Skipping production app-config pin for CI release smoke build")
|
||||
return@doLast
|
||||
}
|
||||
|
||||
val expected = System.getenv("REVOLT_APP_CONFIG_SHA256")?.trim()?.lowercase()
|
||||
if (expected.isNullOrEmpty()) {
|
||||
throw org.gradle.api.GradleException(
|
||||
"REVOLT_APP_CONFIG_SHA256 is required for production release builds"
|
||||
)
|
||||
}
|
||||
if (!Regex("[0-9a-f]{64}").matches(expected)) {
|
||||
throw org.gradle.api.GradleException(
|
||||
"REVOLT_APP_CONFIG_SHA256 must be exactly 64 lowercase hexadecimal characters"
|
||||
)
|
||||
}
|
||||
if (!appConfigFile.isFile) {
|
||||
throw org.gradle.api.GradleException(
|
||||
"Production app config is missing: ${appConfigFile.absolutePath}"
|
||||
)
|
||||
}
|
||||
|
||||
val configText = appConfigFile.readText()
|
||||
val templateMarkers = listOf(
|
||||
"'0.0.0.0'",
|
||||
"YOUR_DOMAIN",
|
||||
"YOUR_GITHUB_USERNAME",
|
||||
"YOUR_REPO_NAME",
|
||||
"ca-app-pub-0000000000000000/0000000000",
|
||||
)
|
||||
val remainingMarker = templateMarkers.firstOrNull(configText::contains)
|
||||
if (remainingMarker != null) {
|
||||
throw org.gradle.api.GradleException(
|
||||
"Production app config still contains template marker: $remainingMarker"
|
||||
)
|
||||
}
|
||||
|
||||
val actual = sha256Hex(appConfigFile)
|
||||
if (actual != expected) {
|
||||
throw org.gradle.api.GradleException(
|
||||
"Production app config SHA-256 mismatch: expected $expected, got $actual"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.matching { it.name == "preReleaseBuild" }.configureEach {
|
||||
dependsOn(verifyReleaseAppConfig)
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.paladinvpn.app"
|
||||
minSdk = flutter.minSdkVersion
|
||||
@@ -176,7 +240,11 @@ android {
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
signingConfig = signingConfigs.getByName("release")
|
||||
signingConfig = if (ciReleaseSmoke) {
|
||||
signingConfigs.getByName("debug")
|
||||
} else {
|
||||
signingConfigs.getByName("release")
|
||||
}
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles(
|
||||
|
||||
Reference in new issue
Block a user