KMP の疑問を雑に解消していくページ

## KMPのターゲットの種類

https://kotlinlang.org/docs/multiplatform-dsl-reference.html#targets

トップレベル？な定義としてはターゲットは4つ

- Kotlin/JVM

JVM 向け。これは普通にJava

- Kotlin/JS

browser と nodejs に分けられる。Alphaだけど Kotlin/Wasm というweb向けAssembly へのコンパイルを可能にするターゲットも作られてたりしてて、そのうちKotlinJSはKotlinWasmになっていく

- Android

Android向け。これは普通にAndroid

- Kotlin/Native

これは階層があって種類が様々。
LLVM（Low Level Virtual Machine） という様々なプラットフォーム向けのバイナリが生成できるコンパイラがあって、Kotlin/NativeはKotlinソースを LLVM IR という中間表現に変換している。
そのLLVM IRを つくればあとはLLVMがバイナリを生成してくれる。
という仕組みらしい。
 
## Kotlin/Nativeの階層

https://kotlinlang.org/docs/native-target-support.html

CIでのテストやRunが可能かで分かれてる

### Tier 1﻿
The target is regularly tested on CI to be able to compile and run.
 
- macosX64
- macosArm64
- iosSimulatorArm64
- iosX64

### Tier 2
The target is regularly tested on CI to be able to compile, but may not be automatically tested to be able to run.

- linuxX64
- linuxArm64
- watchosSimulatorArm64
- watchosX64
- watchosArm32
- watchosArm64
- tvosSimulatorArm64
- tvosX64
- tvosArm64
- iosArm64

### Tier 3﻿
The target is not guaranteed to be tested on CI.
- androidNativeArm32
- androidNativeArm64
- androidNativeX86
- androidNativeX64
- mingwX64 （<- Windowsアプリ用の実行環境らしい）
- watchosDeviceArm64

## ソースセットをどう活用しているのか

https://kotlinlang.org/docs/multiplatform-hierarchy.html#manual-configuration

ターゲットを 適切なソースセットに集約して そのソースセット上で動くものをやっていく感じになる。
基本的には、target を設定すると デフォルトで作られる androidMain や iOSMain , jsMain などを使っていく感じになるが カスタム👇も可能

```gradle
kotlin {
    linuxX64()
    mingwX64()
    macosX64()

    sourceSets {
        val desktopMain by creating {
            dependsOn(commonMain.get())
        }

        linuxX64Main.get().dependsOn(desktopMain)
        mingwX64Main.get().dependsOn(desktopMain)
        macosX64Main.get().dependsOn(desktopMain)
    }
}
```

![スクリーンショット 2024-04-16 9.11.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/700755/d84a69de-9b30-c0ec-e726-737a3c621d75.png)

それぞれのターゲットをdesktopMainというソースセットで扱う設定。

<br>

あとは、ソースセット自体も階層にできる
 -> iOSのターゲットをどれか一つでも設定すると iOSMainとAppleMainとNativeMainという階層がつくられる。


## 事前定義されたソースセット等

https://kotlinlang.org/docs/multiplatform-dsl-reference.html#predefined-source-sets

- commonMain
- commonTest 
- "targetName" "compilationName" 
  - targetName : jvm, js, android,iosなど各種ターゲット
  - compilationName: MainやTestなど, そのターゲット内での特定のコンパイルタスクや目的


### targetNameをSourceSetsからprintしてみる。

```gradle
 tasks.register("printSourceSets") {
     doLast {
         kotlin.sourceSets.forEach { sourceSet ->
             println("Source set: ${sourceSet.name}")
         }
     }
 }
 ```
 
kmpのpluginだけ適応した状態だと、commonMain とTestだけ

```
 > Task :composeApp:printSourceSets
 Source set: commonMain
 Source set: commonTest
 ```
 

kmp wizard で設定される ターゲットが 以下で

```gradle
 @OptIn(ExperimentalWasmDsl::class)
wasmJs {
 moduleName = "composeApp"
 browser {
     ...
 }
 binaries.executable()
}

androidTarget {
 ...
}

jvm("desktop")

listOf(
 iosX64(),
 iosArm64(),
 iosSimulatorArm64()
).forEach { iosTarget ->
 ...
}
 ```

このターゲットで ソースセットをプリントすると

```
 > Task :composeApp:printSourceSets
 Source set: androidDebug
 Source set: androidInstrumentedTest
 Source set: androidInstrumentedTestDebug
 Source set: androidMain
 Source set: androidRelease
 Source set: androidUnitTest
 Source set: androidUnitTestDebug
 Source set: androidUnitTestRelease
 Source set: appleMain
 Source set: appleTest
 Source set: commonMain
 Source set: commonTest
 Source set: desktopMain
 Source set: desktopTest
 Source set: iosArm64Main
 Source set: iosArm64Test
 Source set: iosMain
 Source set: iosSimulatorArm64Main
 Source set: iosSimulatorArm64Test
 Source set: iosTest
 Source set: iosX64Main
 Source set: iosX64Test
 Source set: nativeMain
 Source set: nativeTest
 Source set: wasmJsMain
 Source set: wasmJsTest
 ```
 nativeとappleが入っているのは iOSのターゲットを設定しているから
 

KotlinNativeのターゲットを一つでも設定するとNativeMainが設定される
linuxArm64をターゲットに設定すると👇
```
Source set: linuxArm64Main
Source set: linuxArm64Test
Source set: linuxMain
Source set: linuxTest
Source set: nativeMain
Source set: nativeTest
```

## expect - actural構文の制限ってあるの

https://kotlinlang.org/docs/multiplatform-expect-actual.html

> a function, property, class, interface, enumeration, or annotation.

割となんでもつけられる。
enum とか annotaionにつけられるのが面白い。 

```
 expect enum class Month {
     JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE, JULY,
     AUGUST, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER
 }
 JVM内だと typealias でjavaのものに繋げることができる
 actual typealias Month = java.time.Month
 ```
 
JVMで動くenumを KotlinNativeと共通で使いたい時に楽するためのTips


Commonで定義したexpect な enumに 特定のプラットフォームのactualに 識別子を追加する とかしちゃうと、Commonでenumを使う時のパターンマッチで else が必要になる。


## expect - actural のベストプラクティスは？

https://arc.net/l/quote/wbolrwvy

DIのモジュールをプラットフォーム固有にする ためのみで使うのが理想 

```kotlin
 // In the common source set:
 import org.koin.dsl.module
 
 interface Platform
 
 expect val platformModule: Module
 // In the androidMain source set:
 class AndroidPlatform : Platform
 
 actual val platformModule: Module = module {
     single<Platform> {
         AndroidPlatform()
     }
 }
 // In the iosMain source set:
 class IOSPlatform : Platform
 
 actual val platformModule = module {
     single<Platform> { IOSPlatform() }
 }
 ```

common に interface と それのための expect なmodule を定義して、 実態は各プラットフォームに定義 & actual なモジュール で factoryする。

## マルチモジュールの設定方法はどんなのがよい

Apollo-kotlinがすごい参考になる。

ターゲットの設定を共通化して CompositBuildの空間に置いている。

https://github.com/apollographql/apollo-kotlin/blob/b35c84399409ae4eb30707b7db16df02717d163b/build-logic/src/main/kotlin/api.kt#L15


ターゲットを共通化しているので、各モジュールのbuild.gradleがめっちゃ簡素になってる

https://github.com/apollographql/apollo-kotlin/blob/b35c84399409ae4eb30707b7db16df02717d163b/libraries/apollo-api/build.gradle.kts#L1