Makefile初学需要掌握一个规则,两个函数,三个自动变量。
首先,了解一下基本原则。
(1)若想生成目标,检查规则中的依赖条件是否存在。如不存在,则寻找是否有规则用来生成该依赖文件。
(2)检查规则中的目标是否需要更新,必须先检查它的所有依赖。依赖中有任一个被更新,则目标必须更新。
可能到这有点懵,继续看下面。
目标:依赖条件
命令
注意命令前有一个tab缩进,不可省略。
举个例子,现文件夹mf下文件如下:
test.c文件内容如下:
#include
int main()
{
int a=8;
int b=4;
printf("a+b=%d\n",add(a,b));
printf("a-b=%d\n",minus(a,b));
}
add.c文件内容如下:
//add.c
int add(int value1,int value2)
{
return value1+value2;
}
minus.c文件内容如下:
//minus.c
int minus(int value1,int value2)
{
return value1-value2;
}
现在创建一个文件,为了使用下面的命令,文件名必须为makefile或者Makefile。
写入如下内容:
test:add.o minus.o test.o
gcc test.o add.o minus.o -o test
add.o:add.c
gcc -c add.c -o add.o
minus.o:minus.c
gcc -c minus.c -o minus.o
test.o:test.c
gcc -c test.c -o test.o
内容为四个规则,其实一点都不复杂,解读如下:
第一个规则
test:add.o minus.o test.o
gcc test.o add.o minus.o -o test
它的意思是test依赖于add.o、minus.o、test.o三个目标文件,使用命令gcc test.o add.o minus.o -o test生成。
但目前并不存在add.o、minus.o、test.o,所以要寻找另外的规则用来生成add.o、minus.o、test.o这三个依赖文件。
因此找到第二、三、四条规则,只拿其中第二条做例子:
add.o:add.c
gcc -c add.c -o add.o
add.o依赖add.c使用gcc -c add.c -o add.o命令生成,add.o又成为第一条规则的其中一条依赖。
再根据第三、四条规则,生成minus.o,test.o,这三条规则其实只是执行了预处理、编译、汇编,暂时还没有链接。之后第一条规则链接,最终生成test可执行程序。
上面不懂程序编译四步骤看下面这个:
动态库和静态库-CSDN博客
因此,可以理解基本原则的第一句话。
输入make命令,运行test结果:
现在解释第二句话。
假如说我们写的add函数过于简单,我们改动add.c。
add.c内容如下:
int add(int value1,int value2)
{
return value1+value2+100;
}
然后直接执行make:
我们发现,它只执行了 add.c的预处理、编译、汇编生成add.o文件,然后又和其他两个目标文件链接然后生成可执行程序。
另外两个目标文件并没有经历预处理、编译、汇编这三个过程,大量节省了系统资源和时间。
这是怎么做到的呢?
test:add.o minus.o test.o
gcc test.o add.o minus.o -o testadd.o:add.c
gcc -c add.c -o add.o
因为makefile发现add.c的文件最后改动时间晚于add.o文件,add.o依赖于add.c文件生成,那怎么能晚于add.c文件的最后改动时间呢,于是makefile重新执行这条规则。
然后就又发现test的其中add.o依赖文件的生成晚于test文件的生成,test文件依赖于add.o文件生成,那怎么能晚于add.o文件的生成时间呢,于是makefile重新执行这条规则。
现在可以理解基本原则第二句话。
其中要注意的是makefile默认将第一条规则认为是终极任务。上面任意一条和第一条规则位置相互调换,可能达不到所要实现的目标。
可以在makefile中写入如下的内容:
ALL:test
这将指定生成test为终极任务,而不用再管下面的顺序了。
1.wildcard
src=$(wildcard *.XXX)
找到当前目录下所有后缀为.XXX的文件,组成列表,赋值给src。
2.patsubst
obj=$(patsubst %.XXX1,%.XXX2,$(src))
将src变量里所有后缀为.XXX1的文件替换成后缀为.XXX2文件。
具体这俩函数有啥用,观察修改后的makefile:
src=$(wildcard *.c)
obj=$(patsubst %.c,%.o,$(src))
ALL:test
test:$(obj)
gcc $(obj) -o test
add.o:add.c
gcc -c add.c -o add.o
minus.o:minus.c
gcc -c minus.c -o minus.o
test.o:test.c
gcc -c test.c -o test.o
可以看出src=$(wildcard *.c)这一句相当于src=test.c add.c minus .c
而obj=$(patsubst %.c,%.o,$(src))相当于obj=test.o add.o minus.o。
然后make,运行test:
可以看出这俩函数就是个替换的作用,省的就是写各种.c、.o文件了。
另外可以在makefile文件加上:
clean:
-rm -rf $(obj) test
注意-rm前有一个tab。在命令行输入make clean就只会执行clean的规则,但输入make时忽略clean规则。可以看到我们删除.o文件和test文件。
但千万注意别把clean写错,把源文件删了。因此,防止删除源文件,可以在其后面加上-n会显示clean具体干了啥,但不会执行clean。如:
可以看出实际并未执行clean规则。
clean下面的命令前有一个“-”,这个表示删除不存在的文件时,不会报错。
例如:我们提前删除test.o,然后执行make clean。如下:
其实make clean是要删除test.o的,但当前目录已经没有了,得力于“-”,因此不会报错。
另外,千万要注意的是假如当前目录中有ALL或者clean的文件,则make或者make clean可能不能执行,它将ALL和clean当成一个文件,这时需要makefile加上:
.PHONY:clean ALL
$@:在规则的命令中,表示规则中的目标。
$<:在规则的命令中,表示规则中第一个条件。如果将该变量应用在模式规则中,它可将依赖条件列表中的依赖依次取出,套用模式规则。
$^:在规则的命令中,表示规则中的所有依赖条件,组成一个列表,以空格隔开,如果这个列表中有重复的项则消除重复项。
利用这三自动变量,将makefile内容改为:
src=$(wildcard *.c)
obj=$(patsubst %.c,%.o,$(src))
ALL:test
test:$(obj)
gcc $^ -o $@
add.o:add.c
gcc -c $< -o $@
minus.o:minus.c
gcc -c $< -o $@
test.o:test.c
gcc -c $< -o $@
然后输入make命令:
即便是这样,makefile仍有不足之处,因为如果程序扩展,比如说添加div.c源文件,加了一个除法的源文件,我们仍然需要手动的加一条规则:
div.o:div.c
gcc -c div.c -o div.o
针对该问题,模式规则应运而生。我们观察下面几个目标文件生成:
add.o:add.c
gcc -c $< -o $@
minus.o:minus.c
gcc -c $< -o $@
test.o:test.c
gcc -c $< -o $@
格式基本上是一致的。这时只需要这样:
%.o:%.c
gcc -c %< -o $@
用这一条规则可以替换上面三条规则,而且如果程序扩展,就在当前目录添加源文件,比如说上面的div.c,然后make即可。此时也能看出三个自动变量的作用了。
另外,如果想要指定另一条依赖的生成使用什么规则,可以使用静态模式规则:
$(obj):%.o:%.c
gcc -c %< -o $@
这样的意思就是对obj中.o文件都是从上面这条模式规则来的,而不是从另外的模式规则生成的。
最后,如果想要加一些可选项,例如我想要在编译时加上-g和-Wall选项。可以这样:
src=$(wildcard *.c)
obj=$(patsubst %.c,%.o,$(src))
myArgs=-Wall -g
ALL:test
test:$(obj)
gcc $^ -o $@ $(myArgs)
add.o:add.c
gcc -c $< -o $@ $(myArgs)
minus.o:minus.c
gcc -c $< -o $@ $(myArgs)
test.o:test.c
gcc -c $< -o $@ $(myArgs)