Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Kotlin Symbol Processing API (KSP) を使って Kotlin ...
Search
Takuji Nishibayashi
December 10, 2022
Technology
3.3k
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
180
CameraX使ってみた
takuji31
0
330
kotlinx.datetime 使ってみた
takuji31
0
1.2k
HiltのCustom Componentについて
takuji31
0
450
java.timeをAndroidで使う
takuji31
0
220
KSPを使ってコード生成
takuji31
0
500
kotlinx.serialization
takuji31
0
720
kanmoba-returns-02.pdf
takuji31
0
310
AndroidXとKotlin Coroutines
takuji31
0
460
Other Decks in Technology
See All in Technology
データマネジメント × 法務で作りあげる、 freeeの新しいデータガバナンスへの軌跡
freee
PRO
2
520
Lint の層を組み直した話
bengo4com
1
110
AWS Continuum for Penetration Testing に Deep Dive してみる
asanoharuki
2
1.4k
OneLakeをデータソースにした AI Search(Foundry IQ)の ライフサイクル管理
ryuseiiida
0
130
書き手が人でもAIでも正しくなる道、SREを埋め込んだDeveloper Platform / tech drivers day8
chmikata
4
800
国際カードブランドルール変更に伴うクレジットカード決済取引仕様変更について
k1style
0
250
200人の配信コメント、 全部Jevが仕分けます 〜 マサカリガードを作ってみた 〜
katayamatg
1
360
クラウドエージェントの先駆け Devin。今も最先端を走る、他との違い
kinopeee
1
500
VLA-JEPA: Enhancing Vision-Language-Action Models with Latent World Models
takmin
0
360
AIで選択肢が広がった組織設計の理想と現実
konifar
15
6.7k
DevelopersIO 2026 Tokyo 現時点の衛星通信サービスの現状を整理してみる
maroon1st
0
320
AIに任せられるFlutterコードベースを作る 〜仕様をコードで表し、ハーネスで守る〜
myohei
1
340
Featured
See All Featured
Into the Great Unknown - MozCon
thekraken
41
2.8k
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
360
31k
4 Signs Your Business is Dying
shpigford
187
23k
Digital Ethics as a Driver of Design Innovation
axbom
PRO
1
470
ラッコキーワード サービス紹介資料
rakko
5
5.5M
The #1 spot is gone: here's how to win anyway
tamaranovitovic
4
1.2k
The Hidden Cost of Media on the Web [PixelPalooza 2025]
tammyeverts
2
540
Fashionably flexible responsive web design (full day workshop)
malarkey
409
67k
Large-scale JavaScript Application Architecture
addyosmani
516
110k
The Cost Of JavaScript in 2023
addyosmani
55
10k
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
38
3.1k
GitHub's CSS Performance
jonrohan
1033
470k
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