Build and Release APK / Build APK and publish release (push) Canceled after 0s
Import of ProLibertateGames (upstream main, GPL-3.0) rebranded for Wallacenet and repointed at git.wallacenet.ca. - Identity: applicationId ca.wallacenet.pilgrimsofkatan (+ .dev for the dev channel), launcher names "Pilgrims of Katan" / "Pilgrims of Katan DEV", neutral hexagon branding replacing the Wallace tartan. Uncial Antiqua keeps its OFL licence, and the GPL-3.0 LICENSE plus upstream attribution stay. - OTA: Updater.kt now reads this repo's own Gitea releases (/api/v1/repos/devadmin/PilgrimsOfKatan/releases...) and BuildConfig. OTA_ENABLED is on; Gitea answers the same JSON shape the code already parsed. - Build numbers: versionCode = CI run number + plgVersionRunOffset (1000), with the tag and versionName derived from the same figure. The offset is permanent, because Gitea Actions restarts run numbers at 1 and a reset would otherwise make Android reject the next build as a downgrade. - CI: ci.yml publishes a dev prerelease from dev, release.yml a production release from main, both through scripts/publish-release.sh (Gitea REST API, runnable by hand until an Actions runner is registered). - Docs: docs/CI.md records the channel, numbering and runner decisions; docs/README.md seeds the per-game RULES-* conventions; CLAUDE.md carries the agent ground rules. Verified: ./gradlew assembleDebug and testDebugUnitTest green on JDK 17 / compileSdk 34, both variants stamped, installing side by side under the new identity. Co-Authored-By: Paperclip <[email protected]>
184 lines
8.0 KiB
Kotlin
184 lines
8.0 KiB
Kotlin
plugins {
|
|
id("com.android.application")
|
|
id("org.jetbrains.kotlin.android")
|
|
id("org.jetbrains.kotlin.plugin.serialization")
|
|
}
|
|
|
|
// Which update channel this build belongs to, set by whichever workflow built
|
|
// it: release.yml stamps "production" from main, ci.yml stamps "dev" from the
|
|
// dev branch. Unset for a local build, which counts as dev.
|
|
//
|
|
// GITHUB_RUN_NUMBER alone is not enough to key off, because it counts per
|
|
// workflow — the two pipelines have entirely independent sequences. That is
|
|
// also why versionCode is only ever compared *within* a channel; see
|
|
// UpdateChannel for how switching between them is handled.
|
|
val buildChannel = System.getenv("PLG_CHANNEL").orEmpty()
|
|
val runNumber = (System.getenv("GITHUB_RUN_NUMBER") ?: "").toIntOrNull() ?: 0
|
|
val stampVersion = buildChannel.isNotEmpty() && runNumber > 0
|
|
|
|
// The fixed floor every stamped build number is measured from, in
|
|
// gradle.properties. Gitea Actions restarts its run numbers at 1 for each
|
|
// workflow, so an unstamped build (versionCode 1) and the first release of a
|
|
// fresh instance would otherwise collide — and once a phone has installed
|
|
// versionCode N, Android refuses anything smaller as a downgrade. Adding a
|
|
// permanent offset is what makes "the counter never resets" true by
|
|
// construction: it is read from one place, it never changes, and the tag,
|
|
// versionName and versionCode all get the same number (see docs/CI.md).
|
|
val runOffset = (project.findProperty("plgVersionRunOffset") as String?)?.toIntOrNull() ?: 0
|
|
|
|
// The stamped build number: what the release tag, the versionName and the
|
|
// versionCode are all named after.
|
|
val buildNumber = runNumber + runOffset
|
|
|
|
// The version series, from gradle.properties. Both workflows read the same
|
|
// property when they name a release tag, so the tag and the APK inside it
|
|
// cannot disagree about which series they belong to.
|
|
val versionSeries = (project.findProperty("plgVersionSeries") as String?) ?: "1.0"
|
|
|
|
// Anything that is not an explicit production build is a dev build: ci.yml
|
|
// stamps "dev", and a local build with nothing set counts as dev too.
|
|
//
|
|
// This one flag drives everything that makes a dev build a separate app rather
|
|
// than a replacement for the production one — its own applicationId, its own
|
|
// launcher name, and its own brand colour. They have to move together: a dev
|
|
// package wearing the production name and colours is exactly the confusion
|
|
// having both installed is meant to avoid.
|
|
val isDevBuild = buildChannel != "production"
|
|
|
|
android {
|
|
// Deliberately left at the upstream source package: R and BuildConfig stay
|
|
// in org.prolibertate.games for both channels, so no source file has to
|
|
// care which applicationId it ended up installed as. The *installed*
|
|
// identity is the applicationId below — that is what the rebrand changes
|
|
// (WALA-118), not the JVM package names.
|
|
namespace = "org.prolibertate.games"
|
|
compileSdk = 34
|
|
|
|
defaultConfig {
|
|
// A dev build installs under its own package, so a dev copy and a
|
|
// production copy can sit side by side on one device instead of one
|
|
// replacing the other.
|
|
applicationId = if (isDevBuild) "ca.wallacenet.pilgrimsofkatan.dev" else "ca.wallacenet.pilgrimsofkatan"
|
|
minSdk = 24
|
|
targetSdk = 34
|
|
// The -dev suffix in versionName is what the installed app reads to
|
|
// know its own channel, so it must survive into the shipped APK. It
|
|
// tracks isDevBuild rather than the stamp alone, so an unstamped local
|
|
// build cannot end up claiming a channel its applicationId contradicts.
|
|
// versionCode is the stamped build number and nothing else, so it keeps
|
|
// climbing across a change of series: bumping 1.0 to 1.1 must not make
|
|
// an update look like a downgrade to Android. An unstamped build is 1,
|
|
// below every release, which is the honest answer for a build nobody
|
|
// stamped.
|
|
versionCode = if (stampVersion) buildNumber else 1
|
|
versionName = when {
|
|
stampVersion && buildChannel == "production" -> "$versionSeries.$buildNumber"
|
|
stampVersion -> "$versionSeries.$buildNumber-dev"
|
|
isDevBuild -> "$versionSeries.0-dev"
|
|
else -> "$versionSeries.0"
|
|
}
|
|
|
|
// The launcher label. With both copies on the phone the app drawer shows
|
|
// two entries, and this is what names them apart.
|
|
resValue(
|
|
"string",
|
|
"app_name",
|
|
if (isDevBuild) "Pilgrims of Katan DEV" else "Pilgrims of Katan",
|
|
)
|
|
|
|
// The one colour that separates the two installs: slate blue on
|
|
// production, deep green on dev. The launcher icon and the splash are
|
|
// both built from it, so the drawer shows at a glance which of the two
|
|
// copies is which — the two have to stay in step or the icon and the
|
|
// splash it opens on would be different brands.
|
|
resValue("color", "brand_field", if (isDevBuild) "#2A6B54" else "#2B4A78")
|
|
// A darker shade of the same hue, held behind the window for the
|
|
// instant before the splash draws, so a launch never flashes the wrong
|
|
// colour.
|
|
resValue("color", "splash_background", if (isDevBuild) "#153B2F" else "#182A44")
|
|
|
|
buildConfigField("boolean", "DEV_BUILD", isDevBuild.toString())
|
|
|
|
// In-app OTA updates, pointed at this project's own Gitea releases
|
|
// (docs/CI.md). True in this build — the channel exists. Flipping it
|
|
// false is the kill switch: every check then reports that and fetches
|
|
// nothing, with the machinery and its tests still compiled.
|
|
buildConfigField("boolean", "OTA_ENABLED", "true")
|
|
}
|
|
|
|
signingConfigs {
|
|
// A fixed, checked-in debug keystore (standard "android" password) so
|
|
// every build — local or CI — is signed with the same key. Without this,
|
|
// each CI runner would generate a random debug key and OTA updates would
|
|
// fail to install with a signature mismatch.
|
|
getByName("debug") {
|
|
storeFile = file("debug.keystore")
|
|
storePassword = "android"
|
|
keyAlias = "androiddebugkey"
|
|
keyPassword = "android"
|
|
}
|
|
}
|
|
|
|
buildTypes {
|
|
release {
|
|
isMinifyEnabled = false
|
|
proguardFiles(
|
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
|
"proguard-rules.pro"
|
|
)
|
|
}
|
|
}
|
|
|
|
buildFeatures {
|
|
compose = true
|
|
// For DEV_BUILD, which is how the Compose splash picks its brand colour.
|
|
buildConfig = true
|
|
}
|
|
|
|
composeOptions {
|
|
// Must stay in step with the Kotlin version declared in the root build file.
|
|
kotlinCompilerExtensionVersion = "1.5.14"
|
|
}
|
|
|
|
compileOptions {
|
|
sourceCompatibility = JavaVersion.VERSION_17
|
|
targetCompatibility = JavaVersion.VERSION_17
|
|
}
|
|
|
|
kotlinOptions {
|
|
jvmTarget = "17"
|
|
}
|
|
|
|
packaging {
|
|
resources.excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
|
}
|
|
|
|
testOptions {
|
|
unitTests.isReturnDefaultValues = true
|
|
}
|
|
}
|
|
|
|
dependencies {
|
|
implementation("androidx.core:core-ktx:1.13.1")
|
|
implementation("androidx.activity:activity-compose:1.9.0")
|
|
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.2")
|
|
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.2")
|
|
|
|
val composeBom = platform("androidx.compose:compose-bom:2024.06.00")
|
|
implementation(composeBom)
|
|
implementation("androidx.compose.ui:ui")
|
|
implementation("androidx.compose.ui:ui-graphics")
|
|
implementation("androidx.compose.ui:ui-tooling-preview")
|
|
implementation("androidx.compose.material3:material3")
|
|
debugImplementation("androidx.compose.ui:ui-tooling")
|
|
|
|
// Settings persistence for sound / animation speed / update preferences.
|
|
implementation("androidx.datastore:datastore-preferences:1.1.1")
|
|
|
|
// Wire format for the LAN game protocol.
|
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
|
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1")
|
|
|
|
testImplementation("junit:junit:4.13.2")
|
|
}
|