1. 课程导入

各位同学,欢迎来到《Makefile 系统实战》的第一章。

我先问大家一个问题:你平时是怎么编译代码的?

我猜,八成是直接在终端里敲 gcc main.c tool.c -o app。项目小的时候,这没什么问题。但一旦文件多起来——几十个、上百个源文件——每次改一行代码都要重新敲一遍命令,或者按一下方向键上翻历史,是不是有点烦?

更头疼的是,你只改了 tool.c,却要把所有文件重新编译一遍。一个项目编译一次要 5 分钟,一天改 20 次,那就是 100 分钟白白浪费掉了。

嗯,这就是我们为什么要学 Makefile 的原因。

1.1 什么是 Makefile?

说白了,Makefile 就是一个文本文件。它告诉计算机:你要构建什么、用什么工具构建、以及哪些文件需要重新构建。

你可以把它理解成一张“施工图纸”。图纸上画清楚了:

  • 最终要造出什么(目标)
  • 需要哪些原材料(依赖)
  • 怎么造(命令)

Make 工具就是那个“包工头”。它拿着图纸,按步骤干活。而且它很聪明——只有原材料变了,它才重新造那一部分。

我的习惯: 我每接手一个新项目,第一件事就是写 Makefile。哪怕只有两个源文件,我也会写。为什么?因为养成习惯后,项目大了才不会手忙脚乱。

1.2 为什么需要 Make?

你可能会想:“我手动编译也挺快的啊,有必要学这个吗?”

我跟你讲一个我踩过的坑。几年前我做嵌入式开发,一个项目有 200 多个 .c 文件。我那时候刚入行,不懂 Makefile,每次改完代码就执行一个 shell 脚本,把全部文件重新编译一遍。编译一次要 8 分钟。

有一天我改了一个头文件里的宏定义,然后去泡了杯咖啡。回来发现编译还在跑。那天我改了 6 次,浪费了将近 1 个小时在等编译。

后来我花了一个下午写了个 Makefile,从那以后,增量编译只需要十几秒。你想想看,这效率差距有多大。

Make 的核心价值就三点:

价值 说明
自动化 一条 make 命令搞定所有编译、链接、清理工作
增量编译 只重新编译修改过的文件,节省大量时间
可重复 换一台机器,只要 Makefile 在,构建结果一致

1.3 Makefile 的核心思想:目标、依赖、命令

Makefile 的规则其实就一句话:

目标: 依赖1 依赖2 ...
    命令1
    命令2
    ...

这三个要素,缺一不可。我画了一张图,帮你理解它们的关系:

Makefile 核心三要素 目标 (Target) 要生成的文件(如 app) 依赖 (Prerequisites) 原材料(如 .c / .h 文件) 命令 (Recipe) 如何构建(如 gcc -o app) Make 检查依赖的时间戳,如果依赖比目标新,就执行命令重新生成目标

我来解释一下这张图:

  • 目标:就是你要生成的东西。通常是一个可执行文件、一个 .o 文件,或者一个伪目标(比如 clean)。
  • 依赖:构建目标所需要的文件。如果依赖文件比目标文件新,Make 就知道“哦,需要重新编译了”。
  • 命令:如何从依赖生成目标。注意,命令前面必须是一个 Tab 键,不是空格。这是新手最容易犯的错误。
我曾经踩过的坑: 刚学 Makefile 时,我用了 4 个空格代替 Tab,结果 Make 报错说“missing separator”。我查了半小时才找到原因。记住:命令行必须以 Tab 开头,这是硬性规定。

1.4 第一个 Makefile 示例

光说不练假把式。我们直接上手写一个。

假设你有两个源文件:

// main.c
#include <stdio.h>
void hello();
int main() {
    hello();
    return 0;
}

// hello.c
#include <stdio.h>
void hello() {
    printf("Hello, Makefile!\n");
}

传统编译方式:

gcc -c main.c -o main.o
gcc -c hello.c -o hello.o
gcc main.o hello.o -o app

现在,我们写一个 Makefile:

app: main.o hello.o
    gcc main.o hello.o -o app

main.o: main.c
    gcc -c main.c -o main.o

hello.o: hello.c
    gcc -c hello.c -o hello.o

clean:
    rm -f app main.o hello.o

把这个文件保存为 Makefile(注意大小写,首字母大写 M),然后执行:

$ make
gcc -c main.c -o main.o
gcc -c hello.c -o hello.o
gcc main.o hello.o -o app

看到没?一条 make 命令,自动完成了三步操作。

如果你再执行一次 make:

$ make
make: 'app' is up to date.

因为所有文件都没变,Make 直接告诉你“已经是最新的了”。

如果你修改了 hello.c,再执行 make:

$ make
gcc -c hello.c -o hello.o
gcc main.o hello.o -o app

它只重新编译了 hello.c,然后重新链接。main.o 没变,所以跳过了。这就是增量编译的魅力。

关键点总结:
  • Makefile 由一条条规则组成
  • 每条规则包含:目标、依赖、命令
  • 命令前必须是 Tab,不是空格
  • Make 通过比较文件时间戳决定是否重新编译

1.5 本章小结

这一章我们搞清楚了:

  • Makefile 是“施工图纸”,Make 是“包工头”
  • 核心三要素:目标、依赖、命令
  • 增量编译是 Make 最大的价值
  • 第一个 Makefile 示例,你已经能用了

说实话,这个例子虽然简单,但它包含了 Makefile 的全部核心思想。后面 29 章,我们都是在给这个骨架添加血肉——变量、函数、条件判断、自动推导、多目录管理……

但万变不离其宗。只要你把“目标、依赖、命令”这三个概念刻在脑子里,后面的内容学起来会非常顺畅。