メインコンテンツまでスキップ

Kotlin Multiplatform プロジェクト構造の基本

Kotlin Multiplatformを使用すると、異なるプラットフォーム間でコードを共有できます。この記事では、共有コードの制約、コードの共有部分とプラットフォーム固有の部分を区別する方法、およびこの共有コードが動作するプラットフォームを指定する方法について説明します。

また、共通コード、target (ターゲット)、プラットフォーム固有および中間[source set (ソースセット)]、テストの統合など、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クラスは利用できません。

未解決のJava参照

共通コードでは、Kotlin Multiplatformライブラリを使用できます。これらのライブラリは、異なるプラットフォームで異なる方法で実装できる共通APIを提供します。この場合、プラットフォーム固有のAPIは追加の部分として機能し、共通コードでそのようなAPIを使用しようとするとエラーが発生します。

たとえば、kotlinx.coroutinesは、すべての[target (ターゲット)]をサポートするKotlin Multiplatformライブラリですが、kotlinx.coroutines並行プリミティブをJDK並行プリミティブに変換するプラットフォーム固有の部分も含まれています。たとえば、fun CoroutinesDispatcher.asExecutor(): Executorなどです。APIのこの追加部分は、commonMainでは使用できません。

[Target (ターゲット)]

[Target (ターゲット)]は、Kotlinが共通コードをコンパイルするプラットフォームを定義します。これには、たとえば、JVM、JS、Android、iOS、またはLinuxが含まれます。前の例では、共通コードをJVMとネイティブ[target (ターゲット)]にコンパイルしました。

_Kotlin [target (ターゲット)]_とは、コンパイル[target (ターゲット)]を記述する識別子です。生成されるバイナリの形式、利用可能な言語構造、および許可される依存関係を定義します。

ヒント

[Target (ターゲット)]は、プラットフォームと呼ばれることもあります。サポートされている[target (ターゲット)]の完全なlist (リスト)を参照してください。

まず、特定の[target (ターゲット)]に対してコードをコンパイルするようにKotlinに指示するために、[target (ターゲット)]を_宣言_する必要があります。Gradleでは、kotlin {}ブロック内で定義済みのDSL呼び出しを使用して[target (ターゲット)]を宣言します。

kotlin {
jvm() // JVM [target (ターゲット)]を宣言します
iosArm64() // 64ビットiPhoneに対応する[target (ターゲット)]を宣言します
}

このようにして、各マルチプラットフォームプロジェクトは、サポートされている[target (ターゲット)]のセットを定義します。ビルドスクリプトで[target (ターゲット)]を宣言する方法の詳細については、Hierarchical project structure (階層型プロジェクト構造)セクションを参照してください。

jvmiosArm64[target (ターゲット)]が宣言されると、commonMainの共通コードはこれらの[target (ターゲット)]にコンパイルされます。

ターゲット

どのコードが特定の[target (ターゲット)]にコンパイルされるかを理解するために、[target (ターゲット)]をKotlinソースファイルにアタッチされたラベルと考えることができます。Kotlinは、これらのラベルを使用して、コードのコンパイル方法、生成するバイナリ、およびそのコードで許可される言語構造と依存関係を決定します。

greeting.ktファイルを.jsにもコンパイルする場合は、JS[target (ターゲット)]を宣言するだけです。次に、commonMainのコードは、JS[target (ターゲット)]に対応する追加のjsラベルを受け取ります。これにより、Kotlinに.jsファイルを生成するように指示します。

targetラベル

これが、Kotlinコンパイラがすべての宣言された[target (ターゲット)]にコンパイルされた共通コードで動作する方法です。プラットフォーム固有のコードを記述する方法については、Source sets (ソースセット)を参照してください。

[Source sets (ソースセット)]

_Kotlin [source set (ソースセット)]_とは、独自の[target (ターゲット)]、依存関係、およびコンパイラオプションを持つソースファイルのセットです。これは、マルチプラットフォームプロジェクトでコードを共有する主な方法です。

マルチプラットフォームプロジェクトの各[source set (ソースセット)]:

  • 特定のプロジェクトに対して一意の名前を持ちます。
  • 通常、[source set (ソースセット)]の名前のディレクトリに格納されているソースファイルとリソースのセットが含まれています。
  • この[source set (ソースセット)]のコードがコンパイルされる[target (ターゲット)]のセットを指定します。これらの[target (ターゲット)]は、この[source set (ソースセット)]で使用できる言語構造と依存関係に影響を与えます。
  • 独自の依存関係とコンパイラオプションを定義します。

Kotlinは、事前に定義された[source set (ソースセット)]を多数提供します。その1つがcommonMainです。これは、すべてのマルチプラットフォームプロジェクトに存在し、宣言されたすべての[target (ターゲット)]にコンパイルされます。

Kotlin Multiplatformプロジェクトでは、src内のディレクトリとして[source set (ソースセット)]を操作します。たとえば、commonMainiosMain、およびjvmMain[source set (ソースセット)]を持つプロジェクトは、次の構造を持ちます。

共有ソース

Gradleスクリプトでは、kotlin.sourceSets {}ブロック内で名前で[source set (ソースセット)]にアクセスします。

kotlin {
// ターゲット宣言:
// …

// ソースセット宣言:
sourceSets {
commonMain {
// commonMainソースセットを構成します
}
}
}

commonMainとは別に、他の[source set (ソースセット)]は、プラットフォーム固有または中間です。

プラットフォーム固有の[source set (ソースセット)]

共通コードのみを持つことは便利ですが、常に可能とは限りません。commonMainのコードは、宣言されたすべての[target (ターゲット)]にコンパイルされ、Kotlinでは、そこにプラットフォーム固有のAPIを使用することはできません。

ネイティブおよびJS[target (ターゲット)]を持つマルチプラットフォームプロジェクトでは、commonMainの次のコードはコンパイルされません。

// commonMain/kotlin/common.kt
// 共通コードではコンパイルされません
fun greeting() {
java.io.File("greeting.txt").writeText("Hello, Multiplatform!")
}

解決策として、Kotlinはプラットフォーム固有の[source set (ソースセット)]を作成します。これは、プラットフォーム[source set (ソースセット)]とも呼ばれます。各[target (ターゲット)]には、その[target (ターゲット)]に対してのみコンパイルされる対応するプラットフォーム[source set (ソースセット)]があります。たとえば、jvm[target (ターゲット)]には、JVMに対してのみコンパイルされる対応するjvmMain[source set (ソースセット)]があります。Kotlinでは、これらの[source set (ソースセット)]でプラットフォーム固有の依存関係を使用できます。たとえば、jvmMainではJDKを使用できます。

// jvmMain/kotlin/jvm.kt
// `jvmMain`ソースセットではJava依存関係を使用できます
fun jvmGreeting() {
java.io.File("greeting.txt").writeText("Hello, Multiplatform!")
}

特定の[target (ターゲット)]へのコンパイル

特定の[target (ターゲット)]へのコンパイルは、複数の[source set (ソースセット)]で動作します。Kotlinがマルチプラットフォームプロジェクトを特定の[target (ターゲット)]にコンパイルする場合、この[target (ターゲット)]でラベル付けされたすべての[source set (ソースセット)]を収集し、それらからバイナリを生成します。

jvmiosArm64、およびjs[target (ターゲット)]の例を考えてみましょう。Kotlinは、共通コード用のcommonMain[source set (ソースセット)]と、特定の[target (ターゲット)]用の対応するjvmMainiosArm64Main、およびjsMain[source set (ソースセット)]を作成します。

特定のターゲットへのコンパイル

JVMへのコンパイル中、Kotlinは「JVM」でラベル付けされたすべての[source set (ソースセット)]、つまりjvmMaincommonMainを選択します。次に、それらをまとめてJVMクラスファイルにコンパイルします。

JVMへのコンパイル

KotlinはcommonMainjvmMainを一緒にコンパイルするため、結果のバイナリにはcommonMainjvmMainの両方の宣言が含まれます。

マルチプラットフォームプロジェクトを操作する場合は、次の点に注意してください。

  • コードを特定のプラットフォームにコンパイルする場合は、対応する[target (ターゲット)]を宣言します。

  • コードを保存するディレクトリまたはソースファイルを選択するには、まず、どの[target (ターゲット)]間でコードを共有するかを決定します。

    • コードがすべての[target (ターゲット)]間で共有されている場合は、commonMainで宣言する必要があります。
    • コードが1つの[target (ターゲット)]でのみ使用される場合は、その[target (ターゲット)]のプラットフォーム固有の[source set (ソースセット)]で定義する必要があります(たとえば、JVMの場合はjvmMain)。
  • プラットフォーム固有の[source set (ソースセット)]で記述されたコードは、共通[source set (ソースセット)]からの宣言にアクセスできます。たとえば、jvmMainのコードはcommonMainのコードを使用できます。ただし、逆は当てはまりません。commonMainjvmMainのコードを使用できません。

  • プラットフォーム固有の[source set (ソースセット)]で記述されたコードは、対応するプラットフォーム依存関係を使用できます。たとえば、jvmMainのコードは、GuavaSpringなどのJavaのみのライブラリを使用できます。

中間の[source set (ソースセット)]

単純なマルチプラットフォームプロジェクトには、通常、共通コードとプラットフォーム固有のコードのみが含まれます。commonMain[source set (ソースセット)]は、宣言されたすべての[target (ターゲット)]間で共有される共通コードを表します。jvmMainなどのプラットフォーム固有の[source set (ソースセット)]は、それぞれの[target (ターゲット)]に対してのみコンパイルされるプラットフォーム固有のコードを表します。

実際には、よりきめ細かいコード共有が必要になることがよくあります。

すべての最新のAppleデバイスとAndroidデバイスを[target (ターゲット)]にする必要がある例を考えてみましょう。

kotlin {
androidTarget()
iosArm64() // 64ビットiPhoneデバイス
macosArm64() // 最新のAppleシリコンベースのMac
watchosX64() // 最新の64ビットApple Watchデバイス
tvosArm64() // 最新のApple TVデバイス
}

また、すべてのAppleデバイスのUUIDを生成する関数を追加する[source set (ソースセット)]が必要です。

import platform.Foundation.NSUUID

fun randomUuidString(): String {
// Apple固有のAPIにアクセスする必要があります
return NSUUID().UUIDString()
}

この関数をcommonMainに追加することはできません。commonMainは、Androidを含む、宣言されたすべての[target (ターゲット)]にコンパイルされますが、platform.Foundation.NSUUIDは、Androidでは利用できないApple固有のAPIです。commonMainNSUUIDを参照しようとすると、Kotlinはエラーを表示します。

このコードを各Apple固有の[source set (ソースセット)]:iosArm64MainmacosArm64MainwatchosX64Main、およびtvosArm64Mainにコピーアンドペーストできます。ただし、このようなコードの複製はエラーが発生しやすいため、このアプローチは推奨されません。

この問題を解決するには、_中間[source set (ソースセット)]_を使用できます。中間[source set (ソースセット)]は、プロジェクト内の一部の[target (ターゲット)]にコンパイルされるKotlin [source set (ソースセット)]ですが、すべてではありません。中間[source set (ソースセット)]は、階層型[source set (ソースセット)]または単に階層と呼ばれることもあります。

Kotlinは、デフォルトでいくつかの中間[source set (ソースセット)]を作成します。この特定のケースでは、結果のプロジェクト構造は次のようになります。

中間ソースセット

ここで、下部にある多色のブロックはプラットフォーム固有の[source set (ソースセット)]です。わかりやすくするために、[target (ターゲット)]ラベルは省略されています。

appleMainブロックは、Apple固有の[target (ターゲット)]にコンパイルされるコードを共有するためにKotlinによって作成された中間[source set (ソースセット)]です。appleMain[source set (ソースセット)]は、Apple [target (ターゲット)]に対してのみコンパイルされます。したがって、KotlinではappleMainでApple固有のAPIを使用でき、ここにrandomUUID()関数を追加できます。

Kotlinがデフォルトで作成およびセットアップするすべての中間[source set (ソースセット)]を見つけるには、Hierarchical project structure (階層型プロジェクト構造)を参照し、Kotlinがデフォルトで必要な中間[source set (ソースセット)]を提供しない場合に何をすべきかを学びます。

特定の[target (ターゲット)]へのコンパイル中、Kotlinは、この[target (ターゲット)]でラベル付けされたすべての中間[source set (ソースセット)]を含む、すべての[source set (ソースセット)]を取得します。したがって、commonMainappleMain、およびiosArm64Main[source set (ソースセット)]に記述されたすべてのコードは、iosArm64プラットフォーム[target (ターゲット)]へのコンパイル中に結合されます。

ネイティブ実行可能ファイル
ヒント

一部の[source set (ソースセット)]にソースがない場合でも問題ありません。たとえば、iOS開発では、iOSデバイス固有であるがiOSシミュレーターにはないコードを提供する必要は通常ありません。したがって、iosArm64Mainが使用されることはほとんどありません。

Appleデバイスとシミュレーターの[target (ターゲット)]

Kotlin Multiplatformを使用してiOSモバイルアプリケーションを開発する場合、通常はiosMain[source set (ソースセット)]を使用します。ios[target (ターゲット)]のプラットフォーム固有の[source set (ソースセット)]と思われるかもしれませんが、単一のios[target (ターゲット)]はありません。ほとんどのモバイルプロジェクトでは、少なくとも2つの[target (ターゲット)]が必要です。

  • **デバイス[target (ターゲット)]**は、iOSデバイスで実行できるバイナリを生成するために使用されます。現在、iOSのデバイス[target (ターゲット)]は1つだけです:iosArm64
  • **シミュレーター[target (ターゲット)]**は、マシンで起動されたiOSシミュレーター用のバイナリを生成するために使用されます。AppleシリコンMacコンピューターをお持ちの場合は、シミュレーター[target (ターゲット)]としてiosSimulatorArm64を選択します。IntelベースのMacコンピューターをお持ちの場合は、iosX64を使用します。

iosArm64デバイス[target (ターゲット)]のみを宣言した場合、ローカルマシンでアプリケーションとテストを実行およびデバッグすることはできません。

iosArm64MainiosSimulatorArm64Main、およびiosX64Mainのようなプラットフォーム固有の[source set (ソースセット)]は通常空です。iOSデバイスとシミュレーター用のKotlinコードは通常同じであるためです。それらすべてでコードを共有するには、iosMain中間[source set (ソースセット)]のみを使用できます。

これは、Mac以外の他のApple [target (ターゲット)]にも当てはまります。たとえば、Apple TV用のtvosArm64デバイス[target (ターゲット)]と、AppleシリコンおよびIntelベースのデバイス上のApple TVシミュレーター用のtvosSimulatorArm64およびtvosX64シミュレーター[target (ターゲット)]がある場合、それらすべてにtvosMain中間[source set (ソースセット)]を使用できます。

テストとの統合

実際のプロジェクトでは、メインのプロダクションコードとともにテストも必要です。これが、デフォルトで作成されるすべての[source set (ソースセット)]にMainTestのサフィックスが付いている理由です。Mainにはプロダクションコードが含まれ、Testにはこのコードのテストが含まれています。それらの間の接続は自動的に確立され、テストは追加の構成なしにMainコードによって提供されるAPIを使用できます。

Testの対応物は、Mainと同様の[source set (ソースセット)]でもあります。たとえば、commonTestcommonMainの対応物であり、宣言されたすべての[target (ターゲット)]にコンパイルされるため、共通テストを記述できます。jvmTestなどのプラットフォーム固有のテスト[source set (ソースセット)]は、プラットフォーム固有のテスト(たとえば、JVM固有のテスト、またはJVM APIを必要とするテスト)を記述するために使用されます。

共通テストを記述するための[source set (ソースセット)]を用意するだけでなく、マルチプラットフォームテストフレームワークも必要です。Kotlinは、@kotlin.TestアノテーションとassertEqualsassertTrueなどのさまざまなアサーションメソッドが付属するデフォルトのkotlin.testライブラリを提供します。

各プラットフォームの通常のテストのように、それぞれの[source set (ソースセット)]でプラットフォーム固有のテストを記述できます。メインコードと同様に、各[source set (ソースセット)]にプラットフォーム固有の依存関係(JVMの場合はJUnit、iOSの場合はXCTestなど)を含めることができます。特定の[target (ターゲット)]のテストを実行するには、<targetName>Testタスクを使用します。

マルチプラットフォームテストを作成および実行する方法については、Test your multiplatform app tutorial (マルチプラットフォームアプリのテストチュートリアル)を参照してください。

次のステップ