一、makefile简介
makefile定义了一系列的规则来指定如何编译文件,makefile带来的好处就是"自动化编译”,一旦写好只需要一个make命令,整个工程就完全自动化编译,极大的提高了软件开发的效率;
make主要解决了两个问题:
1.大量代码的关系维护
2.减少重复编译时间
makefile: 管理项目。
命名:makefile Makefile --- make 命令
1 个规则 2 个函数 3 个自动变量
二、makefile的1个规则
目标:依赖条件
(一个tab缩进)命令
hello:hello.c
gcc hello.c -o hello
1.目标的时间必须晚于依赖条件的时间,否则,更新目标。
2.依赖条件如果不存在,找寻新的规则去产生依赖条件。
新建hello.c文件如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
int main(int argc,char *argv[])
{
printf("hello world\n");
return 0;
}
新建makefile如下:
hello:hello.o
gcc hello.o -o hello
hello.o:hello.c
gcc -c hello.c -o hello.o
在终端执行make命令生成hell.o文件及hello可执行文件,再执行以下命令得到输出:
./hello
out:
hello world
修改hello.c如下所示:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
int add(int ,int);
int sub(int ,int);
int div1(int ,int);
int main(int argc,char *argv[])
{
int a = 10;
int b = 5;
printf("%d + %d = %d\n",a,b,add(a,b));
printf("%d - %d = %d\n",a,b,sub(a,b));
printf("%d / %d = %d\n",a,b,div1(a,b));
return 0;
}
新建add.c、sub.c、div1.c、mul.c分别如下所示:
//add.c
int add(int a,int b)
{
return a+b;
}
//sub.c
int sub(int a,int b)
{
return a-b;
}
//div1.c
int div1(int a,int b)
{
return a/b;
}
首先通过多文件联合编译生成可执行文件a.out,执行以下命令:
gcc add.c sub.c div1.c -o a.out
再执行./a.out可得出结果
其次通过makefile直接生成可执行文件,在makefile中写入:
a.out:hello.c add.c sub.c div1.c
gcc hello.c add.c sub.c div1.c -o a.out
make之后打印:
gcc hello.c add.c sub.c div1.c -o a.out
再执行a.out同样可以得出结果
将add.c进行修改如下:
int add(int a,int b)
{
return a+b+1;
}
此时再次使用makefile,发现:只修改add.c,但是编译的时候,其他.c文件也重新编译了
明明只改了一个,全部都重新编译了,于是将makefile改写如下:
a.out:hello.c add.c sub.c div1.c
gcc hello.o add.o sub.o div1.o -o a.out
add.o:add.c
gcc -c add.c -o add.o
sub.o:sub.c
gcc - sub.c -o sub.o
div1.o:div1.c
gcc -c div1.c -o div1.o
hello.o:hello.c
gcc -c hello.c -o hello.o
并且修改div1.c如下:
int div1(int a,int b)
{
return a/b-1;
}
运行之后可以发现,仅仅是对div1.c重新编译
makefile检测原理:
- 修改文件后,文件的【修改时间】发生变化,会出现目标文件的时间早于作为依赖材料的时间,出现这种情况的文件会重新编译。
- 修改sub.c后,sub.o的时间就早于sub.c ,a.out的时间也早于sub.o的时间了,于是重新编译这俩文件了。
ALL来指定终极目标
- makefile的依赖是从上至下的,换句话说就是目标文件是第一句里的目标,如果不满足执行依赖,就会继续向下执行。如果满足了生成目标的依赖,就不会再继续向下执行了。
- make会自动寻找规则里需要的材料文件,执行规则下面的行为生成规则中的目标。
关于makefile指定目标问题,先修改makefile如下:
hello.o:hello.c
gcc -c hello.c -o hello.o
add.o:add.c
gcc -c add.c -o add.o
sub.o:sub.c
gcc - sub.c -o sub.o
div1.o:div1.c
gcc -c div1.c -o div1.o
a.out:hello.c add.c sub.c div1.c
gcc hello.o add.o sub.o div1.o -o a.out
执行make,得到如下结果:
gcc -c hello.c -o hello.o
这是因为,makefile默认第一个目标文件为终极目标,生成就跑路,这时候可以用ALL来指定终极目标,修改如下:
ALL:a.out
hello.o:hello.c
gcc -c hello.c -o hello.o
add.o:add.c
gcc -c add.c -o add.o
sub.o:sub.c
gcc - sub.c -o sub.o
div1.o:div1.c
gcc -c div1.c -o div1.o
a.out:hello.c add.c sub.c div1.c
gcc hello.o add.o sub.o div1.o -o a.out
三、makefile的两个函数和clean
src = $(wildcard *.c)
匹配当前工作用户下的所有.c文件。将文件名组成列表,赋值给变量src。
obj = $(patsubset %.c,%.o, $(src))
将参数3中,包含参数1的部分,替换成参数2。即把src变量里所有后缀为.c的文件替换成.o
模拟执行加了clean部分的makefile,如下所示:
src = $(wildcard *.c) #add.c sub.c div1.c hello.c
obj = $(patsubst %.c, %.o, $(src)) #add.o sub.c div1.o hello.o
ALL:a.out
hello.o:hello.c
gcc -c hello.c -o hello.o
add.o:add.c
gcc -c add.c -o add.o
sub.o:sub.c
gcc - sub.c -o sub.o
div1.o:div1.c
gcc -c div1.c -o div1.o
a.out:$(obj)
gcc $(obj) -o a.out
clean:
-rm -rf $(obj) a.out
rm前面的-
,代表出错依然执行。
删除不存在文件不加这-
,就会报错,告诉你有一个文件找不到。加了-
就不会因为这个报错。
由于没有文件变动,a.out的时间戳晚于所有依赖文件,这里make就没干活
于是,执行时加新指令,先模拟执行clean部分
make clean -n
需要删除则执行:
make clean
四、makefile3个自动变量和模式规则
3个自动变量:
$@ :在规则命令中,表示规则中的目标
$< :在规则命令中,表示规则中的第一个条件,如果将该变量用在模式规则中,它可以将依赖条件列表中的依赖依次取出,套用模式规则
$^ :在规则命令中,表示规则中的所有条件,组成一个列表,以空格隔开,如果这个列表中有重复项,则去重
用自动变量修改makefile,如下:
src = $(wildcard *.c) #add.c sub.c div1.c hello.c
obj = $(patsubst %.c, %.o, $(src)) #add.o sub.c div1.o hello.o
ALL:a.out
a.out:$(obj)
gcc $^ -o $@
add.o:add.c
gcc -c $< -o $@
sub.o:sub.c
gcc -c $< -o $@
div1.o:div1.c
gcc -c $< -o $@
hello.o:hello.c
gcc -c $< -o $@
clean:
-rm -rf $(obj) a.out
上面这个makefile,可扩展性不行。
比如,要添加一个乘法函数,就需要在makefile里面增加乘法函数的部分。不科学,所以,模式规则就来了
%.o:%.c
gcc -c $< -o $@
修改makefile如下:
src = $(wildcard *.c) #add.c sub.c div1.c hello.c
obj = $(patsubst %.c, %.o, $(src)) #add.o sub.c div1.o hello.o
ALL:a.out
a.out:$(obj)
gcc $^ -o $@
%.o:%.c
gcc -c $< -o $@
clean:
-rm -rf $(obj) a.out
继续优化makefile,使用静态模式规则,就是指定模式规则给谁用,这里指定模式规则给obj用,以后文件多了,文件集合会有很多个,就需要指定哪个文件集合用什么规则
$(obj):%.o:%.c
gcc -c $< -o $@
修改后makefile如下:
src = $(wildcard *.c) #add.c sub.c div1.c hello.c
obj = $(patsubst %.c, %.o, $(src)) #add.o sub.c div1.o hello.o
ALL:a.out
a.out:$(obj)
gcc $^ -o $@
$(obj):%.o:%.c
gcc -c $< -o $@
clean:
-rm -rf $(obj) a.out
当前文件夹下有ALL文件或者clean文件时,会导致makefile瘫痪,用伪目标来解决,添加一行:
.PHONY: clean ALL
以及编译时的参数,-g,-Wall, ...
这些,可以放在makefile里面:
src = $(wildcard *.c) #add.c sub.c div1.c hello.c
obj = $(patsubst %.c, %.o, $(src)) #add.o sub.c div1.o hello.o
myArgs = -Wall -g
ALL:a.out
a.out:$(obj)
gcc $^ -o $@
$(obj):%.o:%.c
gcc -c $< -o $@
clean:
-rm -rf $(obj) a.out
.PHONY: clean ALL
练习1:
- 源码add.c,sub.c这些在src目录下,.o文件要放在obj目录下,头文件head.h在inc目录下。
- 将hello.c中的头文件单独拿出来
新建src、obj、inc目录并放入相关文件,修改makefile如下,主要是注意%是匹配的意思,只匹配文件名,目录位置要手动添加
src = $(wildcard ./src/*.c) #./src/add.c ./src/sub.c ...
obj = $(patsubst ./src/%.c,./obj/%.o, $(src)) #./obj/add.o
inc_path = ./inc
myArgs = -Wall -g
ALL:a.out
a.out:$(obj)
gcc $^ -o $@ $(myArgs)
$(obj):./obj/%.o:./src/%.c
gcc -c $< -o $@ $(myArgs) -I $(inc_path)
clean:
-rm -rf $(obj) a.out
.PHONY:clean ALL
注意:
如果makefile的名字有变化,如,叫m6
用m6执行makefile, make -f m6
用m6执行clean make -f m6 clean
练习2:
将当前目录下所有.c文件编译为可执行程序
hello.c文件代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
int add(int ,int);
int sub(int ,int);
int div1(int ,int);
int mul(int ,int);
int main(int argc,char *argv[])
{
int a = 10;
int b = 5;
printf("%d + %d = %d\n",a,b,add(a,b));
printf("%d - %d = %d\n",a,b,sub(a,b));
printf("%d × %d = %d\n",a,b,mul(a,b));
printf("%d / %d = %d\n",a,b,div1(a,b));
return 0;
}
int add(int a,int b)
{
return a+b;
}
int sub(int a,int b)
{
return a-b;
}
int mul(int a,int b)
{
return a*b;
}
int div1(int a,int b)
{
return a/b;
}
其中add.c、sub.c、div1.c、mul.c分别如下结构所示:
//add.c
#include<stdio.h>
int add(int a,int b)
{
return a+b;
}
int main(void)
{
int a = 10; int b = 5;
printf("%d + %d = %d\n",a,b,add(a,b));
return 0;
}
makefile代码如下:
src = $(wildcard *.c)
target = $(patsubst %.c,%, $(src))
ALL:$(target)
%:%.c
gcc $< -o $@
clean:
-rm -rf $(target)
.PHONY:clean ALL
make之后,会生成add、sub、div1、mul、hello的可执行文件