KSP: Fact or kapt?
source link: https://proandroiddev.com/ksp-fact-or-kapt-7c7e9218c575
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
KSP: Fact or kapt?
Fast and ergonomic annotation processors for Kotlin
What is KSP?
KSP (Kotlin Symbol Processor) is a new API from Google for writing Kotlin compiler plugins. Using KSP we can write annotation processors to reduce boilerplate, solve cross-cutting concerns, and move checks from runtime to compile-time.
While we already have annotation processors in Kotlin through kapt, the new KSP API provides promises speed and ergonomics.
Firstly, KSP is faster since kapt depends on compiling Kotlin to Java stubs for consumption by thejavax.lang.model
API used by Java annotation processors. Compiling these stubs takes a significant amount of time, and since KSP can skip this step we can write faster processors.
Secondly, KSP provides an idiomatic Kotlin API. Much of the old javax.lang.model
API is poorly suited for dealing with the idiosyncrasies of Kotlin. By contrast, KSP has a Kotlin-first approach, while still allowing processing of Java files.
Isn’t that very niche?
While this may seem like an extreme departure from standard annotation processor practice, things are heating up for KSP! At the time of writing, the popular annotation processors for Room and Moshi have experimental support, while Dagger, Hilt, and Glide have open issues tracking support. The goal is the complete elimination of kapt, which will lead to the biggest performance gain:
Let’s get started
We’re going to start by building a toy annotation processor. Following this article by Lubos Mudrak, our annotation processor will emit an extension function for summing the Int
properties of a Kotlin data class. In other words, given the following data class:
We would like to emit the following code:
Dependencies
We will start with a Kotlin JVM module with the following dependencies:
Note that we need the google()
repository for KSP.
Provider
Now we can start writing our classes — the first is a provider used to instantiate our processor:
In the create
function, you get a handle on the SymbolProcessorEnvironment
which will allow you to pass options, logging, and code generation as dependencies to your symbol processor.
For our processor itself, we override a process
function where we get a Resolver
as a parameter. We use the resolver
to get the KSType
for Int
since we will be using this later. Then we look for annotations of the type IntSummable
and check them using one of KSP’s inbuilt functions KSNode#validate
. We will return a list of any invalid symbols at the end in order to fulfill the function contract. Finally, we will use the visitor pattern to visit each class declaration marked with our annotation.
Visitor
Visitors in KSP have a signature with two type parameters:
The D
is an opportunity for you to pass some data into the visitor, while the R
is the result you want to return from visiting. The type parameters allow you to create pipelines where you chain the output of one visitor to the input of another visitor.
For our visitor we will keep things simple and perform side-effects on members rather than maintain purity, and so we will use KSVisitor<Unit, Unit>
. KSP provides a convenience class for this called KSVisitorVoid
:
The first part of our visitor performs some easy validation on the class declaration we are visiting, checking whether it is a data class and whether it has a qualified name.
Engine
Now we get to the engine of our visitor. Since we will need the class name, package name, and a list of summable properties in order to generate our code, we are going to store these in members:
We obtain the properties we want by having a property declaration visitor visit every property and checking if the type is assignable from Int
.
Code generation
We’ve now got enough information to emit our generated code. The KSP examples use string templates to construct the code, but we can bring in KotlinPoet to help us here:
We will use FileSpec
and FunSpec
from KotlinPoet to build the function we need to emit. Finally, the CodeGenerator
provided by KSP can provide us an OutputStream
which plugs into KotlinPoet’s FileSpec#writeTo
.
Testing
The good news for testing is we can use our pre-existing tools:
Thilo Schuchort’s excellent compile testing tool comes with extensions for testing KSP. This is shown in a private function from our test class below where we pass in a list of SymbolProcessorProvider
:
Obtaining the output source files is a bit tricky at the moment. I used a technique described by Gabriel Freitas Vasconcelos in the Github issues:
Now we can write our tests:
The full sample is below:
Fact or cap?
We’ve seen a simple example using KSP — the performance enhancements are promising and it certainly embraces Kotlin as a first-class citizen. So is KSP fact or cap? Definitely “fact”!
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK