Files
iris_x_hermes/app/shared/build.gradle.kts
T
ARIA fb980d12b4
CI / Gateway plugin tests (push) Successful in 5m19s
CI / Kotlin tests (android host + desktop) (push) Successful in 7m0s
Release version management: single VERSION file as source of truth
- VERSION at repo root (0.1.2); bump it to cut a release
- App: generated AppVersion.kt (config-cache-safe Gradle task with
  VERSION as declared input) shown in Settings; sent to the gateway
  via X-Iris-App-Version header on the SSE open
- Gateway: reports its own version in hello.ack server_caps.app_version
  (read from the repo-root VERSION via the plugin symlink); stores the
  app's version in the device registry caps (merge, not overwrite, so
  an old app reconnecting without the header doesn't wipe it)
- Settings: app + gateway version rows, mismatch hint, and a best-effort
  Gitea latest-release check (ReleaseCheck) with an 'update available' hint
- Release workflow: reads VERSION from the repo (no manual input), with
  a guard against an empty file
- Docs: frames.schema.json + 04-wire-protocol.md updated for app_version
2026-08-25 14:42:39 +02:00

193 lines
9.3 KiB
Kotlin

import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
kotlin("multiplatform")
kotlin("plugin.serialization")
id("com.android.kotlin.multiplatform.library")
id("org.jetbrains.compose")
id("org.jetbrains.kotlin.plugin.compose")
// Local cache DB (docs/16: "Local DB (KMP) — SQLDelight").
id("app.cash.sqldelight")
}
val composeVersion = "1.11.1"
// material3 and material-icons-extended are published on their own cadence and
// lag the core artifacts; use each one's latest stable release.
val material3Version = "1.9.0"
val materialIconsVersion = "1.7.3"
val coroutinesVersion = "1.11.0"
val serializationVersion = "1.11.0"
val okhttpVersion = "5.5.0"
// 2.0.x is the first line built against Compose 1.8+ (1.9.40 was the last
// 1.7.x-compatible release). HTML artifact previews (M9): Android uses the
// platform WebView, desktop uses KCEF (JCEF/Chromium, downloaded on first use).
val webviewVersion = "2.0.3"
val kcefVersion = "2025.03.23"
// 0.34.0+ moved to Compose 1.8+ (0.33.0 was the last 1.7.x / Kotlin 2.1.x
// release). Provides GFM tables, bold/italic/underscore, and the -code module
// for language-aware syntax highlighting (Highlights).
val markdownVersion = "0.44.0"
// Local cache DB (messages/channels/meta; docs/10 §10.7, docs/16).
val sqldelightVersion = "2.3.2"
// CameraX + ML Kit for in-app QR pairing (docs/20). Android-only.
val cameraxVersion = "1.5.1"
val mlKitVersion = "16.1.1"
// ── App version (generated) ─────────────────────────────────────────────
// The repo-root VERSION file is the single source of truth (bump it to cut
// a release; CI can still override with -PappVersion). Generate AppVersion.kt
// into commonMain so both targets can display it in Settings and send it to
// the gateway (X-Iris-App-Version header).
//
// A real task with the VERSION file as a DECLARED input: on a
// configuration-cache hit the script body does not re-run, so only the task
// (keyed on the file's content) can regenerate AppVersion.kt after a bump.
// The doLast reads everything from the task's own inputs/outputs (which are
// configuration-cache serializable) — it must not reference script-scope
// vals, because a .kts script lambda captures the script object and the
// configuration cache rejects that.
val generatedVersionDir = layout.buildDirectory.dir("generated/app-version")
val generateAppVersion by tasks.registering {
inputs.file(project.file("../../VERSION"))
inputs.property("appVersionOverride", (project.findProperty("appVersion") as? String).orEmpty())
val outFile = generatedVersionDir.map { it.file("AppVersion.kt") }
outputs.file(outFile)
doLast {
val override = inputs.properties["appVersionOverride"] as? String ?: ""
val versionFile = inputs.files.singleFile
val version =
override.ifBlank {
versionFile.takeIf { it.exists() }?.readText()?.trim() ?: "0.1.0"
}
val f = outFile.get().asFile
f.parentFile?.mkdirs()
f.writeText(
"""
|package iris
|
|/** Generated from the repo-root VERSION file - do not edit. */
|object AppVersion {
| const val VERSION = "$version"
|}
|
""".trimMargin(),
)
}
}
kotlin {
android {
namespace = "iris.shared"
compileSdk = 37
minSdk = 29
compilerOptions {
jvmTarget.set(JvmTarget.JVM_17)
}
withHostTest { }
}
jvm("desktop") {
// Desktop runs on the Java 21 runtime (see gradle.properties
// org.gradle.java.home); target 21 so the Markdown renderer 0.44.0
// (Java-21 bytecode) loads. Android keeps its JVM 17 target above.
compilerOptions {
jvmTarget.set(JvmTarget.JVM_21)
}
}
sourceSets {
// AppVersion.kt is generated from the repo-root VERSION file (see
// generateAppVersion above) into commonMain so both targets can read it.
commonMain {
kotlin.srcDir(generatedVersionDir)
}
// Both targets are JVM-based (androidTarget + jvm("desktop")), so
// shared JVM code (File I/O, SHA-256, media cache) lives in jvmMain.
val jvmMain by creating { dependsOn(commonMain.get()) }
val androidMain by getting { dependsOn(jvmMain) }
val desktopMain by getting { dependsOn(jvmMain) }
// Host tests shared by the android (withHostTest) and desktop targets
// (e.g. the SQLDelight cache DB tests, which need the JDBC driver).
val jvmTest by creating { dependsOn(commonTest.get()) }
val androidHostTest by getting { dependsOn(jvmTest) }
val desktopTest by getting { dependsOn(jvmTest) }
commonMain.dependencies {
implementation("org.jetbrains.compose.runtime:runtime:$composeVersion")
implementation("org.jetbrains.compose.foundation:foundation:$composeVersion")
implementation("org.jetbrains.compose.material3:material3:$material3Version")
implementation("org.jetbrains.compose.ui:ui:$composeVersion")
// Full Material icon set (Icons.Filled.ContentCopy for the code
// copy buttons); -core only ships a small subset.
implementation("org.jetbrains.compose.material:material-icons-extended:$materialIconsVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$serializationVersion")
// OkHttp is a JVM library; both targets here are JVM-based
// (androidTarget + jvm("desktop")), so it resolves on both.
implementation("com.squareup.okhttp3:okhttp:$okhttpVersion")
// Markdown rendering for chat bubbles (M8): core parser/renderer,
// Material 3 themed defaults, and the -code module for syntax
// highlighting of fenced code blocks (Highlights).
implementation("com.mikepenz:multiplatform-markdown-renderer:$markdownVersion")
implementation("com.mikepenz:multiplatform-markdown-renderer-m3:$markdownVersion")
implementation("com.mikepenz:multiplatform-markdown-renderer-code:$markdownVersion")
// M9: HTML artifact previews — WebView composable (platform WebView
// on Android, KCEF/JCEF on desktop).
implementation("io.github.kevinnzou:compose-webview-multiplatform:$webviewVersion")
// Local cache DB runtime (generated code from commonMain/sqldelight).
implementation("app.cash.sqldelight:runtime:$sqldelightVersion")
}
commonTest.dependencies {
implementation(kotlin("test"))
}
jvmTest.dependencies {
implementation(kotlin("test"))
// JDBC SQLite driver: in-memory DB for the cache tests.
implementation("app.cash.sqldelight:sqlite-driver:$sqldelightVersion")
}
// M9: KCEF (JCEF/Chromium) for desktop HTML artifact previews. The
// webview library exposes it transitively, but we reference KCEF
// directly (init + progress) so declare it explicitly.
desktopMain.dependencies {
implementation("dev.datlag:kcef:$kcefVersion")
// Local cache DB: JDBC SQLite driver (file in ~/.iris).
implementation("app.cash.sqldelight:sqlite-driver:$sqldelightVersion")
}
// M4: ExoPlayer (Media3) for inline audio/video playback (Android only).
androidMain.dependencies {
// M7: EncryptedSharedPreferences for the pairing token
// (docs/09 §9.7; AndroidSecureStore).
implementation("androidx.security:security-crypto:1.1.0")
implementation("androidx.media3:media3-exoplayer:1.11.0")
implementation("androidx.media3:media3-ui:1.11.0")
// SAF picker (rememberLauncherForActivityResult).
implementation("androidx.activity:activity-compose:1.13.0")
// CameraX + ML Kit for QR pairing (docs/20): the scanner activity
// uses the camera2 CameraX backend and ML Kit's barcode model.
implementation("androidx.camera:camera-core:$cameraxVersion")
implementation("androidx.camera:camera-camera2:$cameraxVersion")
implementation("androidx.camera:camera-lifecycle:$cameraxVersion")
implementation("androidx.camera:camera-view:$cameraxVersion")
implementation("com.google.mlkit:barcode-scanning:$mlKitVersion")
// M5: FCM push (inert without a Firebase project / google-services.json;
// the ntfy listener is the fallback). The google-services plugin is
// applied conditionally in the app module.
implementation("com.google.firebase:firebase-messaging:25.1.2")
// Local cache DB: Android SQLite driver (app database dir).
implementation("app.cash.sqldelight:android-driver:$sqldelightVersion")
}
}
}
sqldelight {
databases {
create("IrisDatabase") {
packageName.set("iris.db")
}
}
}
// Every Kotlin compile depends on the generated AppVersion.kt being current.
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
dependsOn(generateAppVersion)
}