5Software Engineering at Google

构建系统

构建基础设施

构建系统依赖管理构建缓存单体仓库

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第5章:构建系统(Build System)

一、导读

1.1 本章学习目标

第5章讲解 Google 的构建系统——单体仓库、构建缓存、依赖管理。

本章的核心学习目标包括:

  • 理解单体仓库的优势和挑战
  • 掌握构建缓存的原理
  • 学会管理大规模依赖

1.2 为什么本章如此重要

构建系统影响每个工程师的日常效率。Google 的单体仓库包含数十亿行代码,构建系统必须足够快、足够可靠。

1.3 核心问题引导

单体仓库和多仓库各有什么优缺点?

构建缓存如何加速构建?

如何管理大规模依赖?

构建系统应该提供什么保证?


二、核心概念详解

2.1 单体仓库(Monorepo)

Google 的单体仓库:
- 所有代码在一个 Git 仓库
- 数十亿行代码
- 数万名工程师同时提交

优势:
1. 代码共享:直接引用,无需发布
2. 原子变更:跨项目修改一次提交
3. 统一工具:一套工具服务所有人
4. 代码可见:任何人可以查看任何代码

挑战:
1. 仓库大小:需要特殊工具处理
2. 构建速度:需要强大的缓存
3. 权限管理:细粒度的权限控制
4. 分支策略:几乎所有开发在主分支

2.2 构建缓存

构建缓存原理:

1. 内容寻址缓存
   - 用输入的哈希作为缓存键
   - 相同输入 = 相同输出
   - 缓存可以跨机器共享

2. 增量构建
   - 只重新构建变化的部分
   - 依赖图追踪变化传播
   - 未变化的部分使用缓存

3. 远程缓存
   - 构建结果存储在远程服务器
   - 不同工程师共享缓存
   - CI 和本地构建共享缓存

// Bazel 构建示例
// 只有 src/main.go 变化时,只重新编译这个目标
go_binary(
    name = "server",
    srcs = ["main.go"],
    deps = ["//pkg/http", "//pkg/db"],
)

2.3 依赖管理

Google 的依赖管理:

1. 内部依赖
   - 直接引用源码
   - 无需版本管理
   - 修改一起提交

2. 外部依赖
   - 集中管理(Vendoring)
   - 所有项目使用相同版本
   - 升级需要全仓库测试

3. 依赖升级策略
   - 自动化升级工具
   - 渐进式升级
   - 兼容性测试

// 依赖升级流程:
// 1. 更新外部依赖版本
// 2. 运行全仓库测试
// 3. 修复不兼容的代码
// 4. 一次性提交

三、实战案例

3.1 案例一:Bazel 构建配置

python
# BUILD 文件
load("@rules_go//go:def.bzl", "go_library", "go_test")

go_library(
    name = "order",
    srcs = [
        "order.go",
        "order_line.go",
    ],
    importpath = "example.com/order",
    visibility = ["//visibility:public"],
    deps = [
        "//pkg/money",
        "//pkg/event",
    ],
)

go_test(
    name = "order_test",
    srcs = ["order_test.go"],
    embed = [":order"],
)

四、本章小结

单体仓库:代码共享、原子变更、统一工具。

构建缓存:内容寻址、增量构建、远程缓存。

依赖管理:内部直接引用、外部集中管理。


五、思考与练习

单体仓库适合你的团队吗?

如何为你的项目实现构建缓存?

如何管理外部依赖的升级?


5/8构建系统
0%