who_need_help/android/app/build.gradle.kts

271 lines
9.5 KiB
Plaintext

import java.net.URI
import java.nio.file.Files
import java.nio.file.Path
import org.gradle.api.tasks.compile.JavaCompile
plugins {
id("com.android.application")
}
val releaseBaseUrl = providers.gradleProperty("WNH_BASE_URL").orElse("")
val debugBaseUrl = providers.gradleProperty("WNH_DEBUG_BASE_URL").orElse("")
val trackingMinTimeMs = providers.gradleProperty("WNH_TRACKING_MIN_TIME_MS").orElse("0")
val trackingHttpTimeoutMs =
providers.gradleProperty("WNH_TRACKING_HTTP_TIMEOUT_MS").orElse("0")
val instrumentationBuildType =
providers.gradleProperty("WNH_TEST_BUILD_TYPE").orElse("debug")
val androidVersionCode = providers.gradleProperty("WNH_ANDROID_VERSION_CODE").orElse("1")
val androidVersionName = providers.gradleProperty("WNH_ANDROID_VERSION_NAME").orElse("0.1.0")
val releaseSigningStoreFile =
providers.environmentVariable("WNH_ANDROID_SIGNING_STORE_FILE").orNull
val releaseSigningPasswordFile =
providers.environmentVariable("WNH_ANDROID_SIGNING_PASSWORD_FILE").orNull
val releaseSigningKeyAlias =
providers.environmentVariable("WNH_ANDROID_SIGNING_KEY_ALIAS").orNull
fun nonBlank(value: String?): String? = value?.trim()?.takeIf(String::isNotEmpty)
val releaseSigningInputs =
listOf(
nonBlank(releaseSigningStoreFile),
nonBlank(releaseSigningPasswordFile),
nonBlank(releaseSigningKeyAlias)
)
val releaseSigningConfigured = releaseSigningInputs.all { it != null }
val releaseSigningPartiallyConfigured = releaseSigningInputs.any { it != null }
fun readSigningPassword(): String {
val passwordPath =
nonBlank(releaseSigningPasswordFile)
?: throw GradleException("Android release signing password file is not configured")
val path = Path.of(passwordPath)
if (!Files.isRegularFile(path)) {
throw GradleException("Android release signing password file does not exist")
}
val password = Files.readString(path).trimEnd('\r', '\n')
if (password.isBlank() || password.contains('\n') || password.contains('\r')) {
throw GradleException("Android release signing password file is invalid")
}
return password
}
fun manifestOrigin(value: String): URI? =
runCatching { URI(value) }
.getOrNull()
?.takeIf { uri ->
(uri.scheme == "http" || uri.scheme == "https") && !uri.host.isNullOrBlank()
}
fun isOriginPath(uri: URI): Boolean =
uri.path.isNullOrEmpty() || uri.path == "/"
val debugManifestOrigin = manifestOrigin(debugBaseUrl.get())
val releaseManifestOrigin = manifestOrigin(releaseBaseUrl.get())
android {
namespace = "org.whoneedhelp.mobile"
compileSdk = 37
buildToolsVersion = "37.0.0"
testBuildType = instrumentationBuildType.get()
defaultConfig {
applicationId = "org.whoneedhelp.mobile"
minSdk = 24
targetSdk = 37
versionCode =
androidVersionCode.get().toIntOrNull()?.takeIf { it > 0 }
?: throw GradleException("WNH_ANDROID_VERSION_CODE must be a positive integer")
versionName =
androidVersionName.get().trim().takeIf(String::isNotEmpty)
?: throw GradleException("WNH_ANDROID_VERSION_NAME must not be empty")
buildConfigField("long", "TRACKING_MIN_TIME_MS", "${trackingMinTimeMs.get()}L")
buildConfigField(
"long",
"TRACKING_HTTP_TIMEOUT_MS",
"${trackingHttpTimeoutMs.get()}L"
)
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
signingConfigs {
if (releaseSigningConfigured) {
create("release") {
val storePath = Path.of(nonBlank(releaseSigningStoreFile)!!)
if (!Files.isRegularFile(storePath)) {
throw GradleException("Android release signing keystore does not exist")
}
storeFile = storePath.toFile()
storePassword = readSigningPassword()
keyAlias = nonBlank(releaseSigningKeyAlias)
keyPassword = storePassword
}
}
}
buildTypes {
debug {
applicationIdSuffix = ".debug"
versionNameSuffix = "-debug"
buildConfigField(
"String",
"BASE_URL",
"\"${debugBaseUrl.get().replace("\\", "\\\\").replace("\"", "\\\"")}\""
)
manifestPlaceholders["usesCleartextTraffic"] = "true"
manifestPlaceholders["deepLinkScheme"] = debugManifestOrigin?.scheme ?: "https"
manifestPlaceholders["deepLinkHost"] =
debugManifestOrigin?.host ?: "invalid.whoneedhelp.local"
}
create("staging") {
initWith(getByName("debug"))
applicationIdSuffix = ".staging"
versionNameSuffix = "-staging"
buildConfigField(
"String",
"BASE_URL",
"\"${releaseBaseUrl.get().replace("\\", "\\\\").replace("\"", "\\\"")}\""
)
manifestPlaceholders["usesCleartextTraffic"] = "false"
manifestPlaceholders["deepLinkScheme"] = releaseManifestOrigin?.scheme ?: "https"
manifestPlaceholders["deepLinkHost"] =
releaseManifestOrigin?.host ?: "invalid.whoneedhelp.local"
matchingFallbacks += listOf("debug")
}
release {
isMinifyEnabled = true
isShrinkResources = true
if (releaseSigningConfigured) {
signingConfig = signingConfigs.getByName("release")
}
buildConfigField(
"String",
"BASE_URL",
"\"${releaseBaseUrl.get().replace("\\", "\\\\").replace("\"", "\\\"")}\""
)
manifestPlaceholders["usesCleartextTraffic"] = "false"
manifestPlaceholders["deepLinkScheme"] = releaseManifestOrigin?.scheme ?: "https"
manifestPlaceholders["deepLinkHost"] =
releaseManifestOrigin?.host ?: "invalid.whoneedhelp.local"
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
buildFeatures {
buildConfig = true
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
testOptions {
unitTests.isIncludeAndroidResources = false
}
}
tasks.matching { it.name == "preReleaseBuild" || it.name == "preStagingBuild" }.configureEach {
doFirst {
if (name == "preReleaseBuild" && !releaseSigningConfigured) {
val detail =
if (releaseSigningPartiallyConfigured) {
"Release signing is only partially configured"
} else {
"Release signing is not configured"
}
throw GradleException(
"$detail; set WNH_ANDROID_SIGNING_STORE_FILE, "
+ "WNH_ANDROID_SIGNING_PASSWORD_FILE, and "
+ "WNH_ANDROID_SIGNING_KEY_ALIAS"
)
}
val value = releaseBaseUrl.orNull.orEmpty()
val uri = runCatching { URI(value) }.getOrNull()
if (
uri == null ||
uri.scheme != "https" ||
uri.host.isNullOrBlank() ||
uri.userInfo != null ||
!isOriginPath(uri) ||
uri.query != null ||
uri.fragment != null
) {
throw GradleException(
"Staging and release builds require "
+ "-PWNH_BASE_URL=https://your-real-deployment.example"
)
}
validateTrackingConfiguration()
}
}
tasks.matching { it.name == "preDebugBuild" }.configureEach {
doFirst {
val value = debugBaseUrl.orNull.orEmpty()
val uri = runCatching { URI(value) }.getOrNull()
if (
uri == null ||
(uri.scheme != "http" && uri.scheme != "https") ||
uri.host.isNullOrBlank() ||
uri.userInfo != null ||
!isOriginPath(uri) ||
uri.query != null ||
uri.fragment != null
) {
throw GradleException(
"Debug builds require -PWNH_DEBUG_BASE_URL=http(s)://your-development-host"
)
}
validateTrackingConfiguration()
}
}
fun validateTrackingConfiguration() {
val minTime = trackingMinTimeMs.orNull?.toLongOrNull()
val httpTimeout = trackingHttpTimeoutMs.orNull?.toLongOrNull()
if (minTime == null || minTime <= 0) {
throw GradleException(
"Builds require -PWNH_TRACKING_MIN_TIME_MS=POSITIVE_MILLISECONDS"
)
}
if (httpTimeout == null || httpTimeout <= 0 || httpTimeout > Int.MAX_VALUE) {
throw GradleException(
"Builds require -PWNH_TRACKING_HTTP_TIMEOUT_MS=POSITIVE_INT_MILLISECONDS"
)
}
}
tasks.withType<JavaCompile>().configureEach {
options.compilerArgs.add("-Xlint:deprecation")
}
dependencies {
implementation("androidx.activity:activity:1.13.0")
implementation("androidx.webkit:webkit:1.16.0")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test:core:1.7.0")
androidTestImplementation("androidx.test:runner:1.7.0")
androidTestImplementation("androidx.test:rules:1.7.0")
androidTestImplementation("androidx.test.ext:junit:1.3.0")
androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0")
androidTestImplementation("androidx.test.espresso:espresso-web:3.7.0")
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.4.0")
}