8

KSP: Fact or kapt?

 2 years ago
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.

1*YDHHh1Qst1vsUoIn0n1fCw.jpeg?q=20
ksp-fact-or-kapt-7c7e9218c575
Photo by Lenny Kuhne on Unsplash

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 KSTypefor 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.

1*g2n55duc6k0jBXKjFCmPkw.jpeg?q=20
ksp-fact-or-kapt-7c7e9218c575
Photo by ThisisEngineering RAEng on Unsplash

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.

1*jO2bCGjCfkZ4qR9lUPtd4w.jpeg?q=20
ksp-fact-or-kapt-7c7e9218c575
Photo by Garett Mizunaka on Unsplash

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.

1*qep7kQkI3jpDZQTgKSopRQ.jpeg?q=20
ksp-fact-or-kapt-7c7e9218c575
Photo by Alvaro Calvo on Unsplash

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.

1*LpTRcAXX8Ymm_i_eR14k0g.jpeg?q=20
ksp-fact-or-kapt-7c7e9218c575
Photo by Science in HD on Unsplash

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”!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK