1. 简介
简单来讲,Makefile定义了一种编译和链接的规则,它会根据定义的规则在当前文件夹的目录下寻找编译的目标以及其依赖文件。Makefie的好处是可以极大的方便开发者对工程的管理,尤其是对大型的工程项目起到了方便管理,提高开发效率的作用。在完成Makefile的编写后,用户只需一个make指令便可完成整个项目的编译工作。
2. Makefile的写法
#写法
target: prerequiries ....
command
- 目标(target)通常是要产生的文件的名称,目标的例子是可执行文件或OBJ文件。目标也可是一个执行的动作名称,诸如‘clean’(仅仅表达动作的目标称为假想目标)
- 依赖(prerequiries)是用来输入从而产生目标的文件,一个目标经常有几个依赖。
- 命令(command)是
make
执行的动作,一个规则可以含有几个命令,每个命令占一行
注意:每个命令行前面必须是一个Tab字符,即命令行第一个字符是Tab。这是不小心容易出错的地方。
3. make的工作流程
当我们在一个工程文件夹目录下执行make时,具体的流程如下:
- make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
- 如果找到,它会找文件中的第一个目标文件(target),并把这个文件作为最终的目标文件。
- 如果目标文件不存在,或是目标文件所依赖的后面的 .o 文件的文件修改时间要比目标文件这个文件新,那么,他就会执行后面所定义的命令来生成edit这个文件。
- 如果目标文件所依赖的.o文件也存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成.o文件。(这有点像一个堆栈的过程)
- 当然,你的C文件和H文件是存在的啦,于是make会生成 .o 文件,然后再用 .o 文件声明make的终极任务,也就是执行文件edit了。
这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。在寻找的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。make只管文件的依赖性,即,如果在我找了依赖关系之后,冒号后面的文件还是不在,那么对不起,我就不工作啦。
4. 特殊符号
自动变量 | 含义 |
---|---|
$@ | 目标集合 |
$% | 当目标是函数库文件时, 表示其中的目标文件名 |
$< | 第一个依赖目标,如果依赖目标是多个, 逐个表示依赖目标 |
$^ | 所有依赖目标的集合, 会去除重复的依赖目标 |
$+ | 所有依赖目标的集合, 不会去除重复的依赖目标 |
$* | 这个是GNU make特有的, 其它的make不一定支持 |
5. 举例
5.1 多文件编译为一个目标
比如我们现在有singleton.cpp, singleton.h, main.cpp 这三个文件,其中main.cpp需要调用singleton.cpp中的函数,最后需要生成一个main的目标文件,其Makefile的写法如下:
cc = g++
target = main
deps = $(shell find ./ -name "*.h")
src = $(shell find ./ -name "*.cpp")
obj = $(src:%.cpp=%.o)
$(target): $(obj)
$(cc) -o $(target) $(obj)
%.o: %.cpp $(deps)
$(cc) -c $< -o $@
.PHONY:clean
clean:
rm -rf $(obj) $(target)
说明:
- cc = g++ 这是一种赋值语句,有点类似c++中的宏定义,$(cc)就等于 g++.
- $(shell find ./ -name “.h") 这是执行shell命令 find ./ -name ".h”,意思是找出当前目录下所有以.h结尾的文件并输出
- $(src: %.cpp = %.o) 这是指将src中的.cpp都替换为.o, 举个例子:src = singleton.cpp main.cpp, 在obj = $(src:%.cpp=%.o)这条语句执行后,obj = singleton.o main.o
- $< 指第一个依赖目标,该句中指 %.cpp
- $@ 指目标集合,该局中指 %.o
5.2 多文件编译为多个目标
比如我们现在有server.c, client.c两个文件,分别要将其编译生成server和client两个目标文件,其Makefile的写法如下:
cc = gcc
src = $(shell find ./ -name "*.c")
obj = $(src:%.c=%.o)
target1 = client
target2 = server
all:$(target1) $(target2)
$(target1):client.o
$(cc) $^ -o $@ -lpthread #因为源文件用到了pthread.h, 而gcc默认不带这个库,所以需要加上-lpthread使其能找到pthread.h
$(target2):server.o
$(cc) $^ -o $@ -lpthread
%.o:%.c
$(cc) -c $< -o $@ -lpthread
.PHONY:clean #表示clean是个伪造的目标,不是文件
clean:
rm $(obj) $(target1) $(target2)