Kotlin Symbol Processing: Getting Started

Tompee Balauag
Familiar Android
Published in
3 min readFeb 10, 2022

--

Photo by Nathaniel Tetteh on Unsplash

These series of articles hopefully mark my comeback to technical blogging after a brief hiatus and is focused on documenting my findings and humble tips when using the shiny new `Kotlin Symbol Processing` (KSP) API to create code generation libraries. The first part of this series is talking about what KSP is, and how it differs with Java processors and kapt, and how to setup a project. We will use my library called ArcticTern as a reference.

What is KSP?

According to the Kotlin documentation, it is a compiler plugin API. It can be used to develop solutions that would analyze your Kotlin code and generate outputs such as boilerplate code or full implementation of your program elements. This is preferred over developing custom compiler plugins because the latter requires inherent knowledge about compilers and their implementations are most likely tied to compiler versions.

The main advantage of using KSP is that it understand Kotlin’s language features. For those people who worked on kapt before, there is a big problem in trying to analyze and generate Kotlin types because it delegates to javac so it outputs Java TypeElements. Kotlin Metadata helped a lot but still, working on Java elements and Kotlin metadata and knowing when and how to use what is not a trivial thing. Other benefits include faster compilation time (if you only use KSP in your project) and the portability as this isn’t only exclusive to JVM.

Project Structure

For an annotation processing and code generation library, we would typically need 2 modules, one containing your annotations and Java types, and the other one is your compiler. I mentioned Java types specifically because the compiler module has to be a Java library (plugin: java-library). Unfortunately, I wasn’t able to successfully setup a compiler using an android library module, that’s why if you need android platform types, you are better off creating a new android module. Don’t forget to check out ArcticTern as reference.

Typical project structure

Dependencies

Now that we have the modules setup, it’s time to import the dependencies. First, include the plugin in the classpath in your project’s build.gradle.kts. Note that there are compatibility requirements against the Kotlin version. Check out the release page for more information about what version to use.

dependencies {
....
classpath("com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:$version")
}

For the compiler, you would need 2 artifacts.

"com.google.devtools.ksp:symbol-processing:$version"
"com.google.devtools.ksp:symbol-processing-api:$version"

There are other dependencies we would need to make life easy and we will add them now as well. First one is the AutoService. All processors must be declared in resources/META-INF explicitly. Using this dependency allows you to annotate the processor implementation and it will automatically add it in that file. Handy indeed. More on this later.

"com.google.auto.service:auto-service-annotations:$version"
"dev.zacsweers.autoservice:auto-service-ksp:$version"

Second stop is KotlinPoet. This library allows us to write Kotlin code and files in a structured manner. Check out the release page for the updated version.

"com.squareup:kotlinpoet:$version"
"com.squareup:kotlinpoet-ksp:$version"

Project Setup

Aside from wiring the project dependencies, it is important to note that generated files from KSP are not automatically added to source path. Use this handy script to include the generated sources to all build types and variants.

kotlin {
sourceSets.all {
kotlin.srcDir("build/generated/ksp/$name/kotlin")
}
}

Now we’re all setup and ready to go! Watch out for the next part. For the mean time, you can read my previous articles on annotation processing here and here.

--

--