一.make/makefile基本规则
makefile文件中定义了一系列的规则来指定, 哪些文件需要先编译, 哪些文件需要后编译, 哪些文件需要重新编译, 甚至于进行更复杂的功能操作, 因为makefile就像一个Shell脚本一样, 其中也可以执行操作系统的命令. makefile带来的好处就是——“自动化编译”, 一旦写好, 只需要一个make命令, 整个工程完全自动编译, 极大的提高了软件开发的效率。
make/makefile:项目的自动化构建工具
Makefile:是一个文本文件,记录一个项目的构建规则流程
makefile的编写规则:
目标对象:依赖对象
make:是一个解释程序对makefile中记录的构建规则流程逐步解释执行,完成项目的构建
make的解释执行规则:
1.在命令行中敲击make指令,则表示运行make解释程序,程序会在当前目录下找到名称为makefile/Makefile的文件,解释执行其中的项目。
2.在规则中,找到要生成的第一个目标对象, (判断目标对象是否已经存在, 存在的话是否需要重新生成—根据原码文件的最后一次修改时间执行对象生成。
3. make每次在makefile中只会找到第一个目标对象进行生成,生成之后就会退出(也就是说不会去生成第二个对象)。
4. make在生成目标对象的时候,会先查找依赖对象的生成规则,先生成依赖对象,然后再去生成目标对象。
二、Makefile工作原理
在一目录下有几个如下所示的.c文件,然后我们用makefile进行编译
[root@localhost mfe]# ls
fun1.c fun2.c main.c sum.c head.h
fun1.c
1 #include <stdio.h>
2 void fun1()
3{
4 printf("this is fun1\n");
5}
fun2.c
1 #include <stdio.h>
2void fun2()
3{
4 printf("this is fun2\n");
5 }
main.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <unistd.h>
5 #include <sys/types.h>
6 #include "head.h"
7
8 int main(int argc, char *argv[])
9 {
10 printf("this is main!\n");
11 int i = 0;
12 for(i=0; i<argc; i++)
13 {
14 printf("[%d]:[%s]\n", i, argv[i]);
15 }
16
17 fun1();
18 fun2();
19
20 printf("sum(10)==[%d]\n", sum(10));
21
22
23 return 0;
24 }
sum.c
1 #include <stdio.h>
2
3 int sum(int len)
4 {
5 int i = 0;
6 int sum = 0;
7 for(i=0; i<len; i++)
8 {
9 sum += i;
10 }
11
12 return sum;
13 }
head.h
1 void fun1();
2 void fun2();
3 int sum(int len);
1.第一个版本
首先在Linux输入如下命令,打开一个makefile
[root@localhost mfe]# vim makefile
打开makefile后输入如下命令
main:main.c fun1.c fun2.c sum.c
gcc main -o main.c fun1.c fun2.c sum.c head.h
然后在命令终端输入make、
[root@localhost mfe]# make
gcc -o main main.c fun1.c fun2.c sum.c
可以看到成功生成了main文件,然后在运行一下
[root@localhost mfe]# ./main
this is main!
[0]:[./main]
this is fun1
this is fun2
sum(10)==[45]
这是一种最为直接的方法,main是目标对象,依赖于main.c,fun1.c,fun2.c,sum.c,但这种方法效率极低,需要把所有的文件名称都输入而且要输入两遍,当文件比较多时,这种编译方法显然不太可取。
2.第二种方法
下面将采取另一种方法,并有如下定义
预定义变量的使用:$@ $^ $ < 用于指令中
$@表示目标对象
$^表示所有依赖对象
$<表示依赖对象的第一个
因此我们对makefile文件用如上命令改编一下
main:main.c fun1.c fun2.c sum.c
gcc $^ -o $@
重新运行一下
[root@localhost mfe]# make
make: “main”是最新的。
[root@localhost mfe]# ls
fun1.c fun2.c head.h main main.c makefile sum.c head.h
[root@localhost mfe]# ./main
this is main!
[0]:[./main]
this is fun1
this is fun2
sum(10)==[45]
“main”是最新的说明我们成功生成了新的目标对象,这种方法最起码让我们少输入一遍,但还是得把文件名全部输入一遍,因此下面对目标对象:依赖对象进行一个改动
3.第三个版本
makefile中的函数有很多, 在这里介绍两个最常用的。
1.wildcard – 查找指定目录下的指定类型的文件
src=
(
w
i
l
d
c
a
r
d
∗
.
c
)
/
/
找
到
当
前
目
录
下
所
有
后
缀
为
.
c
的
文
件
,
赋
值
给
s
r
c
2.
p
a
t
s
u
b
s
t
–
匹
配
替
换
o
b
j
=
(wildcard *.c)//找到当前目录下所有后缀为.c的文件,赋值给src 2.patsubst – 匹配替换 obj=
(wildcard∗.c)//找到当前目录下所有后缀为.c的文件,赋值给src2.patsubst–匹配替换obj=(patsubst %.c,%.o,
(
s
r
c
)
)
/
/
把
s
r
c
变
量
里
所
有
后
缀
为
.
c
的
文
件
替
换
成
.
o
在
m
a
k
e
f
i
l
e
中
所
有
的
函
数
都
是
有
返
回
值
的
。
当
前
目
录
下
有
m
a
i
n
.
c
f
u
n
1.
c
f
u
n
2.
c
s
u
m
.
c
s
r
c
=
(src)) //把src变量里所有后缀为.c的文件替换成.o 在makefile中所有的函数都是有返回值的。 当前目录下有main.c fun1.c fun2.c sum.c src=
(src))//把src变量里所有后缀为.c的文件替换成.o在makefile中所有的函数都是有返回值的。当前目录下有main.cfun1.cfun2.csum.csrc=(wildcard *.c) 等价于src=main.c fun1.c fun2.c sum.c
obj=$(patsubst %.c,%.o, $(src))等价于obj=main.o fun1.o fun2.o sum.o
src=$(wildcard ./*./c)//获取当前目录下所有以.c结尾的文件名称
main:$(src)//目标对象:依赖对象
gcc $^ -o $@
[root@localhost mfe]# make
make: “main”是最新的。
[root@localhost mfe]# ls
fun1.c fun2.c head.h main main.c makefile sum.c head.h
[root@localhost mfe]# ./main
this is main!
[0]:[./main]
this is fun1
this is fun2
sum(10)==[45]
4.第四个版本
第三个版本虽然不需要我们输入所有文件名称,但仍然不是效率最高的,假如其中某一个文件发生改变,则需要对所有.c文件进行编译,因为目标对象依赖的是全部.c文件,例如main.c发生如下改变
//main.c文件
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <unistd.h>
5 #include <sys/types.h>
6 #include "head.h"
7
8 int main(int argc, char *argv[])
9 {
10 printf("this is main!\n");
11 int i = 0;
12 for(i=0; i<argc; i++)
13 {
14 printf("[%d]:[%s]\n", i, argv[i]);
15 }
16
17 fun1();
18 fun2();
19
20 printf("sum(10)==[%d]\n", sum(10));
21 printf("hello bit\n");
22 return 0;
23 }
[root@localhost mfe]# make
gcc fun1.c fun2.c sum.c main.c -o main head.h
[root@localhost mfe]# ./main
this is main!
[0]:[./main]
this is fun1
this is fun2
sum(10)==[45]
hello bit
需要编译所有.c文件!
5.第五个版本
因此我们可以在项目构建中分两步走——编译与链接
这样的好处是:如果只是修改一个.c文件,以前直接对所有.c进行编译生成可执行程序的过程来说,需要重新编译.c生成可执行程序——效率低下
因此分为两步进行操作,先把每个.c生成自己的.o,然后再把所有的.o链接到一起,好处是一旦一个.c发生改变,只需对一个.c进行编译,生成.o之后重新链接一次就可以生成可执行程序,其他.c不需要重新进行编译。
程序如下
src=$(wildcard ./*.c)
2 obj=$(patsubst %.c,%.o,$(src))
3 main:$(obj)
4 gcc $^ -o $@
5 %.o:%.c
6 gcc -c $< -o $@
运行过程
[root@localhost mfe]# make
gcc -c fun1.c -o fun1.o
gcc -c fun2.c -o fun2.o
gcc -c sum.c -o sum.o
gcc -c main.c -o main.o
gcc fun1.o fun2.o sum.o main.o -o main
[root@localhost mfe]# ./main
this is main!
[0]:[./main]
this is fun1
this is fun2
sum(10)==[45]
6.第六个版本
makefile的清理操作
用途: 清除编译生成的中间.o文件和最终目标文件
make clean 如果当前目录下有同名clean文件,则不执行clean对应的命令, 解决方案:
伪目标声明:
.PHONY:clean:声明一个目标对象与与外部文件无关,表示每次这个对象最终都要重新生成
对版本5补充一个清理操作就大功告成了。
1 src=$(wildcard ./*.c)
2 obj=$(patsubst %.c,%.o,$(src))
3 main:$(obj)
4 gcc $^ -o $@
5 %.o:%.c
6 gcc -c $< -o $@
7 .PHONY:clean
8 clean:
9 rm -rf $(obj) main
[root@localhost mfe]# rm -r main
rm:是否删除普通文件 "main"?y
[root@localhost mfe]# make
gcc fun1.o fun2.o sum.o main.o -o main
[root@localhost mfe]# ./main
this is main!
[0]:[./main]
this is fun1
this is fun2
sum(10)==[45]