Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Kotlin Symbol Processing (KSP) を使ったコード生成 / DroidKaigi 2021

star_zero
October 19, 2021

Kotlin Symbol Processing (KSP) を使ったコード生成 / DroidKaigi 2021

star_zero

October 19, 2021
Tweet

More Decks by star_zero

Other Decks in Programming

Transcript

  1. About me • Kenji Abe • Google Developers Expert for

    Android, Kotlin • AndroidDagashi • DeNA Co., Ltd. • @STAR_ZERO
  2. class HelloWorldProcessorProvider : SymbolProcessorProvider { override fun create( environment: SymbolProcessorEnvironment

    ): SymbolProcessor { return HelloWorldProcessor( environment.codeGenerator, environment.options, environment.logger ) } }
  3. class HelloWorldProcessorProvider : SymbolProcessorProvider { override fun create( environment: SymbolProcessorEnvironment

    ): SymbolProcessor { return HelloWorldProcessor( environment.codeGenerator, environment.options, environment.logger ) } }
  4. class HelloWorldProcessorProvider : SymbolProcessorProvider { override fun create( environment: SymbolProcessorEnvironment

    ): SymbolProcessor { return HelloWorldProcessor( environment.codeGenerator, environment.options, environment.logger ) } }
  5. class HelloWorldProcessorProvider : SymbolProcessorProvider { override fun create( environment: SymbolProcessorEnvironment

    ): SymbolProcessor { return HelloWorldProcessor( environment.codeGenerator, environment.options, environment.logger ) } }
  6. class HelloWorldProcessorProvider : SymbolProcessorProvider { override fun create( environment: SymbolProcessorEnvironment

    ): SymbolProcessor { return HelloWorldProcessor( environment.codeGenerator, environment.options, environment.logger ) } }
  7. class HelloWorldProcessorProvider : SymbolProcessorProvider { override fun create( environment: SymbolProcessorEnvironment

    ): SymbolProcessor { return HelloWorldProcessor( environment.codeGenerator, environment.options, environment.logger ) } } // build.gradle.kts ksp { arg("key", "value") }
  8. class HelloWorldProcessorProvider : SymbolProcessorProvider { override fun create( environment: SymbolProcessorEnvironment

    ): SymbolProcessor { return HelloWorldProcessor( environment.codeGenerator, environment.options, environment.logger ) } } logger.logging("...") logger.info("...") logger.warn("...") logger.error("...") $ ./gradlew build --debug or $ ./gradlew build --info
  9. class HelloWorldProcessor( private val codeGenerator: CodeGenerator, private val options: Map<String,

    String>, private val logger: KSPLogger ) : SymbolProcessor { override fun process(resolver: Resolver): List<KSAnnotated> { // } }
  10. class HelloWorldProcessor( private val codeGenerator: CodeGenerator, private val options: Map<String,

    String>, private val logger: KSPLogger ) : SymbolProcessor { override fun process(resolver: Resolver): List<KSAnnotated> { // } }
  11. class HelloWorldProcessor( private val codeGenerator: CodeGenerator, private val options: Map<String,

    String>, private val logger: KSPLogger ) : SymbolProcessor { override fun process(resolver: Resolver): List<KSAnnotated> { // } }
  12. class HelloWorldProcessor( private val codeGenerator: CodeGenerator, private val options: Map<String,

    String>, private val logger: KSPLogger ) : SymbolProcessor { override fun process(resolver: Resolver): List<KSAnnotated> { // } }
  13. override fun process(resolver: Resolver): List<KSAnnotated> { val files = resolver.getAllFiles()

    val newFiles = resolver.getNewFiles() val symbols = resolver.getSymbolsWithAnnotation("com.example.Factory") val ksClass = resolver.getClassDeclarationByName("com.example.Hoge") // ... }
  14. override fun process(resolver: Resolver): List<KSAnnotated> { val files = resolver.getAllFiles()

    val newFiles = resolver.getNewFiles() val symbols = resolver.getSymbolsWithAnnotation("com.example.Factory") val ksClass = resolver.getClassDeclarationByName("com.example.Hoge") // ... }
  15. override fun process(resolver: Resolver): List<KSAnnotated> { val files = resolver.getAllFiles()

    val newFiles = resolver.getNewFiles() val symbols = resolver.getSymbolsWithAnnotation("com.example.Factory") val ksClass = resolver.getClassDeclarationByName("com.example.Hoge") // ... }
  16. override fun process(resolver: Resolver): List<KSAnnotated> { val symbols = resolver.getSymbolsWithAnnotation("com.example.Factory")

    symbols.forEach { when (it) { is KSClassDeclaration -> { } is KSFunctionDeclaration -> { } is KSPropertyDeclaration -> { } } } // ... }
  17. override fun process(resolver: Resolver): List<KSAnnotated> { val file = codeGenerator.createNewFile(

    Dependencies(false), "com.example.generated", "Hello" ) val code = """ ... """.trimIndent() file.write(code.toByteArray()) file.close() // ... }
  18. override fun process(resolver: Resolver): List<KSAnnotated> { val file = codeGenerator.createNewFile(

    Dependencies(false), "com.example.generated", "Hello" ) val code = """ ... """.trimIndent() file.write(code.toByteArray()) file.close() // ... }
  19. override fun process(resolver: Resolver): List<KSAnnotated> { val file = codeGenerator.createNewFile(

    Dependencies(false), "com.example.generated", "Hello" ) val code = """ ... """.trimIndent() file.write(code.toByteArray()) file.close() // ... }
  20. override fun process(resolver: Resolver): List<KSAnnotated> { val file = codeGenerator.createNewFile(

    Dependencies(false), "com.example.generated", "Hello" ) val code = """ ... """.trimIndent() file.write(code.toByteArray()) file.close() // ... }
  21. // Hoge.kt @Factory class Hoge // Foo.kt @Factory class Foo

    // etc... // SymbolProcessor#process resolver.getSymbolsWithAnnotation("...") // =>
  22. // Hoge.kt @Factory class Hoge // Foo.kt @Factory class Foo

    // etc... // SymbolProcessor#process resolver.getSymbolsWithAnnotation("...") // => [Hoge, Foo]
  23. // Hoge.kt @Factory class Hoge // Foo.kt @Factory class Foo

    // etc... // SymbolProcessor#process resolver.getSymbolsWithAnnotation("...") // => [Hoge, Foo] // HogeFactory.kt class HogeFactory // FooFactory.kt class FooFactory コード生成
  24. // Hoge.kt @Factory class Hoge // Foo.kt @Factory class Foo

    // etc... // SymbolProcessor#process resolver.getSymbolsWithAnnotation("...") // => // HogeFactory.kt class HogeFactory // FooFactory.kt class FooFactory コード生成
  25. // Hoge.kt @Factory class Hoge // Foo.kt @Factory class Foo

    // etc... // SymbolProcessor#process resolver.getSymbolsWithAnnotation("...") // => [] // HogeFactory.kt class HogeFactory // FooFactory.kt class FooFactory コード生成
  26. // Hoge.kt @Factory class Hoge // Foo.kt @Factory class Foo

    // etc... // SymbolProcessor#process resolver.getSymbolsWithAnnotation("...") // => [] // HogeFactory.kt class HogeFactory // FooFactory.kt class FooFactory コード生成 Finish
  27. override fun process(resolver: Resolver): List<KSAnnotated> { val symbols = resolver.getSymbolsWithAnnotation("...")

    val result = symbols.filter { !it.validate() }.toList() symbols.filter { it.validate() }.forEach { symbol -> // コード生成 } return result }
  28. override fun process(resolver: Resolver): List<KSAnnotated> { val symbols = resolver.getSymbolsWithAnnotation("...")

    val result = symbols.filter { !it.validate() }.toList() symbols.filter { it.validate() }.forEach { symbol -> // コード生成 } return result }
  29. override fun process(resolver: Resolver): List<KSAnnotated> { val symbols = resolver.getSymbolsWithAnnotation("...")

    val result = symbols.filter { !it.validate() }.toList() symbols.filter { it.validate() }.forEach { symbol -> // コード生成 } return result }
  30. override fun process(resolver: Resolver): List<KSAnnotated> { val symbols = resolver.getSymbolsWithAnnotation("...")

    val result = symbols.filter { !it.validate() }.toList() symbols.filter { it.validate() }.forEach { symbol -> // コード生成 } return result }
  31. Resolver • getAllFiles ◦ 全てのラウンドでの処理対象となったファイル • getNewFiles ◦ ラウンドごとに処理対象となっているファイル •

    getSymbolsWithAnnotation ◦ ラウンドごとに処理対象となっているものから、該当の Annotationがあるもの • getClassDeclarationByName ◦ クラスパスから指定したクラス名を探す
  32. val symbols = resolver.getSymbolsWithAnnotation("...") symbols.forEach { val file = codeGenerator.createNewFile(

    Dependencies(aggregating = false, it.containingFile!!), "...", "..." ) // コード生成 }
  33. val symbols = resolver.getSymbolsWithAnnotation("...") symbols.forEach { val file = codeGenerator.createNewFile(

    Dependencies(aggregating = false, it.containingFile!!), "...", "..." ) // コード生成 }