⌨️ 第五章 从设计到测试
📄 本篇摘要

构建(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 强在哪?** 答:不只描述构建步骤,还自动管理依赖(拉库、版本冲突)。

相关笔记