Kotlin Multiplatformプロジェクト構造の基本
Kotlin Multiplatformを使用すると、異なるプラットフォーム間でコードを共有できます。この記事では、共有コードの制約、コードの共有部分とプラットフォーム固有の部分を区別する方法、およびこの共有コードが動作するプラットフォームを指定する方法について説明します。
また、共通コード、ターゲット、プラットフォーム固有および中間ソースセット、テスト統合など、Kotlin Multiplatformプロジェクト設定のコアコンセプトについても学習します。これにより、将来的にマルチプラットフォームプロジェクトをセットアップするのに役立ちます。
ここで提示されるモデルは、Kotlinが使用するモデルと比較して簡略化されています。しかし、この基本的なモデルはほとんどの場合で十分であるはずです。
共通コード
_共通コード_は、異なるプラットフォーム間で共有されるKotlinコードです。
単純な「Hello, World」の例を考えます。
fun greeting() {
println("Hello, Kotlin Multiplatform!")
}プラットフォーム間で共有されるKotlinコードは通常、commonMainディレクトリに配置されます。コードファイルの場所は、このコードがコンパイルされるプラットフォームのリストに影響するため重要です。
Kotlinコンパイラはソースコードを入力として受け取り、その結果としてプラットフォーム固有のバイナリのセットを生成します。マルチプラットフォームプロジェクトをコンパイルする場合、同じコードから複数のバイナリを生成できます。たとえば、コンパイラは同じKotlinファイルからJVMの.classファイルとネイティブ実行可能ファイルを生成できます。
すべてのKotlinコードがすべてのプラットフォームにコンパイルできるわけではありません。Kotlinコンパイラは、プラットフォーム固有の関数やクラスを共通コードで使用することを防ぎます。なぜなら、このコードは異なるプラットフォームにコンパイルできないためです。
たとえば、java.io.Fileの依存関係を共通コードから使用することはできません。これはJDKの一部ですが、共通コードはネイティブコードにもコンパイルされ、そこではJDKクラスは利用できません。

共通コードでは、Kotlin Multiplatformライブラリを使用できます。これらのライブラリは、異なるプラットフォームで異なる方法で実装できる共通APIを提供します。この場合、プラットフォーム固有のAPIは追加部分として機能し、共通コードでそのようなAPIを使用しようとするとエラーになります。
たとえば、kotlinx.coroutinesはすべてのターゲットをサポートするKotlin Multiplatformライブラリですが、fun CoroutinesDispatcher.asExecutor(): Executorのようにkotlinx.coroutinesの並行プリミティブをJDKの並行プリミティブに変換するプラットフォーム固有の部分も持っています。このAPIの追加部分はcommonMainでは利用できません。
ターゲット
ターゲットは、Kotlinが共通コードをコンパイルするプラットフォームを定義します。これらは、たとえばJVM、JS、Android、iOS、またはLinuxである可能性があります。前の例では、共通コードをJVMとネイティブターゲットにコンパイルしました。
_Kotlinターゲット_は、コンパイルターゲットを記述する識別子です。それは、生成されるバイナリの形式、利用可能な言語構造、および許可される依存関係を定義します。
ターゲットはプラットフォームとも呼ばれます。サポートされているターゲットの完全なリストはこちらをご覧ください。
特定のターゲットのコードをコンパイルするようにKotlinに指示するには、まずターゲットを_宣言_する必要があります。Gradleでは、kotlin {}ブロック内で定義済みのDSL呼び出しを使用してターゲットを宣言します。
kotlin {
jvm() // Declares a JVM target
iosArm64() // Declares a target that corresponds to 64-bit iPhones
}このようにして、各マルチプラットフォームプロジェクトはサポートされるターゲットのセットを定義します。ビルドスクリプトでのターゲットの宣言について詳しく知るには、階層型プロジェクト構造セクションを参照してください。
jvmとiosArm64ターゲットが宣言されている場合、commonMain内の共通コードはこれらのターゲットにコンパイルされます。
特定のターゲットにどのコードがコンパイルされるかを理解するために、ターゲットをKotlinソースファイルに付加されたラベルとして考えることができます。Kotlinはこれらのラベルを使用して、コードをコンパイルする方法、生成するバイナリ、およびそのコードで許可される言語構造と依存関係を決定します。
greeting.ktファイルを.jsにもコンパイルしたい場合は、JSターゲットを宣言するだけで済みます。その後、commonMain内のコードはJSターゲットに対応する追加のjsラベルを受け取り、Kotlinに.jsファイルを生成するように指示します。
これが、Kotlinコンパイラが宣言されたすべてのターゲットにコンパイルされる共通コードで動作する方法です。プラットフォーム固有のコードの記述方法については、ソースセットを参照してください。
ソースセット
_Kotlinソースセット_は、独自のターゲット、依存関係、およびコンパイラオプションを持つソースファイルのセットです。これは、マルチプラットフォームプロジェクトでコードを共有する主要な方法です。
マルチプラットフォームプロジェクトの各ソースセットは次のとおりです。
- 特定のプロジェクトで一意の名前を持ちます。
- 通常、ソースセットの名前が付いたディレクトリに保存されているソースファイルとリソースのセットを含みます。
- このソースセットのコードがコンパイルされるターゲットのセットを指定します。
- これらのターゲットは、このソースセットで利用可能な言語構造と依存関係に影響を与えます。
- 独自の依存関係とコンパイラオプションを定義します。
Kotlinには、多数の事前定義されたソースセットが用意されています。そのうちの1つはcommonMainで、すべてのマルチプラットフォームプロジェクトに存在し、宣言されたすべてのターゲットにコンパイルされます。
Kotlin Multiplatformプロジェクトでは、src内のディレクトリとしてソースセットを操作します。 たとえば、commonMain、iosMain、jvmMainのソースセットを持つプロジェクトは、次の構造を持ちます。

Gradleスクリプトでは、kotlin.sourceSets {}ブロック内で名前によってソースセットにアクセスします。
kotlin {
// Targets declaration:
// …
// Source set declaration:
sourceSets {
commonMain {
// Configure the commonMain source set
}
}
}commonMain以外に、他のソースセットはプラットフォーム固有または中間である場合があります。
プラットフォーム固有のソースセット
共通コードのみを持つことは便利ですが、常に可能であるとは限りません。commonMain内のコードは宣言されたすべてのターゲットにコンパイルされ、Kotlinはそこにプラットフォーム固有のAPIを使用することを許可しません。
ネイティブおよびJSターゲットを持つマルチプラットフォームプロジェクトでは、commonMain内の次のコードはコンパイルされません。
// commonMain/kotlin/common.kt
// Doesn't compile in common code
fun greeting() {
java.io.File("greeting.txt").writeText("Hello, Multiplatform!")
}解決策として、Kotlinはプラットフォーム固有のソースセット(プラットフォームソースセットとも呼ばれる)を作成します。各ターゲットには、そのターゲットのみにコンパイルされる対応するプラットフォームソースセットがあります。たとえば、jvmターゲットには、JVMのみにコンパイルされる対応するjvmMainソースセットがあります。Kotlinは、これらのソースセットでプラットフォーム固有の依存関係を使用することを許可します。たとえば、jvmMainではJDKを使用できます。
// jvmMain/kotlin/jvm.kt
// You can use Java dependencies in the `jvmMain` source set
fun jvmGreeting() {
java.io.File("greeting.txt").writeText("Hello, Multiplatform!")
}特定のターゲットへのコンパイル
特定のターゲットへのコンパイルは、複数のソースセットで機能します。Kotlinがマルチプラットフォームプロジェクトを特定のターゲットにコンパイルする場合、そのターゲットでラベル付けされたすべてのソースセットを収集し、それらからバイナリを生成します。
jvm、iosArm64、およびjsターゲットの例を考えます。Kotlinは、共通コード用のcommonMainソースセットと、特定のターゲットに対応するjvmMain、iosArm64Main、およびjsMainソースセットを作成します。
JVMへのコンパイル中、Kotlinは「JVM」とラベル付けされたすべてのソースセット、すなわちjvmMainとcommonMainを選択します。その後、それらをまとめてJVMクラスファイルにコンパイルします。
KotlinはcommonMainとjvmMainを一緒にコンパイルするため、結果のバイナリにはcommonMainとjvmMainの両方からの宣言が含まれます。
マルチプラットフォームプロジェクトで作業する場合、次の点に注意してください。
- Kotlinに特定のプラットフォームにコードをコンパイルさせたい場合は、対応するターゲットを宣言します。
- コードを保存するディレクトリまたはソースファイルを選択するには、まずどのターゲット間でコードを共有するかを決定します。
- コードがすべてのターゲット間で共有される場合、
commonMainで宣言する必要があります。 - コードが1つのターゲットのみに使用される場合、そのターゲットのプラットフォーム固有のソースセット(たとえば、JVMの場合は
jvmMain)で定義する必要があります。
- コードがすべてのターゲット間で共有される場合、
- プラットフォーム固有のソースセットで記述されたコードは、共通ソースセットからの宣言にアクセスできます。たとえば、
jvmMain内のコードはcommonMainからのコードを使用できます。しかし、その逆は真ではありません。commonMainはjvmMainからのコードを使用できません。 - プラットフォーム固有のソースセットで記述されたコードは、対応するプラットフォームの依存関係を使用できます。たとえば、
jvmMain内のコードは、GuavaやSpringのようなJava専用ライブラリを使用できます。
中間ソースセット
単純なマルチプラットフォームプロジェクトには、通常、共通コードとプラットフォーム固有のコードしかありません。commonMainソースセットは、宣言されたすべてのターゲット間で共有される共通コードを表します。jvmMainのようなプラットフォーム固有のソースセットは、それぞれのターゲットのみにコンパイルされるプラットフォーム固有のコードを表します。
実際には、より詳細なコード共有が必要になることがよくあります。
すべての最新のAppleデバイスとAndroidデバイスをターゲットにする必要がある例を考えます。
kotlin {
androidTarget()
iosArm64() // 64-bit iPhone devices
macosArm64() // Modern Apple Silicon-based Macs
watchosX64() // Modern 64-bit Apple Watch devices
tvosArm64() // Modern Apple TV devices
}そして、すべてのAppleデバイス用のUUIDを生成する関数を追加するためのソースセットが必要です。
import platform.Foundation.NSUUID
fun randomUuidString(): String {
// You want to access Apple-specific APIs
return NSUUID().UUIDString()
}この関数をcommonMainに追加することはできません。commonMainはAndroidを含む宣言されたすべてのターゲットにコンパイルされますが、platform.Foundation.NSUUIDはAndroidでは利用できないApple固有のAPIです。commonMainでNSUUIDを参照しようとすると、Kotlinはエラーを表示します。
このコードを各Apple固有のソースセット、すなわちiosArm64Main、macosArm64Main、watchosX64Main、およびtvosArm64Mainにコピー&ペーストすることもできます。しかし、このようにコードを複製する方法はエラーの原因となりやすいため、推奨されません。
この問題を解決するには、_中間ソースセット_を使用できます。中間ソースセットは、プロジェクト内のすべてのターゲットではなく、一部のターゲットにコンパイルされるKotlinソースセットです。中間ソースセットは、階層型ソースセット、または単に階層と呼ばれることもあります。
Kotlinは、デフォルトでいくつかの中間ソースセットを作成します。この特定の場合、結果のプロジェクト構造は次のようになります。
ここで、下部の多色のブロックはプラットフォーム固有のソースセットです。分かりやすくするために、ターゲットラベルは省略されています。
appleMainブロックは、Apple固有のターゲットにコンパイルされるコードを共有するためにKotlinによって作成された中間ソースセットです。appleMainソースセットはAppleターゲットのみにコンパイルされます。したがって、KotlinはappleMainでApple固有のAPIを使用することを許可しており、randomUUID()関数をここに追加できます。
階層型プロジェクト構造を参照して、Kotlinがデフォルトで作成および設定するすべての中間ソースセットを見つけ、デフォルトで必要とする中間ソースセットがKotlinによって提供されない場合にどうすべきかを学びましょう。
特定のターゲットへのコンパイル中、Kotlinは、このターゲットでラベル付けされた中間ソースセットを含むすべてのソースセットを取得します。したがって、commonMain、appleMain、およびiosArm64Mainソースセットに記述されたすべてのコードは、iosArm64プラットフォームターゲットへのコンパイル中に結合されます。
一部のソースセットにソースがなくても問題ありません。たとえば、iOS開発では、通常、iOSデバイスに固有だがiOSシミュレーターには固有ではないコードを提供する必要はありません。したがって、
iosArm64Mainはめったに使用されません。
Appleデバイスとシミュレーターのターゲット
Kotlin Multiplatformを使用してiOSモバイルアプリケーションを開発する場合、通常はiosMainソースセットを使用します。iosターゲットのプラットフォーム固有のソースセットだと考えるかもしれませんが、単一のiosターゲットは存在しません。ほとんどのモバイルプロジェクトには、少なくとも2つのターゲットが必要です。
- デバイスターゲットは、iOSデバイスで実行できるバイナリを生成するために使用されます。現在、iOSのデバイスターゲットは
iosArm64のみです。 - シミュレーターターゲットは、お使いのマシンで起動されるiOSシミュレーター用のバイナリを生成するために使用されます。Apple silicon Macコンピューターをお持ちの場合は、
iosSimulatorArm64をシミュレーターターゲットとして選択してください。IntelベースのMacコンピューターをお持ちの場合は、iosX64を使用してください。
iosArm64デバイスターゲットのみを宣言した場合、ローカルマシンでアプリケーションとテストを実行およびデバッグすることはできません。
iosArm64Main、iosSimulatorArm64Main、iosX64Mainのようなプラットフォーム固有のソースセットは、iOSデバイスとシミュレーター向けのKotlinコードが通常同じであるため、通常は空です。それらすべてでコードを共有するには、iosMain中間ソースセットのみを使用できます。
他のMac以外のAppleターゲットにも同じことが当てはまります。たとえば、Apple TV用のtvosArm64デバイスターゲットと、Apple siliconおよびIntelベースのデバイス上のApple TVシミュレーター用のtvosSimulatorArm64およびtvosX64シミュレーターターゲットがある場合、それらすべてにtvosMain中間ソースセットを使用できます。
テストとの統合
実際のプロジェクトでは、主要なプロダクションコードに加えてテストも必要です。これが、デフォルトで作成されるすべてのソースセットにMainとTestのサフィックスが付いている理由です。Mainにはプロダクションコードが含まれ、Testにはこのコードのテストが含まれます。それらの間の接続は自動的に確立され、テストは追加の構成なしでMainコードによって提供されるAPIを使用できます。
Testに対応するものは、Mainと同様にソースセットです。たとえば、commonTestはcommonMainの対応物であり、宣言されたすべてのターゲットにコンパイルされるため、共通テストを記述できます。jvmTestのようなプラットフォーム固有のテストソースセットは、プラットフォーム固有のテスト、たとえばJVM固有のテストやJVM APIを必要とするテストを記述するために使用されます。
共通テストを記述するためのソースセットがあるだけでなく、マルチプラットフォームテストフレームワークも必要です。Kotlinは、@kotlin.TestアノテーションとassertEqualsやassertTrueなどのさまざまなアサーションメソッドが付属するデフォルトのkotlin.testライブラリを提供します。
各プラットフォームのプラットフォーム固有のテストを、それぞれのソースセットで通常のテストのように記述できます。メインコードと同様に、各ソースセットにプラットフォーム固有の依存関係を持つことができます。たとえば、JVMにはJUnit、iOSにはXCTestなどです。特定のターゲットのテストを実行するには、<targetName>Testタスクを使用します。
マルチプラットフォームテストの作成と実行方法については、マルチプラットフォームアプリのテストチュートリアルで学習してください。
