Files
amethyst/.claude/skills/kotlin-multiplatform/references/source-set-hierarchy.md
T
2025-12-30 15:18:48 +02:00

9.3 KiB

Source Set Hierarchy in Amethyst

Visual guide to source set organization with concrete examples from the codebase.

Hierarchy Diagram

┌─────────────────────────────────────────────────────────────┐
│                       commonMain                            │
│  Pure Kotlin, no platform APIs                             │
│  Examples:                                                  │
│  - Nostr event parsing (TextNoteEvent, MetadataEvent)      │
│  - Business logic (data validation, crypto algorithms)     │
│  - Data models (@Immutable data classes)                   │
│  Dependencies: kotlin-stdlib, kotlinx-coroutines           │
└──────────────────────┬──────────────────────────────────────┘
                       │
          ┌────────────┴────────────┬───────────────┐
          │                         │               │
          ▼                         ▼               ▼
┌──────────────────┐    ┌───────────────────┐   ┌──────────────┐
│   jvmAndroid     │    │     iosMain       │   │  Future:     │
│  JVM libraries   │    │   iOS common      │   │  jsMain      │
│  Examples:       │    │   Examples:       │   │  wasmMain    │
│  - Jackson JSON  │    │   - Platform API  │   └──────────────┘
│  - OkHttp HTTP   │    │   - Actuals for   │
│  - url-detector  │    │     crypto/I/O    │
│  Dependencies:   │    │   Dependencies:   │
│  - Jackson       │    │   - Platform libs │
│  - OkHttp        │    └───────┬───────────┘
└────┬─────────┬───┘            │
     │         │                ├─→ iosX64Main (simulator Intel)
     │         │                ├─→ iosArm64Main (device ARM64)
     │         │                └─→ iosSimulatorArm64Main (Apple Silicon)
     ▼         ▼
┌──────────┐ ┌───────────┐
│android   │ │  jvmMain  │
│Main      │ │ (Desktop) │
│Examples: │ │ Examples: │
│- Activity│ │- Window   │
│- ViewModel│ │- MenuBar  │
│- Android │ │- Desktop  │
│  APIs    │ │  Compose  │
│Deps:     │ │ Deps:     │
│- secp256k│ │- secp256k │
│  1-android│ │  1-jvm    │
│- androidx│ │- Compose  │
│          │ │  Desktop  │
└──────────┘ └───────────┘

Dependency Flow

Code in commonMain
  ↓ can use
Nothing (only Kotlin stdlib)

Code in jvmAndroid
  ↓ can use
commonMain + JVM libraries (Jackson, OkHttp)

Code in androidMain
  ↓ can use
commonMain + jvmAndroid + Android framework

Code in jvmMain
  ↓ can use
commonMain + jvmAndroid + JVM + Compose Desktop

Code in iosMain
  ↓ can use
commonMain + iOS platform APIs

Real Examples from Amethyst

commonMain - Pure Kotlin

File: quartz/src/commonMain/.../TextNoteEvent.kt

@Immutable
class TextNoteEvent(
    id: HexKey,
    pubKey: HexKey,
    createdAt: Long,
    tags: Array<Array<String>>,
    content: String,
    sig: HexKey,
) : BaseThreadedEvent(...) {
    // Pure Kotlin - works everywhere
    override fun indexableContent() = "Subject: " + subject() + "\n" + content
}

Why commonMain:

  • Pure Kotlin code
  • No platform APIs
  • Data class with business logic
  • Needed by all platforms

jvmAndroid - JVM Libraries

File: quartz/build.gradle.kts

val jvmAndroid = create("jvmAndroid") {
    dependsOn(commonMain.get())

    dependencies {
        // Normalizes URLs
        api(libs.rfc3986.normalizer)

        // Performant Parser of JSONs into Events
        api(libs.jackson.module.kotlin)

        // Parses URLs from Text
        api(libs.url.detector)

        // Websockets API
        implementation(libs.okhttp)
        implementation(libs.okhttpCoroutines)
    }
}

jvmMain { dependsOn(jvmAndroid) }      // Desktop gets Jackson, OkHttp
androidMain { dependsOn(jvmAndroid) }  // Android gets Jackson, OkHttp

Why jvmAndroid:

  • Jackson, OkHttp are JVM-only libraries
  • Works on Android (JVM) and Desktop (JVM)
  • Does NOT work on iOS (not JVM) or web (not JVM)

Usage in code:

// Can use Jackson in jvmAndroid source set
val mapper = ObjectMapper()
val event = mapper.readValue(json, Event::class.java)

androidMain - Android Platform

File: amethyst/src/main/.../MainActivity.kt

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        enableEdgeToEdge()  // Android API
        super.onCreate(savedInstanceState)

        setContent {  // Compose for Android
            AmethystTheme {
                val accountStateViewModel: AccountStateViewModel = viewModel()
                AccountScreen(accountStateViewModel)
            }
        }
    }
}

Why androidMain:

  • AppCompatActivity is Android framework
  • Activity lifecycle Android-specific
  • AndroidX libraries (viewModel())

Dependencies:

androidMain {
    dependsOn(jvmAndroid)  // Gets Jackson, OkHttp
    dependencies {
        implementation(libs.androidx.core.ktx)
        api(libs.secp256k1.kmp.jni.android)  // Android crypto
    }
}

jvmMain - Desktop Platform

File: desktopApp/src/jvmMain/.../Main.kt

fun main() = application {
    val windowState = rememberWindowState(
        width = 1200.dp,
        height = 800.dp
    )

    Window(  // Compose Desktop API
        onCloseRequest = ::exitApplication,
        state = windowState,
        title = "Amethyst"
    ) {
        MenuBar {  // Desktop-specific
            Menu("File") {
                Item("New Note", shortcut = KeyShortcut(Key.N, ctrl = true))
            }
        }
        NavigationRail { ... }  // Sidebar
    }
}

Why jvmMain:

  • Window, MenuBar, NavigationRail are Compose Desktop
  • Keyboard shortcuts desktop paradigm
  • Different UX from Android (sidebar vs bottom nav)

Dependencies:

jvmMain {
    dependsOn(jvmAndroid)  // Gets Jackson, OkHttp
    dependencies {
        implementation(libs.secp256k1.kmp.jni.jvm)  // Desktop crypto
        implementation(compose.desktop.currentOs)
    }
}

iosMain - iOS Platform

File: quartz/build.gradle.kts

iosMain {
    dependsOn(commonMain.get())
    dependencies {
        // iOS platform dependencies
    }
}

val iosX64Main by getting { dependsOn(iosMain.get()) }
val iosArm64Main by getting { dependsOn(iosMain.get()) }
val iosSimulatorArm64Main by getting { dependsOn(iosMain.get()) }

Why iosMain:

  • iOS platform APIs
  • Native crypto (Security framework)
  • Different from Android/Desktop

Architecture targets:

  • iosX64Main: Intel simulator
  • iosArm64Main: Device (iPhone, iPad)
  • iosSimulatorArm64Main: Apple Silicon simulator

Build Order Matters

CRITICAL: jvmAndroid must be defined BEFORE androidMain and jvmMain:

// ✅ CORRECT ORDER
val jvmAndroid = create("jvmAndroid") { ... }
jvmMain { dependsOn(jvmAndroid) }
androidMain { dependsOn(jvmAndroid) }

// ❌ WRONG - Build error
androidMain { dependsOn(jvmAndroid) }  // jvmAndroid not defined yet!
val jvmAndroid = create("jvmAndroid") { ... }

See comment in quartz/build.gradle.kts:131:

// Must be defined before androidMain and jvmMain
val jvmAndroid = create("jvmAndroid") { ... }

Choosing the Right Source Set

Decision flowchart:

Q: Where should this code go?

├─ Pure Kotlin? (no platform APIs)
│  └─ commonMain
│
├─ JVM library? (Jackson, OkHttp)
│  └─ jvmAndroid
│
├─ Android API? (Activity, Context)
│  └─ androidMain
│
├─ Desktop API? (Window, MenuBar)
│  └─ jvmMain
│
└─ iOS API? (platform.posix, Security)
   └─ iosMain

Future: Web/wasm Source Sets

Not yet implemented, but structure would be:

commonMain
  ├─→ jsMain (JavaScript/Web)
  │    └─ JS-specific: DOM APIs, fetch
  │
  └─→ wasmMain (WebAssembly)
       └─ wasm-specific: limited APIs

Constraints:

  • Cannot use jvmAndroid (Jackson, OkHttp)
  • Cannot use platform.posix
  • Must use pure Kotlin or web-compatible libs (ktor, kotlinx.serialization)

Summary Table

Source Set Extends Can Use Example Code
commonMain - Kotlin stdlib only TextNoteEvent, business logic
jvmAndroid commonMain JVM libs (Jackson, OkHttp) JSON parsing, HTTP
androidMain jvmAndroid Android framework Activity, ViewModel
jvmMain jvmAndroid JVM + Compose Desktop Window, MenuBar
iosMain commonMain iOS platform Security framework
iosX64Main iosMain Simulator (Intel) Architecture-specific
iosArm64Main iosMain Device (ARM64) Architecture-specific
jsMain commonMain JS/DOM Web (future)
wasmMain commonMain wasm APIs WebAssembly (future)