必须配置org.gradle.caching=true才能启用Gradle远程构建缓存,否则本地与CI缓存完全隔离;--build-cache仅为临时覆盖,不可替代该配置;远程缓存须用HTTP服务(如Gradle Enterprise或Build Cache Node),禁用NFS;push需按环境控制,本地默认false,CI设true;输入指纹一致性(源码路径、依赖版本、JDK版本)决定缓存复用能力;命中率低需检查Task Inputs中非确定性值。

CI和本地共用远程构建缓存必须配org.gradle.caching=true
不启用这个开关,Gradle根本不会尝试读写远程缓存——本地和CI各自走各自的本地缓存,完全隔离。它不是“默认开启”的功能,必须显式配置。
常见错误是只在CI脚本里加了--build-cache参数,但没在gradle.properties里设org.gradle.caching=true,结果CI能push但本地拉不到;或者反过来,本地启用了却没给CI配远程地址,导致CI生成的缓存全丢在本地磁盘上。
-
org.gradle.caching=true是全局开关,控制是否参与构建缓存机制 -
--build-cache只是命令行临时覆盖,不能替代配置项 - CI环境建议额外加
--no-daemon避免守护进程干扰缓存命中判断
远程缓存服务选型:推荐用Gradle Enterprise或自建Build Cache Node
别用NFS或共享目录模拟远程缓存——Gradle的缓存协议不是简单文件拷贝,它依赖原子性写入、哈希校验和并发安全的GET/PUT语义。用HTTP服务是最稳的选择。
Gradle官方推荐两种落地方式:
- 商用方案:
Gradle Enterprise,开箱即用,带命中率看板和失效分析,适合中大型团队 - 开源自建:
Build Cache Node(基于Spring Boot),部署轻量,支持Basic Auth,CI用curl就能推送
配置示例(settings.gradle.kts):
buildCache {
remote<HttpBuildCache> {
url = uri("https://cache.your-company.com/cache/")
credentials {
username = "gradle-cache"
password = System.getenv("GRADLE_CACHE_TOKEN") ?: ""
}
push = providers.gradleProperty("gradle.cache.push").getOrElse("false").toBoolean()
}
}
注意:push必须按环境控制——本地默认false,CI流水线才设为true,否则开发机误推脏缓存会污染整个团队。
本地和CI缓存路径要一致,但.gradle目录本身不用共享
很多人误以为得把~/.gradle/caches挂载到CI容器里才算“共享”,其实完全没必要。构建缓存的核心是远程HTTP服务,本地缓存只是个临时中转层。
真正要对齐的是任务输入指纹的计算逻辑,这取决于:
- 源码路径是否固定(CI中用
WORKSPACE,本地用项目根目录,没问题) - 依赖版本是否锁定(
gradle.lockfile必须提交,否则implementation 'x:y:1.+'会导致指纹不一致) - JDK版本是否统一(CI用
adoptium-17-jdk,本地也得用JDK 17,不同版本的javac输出class字节码会有细微差异)
只要这些输入一致,本地构建生成的缓存块,CI拉过去就能直接解压复用,反之亦然。
缓存命中率低?先检查buildScan里的Task Inputs字段
打开一次CI构建的buildScan链接,在每个任务详情页点开Task Inputs,看有没有标红的动态值——比如System.currentTimeMillis()、未声明的fileTree遍历、或project.version拼接进资源路径。
这类非确定性输入会让Gradle每次算出不同的指纹,缓存永远不命中。修复重点:
- 把
new Date()换成构建时间戳常量(从CI环境变量注入) - 所有
inputs.files必须显式声明,不能靠fileTree(dir)隐式扫描 - 禁用
configuration-cache不兼容写法,比如project.afterEvaluate或System.getenv()直读
缓存不是开了就灵,它暴露的是构建脚本本身的不确定性。越早发现这些“毛刺”,团队共享缓存的稳定性越高。


















