Java 的增量编译
Gradle 在 5.0 增加了对 Java 增量编译的支持,通过增量编译,我们能够获得一些优点:
- 更少的编译耗时
- 更少的字节码修改
增量编译概览:
- Gradle 会重新编译受更改影响的所有类。
- 如果某个类发生了更改或者它依赖的类发生了更改,那么这个类也会被重新编译。无论类是否在同一个项目、另一个项目,还是外部库中定义了的其他类。
- 类的依赖关系由其字节码中的类型引用确定。
- 由于常量可以内联,因此对常量的任何更改都将导致 Gradle 重新编译所有源文件。因此,我们应尽量减少在源代码中使用常量,并在可能的情况下间接通过静态方法返回常量。
- 由于
Retention(SOURCE)
的注解不会编译进字节码中,因此对这种注解的更改将导致完全重新编译。 - 可以通过应用软件设计原则(如松耦合)来提高增量编译性能。例如,当
类A
依赖接口B
时,则仅当接口B
更改时,才会重新编译依赖类类A
,而仅当接口B
的某个实现类更改时,不会重新编译类A
。 - 对类分析的结果会缓存在项目目录中,因此在 CI 服务器上 checkout 后的首次构建可能会变慢,所以在 CI 服务器上一般关闭增量编译。
已知问题:
- 如果编译过程中某个 task 造成了编译失败,下一次将会走完全编译
- 如果使用的是读取外部资源的注释处理器(例如配置文件),则需要将这些资源声明为编译任务的输入
- 如果资源文件发生修改,Gradle 会触发完全重编译
注解处理器增量编译
在 Gradle 编译时,会输出以下日志提醒你那些注解处理器没有支持增量编译:
w: [kapt] Incremental annotation processing requested, but support is disabled because the following processors are not incremental: com.x.XXProcessor (NON_INCREMENTAL).
让 APT 支持增量编译
首先 Gradle 支持两种注解处理器的增量编译:isolating 和 aggregating,你需要搞清你的注解处理器属于哪种。然后在 META-INF/gradle/incremental.annotation.processors 文件中声明支持增量编译的注解处理器。
比如我实现的一个支持增量编译的注解处理器的目录结构:
x_processor/src/main/
├── java
│ └── site
│ └── jiyang
│ └── features_impl_processor
└── resources
└── META-INF
├── gradle
│ └── incremental.annotation.processors
└── services
└── javax.annotation.processing.Processor
incremental.annotation.processors 和 javax.annotation.processing.Processor 类似,一行一个注解处理器的声明:
<注解处理器全限定名>,isolating
同时要在后面声明注解处理器的类型,使用 ,
号做分隔。
如果你的注解处理器要在运行时才能决定是否支持增量编译,那么可以声明为 dynamic,然后在注解处理器的 getSupportedOptions
方法中返回包含 org.gradle.annotation.processing.aggregating
的 Set<String>
。
incremental.annotation.processors:
<注解处理器全限定名>,dynamic
|
|
两种增量编译注解处理器的共同限制:
- 只能通过
javax.annotation.processing.Filer
接口去生成文件。任何其他方式生成的文件因为不能正确的被清理,会造成编译失败 - 要支持增量编译的注解处理器不能依赖编译器特有的类。因为 Gradle 包装了 processing API,任何依赖了特定编译器的类的编译都会失败
- 如果使用了
Filer#createResource
, Gradle 将重编译所有源文件。
isolating
最快的注解处理器类别,这类注解处理器独立地搜索每个带注解的元素,并为其生成文件或验证消息。
限制
- 这类 APT 从 AST(Abstract Synax Tree) 获得信息,为带注解的类做出所有决策(生成代码,编译检查等)。这意味着我们甚至可以递归地分析类的超类,方法返回类型,注解等。但是这类 APT 不能基于 RoundEnvironment 中不存在的元素进行决策。如果你的 APT 需要基于其他不相关元素的组合做出决策,你应该将它声明为 aggregating。
重新编译源文件时,Gradle 将重新编译由源文件生成的所有文件。 删除源文件后,从其生成的文件也会被删除。
aggregating
aggregating 类型的 APT, 可以将多个源文件聚合到一个或多个输出文件中。
限制
- 这类 APT 只能读取 CLASS 或 RUNTIME 类型的会保留在字节码中的注解
- 只能读到通过
-parameters
传递给编译器的参数的参数名
Gradle 将始终重新处理(但不会重新编译)APT 已处理的所有带注解的源文件。Gradle 将始终重新编译 APT 生成的任何文件。
KAPT 支持
Kotlin Annotation Processor 也支持了增量编译,在项目的 gradle.propertice
中声明如下配置就能开启:
kotlin.incremental=true
Auto-Service 配置
一些 APT 会使用 auto-service 去生成 META-INF,所以 auto-service 在 1.0.0-rc6 也支持了增量编译