Skip to main content

kotlin-multiplatform

KMP placement and Gradle in Amethyst: which module or source set, expect/actual vs ports, verifyKmpPurity, extracting Android composables to commonsUI, build.gradle.kts/libs.versions.toml edits, secp256k1 JNI variants, R8.

Source facts

Repository
vitorpamplona/amethyst
Last source activity
October 11, 2026 at 16:21
Detected SKILL.md language
English
Stars
1,608
Forks
224

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
2 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
kotlin-multiplatform
description
KMP placement and Gradle in Amethyst: which module or source set, expect/actual vs ports, verifyKmpPurity, extracting Android composables to commonsUI, build.gradle.kts/libs.versions.toml edits, secp256k1 JNI variants, R8.
# Kotlin Multiplatform and Gradle in Amethyst Module roles, the "share vs keep native" rule and the CLI-safe boundary are in `AGENTS.md` (Architecture, Feature Workflow). Package placement inside `commons` / `commonsUI` is in `commons/ARCHITECTURE.md` ("Where does my code go?", "Source sets") and `commonsUI/ARCHITECTURE.md` ("Platform slots"). Read those first; this skill adds the source-set mechanics and the build traps. ## Modules `settings.gradle.kts` includes 16: `amethyst`, `nappletHost`, `benchmark`, `baselineprofile`, `quartz`, `geode`, `commons`, `commonsUI`, `quic`, `nestsClient`, `marmotQuic`, `desktopApp`, `cli`, `relayBench`, `marmotBench`, `quic-interop` (project dir `quic/interop`). ## Source sets | Source set | Holds | Targets | |---|---|---| | `commonMain` | Pure Kotlin. Compiled for iOS/native too, so no JVM APIs | all | | `jvmAndroid` | JVM libraries shared by Android + Desktop: Jackson (`jackson.databind`), OkHttp | android, jvm | | `androidMain` / `jvmMain` | Platform actuals (Keystore/DataStore vs keyring, Coil bridges) | one each | | `iosMain`, `appleMain`, `nativeMain`, `linuxMain`, `macosMain` | quartz only (plus `iosMain` in commons/commonsUI) | Apple / native | | `skikoMain` | commonsUI code shared by Desktop and iOS | jvm, ios | Targets: `quartz` builds android, jvm, iosArm64, iosSimulatorArm64, linuxX64, macosArm64. `commons` and `commonsUI` build android, jvm, iosArm64, iosSimulatorArm64. `quic`, `nestsClient`, `marmotQuic` are android + jvm. There is no `iosX64`. `jvmAndroid` is a custom intermediate set created with `create("jvmAndroid")` before `androidMain`/`jvmMain` depend on it (see `quartz/build.gradle.kts`). Prefer the most common set that compiles: `commonMain` → `jvmAndroid` → platform. Jackson and OkHttp are the house JSON and HTTP stack (`AGENTS.md` › Tech Stack). Don't migrate JVM code to kotlinx.serialization or Ktor. They are only banned in iOS-compiled sets, which the purity gate enforces. ## expect/actual vs ports and slots - **expect/actual** for a small platform verb or value with no app state: `KmpLock`, `ConcurrentSet`, `SecureKeyStorage`, `isDebug`, `formattedDateTime` (commons), `rememberTextSharer`, `rememberShortNotice` (commonsUI). Put the actual in `jvmAndroid` when Android and Desktop share it. - **A port** (interface the shell implements) when the platform side needs app state or services: `AccountViewModelHost`, `UserNoticeSink`, `LocalCacheHost`. - **A slot** (CompositionLocal the front end installs) for things only a front end can draw: `LocalNotePlatform`, `LocalRichTextPlatform`. - Not every quartz singleton is expect: `Secp256k1Instance`, `Log` and `LibSodiumInstance` are plain `object`s in `quartz/.../utils/`. - Don't `expect` a whole screen or navigation; share it and put only the leaf behind a port or slot. ## Purity gate: `verifyKmpPurity` `gradle/kmp-purity.gradle.kts`, applied by `quartz`, `commons` and `commonsUI` and wired into `check`, scans every iOS/native-compiled source set (`commonMain`, `iosMain`, `skikoMain`, …, and their tests) and fails on `com.fasterxml.jackson`, `okhttp3`, `System.currentTimeMillis` (use `TimeUtils.now()`), `Thread.sleep`, `java.util.UUID` (use `kotlin.uuid.Uuid`), `kotlin.jvm.Synchronized` or bare `synchronized(` (use `KmpLock`), `kotlin.jvm.Volatile` (use `kotlin.concurrent.Volatile`). CI runs it on Linux in the lint job; the macOS `test-quartz-ios` job (`:quartz:iosSimulatorArm64Test`, `:quartz:compileTestKotlinIosArm64`) is authoritative. Run `./gradlew :quartz:verifyKmpPurity :commons:verifyKmpPurity :commonsUI:verifyKmpPurity` after touching `commonMain`. ## Gradle traps - **Versions live only in `gradle/libs.versions.toml`.** Add the library there and reference `libs.*`; never hardcode a version or copy one into docs. Check the license first (`AGENTS.md` › Dependency Licensing). - **`test` is an alias for each KMP module's `jvmTest`.** `:quartz:testAndroidHostTest` is a separate suite that nothing runs; see `AGENTS.md` › Build Commands. - **testFixtures in KMP:** the source-set `dependencies { }` block (`KotlinDependencyHandler`) has no `testFixtures(...)`. Use a top-level block with the configuration name as a string, as `quartz/build.gradle.kts` does: `dependencies { "jvmAndroidTestImplementation"(testFixtures(project(":geode"))) }`. - **secp256k1 variants:** `secp256k1-kmp-common` in `commonMain`, `secp256k1-kmp-jni-android` in `androidMain`, `secp256k1-kmp-jni-jvm` in `jvmMain` and JVM tests. All three share one `version.ref`. A JVM classpath with the android variant fails at load with `UnsatisfiedLinkError`. - **R8:** library keep rules ship in `quartz/consumer-rules.pro`. Never add a blanket `-keep class com.vitorpamplona.quartz.** { *; }`. R8 can't see reflection, so a moved/renamed reflective class is a silent release break: before tagging, run `python3 tools/r8-verify/verify_reflection_contract.py amethyst/build/outputs/mapping/<variant>/` (the release workflow checks `playRelease` and `fdroidRelease`) and update `tools/r8-verify/reflection-contract.txt` when you move a listed class. ## Extracting Android code to shared modules Steps and the Android → multiplatform replacement table: [references/extract-composable.md](references/extract-composable.md).
View on GitHub