目录
- 一、make/Makefile的背景
- 二、make/Makefile的基本概念
- 三、依赖关系
- 四、依赖方法
- 五、make/Makefile原理
- 六、Makefile的伪目标
- 七、Makefile的变量
- 八、Makefile的推导能力
- 结尾
一、make/Makefile的背景
- 一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作
- makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
- make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令。
- make是一条命令,Makefile是一个文件,两个搭配使用,完成项目自动化构建。
二、make/Makefile的基本概念
Makefile的基本结构:
目标文件: 依赖文件
<tab>命令(command)
<tab>命令...
实例:
mytest:test.c
gcc test.c -o mytest
.PHONY:clean
clean:
rm -f mytest
注意:Makefile文件中的第一行尽量不要是空行,虽然make工具本身能够处理以空行开头的Makefile,但在某些复杂的构建场景中,空行可能会与Makefile中的其他元素(如变量定义、条件语句等)产生意外的交互。
make的执行目标:
-
如果你想要make执行特定的命令或目标,可以在make命令后明确指定该目标名称。
-
如果没有指定目标,make将默认执行Makefile中的第一个目标(这通常是Makefile中定义的第一个规则所对应的目标)。
三、依赖关系
依赖关系指的是构建过程中,一个目标文件依赖其他文件(源文件、头文件、其他目标文件),依赖关系在Makefile中通过特定的语法来定义,通常使用冒号(:)来分隔目标文件和它的依赖文件。
mytest:test.c
gcc test.c -o mytest
.PHONY:clean
clean:
rm -f mytest
以上面的Makefile文件举例子,mytest是目标文件依赖于test.c文件
四、依赖方法
依赖方法实际上是指Makefile中定义的用于构建目标文件的命令序列。这些命令告诉make如何根据依赖关系来生成目标文件。在Makefile中,每个目标文件后面跟着的一个Tab键开头就是该目标文件的依赖方法。
mytest:test.c
gcc test.c -o mytest
.PHONY:clean
clean:
rm -f mytest
以上面的Makefile文件举例子,gcc test.c -o mytest.c
和rm -f mytest
就是依赖方法
五、make/Makefile原理
这里先对下面图片出现的现象提一个问题,为什么make只能执行一次,而修改源文件后就可以再一次make,为什么make/Makefile总是不让我们重新编译代码呢?系统是怎么做到的?
还记得在常见指令那篇文章中的touch指令里延伸讲到了一个stat指令,stat指令能够查看一个文件的详细信息,包括文件最近访问时间(Access Time)、文件内容最近修改时间(Modify Time)和文件属性最近修改的时间(Change Time)。
以下面的图片为例来讲解下面的问题,首先我们在没有生成可执行程序的情况下执行一次make是可行的,当进行第二次make的时候却不能执行了,这时候我们来使用stat指令查看一下两个文件的文件内容最近修改时间,我们能发现可执行程序的最近修改时间大于源文件,真的是因为这个吗?接下来我们对源文件进行修改,导致源文件的最近修改时间变大,超过了可执行程序,源文件的最近修改时间大于可执行程序就能够再一次执行make了吗?是的!源文件的最近修改时间大于可执行程序就能够再一次执行make了。
问题:为什么make/Makefile总是不让我们重新编译代码呢?
解答:make/Makefile总是不让我们重新编译我们的代码的原因是提高编译效率。当你有一个大型项目,包含许多源文件、库依赖和复杂的构建逻辑时,手动编译整个项目会非常耗时且容易出错。Makefile 通过定义构建规则来自动化这个过程,并且它非常擅长识别哪些部分需要被重新编译,哪些部分已经是最新的,因此不需要重新编译。
问题:系统是怎么做到的?
解答:通过对比源文件和可执行程序的文件内容最近修改时间来体现源文件的新旧。具体来说就是:
第1次的时候,一定是先有源文件,才能有可执行程序
源文件修改时间 < 可执行程序修改时间
第2/n次的时候,我们对源文件进行修改
源文件修改时间 > 可执行程序修改时间
当源文件修改时间 > 可执行程序修改时间时,可以使用make重新对源文件进行编译生成可执行程序,那么源文件与可执行程序之间的关系又会变为源文件修改时间 < 可执行程序修改时间。
问题:如何做到不修改源文件的前提下就能使用make重新对源文件进行编译?
解答:若是想不对源文件进行修改就能使用make重新对源文件进行编译,那么可以对文件使用touch指令,在常见指令这篇文章中讲到了,当文件不存在时touch指令创建文件,当文件存在时则是更新文件时间。
六、Makefile的伪目标
伪目标是一个不实际生成文件的目标,它用于执行一些特定的命令。伪目标在Makefile中通常以.PHONY
声明,以防止与文件名冲突。
上面我们提到了make/Makefile总是不让我们重新编译代码,那么有没有一种方法能够让make/Makefile总是让我们重新编译代码呢?
那么这里就要提到Makefile的伪目标了,被.PHONY
标记的目标成为伪目标,能让make/Makefile每次都重新编译代码。
一般.PHONY我们用来标记这种clean、install等(清理操作、安装操作等)的目标文件,我们将它设置为伪目标,意味着这些伪目标总是被执行的。
七、Makefile的变量
Makefile也支持变量的定义和使用,类似于C语言中的宏。变量的定义和使用可以帮助减少重复代码,提高Makefile的可维护性。
- 变量赋值:Makefile中可以使用=、:=、?=、+=等符号进行变量赋值。
- =:最普通的赋值方式,变量的值在整个Makefile中最终被指定。
- :=:直接赋值,赋予当前位置的值。
- ?=:如果该变量没有被赋值,则赋予等号后面的值。
- +=:将符号后面的值添加到前面的变量上。
- 变量使用:在Makefile中,可以通过$(变量名)的方式来引用变量的值。
将源文件test.c赋值给src,若源文件不止一个,那么可以在源文件的后面添加空格,然后就可以继续添加源文件了。
八、Makefile的推导能力
隐含规则(这里只是举例子并不深入理解)
以下图为例,首先,Makefile 会检查目标 mytest 是否已经是最新的,这里由于 mytest 不存在,Makefile 会继续处理。接下来,Makefile 会查看 mytest 的依赖项 test.o,这里由于 test.o 也不存在,Makefile 需要构建一个构建计划来生成 test.o。由于 Makefile 中没有为 test.o 编写显式的构建规则,Makefile 会搜索是否有适用于 .o 文件从 .c 文件构建的隐含规则。找到了适用的隐含规则(通常是将 .c 文件编译成 .o 文件的规则),Makefile 会自动调用 C 编译器来编译 test.c 并生成 test.o。
自动变量
Makefile 提供了几个自动变量,$^
,$@
。使用这些自动变量可以使 Makefile 更加简洁。
-
$@
:表示规则中的目标文件。在模式规则中,它特别有用,因为它代表了匹配模式的目标文件名。 -
$^
:表示规则中的所有依赖文件(不包括重复的)。这是一个列表,包含了规则中所有的依赖项。
结尾
如果有什么建议和疑问,或是有什么错误,大家可以在评论区中提出。
希望大家以后也能和我一起进步!!🌹🌹
如果这篇文章对你有用的话,希望大家给一个三连支持一下!!🌹🌹