Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Kotlin Symbol Processing API (KSP) を使って Kotlin ...
Search
Takuji Nishibayashi
December 10, 2022
Technology
3.2k
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Kotlin Symbol Processing API (KSP) を使って Kotlin ア プリケーションの開発を効率化する
Takuji Nishibayashi
December 10, 2022
More Decks by Takuji Nishibayashi
See All by Takuji Nishibayashi
compose-hot-reload を試そうとした話
takuji31
0
150
CameraX使ってみた
takuji31
0
310
kotlinx.datetime 使ってみた
takuji31
0
1.1k
HiltのCustom Componentについて
takuji31
0
380
java.timeをAndroidで使う
takuji31
0
200
KSPを使ってコード生成
takuji31
0
470
kotlinx.serialization
takuji31
0
690
kanmoba-returns-02.pdf
takuji31
0
290
AndroidXとKotlin Coroutines
takuji31
0
440
Other Decks in Technology
See All in Technology
【NRUG vol.18】KubernetesにおけるNew Relicデータ取得量削減の考え方
nrug_member
0
110
Oracle AI Database@AWS:サービス概要のご紹介
oracle4engineer
PRO
4
2.9k
エラーバジェットのアラートのタイミングを考える.pdf
kairim0
0
140
脆弱性対応、どこで線を引くか
rymiyamoto
1
380
AIソロプレナー時代に2ヶ月で20人増員した事業創造会社の開発組織の話
miyatakoji
0
650
"何を作るか"を任される エンジニアは、どう育つのか
yutaokafuji
1
670
Snowflakeと仲良くなる第一歩
coco_se
4
460
【Cyber-sec+】経営層を"動かす"ための考え方
hssh2_bin
0
170
2026TECHFRESH畢業分享會 - Lightning Talk - 資料也要 CI/CD? 用 Airbyte 自動化資料同步
line_developers_tw
PRO
0
950
白金鉱業Meetup_Vol.24_「AIエージェントは分けるほど良い」は本当か? / Is it true that “the more you divide AI agents, the better”?
brainpadpr
1
360
SONiC Scale-Up Working Group から探る Scale-UpやUltraEthernet機能の実装方法
ebiken
PRO
2
290
200個のGitHubリポジトリを横断調査したかった
icck
0
120
Featured
See All Featured
Building the Perfect Custom Keyboard
takai
2
790
YesSQL, Process and Tooling at Scale
rocio
174
15k
16th Malabo Montpellier Forum Presentation
akademiya2063
PRO
0
140
Joys of Absence: A Defence of Solitary Play
codingconduct
1
390
Measuring & Analyzing Core Web Vitals
bluesmoon
9
860
How to Grow Your eCommerce with AI & Automation
katarinadahlin
PRO
1
200
Rebuilding a faster, lazier Slack
samanthasiow
85
9.5k
Designing for Performance
lara
611
70k
How Fast Is Fast Enough? [PerfNow 2025]
tammyeverts
3
610
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
28
3.5k
Impact Scores and Hybrid Strategies: The future of link building
tamaranovitovic
0
310
世界の人気アプリ100個を分析して見えたペイウォール設計の心得
akihiro_kokubo
PRO
71
40k
Transcript
Kotlin Symbol Processing API (KSP) を使って Kotlin ア プリケーションの開発を効率化する Kotlin
Fest 2022 @takuji31
自己紹介 西林 拓志(にしばやし たくじ) Twitter/GitHub takuji31 Android アプリケーションエンジ ニア 株式会社はてな
テクノロジーソリューション 本部第 2 グループ マンガアプ リチーム所属 Kotlin 歴 8 年くらい 2
Kotlin Symbol Processing API (以下 KSP) 使ってます か? 3
今日は KSP のプロセッサーの作り方(≠ 使い方)について話 します 4
今日話すこと KSP とは KSP を使う理由 プロセッサーの作り方 インクリメンタル処理 マルチラウンド処理 マルチモジュールでの活用方法 テスト
5
今日話さないこと KSP の使い方、セットアップ方法 Gradle 以外で KSP のプロセッサーを開発する方法 Kotlin Multiplatform での使い方
どういった時に KSP を使うとよいか (質問があれば答えます) etc. 6
注意点 スライド内に出てくる Gradle script は Kotlin DSL です、Groovy は公式ドキュメントみ てください
https://kotlinlang.org/docs/ksp-overview.html スライド内のコードは import や package を省略しています 7
KSP とは 8
KSP とは Kotlin でシンボルを処理するための軽量コンパイラープラグイン Java の Pluggable Annotation Processing API
と同様にアノテーションを使う Kotlin の言語機能に対応している インクリメンタル処理対応 Kotlin Multiplatform 対応 https://github.com/google/ksp 9
KSP を使う理由 10
KSP を使う理由 - 課題解決 アノテーションを処理してコード生成をすることで普段の開発に潜む問題を解決する 頑張って型作って解決していたことをコード生成でカバーしたり Boilerplate コードを自動生成することでコード内のノイズを減らしたり 規模が大きなアプリケーションでモジュールごとに必要な設定を集約したり 型安全なクエリービルダーを作ったり
型安全な画面遷移を実現したり etc. 11
KSP を使う理由 - kapt と比べて kapt と比べて高速 kapt は一旦 Kotlin
から Java のスタブコードを生成しているがこれがとても遅い 現在開発しているアプリの場合だいたい全体のビルド時間の 20%くらいがスタブコ ード生成の時間 Kotlin の言語機能に対応した構造を返してくれるので、Pluggable Annotation Processing API で Kotlin のメタデータを頑張って読むみたいなことが不要 kotlinx-metadata-jvm なるものを使えばできるけどまあまあ難しい メンテナンスモードになってるのでこれ以上更新されない 今から Kotlin で新しくプロセッサーを作って使うのは厳しい 12
KSP を使う理由 - リフレクションと比べて ビルド時に型のチェックが行われるので安全 より高速 バイナリーサイズに影響なし (Android 的文脈で)難読化に強い 13
KSP を使う理由 - コンパイラープラグインと比べて 簡単 Kotlin のバージョンの影響を受けない(KSP のライブラリー自体は Kotlin のバージョンに
合わせる必要がある) 14
KSP を使う理由 - その他 Multiplatform 対応 Kotlin Multiplatform Mobile とかやっていると便利そう
私はやっていないので実際の使用感は不明 公式ドキュメントが充実していて始めやすい https://kotlinlang.org/docs/ksp-overview.html KotlinPoet と組み合わせることでプログラムの構造を簡単に作ることができる https://square.github.io/kotlinpoet/ 15
プロセッサーの 作り方 16
今回作るもの インターフェースにアノテーションをつけるとそのインターフェースを実装した抽象ク ラスを吐き出す 実用性はないが簡単な例ということで // これを @SimpleGeneration interface Hoge //
こうだ abstract class AbstractHoge: Hoge 17
プロセッサーの作り方 Gradle モジュールを作る SymbolProcessor を実装する Visitor を実装する SymbolProcessorProvider を作る サービスプロバイダーの設定をする
18
Gradle モジュールを作る 19
Gradle モジュールを作る 好きな IDE やツールでプロジェクトに Kotlin モジュールを新しく作りましょう 名前は自由 compiler processor
ksp-hogehoge 処理に使うアノテーションや生成されるコードで使うクラスは別のモジュールに分けて もよい 分かれている方が無駄なクラスがアプリケーションの classpath に含まれない etc. 20
build.gradle.kts // ... dependencies { // KSP のAPI implementation("com.google.devtools.ksp:symbol-processing-api:1.7.21-1.0.8") //
KotlinPoet implementation("com.squareup:kotlinpoet:1.12.0") // KotlinPoet のKSP 用拡張 implementation("com.squareup:kotlinpoet-ksp:1.12.0") } 21
SymbolProcessor を実装する 22
SymbolProcessor class ExampleSymbolProcessor( private val codeGenerator: CodeGenerator, private val logger:
KSPLogger ) : SymbolProcessor { override fun process(resolver: Resolver): List<KSAnnotated> { resolver .getSymbolsWithAnnotation(SimpleGeneration::class.qualifiedName!!) .filterIsInstance<KSClassDeclaration>() .forEach { it.accept(SimpleGenerationVisitor(codeGenerator, logger), Unit) } return emptyList() } } 23
Visitor class SimpleGenerationVisitor( private val codeGenerator: CodeGenerator, private val logger:
KSPLogger ) : KSVisitorVoid() { override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) { if (classDeclaration.classKind != ClassKind.INTERFACE) { logger.error("Only interface allowed", classDeclaration) return } val packageName = classDeclaration.packageName.asString() val className = ClassName(packageName, "Abstract" + classDeclaration.simpleName.asString()) val typeSpec = TypeSpec.classBuilder(className) .addModifiers(KModifier.ABSTRACT) .addSuperinterface(classDeclaration.toClassName()) FileSpec.builder(packageName, className.simpleName) .addType(typeSpec.build()) .build() .writeTo( codeGenerator, Dependencies( aggregating = false, classDeclaration.containingFile!! ) ) } } 24
SymbolProcessorProvider を作る 25
SymbolProcessorProvider class ExampleSymbolProcessorProvider : SymbolProcessorProvider { override fun create(environment: SymbolProcessorEnvironment):
SymbolProcessor { return ExampleSymbolProcessor(environment.codeGenerator, environment.logger) } } 26
サービスプロバイダーの設定をする 27
サービスプロバイダーの設定 // resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider jp.takuji31.kotlinfest2022.compiler.ExampleSymbolProcessorProvider 28
インクリメンタル 処理 29
インクリメンタル処理 KSP はインクリメンタル処理に対応している インクリメンタル処理のモードは 2 つ Isolated (分離) Aggregated (集約)
ファイルを書き出すときに Dependencies で依存を指定すれば、依存に変更があった時 に再処理される 生成されるファイルごとにモードが変えられる 30
Isolated (分離) 生成されたファイルが単一もしくは複数のファイルと紐付いている時のモード 依存に指定されたファイルが変更されると再処理される ↑ 以外のファイルが変更された時は処理されない 1 つのアノテーションで 1 つのファイルを生成したいときによく使う
例) アノテーションがつけられたインターフェースの実装クラスを生成する 31
Isolated (分離) Dependencies(aggregating = false, classDeclaration.containingFile!!) 32
Aggregated (集約) 生成されたファイルが不特定の複数のファイルと紐付いている時のモード 依存に指定されたファイル以外のものが変更されると依存に指定されたファイルとまと めて再処理される 基本的に変更があれば毎回処理されるイメージ 1 つのアノテーションで複数のファイルをまとめたファイルを作る時に使われる 例 1)
アノテーションがつけられたインターフェースをまとめたインターフェースを 作る 例 2) DI コンテナーで DI したいインスタンスの情報を収集する 例 3) 同じアノテーションがつけられた複数のクラスから特定のクラスだけを選んで 何か処理する 33
Aggregated (集約) val dependencies: Array<KSAnnotated> = // ... Dependencies(aggregating =
true, *dependencies.mapNotNull { it.containingFile }) 34
依存をちゃんと指定しないと「なぜかコード生成されな い」みたいな事態になる 35
マルチラウンド処理 36
マルチラウンド処理 処理できないシンボルの処理を後回しにする仕組み KSP のプロセッサーで生成したコードはすぐにプロセッサーで認識できない ラウンドを複数回繰り返して、そのラウンドで生成されたファイルがなくなったら終わ り 生成されたファイルがなくなったけど遅延されているシンボルがあればエラー マルチラウンド処理で活用するためにシンボルのバリデーションを行う仕組みがある カスタムでバリデーションルールを作れるが今回は説明しません 37
処理の遅延方法 override fun process(resolver: Resolver): List<KSAnnotated> { val symbolsWithAnnotation =
resolver .getSymbolsWithAnnotation(SimpleGeneration::class.qualifiedName!!) .filterIsInstance<KSClassDeclaration>() symbolsWithAnnotation .filter { it.validate() } .forEach { it.accept(SimpleGenerationVisitor(codeGenerator, logger), Unit) } return symbolsWithAnnotation.filter { !it.validate() }.toList() } 38
マルチモジュールでの活用方法 39
依存しているモジュールやライブラリーのシンボルは KSP の getSymbolsWithAnnotation で取得できな い google/ksp#1075 40
じゃあどうするのか? 41
getDeclarationsFromPackage を使う 42
getDeclarationsFromPackage を使う 特定のパッケージに含まれる定義を全部取り出すメソッド 特定のパッケージに何かしらのメタデータを書き出す 集約するモジュールで ↑ のパッケージに書かれた定義を読んでコード生成する 集約に使う用のアノテーションを作って集約したいモジュールで使う 43
テスト 44
コンパイルのテストってどうやんの? 45
tschuchortdev/kotlin-compile-testing 46
テストコード val source = SourceFile.kotlin( "ExampleClass.kt", """ package jp.takuji31.kotlinfest2022.compiler import
jp.takuji31.kotlinfest2022.compiler.annotation.SimpleGeneration @SimpleGeneration interface SimpleInterface { fun printHelloWorld() } """.trimIndent() ) val compilation = KotlinCompilation().apply { sources = listOf(source) inheritClassPath = true symbolProcessorProviders = listOf(ExampleSymbolProcessorProvider()) kspWithCompilation = true } val result = compilation.compile() assertThat(result.exitCode) .isEqualTo(KotlinCompilation.ExitCode.OK) 47
まとめ SymbolProcessor、Visitor で KSP のプロセッサーは作れる Kotlin のコード生成は KotlinPoet でやると楽 インクリメンタル処理のための依存は
Dependencies で指定 マルチラウンド処理をするためには process メソッドで遅延したいシンボルを渡す 複数モジュールのコードを集約したい時は getDeclarationsFromPackage プロセッサーのテストは kotlin-compile-testing で 48
ドキュメント/サンプルコード https://kotlinlang.org/docs/ksp-overview.html 公式ドキュメント https://github.com/google/ksp/tree/main/examples/playground 公式サンプル https://github.com/takuji31/kotlinfest2022-ksp-example 今回のスライドに出てきたソースコード https://github.com/takuji31/navigation-compose-screen もうちょっと複雑な例 49
Enjoy KSP Life! 50