笔记内容来自本人学习 狄泰软件学院 唐佐林 老师的视频,相关课件截图已授权
本文章未经许可不允许以任何方式转载和复制保存
初始makefile的结构
关键知识
1.当目标对应的文件不存在,执行对应命令
2.当依赖在时间上比目标更新,执行对应命令
3.当依赖关系连续发生时,对比依赖链上的每一个目标
4.可在命令前加上@,作用为命令无回显
技巧
1.工程开发中可将最终可执行文件名和all同时作为makefile中第一条规则的目标
因为,如果只有all目标的话,当前目录下没有all文件,即使hello.out,main.o,func.o文件都是最新的,但是每次make还是会执行all目标下的命令。为了避免不必要的命令执行,可以将hello.out放在all目标的前面,那么make的时候第一条目标是hello.out,而且它是最新的,那么就不会执行下面的命令了。
伪目标的引入
语法:先声明,后使用
本质:伪目标是make中特殊目标.PHONY的依赖
如:
.PHONY : clean
clean :
rm -rf *.o hello.out
伪目标的妙用:规则调用(函数调用)
绕开.PHONY关键字定义伪目标
前提:目录下没有名为FORCE的文件
根据原理,clean的依赖FORCE总是最新的,所以clean下的命令总是会执行。
要绕开的原因:.PHONY关键字是标准的make里才有的,其他厂家可能会修改标准的make进行自己的拓展,所以修改后可能会不支持.PHONY关键字
变量和不同的赋值方式
关键知识
1.makefile中的变量只代表文本数据(字符串)
2.makefile中的变量名规则:
-变量名可以包含字符,数字,下划线
-不能包含“:”,“#”,“=”或空格
-变量名大小写敏感
1.简单赋值(:=)
2.递归复制(=)
3.条件赋值(?=)
4.追加赋值(+=)
PS:
x := foo
y = $(x)b
x := new
结果跟上面的一样,只要给y赋值的时候是递归赋值,那么y的最终值的前面都是x的值。
预定义变量的使用
关于makefile中的宏定义
这样就定义了预处理宏,编译的时候可选代码就会被编译进去了。
3.在makefile文件中妙用-D选项
CC = gcc
RM = rm
CFLAGS += -D _YUQIANG
TARGETS := app
all:$(TARGETS)
$(TARGETS):main.c
$(CC) $(CFLAGS) $^ -o $@
clean:
- ( R M ) − r f ∗ . o − (RM) -rf *.o - (RM)−rf∗.o−(RM) -rf $(TARGETS)
变量的高级主题(上)
变量的高级主题(下)
说明
1.如果makefile文件中定义了跟系统环境变量同名的变量,那么在make执行的时候临时地将系统的同名环境变量的值改写成makefile文件里的同名变量的值,但并不会真正地修改系统环境变量的值,执行完make之后系统环境变量的值还是以前最初的值。所以make的时候使用的是makefile文件里的值。而且,这种临时修改,在其他makefile文件中也有效。
比如:
当前系统环境变量中存在JAVA_HOME这个环境变量。在当前makefile1文件中,定义了
JAVA_HOME := java home
在另一个makefile文件makefile2中要使用JAVA_HOME这个环境变量,那么使用的是makefile1中的值
注意:运行make时指定- e选项,优先使用系统环境变量的值
(本虚拟机中无JAVA_HOME环境变量,所以第四行输出空)
结果解析:
1.局部变量var是有作用域的,作用域只在当前makefile文件中
2.可以通过命令行传递的方式var:=$(var)将var的值传给另一个makefile文件
3.可以使用export关键字声明临时环境变量,其值在执行make时在所有makefile文件中都有效
4.可以使用- f选项指定makefile的文件
1.对于目标变量的有效范围,从第一个make和第二个make another可以看出,对于目标变量的作用域“在指定目标及其连带规则中”的说法,只在在make指定目标的时候自动make其依赖的时候成立,如果单独make其依赖,那么其依赖使用的是makefile文件中的文件变量而不是目标变量。
2.使用override关键字可以让其后的变量不会被命令行变量所覆盖,如上图后两个make语句的执行结果。
条件判断语句
实验
解析
var4和var5的赋值方式不同,所以计算if语句的方式不同。var3是空值,所以var4肯定是空的,var5是递归赋值方式,它的值跟var4的值绑定在了一起,虽然最开始var5是空的,但是在后面可以重新对var4赋值,那么var5就不是空值,因此,var5的值是不确定的,对make来说,认为var5不是空值。
注意
1.if前只能有空格,不能有制表符
2.凡是命令,前面必须是制表符,而不是空格
1.var1和var2的输出说明:
(1)自定义函数只有在规则中调用才起作用,否则是不会执行对应的语句的
(2)call函数的作用是:将函数名及实参替换到下面的命令中, ( 0 ) 表 示 取 函 数 名 , (0)表示取函数名, (0)表示取函数名,(1)表示取函数名后的第一个实参的值,类推;替换完成后,在规则里执行相应的命令。
变量与函数的综合实例
说明
1.与第六课中,规则中的模式替换相比,这种替换是相对于当前目录下的,首先将当前目录下匹配到的%.c文件,然后替换为%.o,即行为方向是将冒号右边匹配到的替换成左边的;而第六课中的模式替换是将变量中匹配到的%.o替换成%.c,行为方向是将冒号左边匹配到的替换成右边的。
make的时候,可以直接make,或者传递一个变量make DEBUG:=true
收获
1.如第25行中,替换的时候可以加目录前缀$(DIR_OBJS)/
自动生成依赖关系(上)
试验
提示
-E选项能告诉编译器只需要做初步的依赖解析,不用进一步操作,能提高效率
结果输出:a b c
自动生成依赖关系(中)
附加
1.在include前面加上减号“-”可以在make的时候忽略警告和错误
1.原意是想先创建一个test文件夹,然后进入test文件夹,在里面创建子文件夹subtest
2.make后的结果:在当前目录下创建了test文件夹,而且subtest文件夹也在当前目录下
3.原因:all目标下的三个命令是在不同进程中的,这三个进程是依次执行的,执行完一条命令,该进程就结束,然后执行下一个进程,类推。当第二个进程执行完cd
test命令后,该进程退出,然后此时所在的目录就是当前目录,而不是在test目录下,所以subtest文件夹在当前目录下创建。
改进
注意
1.此例子中set -e命令可以不写
2.\是续行符,即让所有命令都在同一行上
3.make在一个进程中执行一行命令,如果不加上\,即使写了“;”那上面的四条命令仍然在四个不同进程中执行
解析
1.include的内容在当前目录下不存在该文件,所以make会搜索后面是否有同名的目标,结果是通过模式匹配搜索到了,然后执行该目标规则下的命令
注意
1.图中第17行的前面即KaTeX parse error: Undefined control sequence: \” at position 24: …能有@,因为第16行的末尾有“\̲”̲,会将@转义为普通的“@”字符…(CC)的值则变成@g++,此时的@并不是起到无回显示的作用,而是一个普通字符,make的时候会报错:@g++
not found
2.同理,第15行的末尾也不能有“\”,否则第16行会报错@set not found
3.第15行不用加\,因为此例子的目的是第16和第17行在同一进程中按顺序执行,第15行无关紧要
自动生成依赖关系(下)
试验
解析
1.为什么不先直接mkdir deps,而是把$(DIR_DEPS)做为图中第22行的目标的依赖?(假如不做为依赖,则图中22行的目标为%.deps)
答:因为在前面include的时候,当前目录下没有对应文件,则执行后面的模式匹配到的目标规则及其命令,那么这时候.deps文件将比deps文件夹先创建。如果像图中那么写,那么在执行22行的目标的时候会先检查deps文件夹有没有创建,如果没创建,那么就会执行图中18行的目标下的命令来创建deps文件夹。
2.图中25行为什么要用filter函数来过滤依赖只留下cpp文件?
答:因为g++ -MM -E对文件夹$(DIR_DEPS)无效,make的时候会有警告
3.第二张图中为什么main.deps和sort.deps会被创建两次?
答:因为用意是将所有.deps后缀文件都放在deps文件夹下,在make执行的时候,include时,根据模式匹配在后面匹配到了相应的目标规则,此时make会记录每一条匹配上的目标规则,在创建了main.deps后创建sort.deps,最后创建func.deps。因为$(DIR_DEPS)/%.deps目标依赖于deps文件夹,此时make程序发现,对于前面的deps/main.deps和deps/sort.deps两条目标,它们的依赖deps文件夹的时间戳更新了,所以这时候会触发make对相应规则的重新解析和命令的执行。注意,此时的重新解析是对该模式匹配到的所有规则的重新解析,包括最新的Guapi/func.deps。
PS:对于为什么会有最后的提示“make:对”Guapi/func.o无需做任何事””,在后面的内容include的暗黑操作二讲解
1.图中第15到21行是为了优化,因为比如在make clean的时候就没有必要include
2.图中第26到30行通过ifeq语句来解决重复创建问题,如果已经创建了deps文件夹,那么目标就不用再依赖于它,即使它的时间戳更新了,之前的目标不会再次被执行。
解析
1.前提:当前目录下没有test.txt文件;当前makefile文件中没有test.txt规则
2.照图中那么写,即include前有减号,则不会报错
3.如果include前没有减号,那么会报错:make:*** 没有规则可制作目标”test.txt”。停止
解析
1.在创建了test.txt文件后并将 other:;@echo “this is other”输入到文件中后,此时test.txt已经存在了,make会再次执行include语句,将test.txt的内容包含进来。
2.然后,test.txt的内容成为当前makefile文件中的第一条规则,make的时候执行other目标下的命令@echo “this is other”,屏幕上输出this is other
3.这也解释了前面的问题:为什么会有最后的提示“make:对”Guapi/func.o无需做任何事””
解析
1.前提:当前目录下存在test.txt和b.txt文件,而且b.txt的时间戳比test.txt新
2.在这情况下,虽然当前目录下存在test.txt文件,但是make程序解析到后面有对应的目标名test.txt,最重要的是,它的依赖b.txt的时间戳比自己新,从而test.txt目标下的命令会被执行
3.make all执行结果:
This is test.txt This is all
试验
解析
1.情况一
(1)前提:test.txt存在,其内容为空,且b.txt的时间戳不比test.txt新
(2)make all的执行结果:all depends on :
2.情况二
(1)前提:test.txt存在,其内容是什么不重要,且b.txt的时间戳比test.txt新
(2)make all的执行结果:
This is test.txt
all depends on : b.txt
(3)原因:b.txt的时间戳比test.txt新,所以会执行test.txt目标下的命令,第二条命令让test.txt的时间戳更新了,此时make会重新执行-include test.txt语句,重新把test.txt的内容包含进来,即把all : b.txt包含进来了,所以输出 all depends on : b.txt
自动包含依赖关系(续)
解析
1.主要思路:通过all目标来构建整个项目需要的资源,首先是相关文件夹的创建,然后是最终目标,然后通过 ( E X E ) 的 层 层 依 赖 , 逐 步 构 建 出 a p p . o u t 所 需 的 资 源 。 期 间 利 用 了 i n c l u d e 和 它 的 暗 黑 操 作 来 生 成 . d e p 文 件 , 生 成 之 后 , 会 再 次 包 含 . d e p 文 件 的 内 容 来 形 成 相 关 . o 目 标 规 则 来 供 (EXE)的层层依赖,逐步构建出app.out所需的资源。期间利用了include和它的暗黑操作来生成.dep文件,生成之后,会再次包含.dep文件的内容来形成相关.o目标规则来供 (EXE)的层层依赖,逐步构建出app.out所需的资源。期间利用了include和它的暗黑操作来生成.dep文件,生成之后,会再次包含.dep文件的内容来形成相关.o目标规则来供(EXE)目标的依赖$(OBJS)执行
2.图中第53行在KaTeX parse error: Undefined control sequence: \1 at position 12: (DIR_OBJS)/\̲1̲.o后面加入@的目的是: ( D I R D E P S ) / (DIR_DEPS)/%.dep目标依赖于%.cpp文件,那么如果在某一个cpp文件中新包含了或取消包含了一个头文件,如果不加上 (DIRDEPS)/@,那么当.cpp文件的时间戳更新了之后,无法更新当前.cpp的依赖(多了或者少了头文件),不能自动生成依赖关系
答:第一个all下的命令@echo “command-1”会被第二个all下的命令覆盖掉
答:(1)能编译成功
(2)原因:make有自己的隐式规则,虽然当前makefile文件中没有$(OBJS)对应的目标规则,但是make此时会使用自己的隐式规则,在它的隐式规则中,它发现.o文件可以由.c文件生成,那么它会自动生成%.o : %.c的模式匹配规则,然后又用隐式规则将.c文件编译成.o文件;此外,当前makefile文件中没有定义CC变量,说明make中已经定义了CC变量,在Linux下,CC变量的值就是gcc
make的隐式规则(下)
提示:% : %.c 生成的目标规则名为.c文件全名去掉.c后缀后的文件名字
make中的路径搜索(上)
答:不能。因为.c文件和func.h文件不在Project文件夹下(即跟makefile文件所在目录不同),make默认在当前makefile文件所在的目录下搜索文件。
说明
1.如图所写,需要.h头文件时会默认去inc文件夹下查找;需要.c文件时会默认去src文件夹下查找
2.vpath后可以指定多个文件夹,如vpath %.c src1 src2
make中的路径搜索(下)
1.条件:当前目录下有src和test文件夹,而且src文件夹下有.c文件而且test文件夹下没有.c文件
2.对于vpath和VPATH同时出现,make首先根据vpath的规则来查找相应格式的文件,如果在vpath指定的文件夹里找不到对应文件,那么make会从VPATH变量中的文件夹去查找。
3.如果上述规则还找不到文件,那么make会在自己的隐式规则里查找有没有什么规则可以利用来完成相应的目标。
4.如果上述方法都不行,最后才报错
补充
1.如果目标文件是通过搜索其它路径得到的,此时 @ 代 表 文 件 名 。 但 是 如 果 搜 索 路 径 出 现 在 变 量 G P A T H 中 , @代表文件名。但是如果搜索路径出现在变量GPATH中, @代表文件名。但是如果搜索路径出现在变量GPATH中,@将代表(搜索路径+文件名)。
解析
1.第一次make之后,在makefile所在的目录下生成app.out
2.此时将app.out移动到src文件夹下
3.通过touch main.cpp来更新main.cpp的时间戳,以致于再继续make的时候会重新生成.o文件,从而再次执行app.out目标下的命令来重新生成app.out
4.第二次make的结果表明,app.out在src下重新生成,而不是在makefile文件所在的目录下
5.原因是:GPATH变量的值是src,且src文件夹下已经有了旧的app.out目标文件,那么app.out目标规则下的@g++ -o $@ 中 的 ^中的 中的@是src/app.out
路径搜索综合示例
1.对于wildcard函数,当匹配到的文件不是在makefile文件所在的目录下时,匹配到的文件是别的文件夹下的,那么匹配到的文件会带该文件夹的前缀而不是只有文件名
2.所以需要notdir函数去掉路径前缀
3.对于模式匹配也是一样,如果源文件在别的文件夹下,那么在目标的依赖中通过%.cpp匹配到的文件,文件名包括文件夹前缀
解析
1.对于(一)中,.cpp文件都放在src文件夹中了,所以依赖中匹配到的文件名带src/前缀
2.疑问:既然通过%.cpp匹配到的文件名是src/sort.cpp,那么%代表的不是src/sort吗,为什么匹配到的目标是sort.o而不是src/sort.o呢
3.进一步试验,对比(一)和(二)匹配到的目标名,(二)中带src/前缀了,区别在于在all目标中的依赖的写法。一个是sort.o,一个是src/sort.o,因为是all目标依赖于sort.o或者src/sort.o的,而all目标缺这些依赖,那么make就会以实际你写的sort.o或src/sort.o做为新的目标规则来想办法生成make的依赖,所以匹配到后面的%.o
: %.cpp,那直接把%.o换成sort.o或者src/sort.o了
试验
解析
1.在make之前,当前目录下的文件有
2.在make之后,当前目录下的文件有
3.此makefile文件支持自动生成依赖关系,以及正确运用了make的路径搜索技术
打造专业的编译环境(上)
mod-cfg.mk
cmd-cfg.mk
mod-rule.mk
pro-cfg.mk
pro-rule.mk
模块Makefile文件
工程总Makefile文件
答:不能。因为在依赖中使用自动变量时,make不会把它当作一个目标名。
新的pro-rule.mk文件
第三方库的使用支持
答:这是为了防止一种极端的情况:第三方库中有某个或多个库的名字跟本地自己的库重名,而原则是优先使用本地自己的库。因为使用第三方库时是先将第三方库的库文件复制到build文件夹下,那么如果把$(EXTERNAL_LIB)放在依赖的最后,那么复制时,因为本地的库已经创建好了,文件存在了,复制第三方库的文件过来时就不会覆盖本地的库文件,从而达到优先使用本地自己的库的目的。