Makefile 是一个用于管理软件构建过程的工具,它定义了一系列规则(Rules),说明了如何从源文件(.c, .cpp 等)生成目标文件(可执行文件、库文件等)。make 命令则读取 Makefile 并执行这些规则。

核心概念:规则
Makefile 的核心是规则,一个规则的基本结构如下:
target: prerequisites
command
target(目标):通常是要生成的文件名,比如可执行文件my_program或目标文件main.o,它也可以是一个动作的名称,clean。prerequisites(依赖):生成target所需的文件,如果依赖比目标文件“新”,command就会被执行。command(命令):用于从依赖文件生成目标文件的 shell 命令。注意:每个命令行前面必须按一个Tab键,而不是空格。
工作流程:
make首先会检查target文件是否存在。- 如果不存在,或者任何一个
prerequisites文件的修改时间比target文件新,make就会执行command来更新target。 target存在并且所有依赖都比它旧,make什么也不做,并报告“'target' is up to date”。
make 命令的基本用法
make 命令本身非常简单,它会默认在当前目录下寻找名为 Makefile 或 makefile 的文件。
默认执行
make
- 如果不指定任何目标,
make会查找 Makefile 中的第一个目标(也称为终极目标或默认目标)并尝试构建它。
指定目标
make <target_name>
- 你可以显式地告诉
make去构建哪个目标,这是最常用的方式。 make my_program会尝试构建名为my_program的目标。
查看所有可用目标
make -n # 或者 make --dry-run
- 这个选项会显示
make将要执行的命令,但不会真正执行它们,这对于调试 Makefile 非常有用。
强制重新构建
make -B # 或者 make --always-make
- 这个选项会强制重新构建所有指定的目标,忽略文件的修改时间戳。
输出详细信息
make -p # 或者 make --print-data-base
- 这个选项会打印出
make内部所有的数据库信息,包括所有变量、规则和模式规则,对于理解复杂的 Makefile 或调试非常有帮助。
忽略错误继续执行
make -i # 或者 make --ignore-errors
- 如果某个命令执行失败(返回非零状态码),
make默认会停止。-i选项会忽略错误,继续执行后续命令。
忽行所有命令,只检查依赖
make -q # 或者 make --question
- 这个选项会检查目标是否需要更新,但不执行任何命令。
- 如果目标是最新的,它会返回 0;如果需要更新,它会返回 1,这对于脚本检查非常有用。
一个简单的 Makefile 示例
让我们来看一个经典的 C 语言项目示例。

项目结构:
my_project/
├── main.c
├── utils.c
├── utils.h
└── Makefile
-
main.c:#include <stdio.h> #include "utils.h" int main() { int result = add(5, 3); printf("The result is: %d\n", result); return 0; } -
utils.h:#ifndef UTILS_H #define UTILS_H int add(int a, int b); #endif
-
utils.c:
(图片来源网络,侵删)#include "utils.h" int add(int a, int b) { return a + b; }
Makefile
# 定义变量,方便维护
CC = gcc
CFLAGS = -Wall -g
TARGET = my_program
SRC = main.c utils.c
OBJ = $(SRC:.c=.o)
# 默认目标(终极目标)
# 当只输入 make 时,这个目标会被构建
all: $(TARGET)
# 链接规则:从 .o 文件生成最终的可执行文件
$(TARGET): $(OBJ)
$(CC) $(OBJ) -o $(TARGET)
# 隐式规则:从 .c 文件生成 .o 文件
# $(OBJ): $(OBJ:.c=.c) <- 这行是隐含的,不需要显式写出
# 但我们可以写一个更明确的规则来处理 .h 文件
%.o: %.c utils.h
$(CC) $(CFLAGS) -c $< -o $@
# 清理规则:删除所有生成的文件
clean:
rm -f $(OBJ) $(TARGET)
# .PHONY 声明
# 告诉 make,clean 不是一个文件,而是一个伪目标
# 这样即使目录下存在一个名为 clean 的文件,make 也能正确执行这个规则
.PHONY: all clean
如何使用 make 命令:
-
首次构建项目
make
输出:
gcc -Wall -g -c main.c -o main.o gcc -Wall -g -c utils.c -o utils.o gcc main.o utils.o -o my_program -
再次执行
makemake
输出:
make: 'my_program' is up to date.因为所有源文件都没有被修改,
make知道my_program已经是最新的,无需重新编译。 -
修改一个源文件后执行
make修改utils.c中的add函数,然后再次运行:make
输出:
gcc -Wall -g -c utils.c -o utils.o gcc main.o utils.o -o my_programmake很聪明地发现只有utils.c被修改了,所以它只重新编译了utils.c,然后重新链接了main.o和新的utils.o。main.c没有被重新编译,这节省了大量时间。 -
执行
clean目标make clean
输出:
rm -f main.o utils.o my_program这会删除所有编译生成的中间文件和最终可执行文件。
常用 make 命令总结表
| 命令 | 简写 | 描述 |
|---|---|---|
make --help |
make -h |
显示帮助信息 |
make |
无 | 构建Makefile中的第一个目标(默认目标) |
make <target> |
无 | 构建指定的目标 |
make -n |
make --dry-run |
显示将要执行的命令,但不执行 |
make -B |
make --always-make |
强制重新构建所有目标 |
make -p |
make --print-data-base |
打印 make 的所有内部数据(变量、规则等) |
make -i |
make --ignore-errors |
忽略命令执行错误,继续执行 |
make -q |
make --question |
检查目标是否需要更新(不执行命令),返回状态码 |
make -f <file> |
make --file=<file> |
指定一个非默认名称的 Makefile 文件 |
make -j <N> |
make --jobs=<N> |
允许并行执行 N 个任务,加速编译 |
进阶技巧
- 变量:如示例中的
CC,CFLAGS,用于存储命令和选项,提高可维护性。 - 自动变量:
- 表示规则中的目标文件。
$<:表示规则中的第一个依赖文件。$^:表示规则中的所有依赖文件。- 表示所有比目标文件“新”的依赖文件。
- 表示匹配模式中的“%”部分。
- 函数:
make提供了丰富的函数,如wildcard(查找匹配文件)、patsubst(模式替换)、shell(执行shell命令) 等。 - 伪目标:使用
.PHONY关键字声明的目标,如.PHONY: clean all,它告诉make这个目标不代表一个实际文件,这样即使目录下有同名文件,make也会执行其命令。
掌握这些 make 命令和 Makefile 的基本概念,你就能高效地管理和自动化你的软件开发项目了。
