3. 构建扫描的接入与解读
构建扫描(Build Scan)是 Gradle 构建性能分析的基石。通过 Gradle Enterprise 提供的服务端或免费的 --scan 选项,可以将一次构建的完整任务执行时间、缓存命中率、依赖解析细节、任务失败原因等关键指标上传至可交互的 Web 页面。本章将完整覆盖扫描的三种接入方式、报告的逐段解读方法,以及常见接入故障的排错流程。
3.1 构建扫描的核心价值与数据流
构建扫描不仅仅是一个日志聚合器,它在构建性能优化中承担数据采集与可视化职责。扫描报告包含以下不可替代的数据维度:
- 耗时分布:每个 Task 的启动与结束时间、配置阶段耗时、依赖解析阶段耗时。
- 缓存状态:构建缓存命中/未命中/从远程拉取次数,TOCTOU 竞争导致的缓存失效原因。
- 失败根因:Task 失败的具体异常堆栈、失败时的输入文件哈希快照。
- 依赖图:版本冲突详情、动态依赖解析的版本锁定点。
关键原则:构建扫描的统计分析必须基于可复现的构建。只有当构建环境与输入文件一致时,扫描报告中的时间数据和缓存数据才对优化有直接指导意义。
3.2 接入方式详解
根据使用场景,构建扫描可以通过以下三种方式启用:
| 接入方式 | 适用环境 | 配置/命令 | 备注 |
|---|---|---|---|
命令行 --scan | 本地调试、CI 一次性构建 | | 无需修改构建脚本;回车后选择是否同意条款 |
| 构建脚本插件 | 持续集成、团队统一配置 | | 需要 Gradle Enterprise 服务端 URL |
| 环境变量控制 | 远程容器、免交互构建 | | 免交互上传;仅适用 Gradle Enterprise |
推荐做法:在 CI 环境中始终使用构建脚本插件方式,并配合
GRADLE_ENTERPRISE_BUILD_CACHE_NODE 环境变量指定本地/远程缓存节点。这样可以确保扫描报告与缓存命中率数据关联同一构建会话。
3.3 构建扫描报告解读
扫描报告打开后默认显示“概要”标签页。以下逐一拆解每个区域的数据含义:
3.3.1 耗时阶段总览
- 总构建时间 (Total Build Time):从 Gradle 进程启动到构建完成的实际墙钟时间。如果大于 10 分钟,需要重点关注配置阶段或单任务耗时。
- 配置阶段 (Configuration):解析 build.gradle.kts 和 apply plugin 的时间。理想值应小于总时间的 5%。如果配置阶段 > 3 秒,应检查是否在配置阶段执行了耗时 I/O 操作。
- 依赖解析 (Dependency Resolution):解析仓库、下载 POM/Ivy 文件的时间。此阶段过长通常表明使用了过多动态版本或
changing标志。
3.3.2 缓存命中率与未命中原因
在“Task Execution”标签页中,可以查看每个任务的缓存状态:
| 状态标识 | 含义 | 排查方向 |
|---|---|---|
| FROM-CACHE | 直接从本地或远程缓存恢复 | ─ |
| UP-TO-DATE | 构建无需执行该任务(输入未变) | 检查是否是增量编译无法满足的场景 |
| EXECUTED / NOT-CACHEABLE | 任务被执行,且未命中缓存 | 任务未声明 @CacheableTask 或其输入中有非可缓存属性(如 outputDir 路径带有时间戳) |
| FROM-REMOTE-CACHE-MISS | 本地缓存未命中,尝试远程缓存也失败 | 远程缓存配置错误、网络超时或远程缓存键不匹配 |
常见误区:不要将
UP-TO-DATE 等同于缓存命中。 UP-TO-DATE 是由输出文件已存在且未过期决定,但可能跳过了缓存键校验。在分布式构建中应关注 FROM-CACHE 比例,而非 UP-TO-DATE 比例。
3.3.3 任务失败原因分析
当构建失败时,扫描报告会提供比控制台日志更精准的根因定位:
- Task 列表 → 点击红色失败任务:展开后可以看到执行异常堆栈以及具体的输入文件列表。
- 失败任务的输入哈希快照:如果两个构建的运行环境完全相同但一个失败一个通过,需要对比同任务输入文件的 SHA-256 是否一致。
- 测试任务失败时:报告提供 Test Summary 标签页,可直接查看所有失败的测试类与方法,无需 parse XML。
3.4 实际配置示例
以下是一个完整的 settings.gradle.kts 配置,包含 Gradle Enterprise 插件与构建缓存节点:
// settings.gradle.kts
plugins {
id("com.gradle.enterprise") version "3.16.2"
id("com.gradle.common-custom-user-data-gradle-plugin") version "2.0.1"
}
gradleEnterprise {
// 替换为你的 Gradle Enterprise 服务端地址
server = "https://enterprise.example.com"
buildScan {
// 首次使用扫描时,自动同意条款
termsOfServiceUrl = "https://gradle.com/terms-of-service"
termsOfServiceAgree = "yes"
// 发布构建时自动上传扫描
publishIfAuthenticated = true
publishAlwaysIf(System.getenv("CI") != null)
// 禁止上传敏感信息(可选)
uploadInBackground = false
value("BUILD_BRANCH", System.getenv("CI_COMMIT_BRANCH") ?: "local")
tag("CI") if (System.getenv("CI") != null)
}
}
buildCache {
local {
// 本地缓存目录
directory = File(rootDir, ".build-cache")
enabled = true
}
remote(HttpBuildCache::class.java) {
// 远程缓存节点(需要 Gradle Enterprise 配合)
url = uri("https://enterprise.example.com/cache/")
isPush = System.getenv("CI") != null
credentials {
username = System.getenv("GRADLE_ENTERPRISE_USER") ?: ""
password = System.getenv("GRADLE_ENTERPRISE_PASSWORD") ?: ""
}
}
}
调试技巧:在 CI 构建后,通过扫描报告的 Timeline 视图可以精确观察到每个任务从 SCHEDULED → EXECUTING 的等待时间。如果某个任务处于 SCHEDULED 状态超过 5 秒,说明该任务由于资源限制(如 Worker API 的 maxWorkers 过小)被阻塞;此时应增大
org.gradle.workers.max 或减少并行任务数。
3.5 常见错误与故障处理
| 错误现象 | 根因 | 解决方案 |
|---|---|---|
构建日志提示 --scan 不可用 | Gradle 版本低于 4.3 | 升级到 Gradle 6.0 以上以获得完整扫描支持 |
| 扫描上传失败,显示 403 | Gradle Enterprise 服务未授权或 过期 | 检查 gradleEnterprise.server URL 是否正确;重新生成 API |
| 报告中不显示缓存命中率 | 构建未启用构建缓存 | 在 gradle.properties 中添加 org.gradle.caching=true;或通过 --build-cache 临时启用 |
| 扫描报告一直处于“等待上传”状态 | 网络代理未配置或 DNS 解析失败 | 添加 JVM 代理参数:-Dhttp.proxyHost=... -Dhttp.proxyPort=... |
3.6 性能与稳定性优化建议
- 数据采样:在每日构建或 CI 主干构建上始终启用扫描,但在开发者本地按需通过
--scan启用,避免大规模上传对 GitLab/Runner 网络造成压力。 - 持久化对比:利用 Gradle Enterprise 的对比功能,将同一构建的两个扫描散列(如构建 A 的缓存命中率为 40%,构建 B 为 85%)的差异展示出来,找出缓存失效任务。
- 扫描报告链接输出:确保 CI 步骤在构建完成后将扫描报告 URL 暴露给开发人员:
# Jenkins / GitLab CI 示例
./gradlew build --scan | tee build.log
# 从日志提取扫描 URL
SCAN_URL=$(grep -oP 'https?://[^\s]+' build.log | grep "s/" | head -1)
echo "构建扫描报告: $SCAN_URL"
高级用法:在 Gradle Enterprise 服务端的“构建对比”界面中,可以打开两个不同构建版本的扫描。系统会高亮显示输入文件哈希不同的 Task、配置阶段的耗时差异,以及依赖解析版本的变化情况。这是定位“相同代码、不同机器构建结果不同”问题的首选工具。