增量APT
增量APT
背景
这是一个Gradle提供的特性,其在4.7版本后提供了对增量处理注解的支持。这个功能依赖Java的增量编译,否则也是不生效的。
正文
Gradle通过“类别”Category来识别可以进行增量工作的注解处理器,可以支持两种类型:
isolating
隔离aggregating
聚合
从注册说起
正统的APT注册方式是在META-INF
文件夹(src/main/resources/META-INF)下注册的:
services目录下,创建
javax.annotation.processing.Processor
文本文件,其中写入自己Processor类的全称
这是大家都知道的事情。Google的auto
这个脚手架自动帮我们完成了这一点。
但是对于需要支持增量工作的注解处理器来说,原有的注册方式要改动一下了:
META-INF
目录不变,services
目录改为gradle
,即META-INF/gradle/
- 在
gradle
目录下创建的文本文件名为:incremental.annotation.processors
其中填写的文本内容格式为:
1 | [注解处理器的类全称],[类型] |
中间必须是半角的逗号
而类型这个数据在上边提到了两个,其实还有一个,是:dynamic
。
类型选择
如果你的处理器只能在运行时才能判断自己可不可以支持增量,那么就选择dynamic
,然后在运行时,通过Processor类的getSupportedOptions()
接口进行返回,例如:
1 |
|
这样和在配置文件中写入aggregating
是一个效果。(注意,这里返回的是数组,其中的字符串内容是包含了固定的gradle的包名内容)
需要遵守的规则
想要让你的注解处理器支持增量的话,无非是两种类型。这个在上边说过了,而这两种类型,都会有一些限制,开发者必须遵守这些限制,那么才能顺利的支持增量编译:
必须只能使用
Filer
API进行文件的生成,其他的方式可能会导致失败(如官方所言,是无声的失败),或者生成的文件不能被正确清除,可能污染最终的生成物众所周知,在APT环节中有时为了直接操作、甚至干涉AST的数据结构,我们可能直接转化为其真正的实现者,即:
JavacProcessingEnvironment
,进而去操作JCTree
等数据结构。但在实现上,Gradle是封装了一层Processor的API的,所以这样的强转行为可能导致失败,也就导致这个注解处理器不能支持增量编译。所以,需要开发者使用正统的接口API进行操作,而不要强转对于资源的生成,只支持
Filer
API是自然,对于createResource()
方法中的location
参数来说,只能支持StandardLocation
中的:- CLASS_OUTPUT
- SOURCE_OUTPUT
- NATIVE_HEADER_OUTPUT
其他的参数在增量编译中不支持
isolating
最快的类型,这个类型把每一个注解节点视作一个独立的个体,他们互相不产生影响,每一个来了都能单独进行处理或者校验。它的限制是:
- 不能依赖当前
RoundEnvironment
中的其他Element(非此Annotation的)做决策。所谓增量,就是每次编译的文件不是全量的,只是少部分,所以这里的输入是少的,只是必要的那些才会给你。如果开发者的处理器真的需要其他不相关的节点的话,那么需要声明成为agregating
聚合类型 Filer
API的createXXX接口会需要传入原始类型,JavaPoet项目中的TypeSpec.Builder也有相关API。对于这个接口来说,此类型只能提供一个参数,也就是只能而且必须只有一个原始类型。0或者更多都会导致重新编译所有源文件
aggregating
如果需要聚合多个源码文件才能生成一个或多个的文件或者做校验工作,那么这种处理器就是聚合类型了。官方给出的例子是ServiceRegistry
,这种场景下需要遍历多个文件然后最终产出一份注册表。结合此类型的限制,可以更好的理解:
- 这种类型只能读取
CLASS
或者RUNTIME
的注解 - 对于参数的输入只能通过开发者传递
-parameters
编译参数
结合以上,笔者认为,“聚合”类型的处理器是有可能接收到已经在上一次编译成为class文件的源码的(那么相对来说,isolating就不能接收到class文件,只会收到java源文件)。所谓“聚合”,就是结合之前可能存在的几轮APT操作后,混合着class和Java文件再作为后续的输入进行数据处理。所以,聚合类型有较大的资源获取权限来进行更大范围的处理。
总结
虽然结合了大量参考资料的阅读和亲身测试,但笔者对“聚合”和“隔离”两个类型还是不能给出十分明确的代码层面的区分,这一点恐怕要在多处理器的环境下方可验证。后续如果有最新的进展我也会更新到此处。
对于已有处理器,如何增加对增量编译的支持,这里简单做一个总结:
- 首先,Gradle版本要在4.7之上
- APT支持增量的基础是Java的编译是支持的
- 增量处理器要在
META-INF
下通过新的注册文件告知Gradle构建系统 - 开发者需要按需扩大注解(@annotation)的生效范围,对于聚合来说,SOURCE就不够用了
- 必须规范对AST的操作,实际上也就只能用只读的方式读取AST了,也就是用原生的接口API,否则将无法支持增量编译
- 资源、源文件的生成只能通过
Filer
接口进行