Hands on Google KSP

Nikola Despotoski
3 min readSep 27, 2021

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 processormodule:

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

LEnk is just a name for the project

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 .

Taken from https://github.com/google/ksp#how-ksp-looks-at-source-files

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.

Other

--

--