KSP2 で KSPは Compiler Plugin ではなくなり、AndroidLintやIntelljIdeaなど と共有されているKotlin Compiler APIs 上で 動くようになったとのこと
これによって 内部的には 以下のメリットがあるらしい
- プログラムのライフサイクルを細かく制御
- KSPの実装が単純化-効率化
クライアント視点だと、以下が大きな変更点
- 別のプログラムから、Processorへのエントリーポイントの作成が容易になった
これは特にテストで有用で、Processorをプログラムから呼び出せて 生成されるファイルのPathも指定できるようになる。
ということで、main() 関数から Processor を動かしてみる。
Processor
シンプルにアノテーションをみつけて、シンボル情報をもとに別のクラスを作る みたいなことをやっている。
ここはKSPとKSP2で変わらない
class TestProcessor(
private val codeGenerator: CodeGenerator,
private val kspLogger: KSPLogger,
) : SymbolProcessor {
override fun process(resolver: Resolver): List<KSAnnotated> {
resolver
.getSymbolsWithAnnotation(GreetingAnnotation::class.qualifiedName!!)
.filterIsInstance<KSClassDeclaration>()
.forEach { ksClassDeclaration ->
FileSpec
.builder(ksClassDeclaration.packageName.getQualifier(), ksClassDeclaration.simpleName.asString())
.addType(
TypeSpec
.classBuilder("generate" + ksClassDeclaration.simpleName.asString())
.addFunction(
FunSpec
.builder("test")
.addStatement("println(%S)", "Hello, World!")
.build(),
).build(),
).build()
.writeTo(codeGenerator, Dependencies(false, ksClassDeclaration.containingFile!!))
kspLogger.info("info: ${ksClassDeclaration.containingFile!!}")
}
return emptyList()
}
}
ProcessorProviderを サービスプロバイダに設定する。Processor専用のモジュールを作る、といったやり方は変わらず。
└── src
└── main
├── kotlin
│ └── TestProcessor.kt
└── resources
└── META-INF
└── services
└── com.google.devtools.ksp.processing.SymbolProcessorProvider
KSPに処理してもらうファイル
@GreetingAnnotation
class Greeting(
val name: String,
val age: Int,
val f: () -> Unit
) {
fun normalFunction(): String = name + age
fun returnFunctionFunction(): () -> Unit = f
}
👇 こんなのを作ってもらう
public class generateGreeting {
public fun test() {
println("Hello, World!")
}
}
mainから動かしてみる
以下の依存
implementation("com.google.devtools.ksp:symbol-processing-api:$kspVersion")
implementation("com.google.devtools.ksp:symbol-processing-common-deps:$kspVersion")
implementation("com.google.devtools.ksp:symbol-processing-aa-embeddable:$kspVersion")
implementation(project(":test-processor"))
ksp(project(":test-processor"))
symbol-processing-common-deps は doc には載ってないが、KSPJvmConfig といった Processor を動かす際の設定 を記述するクラスのimport に必要。
main はこうなる
import com.google.devtools.ksp.impl.KotlinSymbolProcessing
import com.google.devtools.ksp.processing.KSPJvmConfig
import com.google.devtools.ksp.processing.KspGradleLogger
import com.google.devtools.ksp.processing.SymbolProcessorProvider
import java.io.File
import java.net.URLClassLoader
import java.util.ServiceLoader
fun main() {
// Step 1: Load processors
val classpath = listOf("test-processor/build/classes/kotlin/main")
val processorClassloader = URLClassLoader(classpath.map { File(it).toURI().toURL() }.toTypedArray())
val processorProviders = ServiceLoader.load(
processorClassloader.loadClass("com.google.devtools.ksp.processing.SymbolProcessorProvider"),
processorClassloader
).toList() as List<SymbolProcessorProvider>
// Step 2: Implement or use KSPLogger
val logger = KspGradleLogger(KspGradleLogger.LOGGING_LEVEL_INFO)
// Step 3: Fill KSPConfig
val kspConfig = KSPJvmConfig.Builder().apply {
moduleName = "mainsrc"
sourceRoots = listOf(File("mainsrc/src/main/kotlin"))
kotlinOutputDir = File("mainsrc/build/generated/ksp/main/kotlin")
javaOutputDir = File("mainsrc/build/generated/ksp/main/java")
jvmTarget = "1.8"
projectBaseDir = File(".")
outputBaseDir = File("mainsrc/build/generated/ksp/main")
cachesDir = File("mainsrc/build/generated/ksp/main/caches")
classOutputDir = File("mainsrc/build/generated/ksp/main/classes")
resourceOutputDir = File("mainsrc/build/generated/ksp/main/resources")
apiVersion = "2.0.0-1.0.23"
languageVersion = "2.0"
}.build()
// Step 4: Run KotlinSymbolProcessing
val exitCode = KotlinSymbolProcessing(kspConfig, processorProviders, logger).execute()
println("KSP finished with exit code: $exitCode")
}
-
Processor のclass path は , Processor のソースファイルではなく, classPath である点に注意(たいてい build/classes/kotlin/main 配下にあるはず)
-
loggerはKSLoggerでもよい
-
KSPConfig: かなり柔軟に色々指定できる
-
実行
これだけで mainから呼び出しが可能
終わり
ほか、細かなAPI差異は以下を参照