なぜ KSP なのか
コンパイラプラグインは、コードの書き方を大幅に強化できる強力なメタプログラミングツールです。 コンパイラプラグインは、コンパイラをライブラリとして直接呼び出し、入力プログラムを解析および編集します。これらのプラグインは、さまざまな用途のために出力を生成することもできます。たとえば、ボイラープレートコードを生成したり、Parcelable のような特別なマークが付いたプログラム要素に対して完全な実装を生成したりすることも可能です。プラグインには他にもさまざまな用途があり、言語で直接提供されていない機能を実装したり微調整したりするためにも使用できます。
コンパイラプラグインは強力ですが、その力には代償が伴います。最も単純なプラグインを作成する場合でも、コンパイラに関するある程度の背景知識に加え、特定のコンパイラの実装の詳細に関する習熟度が必要になります。もう一つの実用的な問題は、プラグインが特定のコンパイラバージョンと密接に結びついていることが多い点です。つまり、新しいバージョンのコンパイラをサポートするたびに、プラグインを更新する必要があるかもしれません。
KSP は軽量なコンパイラプラグインの作成を容易にする
KSP は、コンパイラの変更を隠蔽するように設計されており、それを利用するプロセッサのメンテナンスの負担を最小限に抑えます。KSP は JVM に依存しないように設計されているため、将来的に他のプラットフォームへの適応がより容易になります。また、KSP はビルド時間を最小限に抑えるようにも設計されています。Glide のような一部のプロセッサでは、KSP を使用することで、kapt と比較してフルコンパイル時間を最大 25% 短縮できます。
KSP 自体もコンパイラプラグインとして実装されています。Google の Maven リポジトリにはビルド済みのパッケージがあり、プロジェクトを自分でビルドすることなくダウンロードして使用できます。
kotlinc コンパイラプラグインとの比較
kotlinc コンパイラプラグインは、コンパイラのほぼすべてにアクセスできるため、最大のパワーと柔軟性を備えています。一方で、これらのプラグインはコンパイラ内のあらゆるものに依存する可能性があるため、コンパイラの変更に敏感であり、頻繁なメンテナンスが必要になります。また、これらのプラグインは kotlinc の実装に対する深い理解を必要とするため、学習曲線が急になる可能性があります。
KSP は、明確に定義された API を通じてコンパイラの変更の大部分を隠蔽することを目指していますが、コンパイラや Kotlin 言語自体の大きな変更については、依然として API ユーザーに公開する必要がある場合があります。
KSP は、パワーと引き換えにシンプルさを提供する API を用意することで、一般的なユースケースを満たそうとしています。その能力は、一般的な kotlinc プラグインの厳密なサブセットです。たとえば、kotlinc は式や文を調査したり、コードを変更したりすることさえできますが、KSP では不可能です。
kotlinc プラグインを書くのは非常に楽しいものですが、多くの時間を要する場合もあります。もし kotlinc の実装を学ぶ状況になく、ソースコードの変更や式の読み取りを必要としないのであれば、KSP が適しているかもしれません。
リフレクションとの比較
KSP の API は kotlin.reflect に似ています。両者の主な違いは、KSP では型参照を明示的に解決(resolve)する必要がある点です。これが、インターフェースが共有されていない理由の一つです。
kapt との比較
kapt は、膨大な数の Java アノテーションプロセッサを Kotlin プログラムでそのまま動作させるための優れたソリューションです。kapt に対する KSP の主な利点は、ビルドパフォーマンスの向上、JVM に依存しないこと、より Kotlin らしい(idiomatic な)API、そして Kotlin 固有のシンボルを理解できる能力です。
Java アノテーションプロセッサを修正せずに実行するために、kapt は Kotlin コードを Java スタブ(stub)にコンパイルします。このスタブには、Java アノテーションプロセッサが必要とする情報が保持されます。これらのスタブを作成するために、kapt は Kotlin プログラム内のすべてのシンボルを解決する必要があります。スタブの生成には、フル kotlinc 解析の約 3 分の 1、および kotlinc のコード生成と同程度のコストがかかります。多くのアノテーションプロセッサにとって、これはプロセッサ自体で費やされる時間よりもはるかに長くなります。 たとえば、Glide は事前定義されたアノテーションを持つ非常に限られた数のクラスのみを調べ、そのコード生成はかなり高速です。ビルドオーバーヘッドのほぼすべてがスタブ生成フェーズにあります。KSP に切り替えることで、コンパイラで費やされる時間は即座に 25% 短縮されます。
パフォーマンス評価のために、私たちは KSP で Glide の簡易バージョンを実装し、Tachiyomi プロジェクト用のコードを生成させました。私たちのテストデバイスでは、プロジェクトの総 Kotlin コンパイル時間は 21.55 秒でしたが、kapt がコードを生成するのに 8.67 秒かかったのに対し、KSP 実装では 1.15 秒で済みました。
kapt とは異なり、KSP のプロセッサは入力プログラムを Java の視点から見ることはありません。API は Kotlin にとってより自然なものであり、特にトップレベル関数のような Kotlin 固有の機能に適しています。KSP は kapt のように javac に委譲しないため、JVM 固有の動作を想定しておらず、将来的に他のプラットフォームで使用できる可能性があります。
制限事項
KSP は、最も一般的なユースケースに対するシンプルなソリューションを目指していますが、他のプラグインソリューションと比較していくつかのトレードオフを行っています。以下は KSP の目標ではありません。
- ソースコードの式レベルの情報の調査
- ソースコードの変更
- Java アノテーション処理 API(JSR 269)との 100% の互換性
