KSP 是 Kotlin Symbol Processing 的簡稱,本質上它可以做很多很多種事情,但我們今天主要會延續昨天 KotlinPoet 的部分聚焦於 code generation。
如果是寫一陣子 Java 的資深開發者可能有聽過 Annotation Processing,而 KSP 如果拿來做 code generation 的話,其實跟 Annotation Processing 的結構非常的像,就讓我們一步一步解析囉!
如果對 Java 的 Annotation Processing 有興趣可以參考 Android 十全大補的文章
https://ithelp.ithome.com.tw/articles/10222408
因為內容有點多,我們會拆成上下二集介紹。
KSP 是在 compile 階段可以透過讀取程式碼的資訊來分析、或是產生程式碼的工具,而 annotation 就是其中一種可以方便解析出來的資訊。你或許很少有機會自己寫一個 annotation,以下是一個 custom annotation 的範例程式碼:
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.SOURCE)
annotation class AutoElement
我們可以發現它跟 data class
、enum class
等都一樣遵守 Kotlin 的 pattern。而一般 annotation 的宣告通常都還會伴隨著二種 annotation - Target
跟 Retention
。
代表著這個 annotation 可以作用在哪些物件上,像 CLASS
就是只能標在 class 上,共有以下幾種值:
/** Class, interface or object, annotation class is also included */
CLASS,
/** Annotation class only */
ANNOTATION_CLASS,
/** Generic type parameter */
TYPE_PARAMETER,
/** Property */
PROPERTY,
/** Field, including property's backing field */
FIELD,
/** Local variable */
LOCAL_VARIABLE,
/** Value parameter of a function or a constructor */
VALUE_PARAMETER,
/** Constructor only (primary or secondary) */
CONSTRUCTOR,
/** Function (constructors are not included) */
FUNCTION,
/** Property getter only */
PROPERTY_GETTER,
/** Property setter only */
PROPERTY_SETTER,
/** Type usage */
TYPE,
/** Any expression */
EXPRESSION,
/** File */
FILE,
/** Type alias */
@SinceKotlin("1.1")
TYPEALIAS
Retention
比 Target
稍微難理解,由於 annotation 的作用是給程式碼物件提供額外的標註,所以它的生命週期通常會有不同的需求,像是 KSP 或是 IDE 專用的可能就不需要 compile 之後還留著,只是增加檔案大小而已沒有額外的幫助,但如果要在 runtime 時透過 reflection 等讀取 annotation 的資訊就必須存在 binary 裡。
所以設定正確的 Retention
就很重要,依照不同需求有以下幾種值:
/** Annotation isn't stored in binary output */
SOURCE,
/** Annotation is stored in binary output, but invisible for reflection */
BINARY,
/** Annotation is stored in binary output and visible for reflection (default retention) */
RUNTIME
依照每人使用習慣不同,可能多少會有些出入,但通常會有以下三個 module 分別負責不同的角色:
有些人也習慣把 Annotation module 跟 Processor module 合起來,也沒有太多明顯的差異,大家可以自己決定。
由於 KSP 是個輕量的 compiler plugin,所以不少額外的設定要做,以下會分各個 module 作介紹。
首先在 root project 新增以下設定:
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.7.10' apply false
id 'com.google.devtools.ksp' version '1.7.10-1.0.6' apply false
}
buildscript {
dependencies {
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10'
}
}
KSP 的版本很有趣,由於它依賴於 Kotlin 的版本,所以前半段指的是 Kotlin 的版本,後面才是 KSP 自己的 release 版本,更多資訊可以參考:https://github.com/google/ksp/releases
Annotation module 是單純提供 annotation 的 module,所以就照一般 pure Kotlin library 的設定就行了。
Processor module 則要新增以下設定:
plugins {
id 'org.jetbrains.kotlin.jvm'
}
dependencies {
implementation project(":annotation")
implementation 'com.google.devtools.ksp:symbol-processing-api:1.7.10-1.0.6'
implementation 'com.squareup:kotlinpoet:1.11.0'
implementation 'com.squareup:kotlinpoet-ksp:1.12.0'
}
這邊我們可以發現 Processor module 依賴了 Annotation module,同時也需要 KSP 的 api,最後的二個則是 KotlinPoet 以及 KotlinPoet 在 KSP 上的一些轉換 extension library。
最後就是我們的 Usage module 怎麼定義:
plugins {
id 'com.google.devtools.ksp'
}
sourceSets {
main {
java {
srcDir "${buildDir.absolutePath}/generated/ksp/"
}
}
}
dependencies {
implementation project(":annotation")
ksp project(":processor")
}
一開始也是要 apply 這個 plugin,讓他在 compile 的時候起作用,然後加上 Annotation module 跟 Processor module 的 dependency。
sourceSets
的額外設定是因為 KSP 目前還很新,自動建立的檔案 IDE 可能還不認得,必須手動加上去,但這個問題隨著時間我相信會自然改善。
記得 Processor module 的宣告不是使用 implementation,而是 ksp 喔!
介紹完怎麼設定就差不多了,明天我們繼續講 Processor module 的更多具體細節,敬請不要錯過!