最近在逛developers的时候瞅见这个么东西:
好奇: Kotlin Symbol Processing API
这个是个啥?
Kotlin Symbol Processing (KSP) is an API that you can use to develop lightweight compiler plugins. KSP provides a simplified compiler plugin API that leverages the power of Kotlin while keeping the learning curve at a minimum. KSP is designed to hide compiler changes, minimizing maintenance efforts for processors that use it. KSP is designed not to be tied to the JVM so that it can be adapted to other platforms more easily in the future. KSP is also designed to minimize build times. For some processors, such as Glide, KSP reduces full compilation times by up to 25% when compared to KAPT.
啥玩意?比kapt快25% 喜大普奔...
KAPT is a remarkable solution which makes a large amount of Java annotation processors work for Kotlin programs out-of-box. The major advantages of KSP over KAPT are improved build performance, not tied to JVM, a more idiomatic Kotlin API, and the ability to understand Kotlin-only symbols.
To run Java annotation processors unmodified, KAPT compiles Kotlin code into Java stubs that retain information that Java annotation processors care about. To create these stubs, KAPT needs to resolve all symbols in the Kotlin program. The stub generation costs roughly 1/3 of a full kotlinc analysis and the same order of kotlinc code-generation. For many annotation processors, this is much longer than the time spent in the processors themselves. For example, Glide looks at a very limited number of classes with a predefined annotation, and its code generation is fairly quick. Almost all of the build overhead resides in the stub generation phase. Switching to KSP would immediately reduce the time spent in the compiler by 25%.
For performance evaluation, we implemented a simplified version of Glide in KSP to make it generate code for the Tachiyomi project. While the total Kotlin compilation time of the project is 21.55 seconds on our test device, it took 8.67 seconds for KAPT to generate the code, and it took 1.15 seconds for our KSP implementation to generate the code.
Unlike KAPT, processors in KSP do not see input programs from Java's point of view. The API is more natural to Kotlin, especially for Kotlin-specific features such as top-level functions. Because KSP doesn't delegate to javac like KAPT, it doesn't assume JVM-specific behaviors and can be used with other platforms potentially.
抓重点: 不依赖javac
目前需要kotlin 1.4-M1
支持
在实现注解处理器模块目录的build.gradle.kts文件中添加
plugins {
kotlin("jvm") version "1.4-M1" apply false
}
buildscript {
dependencies {
classpath(kotlin("gradle-plugin", version = "1.4-M1"))
}
repositories {
maven("https://dl.bintray.com/kotlin/kotlin-eap")
}
}
之后我们会发现一个新的注解处理器SymbolProcessor
:
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.ksp.processing
/**
* [SymbolProcessor] is the interface used by plugins to integrate into Kotlin Symbol Processing.
*/
interface SymbolProcessor {
/**
* Called by Kotlin Symbol Processing to initialize the processor.
*
* @param options passed from command line, Gradle, etc.
* @param kotlinVersion language version of compilation environment.
* @param codeGenerator creates managed files.
*/
fun init(options: Map<String, String>, kotlinVersion: KotlinVersion, codeGenerator: CodeGenerator)
/**
* Called by Kotlin Symbol Processing to run the processing task.
*
* @param resolver provides [SymbolProcessor] with access to compiler details such as Symbols.
*/
fun process(resolver: Resolver)
/**
* Called by Kotlin Symbol Processing to finalize the processing of a compilation.
*/
fun finish()
}
-
init()
作为初始化提供一些工具,例如options,CodeGenerator(KSP版本的Filer)和正在运行的Kotlin的版本(KotlinVersion)。 -
process()
提供了Resolver,这是javax中的Types和Elements API的替代。
与apt不同,您不必预先声明支持的选项或注释。
获取注解信息
RoundEnvironment.getElementsAnnotatedWith()
由Resolver.getSymbolsWithAnnotation()
替代
val symbols = resolver.getSymbolsWithAnnotation("tt.reducto.eventbus.annotation.TTModuleEvents")
返回的是一个 List<KSAnnotated>
获取注解类中的成员
fun KSClassDeclaration.getDeclaredFunctions(): List<KSFunctionDeclaration> {
return this.declarations.filterIsInstance<KSFunctionDeclaration>()
}
val type: KSType =
resolver.getClassDeclarationByName(resolver.getKSNameFromString("tt.reducto.eventbus.annotation.TTModuleEvents"))
?.asType(typeArguments)
?: error("没有找到指定注解类型")
写文件
kapt 生成 Kotlin 代码,是将生成的 Kotlin 源文件写入processingEnv.options["kapt.kotlin.generated"]
所指定的目录,这些文件会与主源代码一起编译。
ksp貌似需要配置:
sourceSets["main"].java.srcDir(file("build/generated/ksp/src/main"))
// 类似于getEnclosingElement
val parent = function.parentDeclaration as KSClassDeclaration
//
val packageName = parent.containingFile!!.packageName.asString()
val file = codeGenerator.createNewFile(packageName , className)
file.appendText("package $packageName\n\n")
file.appendText("class $className{\n")
.......
但是感觉没有KotlinPoet
方便
目前官方给的ksp例子都是JVM的,我试图移植到android项目中,没成功,等之后在4.2 canary上试试。
小结
简单上手尝鲜,官方给的几个例子可以在IDEA中跑跑看
吐槽下:连最起码的类似javax 中的Messager
调式 API都没有
最后附上对照表:
Java annotation processing to KSP reference
Program elements
Java | Closest facility in KSP | Notes |
---|---|---|
AnnotationMirror | KSAnnotation | |
AnnotationValue | KSValueArguments | |
Element | KSDeclaration / KSDeclarationContainer | |
ExecutableElement | KSFunctionDeclaration | |
PackageElement | KSFile | KSP doesn't model packages as program elements. |
Parameterizable | KSDeclaration | |
QualifiedNameable | KSDeclaration | |
TypeElement | KSClassDeclaration | |
TypeParameterElement | KSTypeParameter | |
VariableElement | KSVariableParameter / KSPropertyDeclaration |
Types
Because KSP requires explicit type resolution, some functionalities in Java can
only be carried out by KSType and the corresponding elements before resolution.
Java | Closest facility in KSP | Notes |
---|---|---|
ArrayType | KSBuiltIns | |
DeclaredType | KSType / KSClassifierReference | |
ErrorType | null | |
ExecutableType | KSType / KSCallableReference | |
IntersectionType | KSType / KSTypeParameter | |
NoType | KSBuiltIns / null | |
NullType | KSBuiltIns | |
PrimitiveType | KSBuiltIns | |
ReferenceType | KSTypeReference | |
TypeMirror | KSType | |
TypeVariable | KSTypeParameter | |
UnionType | N / A | Kotlin has only one type per catch block. UnionType is also not observable by even Java annotation processors. |
WildcardType | KSType / KSTypeArgument |
Misc
Java | Closest facility in KSP | notes |
---|---|---|
Name | KSName | |
ElementKind | ClassKind / FunctionKind | |
Modifier | Modifier | |
NestingKind | ClassKind / FunctionKind | |
AnnotationValueVisitor | ||
ElementVisitor | KSVisitor | |
AnnotatedConstruct | KSAnnotated | |
TypeVisitor | ||
TypeKind | KSBuiltIns | Some can be found in builtins, otherwise check KSClassDeclaration for DeclaredType |
ElementFilter | Collection.filterIsInstance | |
ElementKindVisitor | KSVisitor | |
ElementScanner | KSTopDownVisitor | |
SimpleAnnotationValueVisitor | No needed in KSP | |
SimpleElementVisitor | KSVisitor | |
SimpleTypeVisitor | ||
TypeKindVisitor | ||
Types | Resolver / utils | Some of the utils are also integrated into symbol interfaces |
Elements | Resolver / utils |
Details
How functionalities of Java annotation processing API can be carried out by KSP.
AnnotationMirror
Java | KSP equivalent |
---|---|
getAnnotationType | ksAnnotation.annotationType |
getElementValues | ksAnnotation.arguments |
AnnotationValue
Java | KSP equivalent |
---|---|
getValue | ksValueArgument.value |
Element
Java | KSP equivalent |
---|---|
asType | ksClassDeclaration.asType(...) // Only available for KSClassDeclaration. Type arguments need to be supplied. |
getAnnotation | // To be implemented. |
getAnnotationMirrors | ksDeclaration.annotations |
getEnclosedElements | ksDeclarationContainer.declarations |
getEnclosingElements | ksDeclaration.parentDeclaration |
getKind |
is operator + ClassKind or FunctionKind |
getModifiers | ksDeclaration.modifiers |
getSimpleName | ksDeclaration.simpleName |
ExecutableElement
Java | KSP equivalent |
---|---|
getDefaultValue | // To be implemented. |
getParameters | ksFunctionDeclaration.parameters |
getReceiverType | ksFunctionDeclaration.parentDeclaration |
getReturnType | ksFunctionDeclaration.returnType |
getSimpleName | ksFunctionDeclaration.simpleName |
getThrownTypes | // Not needed in Kotlin. |
getTypeParameters | ksFunctionDeclaration.typeParameters |
isDefault | // Check whether parent declaration is an interface or not. |
isVarArgs | ksFunctionDeclaration.parameters.any { it.isVarArg } |
Parameterizable
Java | KSP equivalent |
---|---|
getTypeParameters | ksFunctionDeclaration.typeParameters |
QualifiedNameable
Java | KSP equivalent |
---|---|
getQualifiedName | ksDeclaration.qualifiedName |
TypeElement
Java | KSP equivalent |
---|---|
getEnclosedElements | ksClassDeclaration.declarations |
getEnclosingElement | ksClassDeclaration.parentDeclaration |
getInterfaces | ksClassDeclaration.superTypes.map { it.resolve() }.filter {(it?.declaration as? KSClassDeclaration)?.classKind == ClassKind.INTERFACE} // Should be able to do without resolution. |
getNestingKind | // check KSClassDeclaration.parentDeclaration and inner modifier. |
getQualifiedName | ksClassDeclaration.qualifiedName |
getSimpleName | ksClassDeclaration.simpleName |
getSuperclass | ksClassDeclaration.superTypes.map { it.resolve() }.filter { (it?.declaration as? KSClassDeclaration)?.classKind == ClassKind.CLASS } // Should be able to do without resolution. |
getTypeParameters | ksClassDeclaration.typeParameters |
TypeParameterElement
Java | KSP equivalent |
---|---|
getBounds | ksTypeParameter.bounds |
getEnclosingElement | ksTypeParameter.parentDeclaration |
getGenericElement | ksTypeParameter.parentDeclaration |
VariableElement
Java | KSP equivalent |
---|---|
getConstantValue | // To be implemented. |
getEnclosingElement | ksVariableParameter.parentDeclaration |
getSimpleName | ksVariableParameter.simpleName |
ArrayType
Java | KSP equivalent |
---|---|
getComponentType | ksType.arguments.first() |
DeclaredType
Java | KSP equivalent |
---|---|
asElement | ksType.declaration |
getEnclosingType | ksType.declaration.parentDeclaration |
getTypeArguments | ksType.arguments |
ExecutableType
Note: A KSType
for a function is just a signature represented by theFunctionN<R, T1, T2, ..., TN>
family.
Java | KSP equivalent |
---|---|
getParameterTypes | ksType.declaration.typeParameters, ksFunctionDeclaration.parameters.map { it.type } |
getReceiverType | ksFunctionDeclaration.parentDeclaration.asType(...) |
getReturnType | ksType.declaration.typeParameters.last() |
getThrownTypes | // Not needed in Kotlin. |
getTypeVariables | ksFunctionDeclaration.typeParameters |
IntersectionType
Java | KSP equivalent |
---|---|
getBounds | ksTypeParameter.bounds |
TypeMirror
Java | KSP equivalent |
---|---|
getKind | // Compare with types in KSBuiltIns. |
TypeVariable
Java | KSP equivalent |
---|---|
asElement | ksType.declaration |
getLowerBound | // To be decided. Only needed if capture is provided and explicit bound checking is needed. |
getUpperBound | ksTypeParameter.bounds |
WildcardType
Java | KSP equivalent |
---|---|
getExtendsBound | if (ksTypeArgument.variance == Variance.COVARIANT) { ksTypeArgument.type } else { null } |
getSuperBound | if (ksTypeArgument.variance == Variance.CONTRAVARIANT) { ksTypeArgument.type } else { null } |
Elements
Java | KSP equivalent |
---|---|
getAllAnnotationMirrors | KSDeclarations.annotations |
getAllMembers | getAllFunctions and getAllProperties, the latter is not there yet |
getBinaryName | // To be decided, see Java Spec |
getConstantExpression | we have constant value, not expression |
getDocComment | // To be implemented |
getElementValuesWithDefaults | // To be implemented. |
getName | resolver.getKSNameFromString |
getPackageElement | Package not supported, while package information can be retrieved, operation on package is not possible for KSP |
getPackageOf | Package not supported |
getTypeElement | Resolver.getClassDeclarationByName |
hides | // To be implemented |
isDeprecated | KsDeclaration.annotations.any { it.annotationType.resolve()!!.declaration.quailifiedName!!.asString() == Deprecated::class.quailifiedName } |
overrides | KSFunctionDeclaration.overrides // extension function defined in util.kt. |
printElements | // Users can implement them freely. |
Types
Java | KSP equivalent | ||
---|---|---|---|
asElement | ksType.declaration | ||
asMemberOf | ksClassDeclaration.typeArguments + ksClassDeclaration.asType | ||
boxedClass | // Not needed | ||
capture | // To be decided. | ||
contains | KSType.isAssignableFrom | ||
directSuperTypes | (ksType.declaration as KSClassDeclaration).superTypes | ||
erasure | ksType.starProjection() | ||
getArrayType | ksBuiltIns.arrayType.replace(...) | ||
getDeclaredType | ksClassDeclaration.asType | ||
getNoType | ksBuiltIns.nothingType / null | ||
getNullType | // depends on the context, KSType.markNullable maybe useful. | ||
getPrimitiveType | // Not needed | ||
getWildcardType | // Use Variance in places expecting KSTypeArgument | ||
isAssignable | ksType.isAssignableFrom | ||
isSameType | ksType.equals | ||
isSubsignature | functionTypeA == functionTypeB | functionTypeA == functionTypeB.starProjection() | |
isSubtype | ksType.isAssignableFrom | ||
unboxedType | // Not needed. |
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。