Claude CodeでKotlin Multiplatformを使う — 共有コードの書き方とiOS統合
Claude CodeでKotlin Multiplatformを書く際の前提条件、CLAUDE.mdでのソースセット別ルール、Gradleでのビルド・テスト、iOS統合の制約をまとめます。
要点
Claude CodeでKotlin Multiplatform(KMP)のプロジェクトを扱うと、プラットフォーム専用APIを共有コードに書いてビルドを壊す提案が返ってくることがあります。commonMain と androidMain / iosMain の境界は、プロジェクト側でClaude Codeに伝えておく必要があります。
この記事では、KMPプロジェクトの前提条件とセットアップの選択肢を扱います。続いて、CLAUDE.mdと .claude/rules/ でソースセットの境界を教える方法、Gradleでのビルド・テストの回し方、iOS統合で外せないmacOS/Xcodeの制約を順に説明します。Claude Code自体のインストールや基本操作はClaude Codeとは何かをまとめた記事にまとまっています。
KMPプロジェクトの前提とセットアップの選択肢
Kotlin Multiplatformは、Android・iOS・デスクトップ・Web・サーバー間でコードを共有するJetBrainsのオープンソース技術です。ロジックだけを共有してUIはネイティブのままにする構成も、Compose MultiplatformでUIごと共有する構成も選べます。
公式のセットアップ手順は基本的にIDE前提です。Kotlin Multiplatform IDEプラグインの対象は、macOSならIntelliJ IDEA 2025.1.1.1以降です。Android StudioならNarwhal 2025.1.1以降です。WindowsとLinuxではIDEA 2025.2.2以降とAndroid Studio Otter 2025.2.1以降になります。プラグインを入れたら、新規プロジェクトウィザードでAndroid・iOS・デスクトップなどのターゲットを選んで生成します。iOSターゲットを含める場合はmacOSホストにXcodeが必要です。IDEはビルドの裏側でXcodeを呼び出すため、初回起動を済ませて初期セットアップを終わらせておく必要があります。
IDEのウィザードを開かずに始めたい場合は、JetBrainsが公開しているKotlin Multiplatform WizardというWebツールが使えます。ブラウザー上でプロジェクトの雛形を生成できます。IDEを開かず、ターミナルとClaude Codeだけで作業を始められる点が実用的です。ビルドシステムはGradleのほかに、JetBrainsが新規プロジェクト向けに用意するKotlin Toolchainも選べます。公式クイックスタートはこれを「CLIと透過的な設定形式を備え、AIワークフローに向く」ツールと説明しています。ただしクイックスタート自体は、既存のGradle環境を持つ利用者向けに書かれています。この記事のコマンドはGradle前提です。
ANDROID_HOME 環境変数の設定も欠かせません。.profile や .zprofile に次の1行を追記しておきます(パスはmacOSの場合の例です)。Gradleがこの環境変数を参照するので、Claude Codeを起動するシェルにも設定しておく必要があります。
export ANDROID_HOME=~/Library/Android/sdkCLAUDE.mdと.claude/rules/でソースセットの境界を教える
KMPプロジェクトの最大の特徴は、同じKotlinの構文でも書ける場所によって使えるAPIが変わることです。commonMain は宣言した全ターゲットに向けてコンパイルされます。そのため java.io.File のようなJVM専用APIや platform.Foundation.NSUUID のようなiOS専用APIを直接使うと、コンパイルエラーになります。プラットフォーム固有の処理は、ターゲットごとの専用ソースセット(androidMain・iosMain など)に書き分ける必要があります。
Claude Codeはこの境界をプロジェクトの外から推測できません。そこで、置き場所ごとに読み込まれるタイミングが違う点を使い分けます。
境界ルールの置き場所と読み込みタイミング
CLAUDE.md(プロジェクト直下)
全ソースセットに共通する大枠を書きます。ビルドとテストのコマンド、モジュール構成、「commonMainにプラットフォームAPIを書かない」といった一行ルールが向いています。
`.claude/rules/` に `paths` を付けたファイル
Read・Write・Editでglobに合うファイルを扱ったときに読み込まれます。commonMain 用と iosMain 用でファイルを分ければ、作業中でない側の注意点が文脈を使いません。
paths を付けないルールファイルは、CLAUDE.mdと同じく起動時に読み込まれます。サブディレクトリ(たとえば shared/)に置いたCLAUDE.mdは、そのディレクトリ内のファイルをClaudeが読んだときに読み込まれる仕組みです。読み込まれたかどうかは、セッション内で /context を実行して「Memory files」の一覧で確認できます。
paths はYAMLフロントマターにglobパターンを書いて指定します。YAMLのリストでも、カンマ区切りの文字列でも書けます。
---
paths:
- "shared/src/commonMain/**/*.kt"
---
# commonMainで守ること
- java.io.File・android.*・platform.*などプラットフォーム専用APIを
直接importしない
- プラットフォーム固有の実装が要る場合はexpect宣言を追加し、
androidMain・iosMainの両方にactual宣言を書く
- ビルド確認はテストが通る範囲でallTestsタスクを使うcommonMainで書ける範囲とexpect/actual宣言
expect/actual宣言とは、共通コードから見えるAPIの形だけを宣言し、実装は各プラットフォームのソースセットに任せる仕組みです。commonMain で expect を付けた関数・クラス・プロパティを宣言し、androidMain や iosMain など各ソースセットで同じシグネチャに actual を付けて実装します。コンパイラは両者をターゲットごとに1つの宣言へマージし、パッケージが一致しない宣言や実装が欠けたターゲットがあればエラーにします。
公式ドキュメントは commonMain ・jvmMain ・nativeMain の3ソースセットを例に説明しています。Android/iOS構成でも考え方は同じで、commonMain ・androidMain ・iosMain に置き換えて読めます。
package identity
class Identity(val userName: String, val processID: Long)
expect fun buildIdentity(): Identitypackage identity
actual fun buildIdentity() = Identity(
System.getProperty("user.name") ?: "None",
ProcessHandle.current().pid()
)関数だけでなく、プロパティ・クラス・インターフェース・列挙型・アノテーションにも expect を付けられます。一方で、既存のプラットフォーム型をそのまま見せたいときは、actual typealias Month = java.time.Month のように型エイリアスで結びます。この場合、エイリアスは expect 宣言と同じパッケージに置きます。
クラスに expect を付ける形(expected and actual classes)は、公式ドキュメント上でBetaです。使うとコンパイラが警告を出し、抑えるにはGradleのビルドファイルに -Xexpect-actual-classes を追加します。公式は、ファクトリー関数が大きくなったら共通のインターフェースと各プラットフォームの実装に分けるやり方も示しています。依存性注入(DI)を使うプロジェクトでは、プラットフォーム依存もインターフェース越しに注入する方針を勧めています。
Claude Codeにこの型のコードを書かせるときは、次の一往復をタスクの単位にします。
expect/actualを1タスクで閉じる
- 1
commonMainにexpectを書く
関数かインターフェースのシグネチャだけを宣言します。
- 2
宣言したターゲットすべてにactualを書く
androidMainとiosMainなど、プロジェクトが持つソースセットを漏れなく埋めます。 - 3
allTestsで全ターゲットを確認する
実装が欠けたターゲットは、ここでビルドエラーになります。
IDEはexpectとactualのペアをガター(行番号の脇)アイコンから、expectの宣言に対応するactual宣言へ移動できるようにします。ターミナル越しのClaude Codeには画面がないので、同じ確認をビルドとテストの結果で行います。
Gradleでビルド・テストをClaude Codeに任せる
Claude Codeが実行できる範囲はターミナルコマンドに限られるため、IDEのRunボタンの代わりにGradleタスクを直接叩く形になります。テストは allTests タスクで全ターゲットをまとめて実行できます。Androidのレポートは testDebugUnitTest・testReleaseUnitTest、iOSシミュレーターのターゲットは iosSimulatorArm64 という名前で現れます。個別に走らせるときは ./gradlew tasks --all でタスク名を確認します。
./gradlew allTestsレポートはHTMLで出力されます。allTests/index.html には共通テストとiOSテストの結果がまとまります。Androidのレポートは testDebugUnitTest と testReleaseUnitTest のフォルダーに分かれ、allTests のレポートには自動で統合されません。Claude Codeが判断に使うのは、これらのHTMLではなくターミナルの終了コードと標準出力です。テストの合否をClaudeが自分で読める状態にしてから実装を任せる進め方は、Claude CodeでTDDを回す手順で扱っているループと同じ要領で回せます。コルーチンのテストはrunTestと仮想時間の記事にあります。
毎回の実行確認をスキップしたい場合は、.claude/settings.json の許可リストにGradleコマンドを追加します。
{
"permissions": {
"allow": [
"Bash(./gradlew allTests)",
"Bash(./gradlew *Test)"
]
}
}許可ルールの書き方には癖があります。* を含まないルールは、一字一句同じコマンドだけに合います。./gradlew allTests --continue のようにオプションを足すと、1行目のルールには合いません。逆に Bash(./gradlew *Test) の * は、空白を含む任意の文字列に合います。./gradlew の後ろにどんなオプションが続いても、末尾が Test なら許可されます。テストタスクに限る使い方なら問題になりにくいものの、許可の幅は意識しておくと安全です。
androidMain 側と iosMain 側のプラットフォーム実装を並行して進めたい場合は、Claude Codeのサブエージェントでモジュールごとに調査・実装を分担させる方法も有効です。片方の実装がXcodeのビルド待ちで止まっている間に、もう一方のサブエージェントにAndroid側の実装を進めさせられます。
iOS統合とmacOS環境の制約
共有モジュールをiOSアプリに組み込む方法は、大きく分けてローカル統合とリモート統合の2種類です。ローカル統合は3種類です。1つ目は、Xcodeのビルドフェーズにスクリプトを追加する直接統合で、Kotlin Multiplatform IDEプラグインを使う場合のデフォルトです。2つ目は、KMPのiOSフレームワークがローカルのSwiftパッケージに依存するSwiftPM連携です。3つ目がCocoaPodsを介した統合です。リモート統合では、XCFrameworkをSwiftPMまたはCocoaPods経由でサードパーティ依存のように配布します。
どの方法を選んでも、iOSフレームワークのビルドそのものはmacOS上のXcodeが担います。Linuxコンテナの中では完結しません。Claude CodeをGitHub Codespacesで動かす場合、公式のDev Container Featureの設定例はUbuntuのベースイメージを使っています。Linuxのコンテナでは、Apple向けターゲットを含むiOS側のビルドは使えません。Android SDKを入れればAndroid側のGradleタスクは走りますが、Xcodeを呼ぶiOS側のビルドはローカルのmacOS機に残ります。
作業をClaude Codeに任せやすいかどうかを整理すると、次のようになります。
| 作業 | Claude Codeへの委任 | 理由 |
|---|---|---|
| commonMainのロジック実装・テスト | Claude Codeへの委任◎ | 理由allTestsの結果をClaudeが直接読める |
| Androidターゲットのビルド・テスト | Claude Codeへの委任◎ | 理由./gradlewだけで完結する |
| Xcode側のUI実装・シミュレーター操作 | Claude Codeへの委任✕ | 理由GUI操作が中心でCLIから完結しない |
よくあるつまずき
commonMainへのプラットフォームAPI混入は、実装を急ぐほど起きやすいつまずきです。androidMain で使えていたクラスをそのまま commonMain に移した瞬間にビルドが壊れます。paths を付けた .claude/rules/ のルールは、commonMain のファイルを触ったときに読み込まれます。
JetBrainsの公式ドキュメントは、KMP向けのAIコーディングエージェントとして自社の「Junie」を案内しています。Claude Codeはそれとは別に、ターミナルで動く汎用のエージェントとして使います。
共有ロジックにMCPサーバー機能を持たせたい場合は注意が必要です。MCP公式SDKのKotlin実装はTier 3(実験的または部分実装)の分類です。
まとめ
iOSフレームワークのビルドだけはmacOS+Xcodeが要ります。Android側の実装とCIはLinux環境に寄せ、iOS側だけローカルのmacOSで最終確認する分担が扱いやすくなります。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。
- kotlinlang.org/docs/multiplatform/get-started.html
- kotlinlang.org/docs/multiplatform/quickstart.html
- kotlinlang.org/docs/multiplatform/kmp-overview.html
- kotlinlang.org/docs/multiplatform/multiplatform-discover-project.html
- kotlinlang.org/docs/multiplatform/multiplatform-expect-actual.html
- kotlinlang.org/docs/multiplatform/multiplatform-ios-integration-overview.…
- kotlinlang.org/docs/multiplatform/multiplatform-run-tests.html
- kmp.jetbrains.com/
- code.claude.com/docs/en/memory
- code.claude.com/docs/en/settings
- code.claude.com/docs/en/permissions