Slide 1

Slide 1 text

KSP を使ってコード生成 関西モバイルアプリ研究会 A @takuji31

Slide 2

Slide 2 text

自己紹介 西林 拓志(にしばやし たくじ) Twitter/GitHub takuji31 株式会社はてな Android アプリケーションエンジ ニア Android (2009〜) Kotlin (2014〜) 2

Slide 3

Slide 3 text

KSP 使ってますか? 3

Slide 4

Slide 4 text

今日は KSP のプロセッサーの作り方(≠ 使い方)について話 します 4

Slide 5

Slide 5 text

KSP? 5

Slide 6

Slide 6 text

Kotlin Symbol Processor 6

Slide 7

Slide 7 text

google/ksp 7

Slide 8

Slide 8 text

コードにつけられたアノテーションを処理する 8

Slide 9

Slide 9 text

Kotlin friendly 9

Slide 10

Slide 10 text

Incremental proccessing 10

Slide 11

Slide 11 text

KMP 対応 11

Slide 12

Slide 12 text

Supported libs Dagger / Hilt (alpha) Room Moshi Glide etc. 12

Slide 13

Slide 13 text

プロセッサーの 作り方 13

Slide 14

Slide 14 text

source code // これを @SimpleGeneration interface Hoge // こうだ abstract class AbstractHoge: Hoge 14

Slide 15

Slide 15 text

build.gradle.kts // ... dependencies { // KSP のAPI implementation("com.google.devtools.ksp:symbol-processing-api:1.9.21-1.0.15") // KotlinPoet implementation("com.squareup:kotlinpoet:1.15.3") // KotlinPoet のKSP 用拡張 implementation("com.squareup:kotlinpoet-ksp:1.15.3") } 15

Slide 16

Slide 16 text

SymbolProcessor class ExampleSymbolProcessor( private val codeGenerator: CodeGenerator, private val logger: KSPLogger ) : SymbolProcessor { override fun process(resolver: Resolver): List { resolver .getSymbolsWithAnnotation(SimpleGeneration::class.qualifiedName!!) .filterIsInstance() .forEach { it.accept(SimpleGenerationVisitor(codeGenerator, logger), Unit) } return emptyList() } } 16

Slide 17

Slide 17 text

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!! ) ) } } 17

Slide 18

Slide 18 text

SymbolProcessorProvider class ExampleSymbolProcessorProvider : SymbolProcessorProvider { override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor { return ExampleSymbolProcessor(environment.codeGenerator, environment.logger) } } 18

Slide 19

Slide 19 text

Service Provider // resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider jp.takuji31.kotlinfest2022.compiler.ExampleSymbolProcessorProvider 19

Slide 20

Slide 20 text

Incremental Processing 20

Slide 21

Slide 21 text

生成するファイルの依存を定義すれば Processor 側で適 切に処理される 21

Slide 22

Slide 22 text

Isolated 22

Slide 23

Slide 23 text

1:N 23

Slide 24

Slide 24 text

Isolated Dependencies(aggregating = false, classDeclaration.containingFile!!) 24

Slide 25

Slide 25 text

Aggregated 25

Slide 26

Slide 26 text

N:1 26

Slide 27

Slide 27 text

Aggregated (集約) val dependencies: Array = // ... Dependencies(aggregating = true, *dependencies.mapNotNull { it.containingFile }) 27

Slide 28

Slide 28 text

依存をちゃんと指定しないと「なぜかコード生成されな い」みたいな事態になる 28

Slide 29

Slide 29 text

テスト 29

Slide 30

Slide 30 text

tschuchortdev/kotlin-compile-testing 30

Slide 31

Slide 31 text

テストコード val source = SourceFile.kotlin( "ExampleClass.kt", """ package jp.takuji31.kotlinfest2022.compiler import jp.takuji31.kotlinfest2022.compiler.annotation.SimpleGeneration @SimpleGeneration interface SimpleInterface { fun printHelloWorld() } """.trimIndent() ) 31

Slide 32

Slide 32 text

テストコード 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) 32

Slide 33

Slide 33 text

ドキュメント/サンプルコード 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 複雑な例 https://speakerdeck.com/takuji31/kotlin-symbol-processing-api-ksp-woshi-tute- kotlin-a-purikesiyonnokai-fa-woxiao-lu-hua-suru Kotlin Fest 2022 で発表した時のスライド もう少し踏み込んだ話はこちら 33

Slide 34

Slide 34 text

Enjoy KSP Life! 34