The :ammolite module contained no production Kotlin/Java sources (just
a manifest, build.gradle, and proguard stubs) and no module in the
codebase imports com.vitorpamplona.ammolite.*.
Removes:
- ammolite/ directory (5 files)
- :ammolite project include in settings.gradle
- implementation project(':ammolite') from :amethyst
- androidTestImplementation project(':ammolite') from :benchmark
- :ammolite:testDebugUnitTest from CI workflow and pre-push hook
- -keep class com.vitorpamplona.ammolite.** rules from
:amethyst, :commons, and :desktopApp proguard files
- Stale references in CONTRIBUTING.md, CLAUDE.md, and the
gradle-expert skill dependency-graph doc
Small build-graph win: one fewer module to configure, compile, lint,
and spotless-check on every build, and one fewer unit-test target in
both CI and the local pre-push hook.
8.5 KiB
Module Dependency Graph
Visual Hierarchy
┌─────────────────────────────────────────────────────────┐
│ Root Project │
│ (Amethyst) │
└─────────────────────────────────────────────────────────┘
│
┌────────────────┼────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ :amethyst │ │ :desktopApp │ │ :benchmark │
│ (Android) │ │ (JVM) │ │ (Android) │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
│ │ │
└────────────────┼────────────────┘
│
▼
┌─────────────┐
│ :commons │
│ (KMP UI) │
│ │
│ jvmAndroid │
│ / \ │
│ jvm android│
└─────────────┘
│
│
▼
┌─────────────┐
│ :quartz │
│(KMP Library)│
│ │
│ commonMain │
│ │ │
│ jvmAndroid │
│ / | \ │
│jvm and ios │
└─────────────┘
Module Details
:quartz (KMP Nostr Library)
Type: Kotlin Multiplatform Library Targets: JVM, Android, iOS (iosArm64, iosSimulatorArm64) Dependencies:
- External: secp256k1, jackson, okhttp, kotlinx.coroutines, kotlinx.collections.immutable
- Source sets: commonMain → jvmAndroid → {androidMain, jvmMain}, iosMain
Role: Core Nostr protocol implementation, shared across all platforms
:commons (Shared UI Components)
Type: Kotlin Multiplatform Library Targets: JVM, Android Dependencies:
- Module:
:quartz - External: Compose Multiplatform, Material3, kotlinx.collections.immutable
- Source sets: commonMain → jvmAndroid → {androidMain, jvmMain}
Role: Shared Compose UI components for Desktop and Android
:desktopApp (Desktop Application)
Type: JVM Application Targets: JVM (Desktop) Dependencies:
- Modules:
:commons,:quartz - External: Compose Desktop, kotlinx.coroutines.swing
Role: Desktop-specific navigation, layouts, and entry point
:amethyst (Android Application)
Type: Android Application Targets: Android Dependencies:
- Modules:
:commons,:quartz,:nestsClient - External: Android SDK, AndroidX, Firebase, Tor
Role: Android-specific navigation, layouts, and entry point
:benchmark (Android Benchmark)
Type: Android Library Targets: Android Dependencies:
- Modules:
:commons,:quartz - External: AndroidX Benchmark
Role: Performance benchmarking for Android builds
Dependency Flow Patterns
Desktop Build Chain
:desktopApp → :commons (jvmMain) → :quartz (jvmMain)
↓
jvmAndroid
↓
commonMain
Android Build Chain
:amethyst → :commons (androidMain) → :quartz (androidMain)
↓
jvmAndroid
↓
commonMain
Source Set Dependencies
:quartz Source Sets
commonMain (base)
├─ jvmAndroid (shared JVM code)
│ ├─ androidMain (Android platform)
│ └─ jvmMain (Desktop platform)
└─ iosMain (iOS platform)
├─ iosArm64Main
└─ iosSimulatorArm64Main
Key Dependencies per Source Set:
- commonMain: secp256k1-kmp, kotlinx.coroutines, collection, immutable collections
- jvmAndroid: jackson, okhttp, url-detector, rfc3986
- androidMain: secp256k1-kmp-jni-android, lazysodium-android, jna (aar)
- jvmMain: secp256k1-kmp-jni-jvm, lazysodium-java, jna (jar)
:commons Source Sets
commonMain (base UI)
└─ jvmAndroid (shared JVM UI)
├─ androidMain (Android UI utilities)
└─ jvmMain (Desktop UI utilities)
Key Dependencies per Source Set:
- commonMain: Compose Multiplatform, Material3, :quartz
- jvmAndroid: url-detector
- androidMain: AndroidX Compose tooling
- jvmMain: Compose Desktop
Critical Dependency Patterns
1. secp256k1 Variants
// commonMain - API only
api(libs.secp256k1.kmp.common)
// androidMain - JNI Android
api(libs.secp256k1.kmp.jni.android)
// jvmMain - JNI JVM
implementation(libs.secp256k1.kmp.jni.jvm)
Why: Different JNI bindings for Android vs Desktop JVM
2. JNA Variants (for LibSodium)
// androidMain
implementation("com.goterl:lazysodium-android:5.2.0@aar")
implementation("net.java.dev.jna:jna:5.18.1@aar")
// jvmMain
implementation(libs.lazysodium.java)
implementation(libs.jna) // JAR variant
Why: Android needs AAR packaging, JVM needs JAR
3. Compose Alignment
// commons/build.gradle.kts
implementation(compose.ui) // Compose Multiplatform BOM
implementation(compose.material3)
// Version catalog alignment
composeMultiplatform = "1.9.3"
composeBom = "2025.12.01" // AndroidX Compose
Why: Two Compose ecosystems (Multiplatform + AndroidX) must align
Dependency Configuration Types
API vs Implementation
Use api when:
- Dependency types appear in module's public API
- Used in expect/actual declarations visible to consumers
- Example:
secp256k1-kmp-commonin quartz (public types)
Use implementation when:
- Internal implementation detail
- Not exposed to module consumers
- Example:
okhttpin quartz (internal network client)
Example from quartz
// Public API - exposed to consumers
api(libs.secp256k1.kmp.common)
api(libs.jackson.module.kotlin) // Event serialization public
// Internal implementation
implementation(libs.okhttp)
implementation(libs.kotlinx.coroutines.core)
Transitive Dependency Impact
When :desktopApp depends on :commons
- Gets
:quartztransitively (via :commons) - Gets
secp256k1-kmp-jvmtransitively (via :quartz jvmMain) - Does NOT get Android-specific dependencies (scoped to androidMain)
When :amethyst depends on :commons
- Gets
:quartztransitively (via :commons) - Gets
secp256k1-kmp-jni-androidtransitively (via :quartz androidMain) - Does NOT get JVM/Desktop-specific dependencies (scoped to jvmMain)
Verifying Dependencies
Check Module Dependencies
./gradlew :desktopApp:dependencies
./gradlew :amethyst:dependencies
Check Specific Library
./gradlew dependencyInsight --dependency secp256k1
./gradlew dependencyInsight --dependency compose-ui
Visualize with Build Scan
./gradlew :desktopApp:dependencies --scan
# Opens interactive dependency graph in browser
Common Dependency Issues
Issue 1: Wrong secp256k1 Variant in Desktop
Symptom: UnsatisfiedLinkError: no secp256k1jni in java.library.path
Cause: Desktop using Android JNI variant
Fix: Ensure jvmMain uses secp256k1-kmp-jni-jvm
Issue 2: Compose Version Mismatch
Symptom: IllegalStateException: Version mismatch
Cause: Compose Multiplatform plugin vs runtime version mismatch
Fix: Align composeMultiplatform version in libs.versions.toml with Kotlin plugin
Issue 3: Duplicate JNA Classes
Symptom: DuplicateClassException: com.sun.jna.Native
Cause: Both JAR and AAR JNA variants in classpath
Fix: Use AAR (@aar) in androidMain, JAR in jvmMain (never in shared source sets)