构建(build)=把源码变成可运行产品的全部步骤(编译、依赖、打包、资源处理),不止编译。make 是构建自动化的祖师爷:靠依赖关系+时间戳只重做该重做的事。现代语言各有新工具,思想一脉相承。
上一节:E-R 图与 DFD 图 | 下一节:类、实例与对象
一、构建 ≠ 编译
教材开宗明义:编译只是构建的一环。构建还包含:拉取依赖库、处理资源文件、跑代码生成、打包压缩、输出版本号。手敲命令做构建的问题:步骤多、顺序敏感、漏一步就是【在我机器上明明好的】。所以构建必须自动化——写成脚本,一条命令跑完全程。
二、make:依赖驱动的祖师爷
make 的世界观是一张依赖图:
# Makefile 语法:目标: 依赖 换行 TAB 命令
app: main.o utils.o # 最终产品 app 依赖两个目标文件
cc -o app main.o utils.o
main.o: main.c
cc -c main.c
utils.o: utils.c
cc -c utils.c
跑 make 时它逐个检查:目标的修改时间比依赖旧(或目标不存在)才重新执行命令。改了 utils.c,只重编 utils.o 和链接,main.o 原封不动——增量构建,大项目从十几分钟缩到几秒。时间戳是 make 的全部魔法,也是它的小坑:把系统时钟拨回去,make 就会犯迷糊。
教材里的 Ant(Java 世界的 XML 版 make)如今已被 Maven/Gradle 接棒——Ant 只定义【怎么做】,Maven/Gradle 还管【做之前先拉什么依赖】,是现代构建工具的标配。
三、现代工具全景
| 生态 | 工具 | 一句话 |
|---|---|---|
| C/C++ | make/CMake | CMake 生成 Makefile,解决跨平台 |
| Java | Maven/Gradle | 依赖+构建+发布一条龙 |
| JavaScript | npm | package.json 声明依赖,scripts 定制步骤 |
| Python | pip/uv+pyproject | 解释型语言的【构建】主要是打包与依赖管理 |
共同思想没变:声明式描述(要什么、依赖什么)+自动化执行(顺序、增量)。
四、动手 Demo:用 Python 模拟 make 的增量逻辑
import os, time
def make(targets):
"""模拟 make:目标不存在或比依赖旧 -> 重做"""
for target, deps, build in targets:
need = not os.path.exists(target) or any(
not os.path.exists(d) or os.path.getmtime(d) > os.path.getmtime(target)
for d in deps
)
print(f"{target}: {'重做' if need else '跳过(已是最新)'}")
if need:
time.sleep(0.01)
build()
open(target, "w").write("built")
# 场景:编译 main.c(被修改过)而 utils.c 没动
os.makedirs(".workbuddy/tmp_build", exist_ok=True)
src = ".workbuddy/tmp_build/utils.c"; obj = ".workbuddy/tmp_build/utils.o"
open(src, "w").write("int utils() { return 1; }")
open(obj, "w").write("old") # 旧产物
os.utime(src, (time.time() + 5, time.time() + 5)) # 源文件更新(时间戳后移)
make([
("main.o", [".workbuddy/tmp_build/main.c"], lambda: open(".workbuddy/tmp_build/main.c", "w").write("x")),
(obj, [src], lambda: open(obj, "w").write("new")),
])
沙箱实跑输出:
main.o: 重做
.workbuddy/tmp_build/utils.o: 重做
看到什么算成功:两个目标按【源比产物新就重做】的规则各自决策。
读结果:make 的判断只有一条——产物比源旧就要重做(时间戳比较)。第二次运行时产物已更新,两行全变【跳过】——这就是增量构建省时间的原理。(main.o 也重做是因为 main.c 尚不存在——目标文件缺失时 make 的行为,真实项目里先有源文件。)
五、30 秒实验
再跑一遍同样的 make(...)——输出变成两个【跳过(已是最新)】。什么都没改就什么都不做:这是构建工具与 CI(5-10)配合的效率来源。
六、常见误区
| 误区 | 真相 |
|---|---|
| 构建=点一下编译按钮 | 编译只是构建一环;依赖、打包、资源处理都是构建 |
| 手敲命令也一样 | 漏一步顺序错一步就是【在我机器上好的】;构建必须脚本化 |
| 增量构建的产物可以直接信 | 时间戳机制怕时钟错乱;CI 里常干脆全量构建保干净 |
自测三问(点击展开)
1. **构建和编译的关系?** 答:编译是构建的一环;构建还包括依赖管理、资源处理、打包等全部步骤。 2. **make 什么时候重做目标?** 答:目标不存在,或修改时间早于任一依赖——只重做该重做的(增量)。 3. **Maven/Gradle 比 Ant 强在哪?** 答:不只描述构建步骤,还自动管理依赖(拉库、版本冲突)。- ☐ 5-14 学习完成
相关笔记
- CI 与 CD —— 构建是流水线的第 2 岗
- 服务器 9.1-9.4 · 运营管理框架 —— 上线产品背后的运维视角
- ⌨️ 编程原理 · 索引 —— 全课路线图