Der Release-Build lieferte bisher app-release-unsigned.apk - und die installiert Android nicht, auch nicht per Sideload. Ohne Signatur kann das System nicht feststellen, ob ein Update vom selben Absender stammt wie die Erstinstallation; das ist unabhängig davon, woher die Datei kommt. Der Build signiert jetzt. Liegt Android/keystore.properties vor, mit dem eigenen Schlüssel; sonst wird aus der Umgebung gelesen, damit ein Bauknecht sie aus seinen Geheimnissen setzen kann, ohne dass ein Passwort im Verlauf landet. Fehlt beides, wird mit dem Debug-Schlüssel signiert - der Build sagt das dann deutlich, denn eine so signierte Release-APK sieht sonst aus wie eine richtige, lässt sich aber vom nächsten Rechner aus nicht mehr aktualisieren. Schlüssel und Passwörter sind ausgeschlossen. Version und Versionszähler lassen sich beim Bauen setzen, damit sie aus einem Etikett kommen können. Dazu ein Ablauf für Gitea Actions: ein Etikett hochschieben baut die APK, legt das Release an, falls es noch nicht besteht, und hängt sie an. Beides muss gehen - sonst scheitert es je nachdem, ob man das Release vorher von Hand angelegt hat. Android/RELEASE.md erklärt beide Wege und sagt auch, warum die APK nicht ins Repository gehört: Release-Anhänge liegen in Gitea neben dem Repository. Eine 11-MB-Binärdatei je Version im Verlauf wäre unwiderruflich - Git vergisst nichts, ein späteres Löschen bringt den Platz nicht zurück. Geprüft: die signierte APK installiert sich im Emulator, meldet sich mit der übergebenen Version und trägt keine Debug-Kennzeichnung mehr. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
124 lines
4.4 KiB
Kotlin
124 lines
4.4 KiB
Kotlin
import java.util.Properties
|
|
|
|
plugins {
|
|
id("com.android.application")
|
|
id("org.jetbrains.kotlin.android")
|
|
id("org.jetbrains.kotlin.plugin.compose")
|
|
}
|
|
|
|
/**
|
|
* Angaben zum Signaturschlüssel.
|
|
*
|
|
* Sie stehen in `Android/keystore.properties` - einer Datei, die bewusst nicht
|
|
* im Repository liegt. Fehlt sie, wird stattdessen aus der Umgebung gelesen;
|
|
* so kann ein Bauknecht sie aus seinen Geheimnissen setzen, ohne dass ein
|
|
* Passwort je im Verlauf landet.
|
|
*/
|
|
val signing = Properties().apply {
|
|
val file = rootProject.file("keystore.properties")
|
|
if (file.exists()) {
|
|
file.inputStream().use { load(it) }
|
|
} else {
|
|
System.getenv("KEYSTORE_FILE")?.let { setProperty("storeFile", it) }
|
|
System.getenv("KEYSTORE_PASSWORD")?.let { setProperty("storePassword", it) }
|
|
System.getenv("KEY_ALIAS")?.let { setProperty("keyAlias", it) }
|
|
System.getenv("KEY_PASSWORD")?.let { setProperty("keyPassword", it) }
|
|
}
|
|
}
|
|
val hasSigningKey = signing.getProperty("storeFile") != null
|
|
|
|
android {
|
|
namespace = "de.fritob.campermonitor"
|
|
compileSdk = 35
|
|
|
|
defaultConfig {
|
|
applicationId = "de.fritob.campermonitor"
|
|
// Android 8: älter lohnt nicht, dort fehlt zu viel an Bluetooth LE.
|
|
minSdk = 26
|
|
targetSdk = 35
|
|
versionCode = (findProperty("versionCode") as String?)?.toInt() ?: 1
|
|
// Beim Bauen aus einem Etikett heraus überschreibbar:
|
|
// ./gradlew :app:assembleRelease -PversionName=1.2.0
|
|
versionName = (findProperty("versionName") as String?) ?: "1.0"
|
|
}
|
|
|
|
signingConfigs {
|
|
if (hasSigningKey) {
|
|
create("release") {
|
|
storeFile = file(signing.getProperty("storeFile"))
|
|
storePassword = signing.getProperty("storePassword")
|
|
keyAlias = signing.getProperty("keyAlias")
|
|
keyPassword = signing.getProperty("keyPassword")
|
|
}
|
|
}
|
|
}
|
|
|
|
buildTypes {
|
|
release {
|
|
isMinifyEnabled = false
|
|
// Ohne eigenen Schlüssel wird mit dem Debug-Schlüssel signiert.
|
|
// Unsigniert wäre die APK schlicht nicht installierbar - Android
|
|
// weist sie ab, auch beim Sideloading. Der Hinweis unten sagt, was
|
|
// das für Folgen hat.
|
|
signingConfig = if (hasSigningKey) {
|
|
signingConfigs.getByName("release")
|
|
} else {
|
|
signingConfigs.getByName("debug")
|
|
}
|
|
}
|
|
}
|
|
|
|
compileOptions {
|
|
sourceCompatibility = JavaVersion.VERSION_17
|
|
targetCompatibility = JavaVersion.VERSION_17
|
|
}
|
|
|
|
kotlinOptions {
|
|
jvmTarget = "17"
|
|
}
|
|
|
|
buildFeatures {
|
|
compose = true
|
|
buildConfig = true
|
|
}
|
|
|
|
sourceSets["main"].java.srcDirs("src/main/kotlin")
|
|
}
|
|
|
|
dependencies {
|
|
implementation(project(":protocol"))
|
|
|
|
val composeBom = platform("androidx.compose:compose-bom:2024.12.01")
|
|
implementation(composeBom)
|
|
implementation("androidx.compose.material3:material3")
|
|
implementation("androidx.compose.material:material-icons-extended")
|
|
implementation("androidx.compose.ui:ui")
|
|
implementation("androidx.compose.ui:ui-tooling-preview")
|
|
debugImplementation("androidx.compose.ui:ui-tooling")
|
|
|
|
implementation("androidx.core:core-ktx:1.15.0")
|
|
implementation("androidx.activity:activity-compose:1.9.3")
|
|
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.7")
|
|
implementation("androidx.navigation:navigation-compose:2.8.5")
|
|
|
|
// Die Ablage der Geräte und Profile nutzt org.json aus Android selbst -
|
|
// eine weitere Bibliothek lohnt für eine Handvoll Felder nicht.
|
|
}
|
|
|
|
// Sichtbar machen, womit signiert wurde - eine mit dem Debug-Schlüssel
|
|
// signierte Release-APK sieht sonst aus wie eine richtige.
|
|
tasks.matching { it.name == "assembleRelease" }.configureEach {
|
|
doLast {
|
|
if (!hasSigningKey) {
|
|
logger.lifecycle(
|
|
"\n ACHTUNG: mit dem Debug-Schlüssel signiert." +
|
|
"\n Installierbar, aber an diesen Rechner gebunden: auf einem anderen" +
|
|
"\n entsteht ein anderer Debug-Schlüssel, und ein Update liesse sich" +
|
|
"\n nicht mehr über die alte Installation legen." +
|
|
"\n Für einen eigenen Schlüssel: Android/keystore.properties anlegen." +
|
|
"\n Anleitung in Android/RELEASE.md.\n"
|
|
)
|
|
}
|
|
}
|
|
}
|