- 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
193 lines
9.3 KiB
Kotlin
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)
|
|
}
|