Hands on Google KSP
Documentation
KSP documentation is both, the quite and the confusion of my heart. It lacks details how to setup the project, but provides amazing map on how KSP sees symbols. No, there are no stackoverflow posts on how to use KSP beside using it as alternative to kapt
.
Setup
Official setup guide has failed to mention how and where to reference your processor provider. Use META-INF
resource directory to reference your processor processor in your processor
module:
src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider
In this file you reference your ProcessorProvider
:
com.my.package.processor.MySymbolProcessorProvider
This is important step in order your processor provider be recognized and later instantiate the actual processor.
Dependency
In the figure you notice the how modules relate between each other. It is important caveat to that only and only the application module will reference the processor with ksp(project(":processor"))
. And only the application module will apply the ksp plugin and be able to provide arguments to the KSP plugin:
plugins {
id("com.android.application")
id("kotlin-android")
id("com.google.devtools.ksp")
}ksp {
arg("verbose", "true")
}
Processor
Official manual provides you with sufficient information how to utilize the Resolver
to search for annotated symbols and validation of each symbol found.
KSP implemented, what they call, multiple-round symbol processing which you might notice that it runs your processor multiple times. This will allow you to process even newly generated files from any previous round. Multiple round will stop when no more files are generated. At first, I wasn’t getting how it impacts the processing until I end up in this dead end.
Mind Map
Luckily the documentation describes how KSP sees your kotlin files in details. Essentially it also covers in detail how nodes are represented in KSP context.
Each keyword in the kotlin file is a KSNode
the building interface of KSDeclaration
. KSDeclaration
is key interface when it comes to resolving the types or KSType
in your implementation.
On multiple occasions, I was at a position to backtrack to KSDeclaration
to find the KSType
.
Visitors
Symbol or Annotation processing starts by visiting the KSAnnotated
results upon Resolver
has delivered the look up for your symbols.
Each KSNode
has accept(visitor, data)
function that will visit next KSDeclaration
.
Example:
override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: FuncSpec) {
val functionSpecs = classDeclaration.getDeclaredFunctions()
.toSet()
.map {
it.accept(MyFunctionVisitor(resolver), it)
}
return functionSpecs
}
Logically, each class contains functions, as seen in Mind Map section of this article,MyFunctionVisitor
will visit all KSFunctionDeclaration
in it’s visitFunctionDeclaration
function. You may guess, next to visit is function parameters which are represented as KSValueParameters
therefore visitValueParameters
will be called on the next visit when params.accept(visitor, data)
is called. defaultHandler
function will be called if you haven’t provided implementation for visiting particular KSDeclaration
. I leave a TODO()
so I everything blows up and I have to be forced to implement a visit.
KotlinPoet interop
I prefer to use KotlinPoet for code generation, as of 1.10.0 it has nice set of extensions for working with KSP.