- 会不会写
makefile
,从一个侧面说明了一个人是否具备完成大型工程的能力- 一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,
makefile
定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作makefile
带来的好处就是——“自动化编译”,一旦写好,只需要一个make
命令,整个工程完全自动编译,极大的提高了软件开发的效率。make
是一个命令工具,是一个解释makefile
中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi
的make
,Visual C++
的nmake
,Linux
下GNU
的make
。可见,makefile都成为了一种在工程方面的编译方法。- make是一条命令,
makefile
是一个文件,两个搭配使用,完成项目自动化构建。
我们使用vim编辑器创建了test.c
文本文件编译了如下代码:
#include
int main()
{
printf("hello Makefile\n");
return 0;
}
输入指令:
gcc -o mytest test.c//编译允许代码
这个编译运行可执行程序过程还算简单,那如果需要代码代码预处理、编译、汇编、链接生成可执行程序等等过程的时候,那么就需要输入如下指令:
gcc -E test.c -o test.i #预处理
gcc -S test.i -o test.s #编译
gcc -c test.s -o test.o #汇编
gcc test.o -o test #生成可执行程序
./test #找到对应路径下可执行程序运行
使用完之后,或者发生错误,需要删除文件
rm -rf test.i test.s test.o test
上面的指令输入一次还算简单,假设你的代码需要多次修改,就需要重复输入预处理、编译、汇编、链接、删除指令,会很麻烦!这时候我们就需要使用到
Makefile
文件/makefile
文件(首字母m
大小写都可以)。
使用vim
编辑器创建Makefile
文件,向里面编入如下指令
test:test.o
gcc test.o -o test
test.o:test.s
gcc -c test.s -o test.o
test.s:test.i
gcc -S test.i -o test.s
test.i:test.c
gcc -E test.c -o test.i
clean:
rm -rf test test.o tes.s test.i
- ①
test:test.o
为依赖关系,即test文件生成需要依赖于test.c文件该指令写在行首;gcc test.o -o test
为依赖方法,通过gcc
指令处理test.o
文件得到test
文件,该指令前有一个tab
键的空格;所以必须要有test.o
文件,而test.o
文件的生成依赖于test.s
文件,test.s
文件依赖于test.i文件,test.i
文件依赖于test.c
文件,依赖关系后面伴随着依赖(实现)方法,由此可以看出test
文件需要依靠层层依赖关系以及依赖方法才能得到;(像递归一样)- ②而clean指令后面为空格,无依赖关系表明clean不依赖于任何文件、指令,同时有自己的依赖(实现)方法。
指令实现顺序如下:
只输入make指令:
指令实现顺序如下:
只输入make指令:
①只输入
make
指令,只会执行Makefile
文件实现的第一条指令(从上到下),一般我们程序执行指令放在作为Makefile
文件的第一条指令;其他指令通过make+Makefile指令/文件名
实现!
②Makefile文件的各文件依赖关系指令可以在任意位置(不一定是从上到下),系统会在Makefile文件中找到得到目标文件需要实现的依赖方法,所以对应的依赖关系缺失会导致指令运行不起来!总而言之就是make会自动推导Makefile中的依赖关系(栈式结构)
我们使用make指令对test文件进行多次编译运行,发现只有第一次make指令被执行了,再次输入make指令却不被执行!这是为什么呢?就像机器提示那样:test文件没有更新,所以不需要进行多次编译!为什么机器会对我们的多次编译的指令进行拦截呢?多次编译消耗时间,会使Linux机器的工作效率大大降低,我们现在之所以没有感受出来,是因为写的代码太少了,当程序代码量很大的时候,编译一次编译需要消耗几十分钟、几个小时甚至更久,我相信对同样的代码编译多次的行为,作为程序员的你是第一个不答应的!
怎么做到对相同源文件只做一次编译呢?
一般情况下是由源文件生成可执行文件(即先有源文件,才有可执行文件),所以源文件最近修改时间比可执行文件早(老)。如果我们更改了源文件,此时源文件的修改时间比旧的可执行文件晚,所以此时我们使用make
指令就可以将修改后的源代码重新编译生成新的可执行文件。该过程是怎么样的呢?接下来让我们认识一条指令!
stat指令
语法:
stat 选项 文件名
功能: 显示文件\文件系统的详细信息,包括文件的访问时间,文件内容修改时间,文件属性的修改时间。
①Access
:文件的访问时间,查看、修改文件内容都可以算访问,Access
显示的时间更新;Modify
:文件内容修改时间,只有对文件内容进行增删等修改文件内容操作,Modify
时间更新;Change
:文件属性的修改时间,文件大小、文件拥有者、文件所属组等文件属性修改,Change
显示时间会被更新。②文件内容修改一次,Modify显示的时间会被更新;文件属性内容修改一次,Change
显示的时间会被更新;因为平时我们访问文件的次数比较频繁,所以访问文件达到一定次数Access
显示的时间才会被更新。③修改源文件内容而引起的时间变化,源文件是否被编译比较源文件和可执行文件的是Modify
显示的时间。
使用touch指令更新时间
指令1:
touch -a 文件名
(使用该指令更新文件的Access
显示的时间、Change
显示的时间)
指令2:touch -m 文件名
(使用该指令更新文件的Modify
显示时间)
先使用touch -m 文件名
更新源文件的Modify
显示时间,然后使用make
指令重新编译源文件!
$@
表示冒号左边的文件名,$^
表示冒号右边的文件名,在Makefile文件可以使用@
放在语句前进行注释!
使用
.PHONY
可以将test
指令(文件)变成伪目标,使该指令(文件)总是被执行!一般情况,我们将clean
指令(文件)设置为伪目标!
1
make
会在当前目录下找名字叫“Makefile”
或“makefile”
的文件。
2. 如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“test”
这个文件,并把这个文件作为最终的目标文件。
3. 如果test
文件不存在,或是test
所依赖的后面的test.o
文件的文件修改时间要比test这个文件新,就会执行后面所定义的命令来生成test
这个文件。
4. 如果test
所依赖的test.o
文件不存在,那么make
会在当前文件中找目标为test.o
文件的依赖性,如果找到则再根据那一个规则生成test.o
文件。(这有点像一个堆栈的过程)
5.test.o
的依赖方法存在,make
会生成test.o
文件,然后再用test.o
文件声明make
的终极任务,也就是执行test
文件了。
6.make
的依赖性:make
会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。
7. 在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make
就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make
不执行。
8.make
只管文件的依赖性,即,如果在make
找到依赖关系之后,冒号后面的文件还是不在,make
不执行
9.工程是需要被清理的,像clean
这种指令没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行,可以显示要make执行。即命令——“make clean”
,以此来清除所有的目标文件,以便重编译。
10.一般我们将clean
的目标文件设置为伪目标,用.PHONY
修饰伪目标的特性是,总是被执行的。
11.make
指令会判定文件的新旧,判定是否需要重新进行执行依赖关系,进行编译!