mirror of
https://github.com/twinksoftware/Gearslip.git
synced 2026-11-10 16:02:25 +01:00
Builds after a tag name themselves after it plus the commits since, and the version code is worked out from the numbers instead of set by hand.
208 lines
9.3 KiB
Kotlin
208 lines
9.3 KiB
Kotlin
import java.util.Properties
|
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
|
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
|
|
|
|
/**
|
|
* Gearslip: a phone-side Android Auto client built against the reverse-engineered AA wire
|
|
* protocol, so a non-Google app can authenticate to and project onto a real car head unit
|
|
* with no gearhead in the path. No third-party dependencies: the X.509 certificate is
|
|
* generated by hand (see `SelfSignedCert`) so there is no BouncyCastle version to track and
|
|
* no second crypto provider to collide with Conscrypt.
|
|
*/
|
|
plugins {
|
|
alias(libs.plugins.android.application)
|
|
alias(libs.plugins.kotlin.compose)
|
|
}
|
|
|
|
// The release signing key, read from an untracked keystore.properties beside this file or at the
|
|
// repository root. Third-party apps allowlist Gearslip by the SHA-256 of this certificate, so the
|
|
// key must stay the same for the life of the project - and must never be committed.
|
|
val keystoreProperties = Properties().apply {
|
|
listOf(
|
|
rootProject.file("keystore.properties"),
|
|
project.file("keystore.properties"),
|
|
).firstOrNull { it.exists() }?.inputStream()?.use { load(it) }
|
|
}
|
|
|
|
// The version comes from the newest v* git tag, so a build can never disagree with its tag. A
|
|
// build of the tagged commit is named after it ("0.4.0-beta"); anything later adds the commits
|
|
// since and the commit ("0.4.0-beta+3.ba4374e"), so a test build can't pass for a release.
|
|
// The version code is worked out from the numbers, ((major*100 + minor)*100 + patch)*10 + stage,
|
|
// where stage is 1 for alpha, 2 for beta and 9 for a finished release. It only ever goes up as
|
|
// long as minor and patch stay under 100. Changelog entries use the same number.
|
|
class GitVersion(val name: String, val code: Int)
|
|
|
|
val gitVersion: GitVersion = run {
|
|
val described = runCatching {
|
|
providers.exec {
|
|
commandLine("git", "describe", "--tags", "--match", "v*", "--abbrev=7")
|
|
isIgnoreExitValue = true
|
|
}.standardOutput.asText.get().trim()
|
|
}.getOrNull().orEmpty()
|
|
val match = Regex("""^v((\d+)\.(\d+)\.(\d+)(?:-([A-Za-z]+))?)(?:-(\d+)-g([0-9a-f]+))?$""").find(described)
|
|
?: return@run GitVersion("0.0.0-dev", 1) // no git or no tags, e.g. a source download
|
|
val (base, major, minor, patch, suffix, since, commit) = match.destructured
|
|
val stage = when (suffix.lowercase()) {
|
|
"" -> 9
|
|
"beta" -> 2
|
|
"alpha" -> 1
|
|
else -> 0
|
|
}
|
|
GitVersion(
|
|
name = if (since.isEmpty()) base else "$base+$since.$commit",
|
|
code = ((major.toInt() * 100 + minor.toInt()) * 100 + patch.toInt()) * 10 + stage,
|
|
)
|
|
}
|
|
|
|
// Keeps build output out of the module folder: <root>/build/gearslip/... instead of
|
|
// <root>/gearslip/build/..., so everything this project produces lands under one
|
|
// root-level build/ directory. Set before android {}, which works out the path to R8's default
|
|
// rules from it.
|
|
layout.buildDirectory.set(rootProject.layout.buildDirectory.dir("gearslip"))
|
|
|
|
android {
|
|
namespace = "app.seb3thehacker.gearslip"
|
|
compileSdk = 37
|
|
|
|
defaultConfig {
|
|
applicationId = "app.seb3thehacker.gearslip"
|
|
minSdk = 31
|
|
targetSdk = 37
|
|
versionCode = gitVersion.code
|
|
// -PgearslipVersionName=0.1.99-test makes a build that thinks it's older, to try the updater.
|
|
versionName = project.findProperty("gearslipVersionName")?.toString() ?: gitVersion.name
|
|
manifestPlaceholders["appLabel"] = "Gearslip"
|
|
// Where opted-in phones send their daily usage note: the stats-worker/ Worker's address,
|
|
// set as gearslipStatsUrl in gradle.properties. Left empty, nothing is ever sent.
|
|
buildConfigField("String", "STATS_URL", "\"${project.findProperty("gearslipStatsUrl") ?: ""}\"")
|
|
}
|
|
|
|
signingConfigs {
|
|
// Declared only when the properties file is present, so a clone without the key still
|
|
// builds debug; assembleRelease then falls back to unsigned rather than failing here.
|
|
if (keystoreProperties.getProperty("storeFile") != null) {
|
|
create("release") {
|
|
storeFile = rootProject.file(keystoreProperties.getProperty("storeFile"))
|
|
storePassword = keystoreProperties.getProperty("storePassword")
|
|
keyAlias = keystoreProperties.getProperty("keyAlias")
|
|
keyPassword = keystoreProperties.getProperty("keyPassword")
|
|
}
|
|
}
|
|
}
|
|
|
|
buildTypes {
|
|
release {
|
|
signingConfig = signingConfigs.findByName("release")
|
|
isMinifyEnabled = true
|
|
isShrinkResources = true
|
|
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
|
}
|
|
// Its own package, so a debug build installs beside the release one instead of over it.
|
|
// Signed with the release key when it's here: both builds declare the car template
|
|
// permission, and Android refuses a second app declaring it under a different key.
|
|
debug {
|
|
applicationIdSuffix = ".dev"
|
|
signingConfigs.findByName("release")?.let { signingConfig = it }
|
|
manifestPlaceholders["appLabel"] = "Gearslip Debug"
|
|
// English (XA) and Arabic (XB) under the phone's languages: stretched, accented and
|
|
// right-to-left text, which shows what's still hardcoded and what doesn't fit.
|
|
isPseudoLocalesEnabled = true
|
|
// Shrunk with R8 like a release, so a debug install behaves like what ships (a keep
|
|
// rule missing for release shows up here first). -PnoR8 skips it for a quicker build.
|
|
if (!project.hasProperty("noR8")) {
|
|
isMinifyEnabled = true
|
|
isShrinkResources = true
|
|
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
|
}
|
|
}
|
|
}
|
|
|
|
buildFeatures {
|
|
buildConfig = true
|
|
}
|
|
|
|
// Lists each language that has a translation in the app's locale config, so Android 13+
|
|
// offers it under the system's per-app languages. English is the unqualified default
|
|
// (res/resources.properties).
|
|
//
|
|
// Only languages that are mostly translated ship, so the app never comes up half in English.
|
|
// Danish and Estonian are still early on Weblate; add them here once they're close to done.
|
|
androidResources {
|
|
generateLocaleConfig = true
|
|
localeFilters += listOf(
|
|
"en", "ar", "cs", "de", "es", "fr", "it", "ja", "ko", "nl", "pl", "pt-rBR", "ro", "ru",
|
|
"sv", "tr", "uk", "zh-rCN",
|
|
)
|
|
}
|
|
|
|
lint {
|
|
// Weblate fills languages in a string at a time; the rest fall back to English.
|
|
disable += "MissingTranslation"
|
|
}
|
|
|
|
compileOptions {
|
|
sourceCompatibility = JavaVersion.VERSION_21
|
|
targetCompatibility = JavaVersion.VERSION_21
|
|
}
|
|
|
|
defaultConfig {
|
|
// Speech-to-text (sherpa-onnx 1.13.8, static-link-onnxruntime build, in jniLibs) is the
|
|
// only native code. One APK for all three, since the updater takes the release's only APK;
|
|
// 32-bit x86 is left out, nothing that drives a car runs it.
|
|
ndk { abiFilters += listOf("arm64-v8a", "armeabi-v7a", "x86_64") }
|
|
}
|
|
|
|
// Compressed, the three copies cost a 33 MB download instead of 75 MB; the phone unpacks
|
|
// only its own at install.
|
|
packaging { jniLibs { useLegacyPackaging = true } }
|
|
|
|
testOptions {
|
|
// GearslipLog calls android.util.Log, which is a stub on the local JVM.
|
|
unitTests.isReturnDefaultValues = true
|
|
}
|
|
|
|
}
|
|
|
|
// Copies the produced APK to the root build/ folder (<root>/build/Gearslip-debug.apk) after the
|
|
// fact rather than fighting AGP's variant-output API across versions - simpler and doesn't break
|
|
// when that API moves again. A copy, not a move: AGP packages incrementally into the APK it left
|
|
// last time, and with that file gone a build came out holding nothing but classes.dex.
|
|
tasks.register("renameApk") {
|
|
doLast {
|
|
val rootBuildDir = rootProject.layout.buildDirectory.get().asFile
|
|
layout.buildDirectory.dir("outputs/apk").get().asFile.walkTopDown()
|
|
.filter { it.extension == "apk" }
|
|
.toList()
|
|
.forEach { apk ->
|
|
val renamed = File(rootBuildDir, "Gearslip-${apk.parentFile.name}.apk")
|
|
apk.copyTo(renamed, overwrite = true)
|
|
}
|
|
}
|
|
}
|
|
tasks.matching { it.name.startsWith("assemble") }.configureEach { finalizedBy("renameApk") }
|
|
|
|
dependencies {
|
|
// Only for the XML window theme (Theme.Material3.DynamicColors); every screen is Compose.
|
|
implementation(libs.google.material)
|
|
implementation(platform(libs.androidx.compose.bom))
|
|
implementation(libs.androidx.compose.ui)
|
|
implementation(libs.androidx.compose.material3)
|
|
implementation(libs.androidx.compose.material.icons.core)
|
|
implementation(libs.androidx.activity.compose)
|
|
implementation(libs.androidx.lifecycle.runtime.compose)
|
|
// Used from the host side, not the app side: Gearslip renders other apps' templates rather
|
|
// than publishing its own, so this is here for the AIDL interfaces and the model classes
|
|
// their Bundleables deserialize into.
|
|
implementation(libs.androidx.car.app)
|
|
implementation(libs.androidx.car.app.projected)
|
|
implementation(libs.androidx.media)
|
|
testImplementation(libs.junit)
|
|
}
|
|
|
|
tasks.withType<KotlinJvmCompile>().configureEach {
|
|
compilerOptions {
|
|
jvmTarget.set(JvmTarget.JVM_21)
|
|
}
|
|
}
|
|
|