3. 构建扫描的接入与解读

构建扫描(Build Scan)是 Gradle 构建性能分析的基石。通过 Gradle Enterprise 提供的服务端或免费的 --scan 选项,可以将一次构建的完整任务执行时间、缓存命中率、依赖解析细节、任务失败原因等关键指标上传至可交互的 Web 页面。本章将完整覆盖扫描的三种接入方式、报告的逐段解读方法,以及常见接入故障的排错流程。

3.1 构建扫描的核心价值与数据流

构建扫描不仅仅是一个日志聚合器,它在构建性能优化中承担数据采集与可视化职责。扫描报告包含以下不可替代的数据维度:

  • 耗时分布:每个 Task 的启动与结束时间、配置阶段耗时、依赖解析阶段耗时。
  • 缓存状态:构建缓存命中/未命中/从远程拉取次数,TOCTOU 竞争导致的缓存失效原因。
  • 失败根因:Task 失败的具体异常堆栈、失败时的输入文件哈希快照。
  • 依赖图:版本冲突详情、动态依赖解析的版本锁定点。
关键原则:构建扫描的统计分析必须基于可复现的构建。只有当构建环境与输入文件一致时,扫描报告中的时间数据和缓存数据才对优化有直接指导意义。
构建扫描数据流 构建执行 --scan 标记 Gradle Enterprise Agent 上传到服务端 解析 & 索引 生成 Web 报告 开发者查看 缓存分析优化 提示:免费版的扫描报告有效期为 7 天;Gradle Enterprise 私有部署可永久保留并支持历史趋势分析。

3.2 接入方式详解

根据使用场景,构建扫描可以通过以下三种方式启用:

接入方式适用环境配置/命令备注
命令行 --scan本地调试、CI 一次性构建
./gradlew build --scan
无需修改构建脚本;回车后选择是否同意条款
构建脚本插件持续集成、团队统一配置
// settings.gradle.kts
plugins {
    id("com.gradle.enterprise") version "3.16.2"
}
gradleEnterprise {
    server = "https://enterprise.yourcompany.com"
}
需要 Gradle Enterprise 服务端 URL
环境变量控制远程容器、免交互构建
export GRADLE_ENTERPRISE_URL=https://enterprise.yourcompany.com
./gradlew build
免交互上传;仅适用 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 任务失败原因分析

当构建失败时,扫描报告会提供比控制台日志更精准的根因定位:

  1. Task 列表 → 点击红色失败任务:展开后可以看到执行异常堆栈以及具体的输入文件列表。
  2. 失败任务的输入哈希快照:如果两个构建的运行环境完全相同但一个失败一个通过,需要对比同任务输入文件的 SHA-256 是否一致。
  3. 测试任务失败时:报告提供 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 以上以获得完整扫描支持
扫描上传失败,显示 403Gradle 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、配置阶段的耗时差异,以及依赖解析版本的变化情况。这是定位“相同代码、不同机器构建结果不同”问题的首选工具。