前言
前面第五章我们讲了gcc/g++的使用及编译过程,你会发现命令很长,写起来很烦!
有没有简单一点的方式来执行程序呢?
有的!这就是我们今天要讲的Linux项目自动化构建工具 make/Makefile。
有时候会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力。
为什么这么说呢?
因为一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,
如果要我们一下下敲命令不知道要敲到什么时候去,而makefile定义了一系列的规则来指定,
哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,
甚至于进行更复杂的功能操作,这就是makefile带来的好处——“自动化编译”,
一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
make命令,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,
比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。
可见,makefile都成为了一种在工程方面的编译方法。
总结起来就是:make是一条命令,makefile是一个文件,两个搭配使用,完成项目自动化构建。
让我们一起来学习make/makefile吧!
makefile的编写
在讲述之前回想一下,gcc是怎么执行程序的?
每次执行,它需要这样一个命令:
gcc -o test test.c
每次都要打一遍 gcc -o test test.c,确实有点麻烦,有没有办法简化呢?有的!
接下来我们先来见见“🐖”跑,后面再详细讲解。
见见🐖跑
第一步:建立makefile文件
建立makefile或者Makefile文件都可以
第二步:配置
test:test.c gcc -o test test.c
注意第二行必须是Tab而不是空格!
ESc键+:wq
保存退出
第三步:执行
怎么使用呢?
命令如下:
make 或者 make test
可以看到生成了test文件
然后执行看看
./test
正常运行!
具体含义
接下来讲述一下它的具体含义,我们以编译过程的makefile文件内容为例:
hello:hello.o @gcc -o hello hello.o hello.o:hello.s @gcc -c hello.s -o hello.o hello.s:hello.i @gcc -S hello.i -o hello.s hello.i:hello.c @gcc -E hello.c -o hello.i .PHONY:clean clean: @rm -f hello.o hello.s hello.i
这是它的完整代码。
依赖关系与依赖方法
他们之间有着依赖关系
- hello ,它依赖 hello.o
- hello.o , 它依赖 hello.s
- hello.s , 它依赖 hello.i
- hello.i , 它依赖 hello.c
有依赖关系还不够,你得告诉它要干什么对吧,这就需要依赖方法
gcc hello.* -option hello.* ,就是与之对应的依赖关系
make原理
然后讲一下make在默认的方式下,也就是我们只输入make命令是如何工作的,原理是什么?
- make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
- 如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“hello”这个文件,并把这个文件作为最终的目标文件。
- 如果hello文件不存在,或是hello所依赖的后面的hello.o文件的文件修改时间要比hello这个文件新(可以用 touch 测试),那么,他就会执行后面所定义的命令来生成hello这个文件。
- 如果hello所依赖的hello.o文件不存在,那么make会在当前文件中找目标为hello.o文件的依赖性,如果找到则再根据那一个规则生成hello.o文件。(这有点像一个堆栈的过程)
- 当然,你的C文件和H文件是存在的啦,于是make会生成hello.o文件,然后再用hello.o文件声明make的终极任务,也就是执行文件hello了。
- 这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。
- 在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。
- make只管文件的依赖性,即,如果在我找了依赖关系之后,冒号后面的文件还是不在,那么对不起,我就不工作啦。
touch命令可以将存在的文件的时间修改为现在的时间
命令如下
touch hello.o
stat可以查看文件最近一次的查看文件、修改文件内容、修改文件属性的时间
命令如下:
stat hello.o
最近访问:以前设计的是只要访问了,就修改时间,现在则是访问几次才修改访问时间(不修改文件的情况下)
最近更改和最近改动的关系就是,修改文件内容就会修改“最近更改”的时间,修改文件属性就会修改“最近改动”的时间,一般情况下,修改文件内容后,“最近更改”和“最近改动”都会修改时间,因为修改文件内容不可避免会改变文件的大小,也就改变了文件的属性。
还有个问题就是,我们前面说如果hello文件不存在,或是hello所依赖的后面的hello.o文件的文件修改时间要比hello这个文件新,那么,他就会执行后面所定义的命令来生成hello这个文件。
我们用下面这个例子来验证一下
- 当生成所有文件后修改hello.o文件的“修改时间”为现在,也就是让hello.o文件比hello文件新,然后用hello.s文件作对比
- make一下
- 我们会发现hello.o文件的访问时间被修改了,而hello.s文件的则是没有改变
- 这是因为系统中有这么一套机制,当hello.o文件新于hello文件,那么它就要重新编译,而hello.s则不用改变,来提高编译的效率。
[venus@localhost Hello]$ make [venus@localhost Hello]$ ll 总用量 60 -rwxr-xr-x. 1 venus venus 24272 7月 29 17:26 hello -rw-r--r--. 1 venus venus 154 7月 29 17:00 hello.c -rw-r--r--. 1 venus venus 16843 7月 29 17:26 hello.i -rw-r--r--. 1 venus venus 1496 7月 29 17:26 hello.o -rw-r--r--. 1 venus venus 450 7月 29 17:26 hello.s -rw-r--r--. 1 venus venus 666 7月 29 17:07 makefile [venus@localhost Hello]$ stat hello.o 文件:hello.o 大小:1496 块:8 IO 块:4096 普通文件 设备:fd00h/64768d Inode:220642 硬链接:1 权限:(0644/-rw-r--r--) Uid:( 1000/ venus) Gid:( 1000/ venus) 环境:unconfined_u:object_r:user_home_t:s0 最近访问:2023-07-29 17:26:29.634145794 +0800 最近更改:2023-07-29 17:26:29.631145778 +0800 最近改动:2023-07-29 17:26:29.631145778 +0800 创建时间:2023-07-29 17:26:29.630145773 +0800 [venus@localhost Hello]$ stat hello.s 文件:hello.s 大小:450 块:8 IO 块:4096 普通文件 设备:fd00h/64768d Inode:220641 硬链接:1 权限:(0644/-rw-r--r--) Uid:( 1000/ venus) Gid:( 1000/ venus) 环境:unconfined_u:object_r:user_home_t:s0 最近访问:2023-07-29 17:26:29.631145778 +0800 最近更改:2023-07-29 17:26:29.625145748 +0800 最近改动:2023-07-29 17:26:29.625145748 +0800 创建时间:2023-07-29 17:26:29.622145732 +0800 [venus@localhost Hello]$ touch hello.o [venus@localhost Hello]$ stat hello.o 文件:hello.o 大小:1496 块:8 IO 块:4096 普通文件 设备:fd00h/64768d Inode:220642 硬链接:1 权限:(0644/-rw-r--r--) Uid:( 1000/ venus) Gid:( 1000/ venus) 环境:unconfined_u:object_r:user_home_t:s0 最近访问:2023-07-29 17:27:10.131354671 +0800 最近更改:2023-07-29 17:27:10.131354671 +0800 最近改动:2023-07-29 17:27:10.131354671 +0800 创建时间:2023-07-29 17:26:29.630145773 +0800 [venus@localhost Hello]$ make [venus@localhost Hello]$ stat hello.o 文件:hello.o 大小:1496 块:8 IO 块:4096 普通文件 设备:fd00h/64768d Inode:220642 硬链接:1 权限:(0644/-rw-r--r--) Uid:( 1000/ venus) Gid:( 1000/ venus) 环境:unconfined_u:object_r:user_home_t:s0 最近访问:2023-07-29 17:27:39.926508348 +0800 最近更改:2023-07-29 17:27:10.131354671 +0800 最近改动:2023-07-29 17:27:10.131354671 +0800 创建时间:2023-07-29 17:26:29.630145773 +0800 [venus@localhost Hello]$ stat hello.s 文件:hello.s 大小:450 块:8 IO 块:4096 普通文件 设备:fd00h/64768d Inode:220641 硬链接:1 权限:(0644/-rw-r--r--) Uid:( 1000/ venus) Gid:( 1000/ venus) 环境:unconfined_u:object_r:user_home_t:s0 最近访问:2023-07-29 17:26:29.631145778 +0800 最近更改:2023-07-29 17:26:29.625145748 +0800 最近改动:2023-07-29 17:26:29.625145748 +0800 创建时间:2023-07-29 17:26:29.622145732 +0800
.PHONY伪目标
从前面我们可以看到make会根据源文件和目标文件的新旧来判断是否需要重新执行依赖关系进行编译,依赖关系不一定总是被执行,那如果我想要对应的依赖关系总是被执行呢?这就需要.PHONY伪目标来实现。
拿最后的clean为例,去掉.PHONY:clean
假设有这么一种情况,同目录下,有个和clean同名的文件,此时执行make clean操作会发生什么?
可以看到的是make clean命令无法删除对应的文件了
而声明了.PHONY之后,即使当前目录下存在与伪目标同名的文件或目录,make 仍然会执行伪目标规则。
我们把.PHONY:clean加上去
结果表明通过使用.PHONY声明伪目标,我们可以确保当执行相应的操作时,不受同名文件或目录的干扰。
特殊符号含义
@:在指令前加上@就不会有回显了
这是没加的情况:
加上@以后
$@ $^:可以把指令中的文件名,冒号左边的改为$@,冒号右边的改为$^,效果和直接写出文件名是一样的。
后记
本篇讲述了make/makefile的使用,makefile中的依赖关系、依赖方法、make的原理、伪目标及特殊符号含义等内容,篇幅较长,也较为复杂,希望大家可以多动手来理解其含义~