12.4.8 多轮处理

原文链接: https://kotlinlang.org/docs/ksp-multi-round.html

12.4.8 多轮处理

KSP 支持多轮处理,也就是在多轮中处理文件。每轮处理的输出会作为后续各轮的额外输入。

要使用多轮处理,请让 SymbolProcessor.process() 以 List<KSAnnotated> 的形式返回被推迟的符号。KSP 会在下一轮处理这些符号。

要推迟无效符号,请用 KSAnnotated.validate() 过滤它们,例如:

1
2
3
4
5
6
7
8
override fun process(resolver: Resolver): List<KSAnnotated> {
    val symbols = resolver.getSymbolsWithAnnotation("com.example.annotation.Builder")
    val result = symbols.filter { !it.validate() }
    symbols
        .filter { it is KSClassDeclaration && it.validate() }
        .map { it.accept(BuilderVisitor(), Unit) }
    return result
}

当某一轮完全没有生成新文件时,多轮处理就会结束。如果有被推迟的符号仍未处理,KSP 会为每个仍有未处理推迟符号的处理器记录一条错误。

把符号推迟到下一轮

当需要其他处理器提供更多信息时,处理器可以把符号推迟到后面的轮次。处理器可以跨多轮持续推迟某个符号,直到所需信息可用。一旦信息可用,处理器就可以处理该符号。

只在以下情况下才推迟符号:

  • 在处理该符号之前还需要额外信息。

  • 该符号来自源代码。

注意: 绝不要推迟来自 classpath 的符号。KSP 会自动过滤掉 classpath 符号。

例如,一个为带注解的类生成 builder 的处理器,可能要求所有构造函数参数类型都能解析为具体类型。在第一轮中,某个参数类型可能无法解析。而在后面的某一轮中,由于期间生成的文件,它可能变得可以解析。处理器那时就可以处理该类的。

校验符号

校验是一种判断是否应把符号推迟到后续轮次的便捷方式。处理器应当定义正确处理某个符号所需的信息。

提示: 校验通常需要类型解析,而这是有开销的。只检查处理该符号所必需的信息。

默认的校验行为可能并不适合所有用例。要自定义校验,请使用 KSValidateVisitor 并提供一个用于选择待校验符号的 predicate lambda。

在实现自定义校验时,请使用 KSType.isError 判断类型是否有效。如果 isError 为 true,说明 KSP 无法解析该类型。利用这一信息来决定是否把处理推迟到后续轮次。

访问文件与符号

新生成的文件和已有文件都可以通过 Resolver 访问。

KSP 提供两个用于访问文件的 API:

  • Resolver.getAllFiles() 返回先前已有文件和新生成文件的列表。

  • Resolver.getNewFiles() 只返回上一轮生成的文件。

请把 Resolver.getSymbolsWithAnnotation() 作为获取相关符号的主要入口。

在每一轮中,Resolver.getSymbolsWithAnnotation() 只返回新生成文件中的符号,以及上一轮推迟的符号。这有助于避免不必要的重复处理。

处理器的实例化

KSP 只会创建一次处理器实例。你可以在处理器实例中存储信息,并在多轮之间复用它。

不过,并非所有 KSP 符号都能跨轮复用。当处理器生成新文件时,符号解析结果可能发生变化,从而影响此前已解析符号的有效性。

注意: 只使用当前轮次传给处理器的那个 Resolver 实例。不要保存 Resolver 并跨轮复用。

错误与异常处理

错误

处理器通过调用 KSPLogger.error() 报告错误。

当处理器报告错误时,KSP 会调用 SymbolProcessor.onError() 而不是 SymbolProcessor.finish()。当前轮次结束后处理就会停止。

在这一轮中,其他处理器会正常继续处理。KSP 会在所有处理器完成当前轮次之后才处理错误。

异常

KSP 会区分由 KSP 抛出的异常和由处理器抛出的异常。两类异常都会立即终止处理,并通过 KSPLogger 记录为错误。

注意: 请把 KSP 抛出的异常报告给 KSP 开发者以便调查。请在 KSP 问题跟踪器中创建 issue。

在发生错误或异常的那一轮结束时,KSP 会对所有处理器调用 SymbolProcessor.onError()。SymbolProcessor 为 onError() 提供了默认的空实现。重写该方法即可实现自定义的错误处理逻辑。