目录
一.什么是make/Makefile
Makefile
在Windows下,我们使用VS、VS Code这些ide编写C/C++程序去实现一个工程后,这些ide就会帮我们处理处理生成这些程序的可执行文件。
而在Linux下如果我们需要自己去通过指令来实现这个工程,一个工程中的源文件不计数,其按照类型、功能、模块分别放在若干个目录中,如果去一一连接实现这是十分不方便的。
而Linux提供了项目自动化编译工具——Makefile
,其定义了一系列的规则来指定,那些文件需要先编译,那些文件需要后编译,那些文件需要重新编译,甚至于进行更复杂的功能操作。Makefile一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
所以,会不会写Makefile,从侧面说明了一个人是否具备完成大型工程的能力。
make
make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,Makefile都成为了一种在工程方面的编译方法。
如下:我们利用Makefile生成C语言文件hello.c的可执行文件
当一个c程序员所处的开发环境只有一个通过终端连接的Linux时,Makefile几乎时构建复杂工程的唯一选择,也是项目是否具备工程化的一个分水岭,总而言之,学习Linux,Makefile足够的重要。
总结:
在Linux下,make是一个命令,Makefile是一个文件,二者相辅相成,共同实现项目的"自动化构建"。
二.Makefile逻辑
1.简单依赖
Makefile主要由依赖关系
和依赖方法
组成,而依赖关系由目标
、依赖
组成
最经典的格式如下:
target:dependence #依赖关系
command #依赖方法
其中,target
是要生成的目标,dependence
是要生成target所需要的依赖,两者组成一个依赖关系,而command
是要生成target所要执行的命令。
注意:
- 依赖关系中的依赖可以有多个,也可以没有
- Makefile也可以写成makefile,写成其他make无法识别。(该文为Makefile)
- 每条依赖关系前必须以【Tab】键开头
以我们上面的测试代码为例:
为了生成hello文件,需要依赖于hello.c文件,最后通过下面的依赖生成目标文件hello。
2.复杂依赖
上面测试用例中的依赖关系
,依赖
为该目录下的文件,当依赖关系
中的依赖
不是该目录下的文件时,在生成目标文件
时,系统就会自动在Makefile中的其它目标文件
中查找相同的文件,如果找到的文件依然不在该目录下则重复进行上述操作。(系统采用栈的结果进行操作)
我们先将上面的测试用例修改如下:
- 生成hello文件,依赖hello.o文件
- 生成hello.o文件,依赖hello.s文件
- 生成hello.s文件,依赖hello.i文件
- 生成hello.i文件,依赖hello.c文件
- hello.c文件在该目录下找到,直接使用
在我们生成一个hello文件同时生成了其它的三个目标文件
,并存入该目录下
依赖关系如下图:
- 其它
目标文件
的位置发生改变也不会影响target1
的生成,依赖
是在Makefile文件下查找相同的目标文件
,和目标文件的位置
没有关系。依然采用上面的方法生成文件。
三.make指令
1.make的使用
在Linux下,我们输入make命令后,系统会在当前目录下查找Makefile文件,找到后会执行文件中的第一个依赖关系和依赖方法,在屏幕打印出依赖方法并生成目标文件。
我们将上面的Makefile测试案例修改如下:
产生两组依赖关系。(clean下面会讲)
如下为我们通过make测试
当我们想要删生成Makefile中其他的目标文件时,需要通过指定目标文件方式
make target1 target2 #make后可以有多个target目标文件
- make target
- make target1 target2
2.clean清理
在一个工程中,总要对一些不需要的文件进行清理,我们常常使用clean
来作为项目清理的目标文件。
因为清理文件不需要依赖其它文件,所以clean也不存在依赖关系。
当clean不是Makefile第一个目标文件时,我们需要在make后加clean来编译它,就像之前测试的那样。
3.伪目标
使用.PHONY
修饰的目标,称为伪目标,格式如下
.PHONY:target
功能:使该文件总是可以被执行
当我们多次使用make去生成同一个目标文件时,第一次可以编译通过,但是之后,就无法通过,会给出如下结果:
这时我们就可以使用.PHONY
来修饰目标文件hello,使其变成伪目标,总是可以被编译
注意:不是只有使用.PHONY
修饰后才可以总是被编译,clean目标文件也可以总是编译,如下:
4.make如何确定是否编译
上面我们已经测试,在不使用.PHONY
的情况下,make只能被使用一次,之后就无法被编译,那make是如何确定是否编译呢?
当我们在编写完成一个工程后,想要去执行实现这个工程,(一般工程都很大,不是我们测试的这么点)是需要浪费很多时间和性能的,我们不能让其肆无忌惮的执行,只有当源文件发生修改,后才可以重新实现这个工程生成可执行文件。
make判断是否重复编译,根据源文件和目标文件的修改时间对比判断。
- 当源文件的修改时间小于目标文件,不进行编译
- 当源文件的修改时间大于目标文件,进行编译
在Linux中,每个文件都有三个时间,如下:
- 访问时间 (Access):最近访问文件内容时间,比如 cat、vim、less;
- 修改时间 (Modify):最近修改文件内容时间,比如 nano、vim;
- 改动时间 (Change):最近更改文件属性的时间,包括文件名、大小、内容、权限、属主、属组等,比如 nano/vim (文件大小改变),
我们可以通过stat
指令来查看文件的这三种时间
访问时间的影响
为什么要对比两个数的修改时间来判断make是否重复编译呢?
在Linux下,访问一个文件可以通过cat、less
指令来进行,而通过这两个指令访问后的文件不会修改它本身的访问时间,有以下两个原因:
- 在LInux中,访问文件是一个十分频繁的操作,而修改文件访问时间每次都需要进行IO操作,如果每次访问都对访问时间进行修改,那会增加系统的负担。
- 一个文件是否被读取是由这个文件的权限决定的,既然文件是可读的,那么说明文件的拥有者和所属组不建议你去读取,也就没必要每次修改访问时间了。
所以Linux访问时间改变有如下两种情况:
- 访问累计到一定的次数或积累一段时间会更新
- 文件的修改时间发生改变时,随之改变
- 上图只使用vim打卡文件,并未对其进行修改,但也会时修改时间改变
综上:如果要判断一个文件是否发生修改判断其修改时间是否发生变化即可。
注意:修改时间发生变化,文件不一定发生修改
修改时间的影响
当源文件的修改时间改变,说明源文件进行了修改,此时使用make即可再次编译,我们先使用vim更新修改时间判断
我们这里在使用touch命令来更新源文件的所有时间(touch file:file不存在创建file,file存在更新file所有时间),来判断make是否被执行和是否修改源文件内容无关。
我们从上面的知识可以得出结论make是否被编译和源文件的修改时间有关,那我们就可以知道,PHONY
就是使目标文件是否被编译不根据修改时间来判断,从而达到总是被编译的效果。