Gradle修改少量代码却触发大范围编译,大概率是增量构建失效而退化为全量执行;根源在于输入指纹追踪或class文件映射被破坏,如手动修改class、sourceSets未重载、不支持增量的注解处理器、隐式文件时间戳变更等。

Gradle 修改少量代码却触发大范围编译,大概率不是代码改得多,而是增量构建没生效——它退化成了全量执行。
为什么 compileJava 会扫完整个 src/main/java
Gradle 默认开启增量编译(options.incremental = true),但这个机制依赖两个前提:源文件的「输入指纹」可被准确追踪,且编译器输出的 class 文件能与源文件一一映射。一旦破坏任一条件,就会 fallback 到全量编译。
- 常见破坏点:手动修改了
build/classes/java/main下的 class 文件(比如调试时热替换失败残留) - 修改了
sourceSets配置但没重载项目(如动态加了src/gen/java,但 IDE 没刷新,Gradle Daemon 还按旧路径扫描) - 用了不支持增量的注解处理器(例如老版本
lombok或某些自定义AnnotationProcessor),它会让整个compileJava任务标记为@InputChanges不安全 - 启用了
annotationProcessorPath但没声明classpath的稳定性,Gradle 无法判断处理器是否变更
如何验证当前是不是真增量
运行 ./gradlew compileJava --info,重点看日志里有没有这行:
Skipping task ':compileJava' because it has no source files.
或者更关键的是:
Compiling with JDK Java compiler API. Incremental compilation of 3 out of 12 classes completed.
- 如果看到
Skipping task,说明该任务被跳过,上游输入无变化 - 如果看到
Incremental compilation of X out of Y,说明增量生效 - 如果只看到
Compiling with JDK Java compiler API.后直接进编译流程,没提“incremental”,基本就是退化了
gradle.properties 里这几个开关必须开
增量编译不是默认“全自动可靠”,它需要显式启用并规避已知干扰项:
-
org.gradle.configuration-cache=true(配置缓存开启后,对构建逻辑的变更更敏感,反而有助于稳定输入边界) -
org.gradle.parallel=true(并行本身不直接影响增量,但避免模块间隐式依赖导致误判变更) -
org.gradle.caching=true(构建缓存开启后,即使某次触发全量,后续相同输入也能命中缓存,掩盖部分问题) - 禁用
org.gradle.jvmargs=-XX:MaxMetaspaceSize=512m这类过小元空间设置——Metaspace 不足会导致 javac 内部状态异常,间接让增量判定失效
最容易被忽略的「伪变更」来源
你以为只改了一个 .java,但 Gradle 看到的输入可能早已污染:
-
resources目录下有同名.properties或.xml被 IDE 自动保存(时间戳更新),而你的processResources任务又依赖于compileJava输出,形成隐式链式触发 - 使用了
@Generated注解或 Lombok 的@Builder,但生成的代码写入了build/generated/sources,而该目录被错误加入sourceSets.main.java.srcDirs,导致每次生成都当成新源码 - Git hooks 或 CI 脚本在 checkout 后执行了
chmod或touch,悄悄更新了文件 mtime,Gradle 按时间戳判断为变更(哪怕内容没变)
真正难排查的,往往不是 Gradle 做错了什么,而是它忠实地响应了你没意识到的输入变化。别急着调参数,先用 --info 看清它到底认为哪些文件变了。


















