Makefile 使用指南
# 安装 Make
在 MacOS 的终端中输入
brew install make gcc
会自动安装相关工具
我们可以使用 make -v
来验证安装是否成功
(base) martian148@Mac ~ % make -v
GNU Make 3.81
Copyright (C) 2006 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
This program built for i386-apple-darwin11.3.0
2
3
4
5
6
7
8
# 基础操作
假设我们现在有 3 个文本文件 a.txt
,b.txt
,c.txt
现在,我们需要合并 a.txt
和 b.txt
生成中间文件 m.txt
,然后用 m.txt
和 c.txt
合并生成 x.txt

现在我们来编写 makefile
# 规则
编写的格式为
目标文件: 依赖文件1 依赖文件2
命令
2
在这里,我们生成 m.txt
的命令就是
m.txt: a.txt b.txt
cat a.txt b.txt > m.txt
2
同理,我们能得出生成 x.txt
的命令
x.txt: m.txt c.txt
cat m.txt c.txt > x.txt
2
根据 make 的默认第一条规则,就是认为 x.txt
为目标文件,所以需要吧 x.txt
放在前面,然后当运行发现缺少 m.txt
时,make 会自动去寻找后面有没有命令生成 m.txt
简单来说就是
你想要生成什么,那个目标就得放在第一条
# 伪目标
因为 m.txt
和 x.txt
都是自动生成的文件,我们可以使用 clean
命令来删除他们
我们需要编写 clean
命令
clean:
rm -f m.txt
rm -f x.txt
2
3
执行 make clean
后,m.txt
和 x.txt
都被删除了
但是如果我的目录中存在一个名字为 clean
的文件,make 会认为 clean 已经存在就不执行 make clean
的命令
这里就需要一个 .PHONY: clean
命令告诉 make:“clean”是一个伪目标,不是一个实际存在的文件,无论有没有叫 clean
的文件,都需要执行 make clean
命令
.PHONY: clean
clean:
rm -f m.txt
rm -f x.txt
2
3
4
# 使用 makefile 编译 C 程序
假设有一个 C 项目,包含 hello.c
、hello.h
和 main.c
// hello.c
#include <stdio.h>
int hello()
{
printf("hello, world!\n");
return 0;
}
2
3
4
5
6
7
8
9
// hello.h
int hello();
2
// main.c
#include <stdio.h>
#include "hello.h"
int main()
{
printf("start...\n");
hello();
printf("exit.\n");
return 0;
}
2
3
4
5
6
7
8
9
10
11
我们很容易得出逻辑图

我们很容易就能写出 Makefile
# 生成可执行文件:
world.out: hello.o main.o
cc -o world.out hello.o main.o
# 编译 hello.c:
hello.o: hello.c
cc -c hello.c
# 编译 main.c:
main.o: main.c hello.h
cc -c main.c
clean:
rm -f *.o world.out
2
3
4
5
6
7
8
9
10
11
12
13
14
执行 make
可以看到
$ make
cc -c hello.c
cc -c main.c
cc -o world.out hello.o main.o
2
3
4
运行 world.out
$ ./world.out
start...
hello, world!
exit.
2
3
4
如果我们修改 hello.c
中的内容为
#include <stdio.h>
int hello()
{
printf("hello, bob!\n");
return 0;
}
2
3
4
5
6
7
再次 make
$ make
cc -c hello.c
cc -o world.out hello.o main.o
2
3
会发现,没有再次编译 main.o
因为 main.o
的没有改变
# 使用隐式规则
其实在前面那个例子里面,我们把生成 .o
的规则都删掉,程序也能正常编译
# 生成可执行文件:
world.out: hello.o main.o
cc -o world.out hello.o main.o
clean:
rm -f *.o world.out
2
3
4
5
6
$ make
cc -c -o hello.o hello.c
cc -c -o main.o main.c
cc -o world.out hello.o main.o
2
3
4
其实是 make 内部自定义了一些规则,对于 .o
内置了
xyz.o: xyz.c
cc -c -o xyz.o xyz.c
2
就不用我们手写了
# 使用变量
我们在编写 makefile
的时候会重复写很多文件名,一来容易写错,二来如果要改名就要重新写一次
我们可以使用变量来解决反复引用的问题
看到我们之前的 makefile
world.out: hello.o main.o
cc -o world.out hello.o main.o
clean:
rm -f *.o world.out
2
3
4
5
其中 world.out
出现了三次,我们可以定义一个变量来替换他
TARGET = world.out
$(TARGET): hello.o main.o
cc -o $(TARGET) hello.o main.o
clean:
rm -f *.o $(TARGET)
2
3
4
5
6
7
变量定义用变量名 = 值
或者变量名 := 值
,通常变量名全大写。引用变量用$(变量名)
注意到hello.o main.o
这个“列表”也重复了,我们也可以用变量来替换:
OBJS = hello.o main.o
TARGET = world.out
$(TARGET): $(OBJS)
cc -o $(TARGET) $(OBJS)
clean:
rm -f *.o $(TARGET)
2
3
4
5
6
7
8
如果我们能用一种方法能自动识别当前目录下的所有 .c
文件,然后对应到 .o
最后赋值给 OBJS
就好了,实际上我们可以通过函数来做到这一点
# $(wildcard *.c) 列出当前目录下的所有 .c 文件: hello.c main.c
# 用函数 patsubst 进行模式替换得到: hello.o main.o
OBJS = $(patsubst %.c,%.o,$(wildcard *.c))
TARGET = world.out
$(TARGET): $(OBJS)
cc -o $(TARGET) $(OBJS)
clean:
rm -f *.o $(TARGET)
2
3
4
5
6
7
8
9
10
# 内置变量
我们可以使用 $(CC)
代替命令 cc
$(TARGET): $(OBJS)
$(CC) -o $(TARGET) $(OBJS)
2
系统内部默认了 $(CC)=cc
,但是我们也可以自定义他 $(CC)=gcc
# 自动变量
我们也可以用一些特殊意义的字符来代表一些含义,这些字符叫做自动变量
$@
表示目标文件$^
表示所有依赖文件$<
表示第一个依赖文件$?
所有比目标新的依赖文件$*
目标文件去掉拓展名,例如foo.o
的$*
是foo
前面的命令可以转化成为
world.out: hello.o main.o
cc -o $@ $^
2
为了更好的调试,我们可以把变量打印出来
world.out: hello.o main.o
@echo '$$@ = $@' # 变量 $@ 表示target
@echo '$$< = $<' # 变量 $< 表示第一个依赖项
@echo '$$^ = $^' # 变量 $^ 表示所有依赖项
cc -o $@ $^
2
3
4
5
gcc -c -o hello.o hello.c
gcc -c -o main.o main.c
$@ = world.out
$< = hello.o
$^ = hello.o main.o
cc -o world.out hello.o main.o
2
3
4
5
6
# 使用规则模式
当我们没有创建 .o
的生成规则的时候,makefile 会自动生成一条规则,对于 xyz.o xyz.c
他的规则如下
$(CC) $(CFLAGS) -c -o $@ $<
这里的变量 $(CC)
和 $(CFLAGS)
可以自己设定
$(CFLAGS)
是附加的编译指令
我们如果要自定义这个规则也是可以的
# 模式匹配规则:当make需要目标 xyz.o 时,自动生成一条 xyz.o: xyz.c 规则:
%.o: %.c
@echo 'compiling $<...'
cc -c -o $@ $<
2
3
4
通过这个指令,我告诉了计算机,如果出现了一个 .o
就需要自动匹配一个 .c
OBJS = $(patsubst %.c,%.o,$(wildcard *.c))
TARGET = world.out
$(TARGET): $(OBJS)
$(CC) -o $@ $^
%.o: %.c
$(CC) -c -o $@ $<
clean:
rm -f *.o $(TARGET)
2
3
4
5
6
7
8
9
10
11
运行 make
$ make
cc -c -o hello.o hello.c
cc -c -o main.o main.c
cc -o world.out hello.o main.o
2
3
4
模式规则的命令完全由我们自己定义,因此,它比隐式规则更灵活。
但是,模式规则仍然没有解决修改hello.h
头文件不会触发main.c
重新编译的问题
# 自动生成依赖
前面我们讲了隐式规则和模式规则,这两种规则都可以解决自动把.c
文件编译成.o
文件,但都无法解决.c
文件依赖.h
文件的问题。
因为一个.c
文件依赖哪个.h
文件必须要分析文件内容才能确定,没有一个简单的文件名映射规则。
但是,要识别出.c
文件的头文件依赖,可以用GCC提供的-MM
参数
在终端中运行
$ cc -MM main.c
main.o: main.c hello.h
2
一个简单的想法就是把对一个 .c
文件都生成一个依赖项,把它保存到 .d
中,然后再用 include
引入到 Makefile 相当于自动化匹配了 .c
的文件依赖
我们修改 Makefile
文件
# 列出所有 .c 文件:
SRCS = $(wildcard *.c)
# 根据SRCS生成 .o 文件列表:
OBJS = $(SRCS:.c=.o)
# 根据SRCS生成 .d 文件列表:
DEPS = $(SRCS:.c=.d)
TARGET = world.out
# 默认目标:
$(TARGET): $(OBJS)
$(CC) -o $@ $^
# xyz.d 的规则由 xyz.c 生成:
%.d: %.c
rm -f $@; \
$(CC) -MM $< >$@.tmp; \
# 这里
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.tmp > $@; \
rm -f $@.tmp
# 模式规则:
%.o: %.c
$(CC) -c -o $@ $<
clean:
rm -rf *.o *.d $(TARGET)
# 引入所有 .d 文件:
include $(DEPS)
.PHONY: echo
echo:
@echo "SRCS: $(SRCS)"
@echo "OBJS: $(OBJS)"
@echo "DEPS: $(DEPS)"
@echo "TARGET: $(TARGET)"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
- 我们先定义了
SRCS
OBJS
DEPS
,通过echo
命令可以看到这些变量的值
$ make echo
SRCS: hello.c main.c
OBJS: hello.o main.o
DEPS: hello.d main.d
TARGET: world.out
2
3
4
5
然后我们定义了
.d
的生成规则- 先删除
.d
- 通过
-MM
命令获得依赖,例如 :main.o: main.c hello.h
- 然后
's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.tmp >
命令通过函数把main.o : main.c hello.h
转化成main.o main.d : main.c hello.h
表面了main.o main.d
都依赖于main.c hello.h
- 最后删除
tmp
文件
- 先删除
include $(DEPS)
的意思就是- 把
$(DEPS)
里面列出的所有文件,当成 Makefile 的一部分,读进来。 - 就相当于在 makefile 里面写入了
main.o main.d : main.c hello.h
和hello.o hello.d : hello.c
- 把