基本规则:
target... : prerequisite...
commands 命令一定要以Tab开头
target是生成的目标,可以是一个具体的文件,也可以是一个虚拟的标签
prerequisites是生成target所需要的文件
command指明怎么由prerequisites生成target
核心:如果prerequisites中有一个文件比target新,command就要执行。如果target后没有依赖文件,那就不会自动执行
找到makefile文件中第一个target并将其作为最终目标
若是target不存在或者后面prerequisites的比目标新,则执行command
变量也就是一个名字/字符串 类似于C语言中的宏定义。
用 $变量名 来引用变量 wildcard会进行通配符展开
make可以自动推导 文件依赖 以及 命令
典型依赖
看到 .o 文件自动把 同名的.c 文件加到文件依赖中
并且由 .c 文件生成 .o文件的命令也会包含在command中
clean: 或者
.PHONY:clean (指明clean是一个伪目标)
clean:
Makefile包含了一下5个东西:
显示规则
隐晦规则:make可以自动推导的规则
变量的定义:在执行makefile时会进行替换,类似宏替换,但是是延迟方式,只有要用到该规则的时候才展开。
文件指示: (1)引用其他makefile (2)指定makefile有效部分 (3)定义一个多行的命令
注释: # 单行注释
include 文件名:会把文件内容复制过来,和C语言一样
不指定路径的话: 默认当前文件夹下,或者/usr/include /usr/local/bin或者在make时指定寻找的文件夹。
-include:即使找不到,出错了也继续执行
这个定义了的话,每个makefile都会包含这里面定义了的文件
规则分为两部分: (文件)依赖关系 和 生成目标的方法 最前面的target会作为最终目标。
* : 表示任意多个字符
? : 匹配一个字符
[]: 匹配括号中指定的字符数
方法一:
不在Makefile文件中定义 VPATH 变量,make只会在当前文件夹下搜寻依赖文件和目标文件
VPATH中的多个目录用 : 分割
方法二:
make的时候带上关键字vpath
vpath pattern directories 为符合模式< pattern>的文件指定搜索目录 directories
vpath pattern 清除符合模式< pattern>的文件的搜索目录。
vpath 清除所有已被设置好了的文件搜索目录
伪目标并不是一个文件,而是一个标签。只能显示的指明才能执行 如 make clean
最好不要和文件重名。
可以使用标记 .PHONY来指明伪目标:即使和文件重名也要显示的指明才执行。
伪目标也可以有文件依赖和作为最终目标
规则中的目标可能不止一个,可以有多个
自动化变量 $@ 表示目前规则中所有的目标集合
可以看出这里相当于把目标依次带入到$@中
$表示执行一个Makefile函数,函数名叫 subst 后面是参数
适用于多目标规则。
格式:
targets... : targets_pattern : prerequisites-pattern
commands targets定义了一系列的目标,是目标的集合
target-pattern定义了目标的模式,例如 %.o
prerequisites-pattern定义依赖文件的模式
自动化变量 $< 表示所有的依赖集合中的第一个依赖文件
C/C++的编译器支持一个 -M 的选项。可以自动找出文件包含的头文件并生成依赖关系
比如 cc -M main.c会输出 main.o : main.c defs.h
GNU的编译器要改成 -MM
给了一个例子中间部分我是真的没看懂
命令新开一行以Tab开头或者跟在依赖规则之后以分号 ; 分隔
make会在命令执行之前把命令打印到屏幕上 如果在命令之前加上@则不打印
make -n 或者 --just-print 只显示命令而不执行,可以调试Makefile
make -s或者--silent 则不显示命令
注意 :如果需要上一条命令的结果应用在下一条命令的,应该使用分号;将命令写在同一行
比如第一条是cd命令,第二条是在cd之后的文件夹下运行就应该放一行中
make执行完一个命令之后,会检查命令的返回码,如果执行成功则会继续执行下一条,如果一个命令执行出错,则可能会终止当前规则,甚至可能导致所有的规则终止。
为了忽略命令的出错而继续执行,可以再命令开头加一个减号- 或者make -i/--ignore-errors
make -k/--keep-going是终止该规则的执行,但是其他的仍然会执行
适用于比较大的工程,每个目录写自己的makefile。
写法:
subsystem:
cd subdir && $(MAKE)
或者
subsystem:
$(MAKE) -C subdir
上级Makefile的变量可以传递到下级Makefile中,但是不会覆盖,除非指定了-e参数
export variables... 传递到下级
unexport variables... 不传递
直接export表示传递所有变量
注意:有两个变量一定会传递下去: SHELL MAKEFLAGS
命令包就是多条语句构成的一个集合
以define开头endef结束。
define name
Tab commands
endef
Makefile中的变量和C中的宏类似
变量名可以包含字符、数字、下划线。不能包含:# =以及空字符 大小写敏感
使用变量时 $变量名 但是更好的方式是 $(变量名) 或者 ${变量名}
要使用真实的 \(符号需要\)
用变量定义变量的方式(左边和右边都有变量): 方式一: 用 = 号。右侧变量的值可以定义在文件中的任意位置。(类似于非阻塞式的赋值)
这种会有潜在的问题:递归定义 如果调用了函数的话可能会造成递归调用(例如 函数wlidcard shell)
方式二: 用 := 号定义。 这样的前面定义的变量不能使用后面的变量,(类似于阻塞式的赋值)
自动变量MAKELEVEL会记录当前make递归调用的层数
例子1:定义一个变量,其值是一个空格
以#(注释)标注了结尾 例子2:
这里dir的值为 /foo/bar+一个空格
操作符 ?=
例如 FOO ?= bar 意思是如果FOO没有被定义过,那么FOO的值就为bar,如果FOO被定义过则这一句不起作用。
变量值的替换
格式 $ 把变量var中所有以a“结尾”的a替换成b。结尾是指“空格”或者“结束符”
另一种是以静态模式定义的,例如:${var:%.o=%.c}
变量的值再当成变量(相当于多层)
例如
x = y
y = z
z = u
a := $($(x) )
条件语句
ifdef xxx
xxxx
else
xxxx
endif
把变量的值再当做变量也可以用在左边
dir = foo
$(dir)_source := $(wildcard foo/*.c)
符号 +=
有些参数通常是make的命令行参数设置的,Makefile中定义的值会忽略,如果需要在Makefile中定义这些参数则需要用override。
override会重新定义来自环境变量和命令行参数的变量
利用define来定义多行变量
define xxx
xxxx
endef
仍然和宏替换一样,会用字符串替代引用的位置
make运行时系统环境变量会载入到Makefile文件中
但是如果Makefile已经定义了这个变量或者这个变量由make命令行带入,那么系统的环境变量将被覆盖
嵌套调用make时,默认情况下,通过make命令行设置的变量会作为系统环境变量的方式传递到下层
可以为某个目标设置局部变量,它可以和全局变量同名
语法 target : variable-assignment(变量以及赋值)
或者 target : override variable-assignment针对前面override关键字
这个变量会作用到该目标已经有该目标所引发的所有目标中去
和5.7类似,既然变量可以定义在某个规则上,那么变量也可以定义在符合某种模式的所有目标上
例如 %.o : CFLAGS = -O
例子:
ifeq(a,b) else endif
ifeq() endif
语法:
条件关键字 endif 或者
条件关键字 else endif
条件关键字有四个:
ifeq: ifeq(a,b) ifeq "a" 'b'(单双引号都可以)
ifneq: 同上
ifdef: ifdef 变量名 *注意:*测试该变量是否有值,即值非空,但是并不会把变量扩展到当前位置。 例子:
ifndef: 同上
调用函数 $(函数名,参数)或者 ${函数名,参数}
函数名与参数之间以空格分割
多个参数之间以逗号分割
$(foreach var,list,text)
把list中的每一个单词逐一代入var中,执行text。最后的返回值是list中每个单词执行完text之后的结果连起来,用空格分割。 var相当于只是一个临时变量,作用于只在foreach函数中。
$(if condition,then-part)
$(if condition,then-part,else-part)
condition 返回的是非空字符串那就是真。
可以用来创建参数化的函数。
你可以定义一个表达式,表达式中含有许多个参数,然后用call函数向表达式传递参数
$(call expression,para1,para2,....)
执行时,依次把para1代入 $(1),para2代入$(2)...
origin函数可以告诉你变量是哪里来的
$(origin 变量名) 是变量名而不是引用,引用是$变量名。
返回值:
undefined : 未定义
default : 默认的定义
environment : 环境变量并且当Makefile执行时 -e参数没有打开
file : 定义在Makefile中
command line : 被命令行定义的
override : 被override指示符定义的
automatic : 自动化变量
它的参数就是操作系统shell命令,返回值为命令在shell中的执行结果
和反引号 ` 是相同的功能
$(shell ...)后面的h是shell命令。 *注意:*shell函数会生成一个shell程序来执行命令,要注意运行性能
make提供了一些函数来控制make的运行
$(error text)产生一个错误并终止make的运行
$(warning text)产生警告信息
只有在这两个函数被调用时才会起作用。变量在需要的时候才会展开,所以定义在变量中的话,只有用到这个变量的时候才会起作用。
0 : 成功执行
1 : 出现错误
2 : 使用了-q选项并且make使得一些目标不需要更新
GNU make会依次寻找文件并读取文件执行: GUNmakefile → makefile → Makefile
make -f/--file 来指定读取并执行的Makefile文件,如果使用多次,那么多个makefile会连在一起给make执行。
默认情况下:make的最终目标是第一个目标
make+目标名称:典型例子就是make clean
make有一个环境变量叫 MAKECMDGOALS存放的是make的最终目标的列表 ,如果命令行上没有指定目标那么这个变量就是空值
GNU开源软件的Makefile往往有一套功能
有时候不想makefile的命令执行,只是想检查一下命令或者执行序列。可以使用make的下列参数:
-n --just-print --dry-run --recon
这些都是不执行参数。只打印命令但是不执行
-t --touch
相当于对目标文件执行touch操作,只是更新一下目标的时间。
-q --question
寻找目标,如果存在则什么信息都不会输出,如果目标不存在则打印出错信息
-W file --what-if=file --assume-new=file --new-file=file
file指定一个文件,一般是源文件或者依赖文件,make会根据规则推导来运行依赖于这个 文件的命令。
make会在自己的隐含规则库中寻找可用的规则。如果有多个符合,那么前面的被使用到
这会使得有时候指定了依赖文件但是却不生效。比如 foo.o:foo.p(pascal)按照规则会使用foo.c
make -r/--no-builtin-rules可以取消部分make预设的隐含规则
用后缀规则来定义的隐含规则仍然会执行。
后缀规则:就是说某些特定的文件名后缀还是会使用隐含规则。比如 .o : .c
默认后缀列表: .out .a .ln .o .c .cc .C .p .f .F .r .y .s .S .mod .sym .def .h .info .dvi .tex .texinfo .texi .txinfo .w .ch .web .sh .elc .el
常用隐含规则
|名称|规则|命令| |:--:|:-:|:--:| |编译C程序的隐含规则|<n>.o的目标会自动推导依赖目标为 <n>.c|$(CC) -c $(CPPFLAGS) $(CFLAGS)| |编译C++的隐含规则|<n>.o的目标会自动推导依赖目标为<n>.cc或<n>.C|$(CXX) -c $(CPPFLAGS) $(CFLAGS)| |汇编预处理的隐含规则|<n>.s的目标会自动推导依赖目标为<n>.S|默认使用c预编译器cpp $(AS) $(ASFLAGS)| |汇编隐含规则|<n>.o的目标会自动推导依赖目标为<n>.s|默认使用编译器as $(AS) $(ASFLAGS)| |链接obj文件的隐含规则|<n>的目标推导依赖目标为<n>.o|$(CC) $(LDFLAGS) <n>.o $(LOADLIBES) $(LDLIBS)对只有一个源文件的工程有效,对多个Obj文件也有效| 例子:
隐含规则的命令会使用到很多预先定义的变量。(由以上的表格我们也可以发现这一点)
一般这些变量分为两种:一种是命令的变量,比如$(CC),一种是命令的参数变量,比如$(CFLAGS)
关于命令的变量:(只写了部分可能用到的) |变量名|功能|默认命令| |:----:|:--:|:-----:| |AS|编译汇编程序|as| |CC|C语言编译程序|cc| |CXX|C语言编译程序|g| |CPP|c程序预处理|$(CC) -E| |RM|删除文件|rm -f| 关于命令参数的变量 |变量名|说明| |:----:|:--:| |ASFLAGS|汇编语言编译器参数| |CFLAGS|c语言编译器参数| |CXXFLAGS|c++编译器参数| |CPPFLAGS|c预处理器参数,Fortran和c翻译器也会用到| |LDFLAGS|链接器参数|
比如一个 .o文件需要先由 Yacc .y生成一个 .c文件,再由 .c编译生成 .o文件。
这种.c的目标我们称为中间目标
不论怎么样,不论中间目标有多少,Makefile都会执着的把你写的规则和隐含规则结合起来分析,努力地生成最终目标
除非中间目标不存在,才会触发中间规则。比如例子的.c文件不存在,才会考虑 .y->.c->.o
最终目标生成成功,那么中间过程中生成的所有中间目标都会被删除
强制说明一个文件或目标是中间目标: .INTERMEDIATE : mid
阻止make自动删除中间目标: .SECONDARY : sec 或者以模式的方式指定为.PRECIOUS的依赖目标。
隐含规则链中,同一个目标不能出现两次或者以上,为了防止自动推导时出现无限递归
make会优化一些特殊的隐含规则,而不生成中间文件
使用模式规则可以定义一个隐含规则。
模式规则和一般规则类似,只是目标的定义需要有%字符。%表示一个或多个任意字符
注意: 变量和函数的展开在make载入Makefile时,而模式中的%展开发生在运行时
模式规则中,至少规则的目标定义中要包含%。目标中的%的值决定了依赖文件中的%的值。
所谓自动化变量就是,这种变量会把模式中定义的一系列文件自动挨个的取出,直到所有的符合模式的文件都取完。
只出现在规则的命令中
所有的自动化变量及其说明
|自动化变量|意义说明| |:--------:|:-----:| |$@|表示规则中的所有目标文件| |$%|仅当目标是函数库文件,表示规则中的目标成员名。如果目标不是函数库文件,则为空。如果目标是foo.a(bar.o)那么$%为bar.o,$@为foo.a| |$<|依赖目标集的第一个目标| |$?|所有比目标新的依赖目标的集合| |$^|所有的依赖目标的集合,如果依赖目标中有多个重复的,这个变量会去重| |$+|所有依赖目标的集合,不去重| |$*|表示目标模式中%及其之前的部分。如果目标不是模式定义,但是目标的后缀名是make所识别的,那么$*就是除了后缀的部分,否则为空值| 注意: $@ $< $% $*在拓展时只有一个文件,另外的是文件列表
上面的变量还可以再后面跟上D/F,分别表示文件的目录名以及当前目录下符合模式的文件名。如$(@D)表示$@的目录部分
在定义的模式中。我们把%匹配的内容叫做“茎”。
函数库文件也就对OBJ文件打包得到的文件。也就是多个OBJ文件的集合。
一般用ar命令来完成打包工作
可以用如下形式指定函数库文件及其组成: archive(member)
多个member之间用空格分开 e.g. foolib(hack.o abc.o):hack.o abc.o
make搜索隐含规则时,如果目标是刑如 archive(member).那么会把目标变成(member)
本文章使用limfx的vsocde插件快速发布