make/Makefile
参考文章:爱编程的大丙
1.什么是make /makefile?为什么要使用?
make是一个命令工具,是一个解释makefile中指令的命令工具。
makefile是一个文件,关系到了整个工程的编译规则。
使用 GCC 的命令行进行程序编译在单个文件下是比较方便的,当工程中的文件逐渐增多,甚至变得十分庞大的时候,使用 GCC 命令编译就会变得力不从心。
这种情况下我们需要借助项目构造工具 make 帮助我们完成这个艰巨的任务。一般来说,大多数的IDE都有这个命令,比如:Visual C++的nmake,QtCreator的qmake等。
make工具在构造项目的时候需要加载一个叫做makefile的文件,makefile关系到了整个工程的编译规则。一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。
makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
2.如何使用make/makefile
makefile文件有两种命名方式 makefile 和 Makefile, 构建项目的时候在哪个目录下执行构建命令 make这个目录下的 makefile 文件就会别加载,因此在一个项目中可以有多个 makefile 文件,分别位于不同的项目目录中。
(1)makefile基础框架
1. 规则
Makefile的框架是由规则构成的。make命令执行时先在Makefile文件中查找各种规则,对各种规则进行解析后运行规则。规则的基本格式为:
#目标文件【:】依赖文件 # 构成有依赖关系
target1,target2...: depend1, depend2, ...
【Tab缩进】command # 依赖方法
...
每条规则由三个部分组成分别是目标(target), 依赖(depend)和命令(command)。
命令(command):
- 当前这条规则的动作,一般情况下这个动作就是一个 shell 命令
- 例如:通过某个命令编译文件、生成库文件、进入目录等。
- 动作可以是多个,每个命令前必须有一个Tab缩进并且独占占一行。
gcc -o mybin test.c #编译test.c,形成mybin的可执行文件
依赖(depend):
- 规则所必需的依赖条件,在规则的命令中可以使用这些依赖。
- 例如:生成可执行文件的目标文件(*.o)可以作为依赖使用
- 如果规则的命令中不需要任何依赖,那么规则的依赖可以为空
- 当前规则中的依赖可以是其他规则中的某个目标,这样就形成了规则之间的嵌套
- 依赖可以根据要执行的命令的实际需求, 指定很多个
目标(target):
- 规则中的目标,这个目标和规则中的命令是对应的
- 通过执行规则中的命令,可以生成一个和目标同名的文件
- 规则中可以有多个命令, 因此可以通过这多条命令来生成多个目标, 所有目标也可以有很多个
- 🌷****通过执行规则中的命令,可以只执行一个动作,不生成任何文件,这样的目标被称为伪目标。 .PHONY:伪目标文件
# 举例: 有源文件 a.c b.c c.c head.h, 需要生成可执行程序 app
################# 例1 #################
app:a.c b.c c.c
gcc a.c b.c c.c -o app
################# 例2 #################
# 有多个目标, 多个依赖, 多个命令
app,app1:a.c b.c c.c d.c
gcc a.c b.c -o app
gcc c.c d.c -o app1
################# 例3 #################
# 规则之间的嵌套
app:a.o b.o c.o
gcc a.o b.o c.o -o app
# a.o 是第一条规则中的依赖
a.o:a.c
gcc -c a.c
# b.o 是第一条规则中的依赖
b.o:b.c
gcc -c b.c
# c.o 是第一条规则中的依赖
c.o:c.c
gcc -c c.c
2.规则的实现
在调用 make 命令编译程序的时候,make 会首先找到 Makefile 文件中的第 1 个规则,分析并执行相关的动作。
但是需要注意的是,好多时候要执行的动作(命令)中使用的依赖是不存在的,如果使用的依赖不存在,这个动作也就不会被执行。
对应的解决方案是先将需要的依赖生成出来,我们就可以在makefile中添加新的规则,将不存在的依赖作为这个新的规则中的目标,当这条新的规则对应的命令执行完毕,对应的目标就被生成了,同时另一条规则中需要的依赖也就存在了。
这样,makefile中的某一条规则在需要的时候,就会被其他的规则调用,直到makefile中的第一条规则中的所有的依赖全部被生成,第一条规则中的命令就可以基于这些依赖生成对应的目标,make 的任务也就完成了。
============================================================
#按照程序翻译的过程实现mybin
#mybin依赖test.o,但是文件中没有test.o
#test.o依赖test.s,但是文件中没有test.s
#test.s依赖test.c,但是文件中“存在” test.c
#因此先,生成 test.i
#才能一步一步,完成最初的规则,生成mybin
============================================================
# 举例:
########################################################
#按照程序翻译的过程实现mybin
#mybin依赖test.o,但是文件中没有test.o
#test.o依赖test.s,但是文件中没有test.s
#test.s依赖test.c,但是文件中“存在” test.c
#因此先,生成 test.i
#才能一步一步,完成最初的规则,生成mybin
##########################################################
mybin:test.o
gcc -o mybin test.o
test.o:test.s
gcc -c -o test.o test.s
test.s:test.i
gcc -S -o test.s test.i
test.i:test.c
gcc -E -o test.i test.c
(2)如何与make搭配使用?
🌷在调用 make 命令编译程序的时候,make 会首先找到 Makefile 文件中的第 1 个规则,分析并执行相关的动作。
如果想要执行 makefile 中非第一条规则对应的命令, 那么就不能直接 make, 需要将那条规则的目标也写到 make的后边, 比如只需要执行规则3中的命令, 就需要: make test.i
mybin:test.o
gcc -o mybin test.o
test.o:test.s
gcc -c -o test.o test.s
test.s:test.i
gcc -S -o test.s test.i
test.i:test.c
gcc -E -o test.i test.c
3.文件的时间戳
编写完毕makefile文件之后,在使用make的时候,通常只有第一次可以正常执行,显示出shell命名。之后即使再使用make,也会提示目标文件已经最新了。
这是因为make 命令执行的时候会根据文件的时间戳判定是否执行makefile文件中相关规则中的命令。
- 目标是通过依赖生成的,因此正常情况下:目标时间戳 > 所有依赖的时间戳, 如果执行 make 命令的时候检测到规则中的目标和依赖满足这个条件, 那么规则中的命令就不会被执行。
- 当依赖文件被更新了, 文件时间戳也会随之被更新, 这时候 目标时间戳 < 某些依赖的时间戳, 在这种情况下目标文件会通过规则中的命令被重新生成。
- 如果规则中的目标对应的文件根本就不存在, 那么规则中的命令肯定会被执行。
make和Makefile怎么知道生成的code文件是不是最新的呢?
这里是通过对比时间知道的。 (对比Modify的时间)
🌷stat 文件 —查询文件的acm时间,时间戳
- Access — 访问时间(非实时更新,占用内存)
- Modify — 修改时间(针对内容的修改)
- Change — 改变时间(针对属性的修改)
============================================================
🌷补充内容:
- 内容的修改能够引起Modify的变化,有的时候也会引起Change的变化。
因为内容的增删,会导致文件大小的变化。而文件大小属于文件属性。
- 当我们每次去访问一个文件,该文件的Access时间可能不会改变,取决于Linux系统自己的评判标准,那为什么呢?
a. 这个就要谈谈Linux历史了,Linux最开始是每次访问过后,文件的Access时间都会改变,但是后面人们发现,在使用Linux的时候,有时候会经常去查看文件,文件属性(时间)也是数据,如果每次都要对时间进行更新的话,那就意味着每次都会涉及到让操作系统向对应的磁盘文件当中的文件属性Access时间进行更改,就会降低效率。
============================================================
🌷怎么更新这3个时间,使用命令:
# touch 文件名 //如果该文件存在就会更新时间,不存在就会创建
4. 常用语法补充
语法一:.PHNOY 文件名
🌷通过执行规则中的命令,可以只执行一个动作,不生成任何文件,这样的目标被称为伪目标。 并且,这个文件具备总被执行的特性。
.PHONY:伪目标文件
但是我们一般不会将目标文件设置为伪目标,一般建议将clean设置为伪目标。
============================================================😈不将源文件设置为伪目标的原因:
如果源文件里面很多的源文件,那么如果将该源文件设置为伪目标后,每次make执行编译的时候,都会将所有文件,不管改没改变,都重新编译一遍,会导致效率低下的问题。
============================================================
语法二:$@ 与 $^
make会识别推导,$ @ 目标文件 ,$^ 依赖文件