原博客
https://blog.csdn.net/weixin_38391755/article/details/80380786
https://www.cnblogs.com/nosadness/p/5136652.html
Makefile是什么?
个人理解是描述文件依赖关系的文件,make是一个解释Makefile文件指令的命令工具
Makefile是为了实现工程的自动编译而存在的
关于程序的编译
程序编译的过程大概是
预编译 编译 汇编 链接
预编译阶段(.cpp-->.i文件)
- 1删除#define并做文本替换
- 2处理预编译命令 如 #ifndef,#if 0,#endif等
- 3递归展开头文件
- 4删除注释
- 5添加行号和文本标识符
- 6保留#pargma编译器处理
//pargma它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作
//猜想编译阶段才处理
编译阶段(.i-->.s文件)
- 1语法检测,函数和变量的声明和定义
- 2代码的优化
- 3生成汇编指令
汇编阶段(.s-->.o文件)
将汇编代码翻译成二进制机器指令
链接阶段(.o-->.exe)
链接阶段主要是针对.o文件,无关源文件,但是.o的中间文件太多,所以我们给中间文件打个包,在windows下包叫作“库文件” (.lib),在linux下叫作Archive File文件,也就是.a文件
Makefile的规则
target...:prerequisites.....
command
..............
target是目标文件,可以是objectfile,也可以是执行文件,也可以是一个标签Lablel,(一种为目标)
prerequisite是要生成target所需的文件或目标
command就是make需要执行的命令 (任意的shell命令)
target依赖于prerequisite中的文件,prerequisite中如果有一个以上的文件更新时间晚于target,那么command中的命令就会被执行
三个奇怪的变量
$@ 目标文件
$^ 所有的依赖文件
$< 第一个依赖文件
$? 表示比目标还要新的依赖文件列表
edit:main.o kbd.o command.o display.o\
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
main.o:main.c defs.h
cc -c main.c
//cc -c 汇编
kbd.o:kbd.c defs.h command.h
cc -c kbd.c
command.o:command.c defs.h command.h
cc -c command.c
display.o:display.c defs.h buffer.h
cc -c display.c
insert.o:insert.c defs.h bufer.h
cc -c insert.c
search.o:search.c defs.h buffer.h
cc -c search.c
files.o:files.c defs.h buffer.h command.h
cc -c files.c
utils.o:utils.c defs.h
cc -c utils.c
clean:
rm edit main.o kbd.o command.o display.o\
insert.o search.o files.o utils.o
反斜杠\ 是换行符的意思
我们可以把以上的内容保存为名为 Makefile 或者 makefile 的文件
然后在该目录下直接输入命令 make ,就可以生成edit文件了,执行make clear 删除执行文件edit和所有的中间目标文件
说明
clean不是一个文件,它只不过是一个动作的名字,冒号后面什么都没有,说明clean不依赖任何文件,当然也不能执行后面的command,要执行command,需要使用make clear
make命令后需要显示指出这个lable的名字
make是如何工作的
1make命令会在当前目录下查找 名为 Makefile 或者 makefile
2如果找到了,它会找到文件中的第一个目标文件,并把这个文件作为最终的目标文件,也就是例子中的edit
3 如果edit文件不存在,或者edit所依赖的后面的.o文件的修改时间要比edit这个文件晚,那么就执行后面的command命令
4 如果edit所依赖的.o文件也存在依赖,那么make会在当前文件中查找目标文件.o的依赖,如果找到则再生成.o文件
5 Makefile 文件里的.c 和.h文件必须存在
make会一层一层找出文件的依赖关系,直到找到编译出第一个目标文件,过程中如果make最终依赖的文件不存在则make直接退出
并且保存,但是command的命令和编译是否成功make是不关心的,make只管文件的依赖性,如果我找到依赖后冒号前面的文件在执行command后还是不存在,则make报错
Makefile中使用变量
类似于c语言里的宏
objects=main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
使用 $(objects)的方式来使用这个变量
objects = main.o kbd.o command.o display.o \
insert.osearch.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit $(objects)
Makefile中的自动推导
GNU的make可以自动推导文件以及文件依赖关系后面的命令,于是我们就没有必要去在每一个.o文件后面都写上类似的命令
kbd.o:defs.h command.h
只要make看到一个[.o]文件,它就会自动把[.c]文件加在依赖关系中,如果make找到一个a.o文件,那么a.c就会被加入a.o的文件依赖列表,并且推导出cc -c a.c 就会被推导出来,所以我们上面的Makefile文件就可以被精简被
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h
.PHONY : clean
clean :
rm edit $(objects)
上文中“.PHONY”表示clean是一个伪目标文件
清空目标文件的规则
每一个Makefile文件中都会定义一个清空目标文件的规则(.o和执行文件 )这样有便于重新编译
clean:
rm edit $(objects)
使用make clean执行
更稳健的做法
.PHONY:clean
clean:
-rm edit $(objects)
在rm命令前面加一个小减号的意思就是,也许某些文件会出现错误,但是忽略掉,继续做后面的事情
Makefile 总述
Makefile里主要包含
5大方面的内容
- 1显示规则
- target: prerequisites
- command
- 显示阐释文件之间的依赖关系
- 如何生成一个或者多个目标文件,要生成的文件,文件的依赖,生成命令
- 2隐晦规则
- make的自动推导功能,如果.o文件作为一个目标文件,则对应的.c文件会被自动加入目标文件的依赖列表
- cc -c xxx.c 这个生成xxx.o 的命令make也会自动帮你推导出来
- 3变量的定义,一般由普通字符主成,用$(xxxxxxxxx)引用,类似于c语言的宏,在Makefile文件执行的时候,变量会被替换到引用的位置上去
- 4文件指示
~~~~在Makefile文件中包含另一个Makefile文件(类似c语言的#include)
使用include关键字可以把别的Makefile包含进来
include<filename> fi;lename可以是当前操作系统shell的文件格式(也可以包含路径和通配符)
在include前面可以有一些空格,但是绝对不能是“tab”键
如果文件都没指定绝对路径或者相对路径,则默认查找顺序为 当前文件夹, 如果make执行时有指定 -I 或者 --include-dir 那么make就会在参数指定目录下查找,如果目录/include(一般是:/usr/locla/bin或/usr/include)存在的话make也会去找
如果有文件没有找到的的话,make会产生一条警告信息,但是不会马上报错,它会继续载入其他文件,一旦读取完整个Makefile文件,make会再次寻找,如果还是没有就报错
如果你想让make忽略那些没有读取到的文件前面就加小减号 -include<filename> 等价sinclude
~~~~ 指根据某些特殊情况,特定部分的Makefile文件生效(类似于c语言中的#if一样)
~~~~~~~~ 定义一个多行命令
- 5注释 Makefile中只有行注释,和shell一样注释用#字符,类似于c语言中的//
在Makefile中的命令必须用“tab”键开始
Makefile文件的命名
make命令会在当前文件夹下依次寻找
GNUmakefile,makefile,Makefile
推荐使用 Makefile,也可以添加后缀,Make.Linux,然后使用make -f Make.Linux (-file)
环境变量 MAKEFILES
如果你的当前环境中定义了环境变量MAKEFILES,那么,make会把这个变量中的值做一个类似于include的动作。这个变量中的值是其它的Makefile,用空格分隔。只是,它和include不同的是,从这个环境变中引入的Makefile的“目标”不会起作用,如果环境变量中定义的文件发现错误,make也会不理。
但是在这里我还是建议不要使用这个环境变量,因为只要这个变量一被定义,那么当你使用make时,所有的Makefile都会受到它的影响,这绝不是你想看到的。在这里提这个事,只是为了告诉大家,也许有时候你的Makefile出现了怪事,那么你可以看看当前环境中有没有定义这个变量。
make的工作方式
GNU的make的工作方式如下
- 1 读入所有的Makefile
- 2 读入被include的其他Makefile
- 3初始化文件中的变量,仅做替换,不做展开,懒执行,只有当依赖关系确定后变量确定被使用后才会被展开替换
- 4推导隐晦规则,并且分析所有规则
- 5为所有的目标文件创建关系链
- 6根据依赖关系,决定哪些目标文件需要重新生成
- 7执行生成命令
1-5步为第一个阶段,6-7为第二个阶段。第一个阶段中,如果定义的变量被使用了,那么,make会把其展开在使用的位置。但make并不会完全马上展开,make使用的是拖延战术,如果变量出现在依赖关系的规则中,那么仅当这条依赖被决定要使用了,变量才会在其内部展开。
Makefile的书写规则
规则1 依赖关系
规则2 生成目标的命令
规则的顺序非常重要,Makefile中只有一个最终目标文件,所以让make知道你的最终目标是什么非常关键
定义在Makefile中的目标文件有很多个,但是只有在第一条规则中的target才会被定义成最终目标,如果第一条规则中的target有多个,则第一条规则中的多个target中的第一个target才会被当做目标文件
在规则中使用通配符
make支持三个通配符
“~”
~/test 当前用户的$HOME目录下的test目录
~zxy/test 表示用户zxy宿主目录下的test目录
windows和dos下,用户没有宿主目录,所以波浪号所代指的具体目录有环境变量HOME而定
“*”
*.c 所有后缀为c的文件
如:“*”,那么可以用转义字符“\”,如“\*”来表示真实的“*”字符,而不是任意长度的字符串。
“?”
zxy?.c代表以zxy开头和.c结尾,中间有一个字符的文件
? 匹配任意一个字符
通配符和正则表达式 科普链接
https://blog.csdn.net/zgqxiexie/article/details/51184602
Makefile中的自动化变量
变量定义:
$^
所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量
会去除重复的依赖目标,只保留一份。
$@
表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,"$@"就是匹配于
目标中模式定义的集合
$?
所有比目标新的依赖目标的集合。以空格分隔。
$<
依赖目标中的第一个目标名字。如果依赖目标是以模式(即"%")定义的,那么"$<"将
是符合模式的一系列的文件集。注意,其是一个一个取出来的。
$(@D)
表示"$@"的目录部分(不以斜杠作为结尾) ,如果"$@"值是"dir/foo.o",那么"$(@D)"就
是"dir",而如果"$@"中没有包含斜杠的话,其值就是"."(当前目录) 。
$(@F)
表示"$@"的文件部分,如果"$@"值是"dir/foo.o",那么"$(@F)"就是"foo.o","$(@F)"相
当于函数"$(notdir $@)"
举例详解:
有main.c test.c test1.c test2.c 四个源文件
例子1:
%.o : %.c
gcc -c $< -o $@
把所以的c文件编译生成对应的o文件,$<代表每次取的c文件,$@代表每次c文件对应的目标文件
例子2:
main : main.o test.o test1.o test2.o
gcc -o $@ $^
把所有的o文件编译生成可执行的main文件,$^代表所以的依赖文件集合(main.o test.o test1.o test2.o),@代表目标文件(main)
例子3:
lib : test.o test1.o test2.o
ar r lib $?
把有更新的依赖文件重新打包到库lib中, 如果只有test1.o更新,则$?代表test1.o, 如果test.o test1.o都有更新,则$?代表test.o test1.o的集合。
总结:
$^ 所有依赖目标的集合
$? 所有有更新的依赖目标集合
$< 依赖目标中的第一个目标,如果依赖以(%)模式定义,则一个一个取出来的
$@ 目标文件
$(@D) $@的目录部分
$(@F) $@的文件部分
记忆方法:
dst:source1.o source2.o source3.o source4.o
xx ......
$^ 其中^表示水平的范围限定,包含所有的依赖文件集合(source1.o source2.o source3.o source4.o )
$? 其中?表示哪些依赖文件有更新是未知的,有更新的依赖文件集合(?)
$< 其中<表示从集合中取值,第一个依赖的文件 (source1.o)
$@ 目标文件 (dst)
$(@D) $@的目录部分
$(@F) $@的文件部分
————————————————
版权声明:本文为CSDN博主「丁香树下丁香花开」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/csdn66_2016/article/details/74199681
文件搜索
引入文件搜索的原因
在一些大工程里,有大量源文件,我们通常的做法是把源文件分类放到文件夹里,当make需要去寻找文件的依赖关系的时候,我们可以在文件后面加上文件的搜索路径,以提高搜索速度
方法1
引入Makefile文件中的特殊变量VPATH
VPATH=src:../headers
这个语句定义了两个目录 src和../headers,
当前目录---->src和../headers ,make会按照这个顺序搜索
方法2(推荐这个方法)
引入make的关键字"vpath"(全部小写),它不是一个变量,是一个make的关键字
更灵活,指定不同的文件在不同的目录中搜索
它的三种方法
vpath 要搜索的文件集合 目录:目录
- vpath <pattern> <directories> 为符合模式<pattern>的文件指定搜索目录<directories>
- vpath <pattern> 清除符合模式<pattern>的文件的搜索目录
- vpath 清除所有已被设置好了的文件的搜索目录
vapth使用方法中的<pattern>需要包含 % 字符,%字符表示匹配零或者若干字符
%.h表示所有以.h结尾的文件,<pattern>指定了要搜索的文件集合,<directories>指定了文件集的搜索的目录
vpath %.h ../headers
该语句表示 在../headers下搜索所有以.h结尾的文件(当前文件夹依然是最高搜索级别)
我们可以连续的使用vpath语句指定文件集的搜索范围,如果出现了相同的<pattern>或者被重复的<pattern>,那么make会按照vpath语句的先后顺序来执行搜索
vpath %.c foo
vpath % blish
vpath %.c bar
表示.c结尾的文件先在foo目录,然后在blish,最后在bar目录
vpath %.c foo:bar
vpath % blish
表示.c结尾的文件,先在foo目录,然后在bar目录搜索,最后才是blish目录
伪目标
引入伪目标的原因
目标文件和伪目标重名
clean:
rm *.o temp
使用make clear 触发命令执行
伪目标并不是一个文件,它更像是label,所以make无法生成它的依赖关系和决定他是否执行。
伪目标的取名不能和文件名重复,不然就失去了 伪目标 的意义了 。我们为了避免这种情况的发生引入了一个特殊标记
“.PHONY”,使用.PHONY来表明目标是一个伪目标,不管是否有这个文件,这个目标就是伪目标
.PHONY : clean
clean:
rm *.o temp
伪目标一般没有依赖的文件,但是我们也可以为伪目标指定所依赖的文件。伪目标同样可以作为“默认目标”
all : prog1 prog2 prog3
.PHONY : all
prog1 : prog1.o utils.o
cc -o prog1 prog1.o utils.o
prog2 : prog2.o
cc -o prog2 prog2.o
prog3 : prog3.o sort.o utils.o
cc -o prog3 prog3.o sort.o utils.o
伪目标特性 总是需要被执行 ,所以其依赖的三个目标总是比all新,所以这三个目标总是会被 被迫执行
,这个我们就实现了一次生成多个目标的需求
随便提一句,从上面的例子我们可以看出,目标也可以成为依赖。所以,伪目标同样也可成为依赖。看下面的例子:
.PHONY: cleanall cleanobj cleandiff
cleanall : cleanobj cleandiff
rm program
cleanobj :
rm *.o
cleandiff :
rm *.diff
“makeclean”将清除所有要被清除的文件。“cleanobj”和“cleandiff”这两个伪目标有点像“子程序”的意思。我们可以输入“makecleanall”和“make cleanobj”和“makecleandiff”命令来达到清除不同种类文件的目的
多目标
Makefile的规则中支持多个目标,但是这多个目标都要依赖同一个或者一批文件,并且其生成的命令大体相同
例子
bigoutput littleoutput : text.g
generate text.g -$(subst output,, $@)>$@
上述规则等价于
bigoutput:text.g
generate text.g -big > bigoutput
littleoutput: text.g
generate text.g -little >littleoutput
其中-$要执行一个Makefile的函数,函数名为subst,后面是参数,这个函数是截取字符串的意思
$@表示目标的集合,就像一个数组,$@依次取出目标,并执行命令。
静态模式
<targets...>:<target-pattern>:<prereq-patterns...>
<commands>
targets定义了一系列的目标文件,可以有通配符,是目标的一个集合
target-pattern指定的是目标集的模式
prereq-patterns 它是对target-pattern这类target所需要的依赖文件的文件集的一个定义
许多targets---》target-pattern符合某类特殊pattern的target集合----》这类特定的targets集合所依赖的文件的集合