1. 什么是Makefile
- Makefile就和shell脚本一样,能自动批量处理文件。Makefile可以对整个工程按给定的规则进行编译,这中对整个工程的自动化编译可以极大地提高软件开发效率。
2. 总的规则
- make命令需要一个无后缀的Makefile文件,在这个文件书写编译的规则
- 编译的规则
- 如果这个工程没有编译过,则所有的C文件都要编译并被链接
- 如果工程中的某几个c文件被修改了,那只需要编译被修改的c文件,并链接目标程序
- 如果这个工程的头文件被修改了,那需要编译引用这几个头文件的c文件,并链接目标程序。
- 如何Makefile写的好,所有的一切只用make命令就可以完成,它能根据修改情况自动重编译。
3. 核心语法
target ...:prerequisites: ...
command
...
...
- target:可以是object file(目标文件),也可以是一个可执行文件,还可以是一个label(标签)
- prerequisites:生成该target所依赖的文件(target也可以包含在内)
- command:该target要执行的命令(gcc,shell,等命令)
总的来说,target这个一个或多个目标文件的生成依赖于prerequisites中的文件,同过command的规则生成。如果prerequisites中的文件比target新,command的命令就会执行。
4. 编译过程
- 输入make命令,make会在当前目录下寻找名为"Makefile"的文件,
- 找到后它会找第一个target文件,并将其作为最终的目标文件。
- 如果该文件不存在,或者所依赖的文件比它新,则执行command的命令生成target文件
- 如果target的依赖文件不存在,则make会在文件中寻找以该依赖文件为目标文件的依赖关系,再根据规则生成依赖文件。
整个make根据依赖性组织,make会一层一层地去找文件的依赖关系,直到最终编译出第一个目标文件。在寻找过程中,如果出现错误,例如找不到依赖文件,make会直接退出,并报错。而对于定义的命令的错误,或是编译不成功,make根本不理会,make只关心文件的依赖关系。
5. Makefile的变量
objects = main.o a.o b.o
target: $(object)
cc -o target $(object)
- 如果某些文件被重复使用,可以声明一个变量,和c语言的宏差不多。
- 变量名 = 文件名 文件名
- 以 $(变量名) 来使用变量
6. 清空目标文件
- 每个Makefile文件都应该写一个目标清空的规则,这不仅便于重编译,也利于保持文件的清洁。
7. Makefile文件的结构
- 显示规则:显示规则说明了如何生成一个或者多个目标文件,由我们自己指出要生成的文件、文件的依赖文件和生成的命令。
- 隐晦规则:make有自动推导的功能
- 变量的定义:在Makefile中我们可以定义一系列的变量,变量一般是字符串,像C语言的宏,当Makefile被执行时,变量会被扩展到相应的引用位置上
- 指示文件:在Makefile中引用的另一个Makefile文件,就像C语言的include一样。
5 . 注释:Makefile中只有行注释,用#,可以用 \ 转义显示#
8.伪目标
- 像clean那样,我们并没有生成"clean"这个文件,"伪目标"并不是一个文件,只是一个标签。由于"伪目标"不是一个文件,所有make无法生成它的依赖关系和决定它是否要执行。我们只能通过显示地指明这个"目标",才能让其生效。"伪目标"的取名不能和文件名重名,不然失去了"伪目标"的意义。
- 用 .PHONY 来显示地指明目标是"伪目标"
9. 常见指令
- 显示命令:通常make会把要执行的命令行在命令执行前输出到屏幕上,在命令前用@字符,那命令不会显示
- make -n 或者 make --just-print:只显示命令,不执行命令,这个有利于调试
- make -s 或者 make --silent 或者 make --quiet:全面禁止命令的显示
- 命令执行:如果要让上一条命令的结果应用在下一条,应该使用分号分隔。
- 命令出错:每当命令运行后,make会检测每个命令的返回码,如果命令执行成功make会执行下一条,当规则中的所有命令成功返回后,这个规则就算成功完成。如果一个规则中的某个命令出错(命令退出码非零),make会终止执行当前规则,这有可能终止所有规则的执行。但有时候,命令的错误并不代表执行不下去,我们不希望这种错误导致终止规则的运行。
- 在Makefile的命令行前加 - :标记为不管命令出不出错都认为是成功的
- make -i 或者 make --ignore-errors:这是全局的办法,Makefile中的所有命令都会忽略错误。
- make -k 或者 make --keep-going:如果某个规则中的命令出错了,那么就终止该规则的执行,但继续执行其他规则。
10. 变量
- 在Makefile中定义的变量,就像C语言中的宏一样,它代表一个文本字符,在Makefile中执行的时候其会自动原模原样地展开在所使用的地方。
- 命名规则:可以包含字符、数字、下划线(可以是数字开头),但不应该含有 : # = 或者空字符(空格或回车等),变量是大小写敏感的。
- 变量在命名时需要赋初值,使用时在变量名前加上
,可以用$$。
- 多行变量
define 变量名
变量的值
···
endef
11. 条件判断
target: $(object)
条件()
command
else
command
endif
- ifeq(<arg1>,<arg2>) :比较两个参数是否相等
- ifneq(<arg1>,<arg2>) : 比较两个参数是否不等
- ifdef 变量名 : 测试变量是否有值
- ifdef 变量名 : 测试变量是否为空
12. 函数
- 语法
- $(<function> <arguments>)
- ${<function> <arguments>}
- <function>:函数名
- <arguments>:参数,参数间以逗号 , 分隔
- 函数和参数直接用 空格 分隔
- 函数调用以$开头
- 字符串处理函数:
-
subst: $(subst <from>,<to>,<text>)
- 字符串替换函数
- 把 <text> 中的<from>字符串替换成<to>
- 函数返回被替换后的字符串
-
patsubst: $(patsubst <pattern>,<replacement>,<text>)
- 模式字符串替换函数
- 查找 <text> 中的单词是否符合模式<pattern>,( 单词以 空格、 Tab、 回车、 换行 分隔),如果匹配,则用 <replacement> 替换。
- 函数返回被替换后的字符串
-
strip: $(strip <string>)
- 去空格函数
- 去掉<string>字串中开头和结尾的空字符
- 函数返回被去掉空格的字符串
-
findstring: $(findstring <find>,<in>)
- 查找字符串函数
- 在字符串<in>中查找<find>字串
- 如果找到,返回<find>,否则返回空字符串
-
filter: $(filter <pattern...>,<text>)
- 过滤函数
- 以<pattern> 模式过滤<text>字符串中的单词,保留符合模式<pattern>的单词,可以有多个模式
- 返回符合模式<pattern>的字串
-
filter-out: $(filter-out <pattern ...>,<text>)
- 反过滤函数
- 以<pattern> 模式过滤<text>字符串中的单词,去除符合模式<pattern>的单词,可以有多个模式
- 返回不符合模式<pattern>的字串
-
sort: $(sort <list>)
- 排序函数
- 给字符串<list>中的单词排序(升序)
- 返回排序后的字符串
-
word: $(word <n>,<text>)
- 取单词函数
- 取字符串<text>中的第<n>个单词(从1开始)
- 返回字符串<text>中的第<n>个单词,如果<n>比<text>中的单词数大,那么返回空字符串
-
wordlist: $(wordlist <ss>,<e>,<text>)
- 取单词串函数
- 从字符串<text>中取从<ss>开始到<e>的单词串,<ss>和<e>是一个数字
- 返回字符串<text>中从<ss>到<e>的单词字串,如果<ss>比<text>中的单词数要大,则返回空字符串。如果<e>大于<text>的单词数,那么返回从<ss>开始,到<text>结束的单词串
-
words: $(words <text>)
- 单词个数统计函数
- 统计<text> 中字符串中的单词个数
- 返回<text>中的单词数
-
firstword: $(firstword <text>)
- 首单词函数
- 取字符串<text> 中的第一个单词
- 返回字符串<text>的第一个单词
- 文件名操作函数
每个函数的参数字符串都会被当做一个或是一系列的文件名来对待
-
dir: $(dir <names...>)
- 取目录函数
- 从文件名序列<names>中取出目录部分。目录部分是指最后一个反斜杠 / 之前的部分,如果没有反斜杠,那么返回 ./
- 返回文件名序列<names>的目录部分
-
notdir: $(notdir <names...>)
- 取文件函数
- 从文件名序列<names>中取出非目录部分。目录部分是指最后一个反斜杠 / 之后的部分
- 返回文件名序列<names>的非目录部分
-
suffix: $(suffix <names...>)
- 取后缀函数
- 从文件名序列<names>中取出各个文件名的后缀
- 返回文件名序列<names>的后缀序列,如果文件没有后缀,则返回空字符串
-
basename: $(basename <names...>)
- 取前缀函数
- 从文件名序列<names>中取出各个文件名的前缀部分
- 返回文件名序列<names>的前缀序列,如果文件没有前缀,则返回空字符串
-
addsuffix: $(addsuffix <suffix>,<names...>)
- 加后缀函数
- 把后缀<suffix>加到<names>中的每个单词后面
- 返回加过后缀的文件名序列
-
addprefix: $(addprefix <prefix>,<names...>)
- 加前缀函数
- 把前缀<prefix>加到<names>中的每个单词后面
- 返回加过前缀的文件名序列
-
join: $(join <list1>,<list2>)
- 连接函数
- 把<list2>中的单词对应地加到<list1>的单词的后面。如果<list1>的单词个数要比<list2>的多,那<list1>中的多出来的单词将保持原样。如果<list2>的单词个数要比<list1>多,那<list2>多出来的单词将被复制到<list1>中
- 返回连接过后的字符串
-
dir: $(dir <names...>)
- 取目录函数
- 从文件名序列<names>中取出目录部分。目录部分是指最后一个反斜杠 / 之前的部分,如果没有反斜杠,那么返回 ./
- 返回文件名序列<names>的目录部分
- foreach函数
- foreach: $(foreach <var>,<list>,<text>)
- 用来做循环
- 把参数<list>中的单词逐一取出放到参数<var>所指定的变量中的,然后再执行<text>所包含的表达式。每次<text>会返回一个字符串,循环过程中,<text>的所返回的每个字符串会以空格分隔,最后当整个循环结束时,<text>所返回的每个字符串所组成的整个字符串(以空格分隔)将会是foreach函数的返回值
- <var> 最好是一个变量名
- <list>可以是一个表达式
- <text>中一般会使用<var>这个参数来依次枚举<list>中的单词
- if函数
- if:
- $(if <condition>,<then-part>)
- $(if <condition>,<then-part>,<else-part>)
- if函数可以不包含else,如<condition>返回的为非空字符串,那表达式相当于返回值为真
- <then-part>和<else-part>只会有一个被计算
- call函数
- call: $(call <expression>,<parm1>,<parm2>,...,<parm>)
- 唯一一个可以用来创建新的参数化的函数
- 当make执行这个函数时,<expression>参数中的变量,如
(2)等,会被参数<parm1>、<parm2>、<parm3>依次取代,而<expression>的返回值就是call函数的返回值
6.origin函数
- origin: $(origin <variable>)
- 他并不操作变量的值,它只是告诉你你的这个变量是哪里来的
- <variable>是变量的名字,不应该是引用,所以最好不要在<variable>中使用$字符。
- origin函数的返回值:
- undefined:如果<variable>从来没有定义过,origin函数返回这个值undefined
- default:如果<variable>是一个默认的定义
- environment:如果<variable>是一个环境变量,并且当Makefile被执行时,-e参数没有被打开
- file: 如果<variable>这个变量被定义在Makefile中
- command line:如果<variable>这个变量是被命令行定义的
- override: 如果<variable>是被override指示符重新定义的
- automatic:如果<variable>是一个命令运行中的自动化变量
- shell函数
contents := $(shell cat foo)
files := $(shell echo *.c)
- 它的参数就是操作系统的shell命令,shell函数把执行操作系统命令后的输出作为函数返回。
- 控制make的函数
make提供了一些函数来控制make的运行,通常你需要检测一些运行Makefile时的运行时信息,并且根据这些信息来决定是让make继续执行还是停止
- $(error <text ...>)
- 产生一个致命错误,<text>是错误信息。error函数不会在一被使用就会产生错误信息,所有如果把其定义在某个变量中,并在后续的脚本中使用这个变量,那也是可以的
- $(warning <text ...>)
- 它并不会让make退出,只是输出一段警告信息,而make继续执行。
13. make的运行
1.make的退出码
- 0:表示执行成功
- 1: 如果make运行时出现任何错误,返回1
- 2: 如果使用了make的 -q 选项,并且make使得一些目标不需要更新,那返回2
- 指定Makefile
- 指定一个特殊名字的Makefile
- make -f 文件名
- make --file 文件名
- make --makefile 文件名
3.指定目标
- 一般make的最终目标是第一个目标,如果要指定完成某个目标,可以在make命令后直接跟目标名
4.伪指令
- 在Unix世界中,特别是GNU的开源软件发布时,它的Makefile文件中都包含编译,安装,打包等功能,我们可以做个参照
- all:这个伪指令是所有目标的目标,其功能一般是编译所有的目标
- clean:这个伪指令功能是删除所有被make创建的文件
- install:这个伪指令功能是安装已编译好的程序,其实就是把目标执行文件拷贝到指定的目标中去
- print:这个伪指令的功能是列出改变过的源文件
- tar:这个伪指令功能是把源程序打包备份,就是一个tar文件
- dist:这个伪指令的功能是创建一个压缩文件,一般是把tar文件压成z文件或gz文件
- TAGS:这个伪指令的功能是更新所有的目标,以备完整地重编译使用
- check和 test:这两个一般是用来测试Makefile的流程
- 检查规则
- make -n
- make --just-print
- make --dry-run
- make --recon
- 不执行参数,这些参数只是打印命令,不管目标是否更新,把规则和连带规则下的命令打印出来,但不执行,这些对调试很有帮助
- make -t
- make --touch
- 这个参数的意思是把目标文件的时间更新,但不更改目标文件,make假装编译目标,但不是真正的编译目标,只是把目标变成已编译过的状况
- make -q
- make --question
- 这个参数的行为是找目标的意思,如果目标存在,什么也不输出,当然也不会执行,如果目标不存在,会打印出一条出错信息
- make -W <file>
- make --what-if=<file>
- make --assume-new=<file>
- make --new-file=<file>
- 这个参数需要指定一个文件,一般是源文件,make会根据规则推导来运行依赖于这个文件的命令,
- make的参数
- 忽略和其他版本make的兼容性
- make -b
- make -m
- 认为所有的目标都需要更新(重编译)
- make -B
- make --always-make
- 指定读取Makefile的目标,如果有多个 -C 参数,make的解释是后面的路径以前面的作为相对路径,并最后的目录作为被指定目录
- make -C <dir>
- make --directory=<dir>
- 输出make的调试信息
- make -debug[=<options>]
- 如果没有参数,就输出最简单的调试信息
- a:就是all,输出所有的调试信息
- b:就是basic,只输出简单的调试信息,即输出不需要重编译的目标
- v:也就是verbose,在b选项的级别之上,输出的信息包括哪个make被解析,不需要重编译的依赖文件
- i:就是implicit,输出隐含规则
- j:就是jobs,输出执行规则中命令的详细信息,如命令PID,返回码等
- m:就是Makefile,输出make读取Makefile,更新Makefile,执行Makefile的信息
- make -d 相当于 make -debug=a
- 指明环境变量的值覆盖Makefile中定义的变量的值
- make -e
- make --environment-overrides
- 指定需要执行的Makefile
- make -f=<file>
- make --file=<file>
- make --makefile=<file>
- 显示帮助信息
- make -h
- make --help
- 在执行时忽略所有的错误
- make -i
- make --ignore-errors
- 指定一个被包含Makefile的搜索目标,可以使用多个 -I 参数来指定多个目标
- make -I <dir>
- make --include-dir=<dir>
- 指同时运行命令的个数,如果没有这个参数,make运行命令时能运行多少就运行多少。如果有一个以上的 -j 参数,那么仅最后一个 -j 才有效
- make -j [<jobsnum>]
- make --jobs[=<jobsnum>]
- 出错也不停止运行,如果生成一个目标失败了,那么依赖于其上的目标就不会被执行
- make -k
- make --keep-going
- 指定make运行命令的负载
- make -l <load>
- make --load-average[=<load>]
- make -max-load[=<load>]
- 仅输出执行过程中的命令序列,但并不执行
- make -n
- make --just-print
- make --dry-run
- make --recon
- 不重新生成指定的<file>,即使这个目标的依赖文件新于它
- make -o <file>
- make --old-file=<file>
- make --assume-old=<file>
- 输出Makefile中的所有数据,包括所有规则和变量,这个参数会让一个简单的Makefile都会输出一堆信息。如果只想输出信息而不想执行Makefile,可以使用 make -qp 的命令。如果想查看执行Makefile前的预设变量和规则,可以使用 make -p -f /dev/null。这个参数输出的信息包含你的Makefile文件的文件名和行号,所以这个参数调试Makefile会很有用。
- make -p
- make --print-data-base
- 不运行命令,也不输出,仅仅检查所指定的目标是否需要更新,如果是0,则说明要更新,如果是2,则说明有错误发生
- make -q
- make --question
- 禁止make使用任何隐含规则
- make -r
- make --no-builtin-rules
- 禁止make使用任何作用于变量上的隐含规则
-make -R- make --no-builtin-variables
- 在命令运行时不输出命令的输出
- make -s
- make --silent
- make --quiet
- 取消 -k 选项的作用,因为有些时候make的选项是从环境变量"MAKEFLAGS"中继承下来的,所以可以在命令行中使用这个参数来让环境变量中的 -k 选项失效
- make -S
- make --no-keep-going
- make --stop
- 只是把目标的修改日期变成最新的,也就是阻止生成目标的命令运行
- make -t
- make --touch
- 输出make程序的版本、版权等关于make的信息
- make -v
- make --version
- 输出运行Makefile之前和之后的信息,这个参数对于跟踪嵌套式调用make时很有用
- make -w
- make --print-directory
- 禁止 -w 选项
- make --no-print-directory
- 假定目标<file>需要更新,如果 -n 选项使用,那么参数会输出该目标更新时的运行动作,如果没有 -n ,那就像运行Unix的 touch 命令一样,使得 <file>的修改时间为当前时间
- make -W <file>
- make --what-if=<file>
- make --new-file=<file>
- make --assume-file=<file>
- 只有make发现有未定义的变量,就输出警告信息
- make --warn-undefined-variables
14. 隐含规则
- 如何不明确地写下规则,make就会在这些预先设置的隐含规则中寻找所需要规则
- make -r 或 make --no-builtin-rules 可以取消部分预设的隐含规则
- 常见的隐含规则
- 编译C程序的隐含规则
- <n>.o 的目标的依赖目标会自动推导为 <n>.c ,并且其生成命令时
(CPPFLAGS) $(CFLAGS)
- <n>.o 的目标的依赖目标会自动推导为 <n>.c ,并且其生成命令时
- 编译C++程序的隐含规则
- <n>.o 的目标的依赖目标会自动推导为 <n>.cc 或者 <n>.c ,并且其生成命令是
(CPPFLAGS) $(CFLAGS)
- <n>.o 的目标的依赖目标会自动推导为 <n>.cc 或者 <n>.c ,并且其生成命令是
- 汇编和汇编预处理的隐含规则
- <o>.o 的目标的依赖目标会自动推导为<n>.s ,默认使用编辑器 as,生成命令是:
(ASFLAGS) 。<n>.s 的目标的依赖目标会自动推导为<n>.S,默认使用C预编译器cpp,其生成命令是:
(ASFLAGS)
- <o>.o 的目标的依赖目标会自动推导为<n>.s ,默认使用编辑器 as,生成命令是:
- 链接Object文件的隐含规则
- <n>目标依赖于<n>.o,通过运行C的编辑器来运行链接程序生成(一般是ld),其生成命令是:
(LDFLAGS) <n>.o
(LDLIBS)。这个规则只对一个源文件的工程有效,同时也对多个object文件的也有效
- <n>目标依赖于<n>.o,通过运行C的编辑器来运行链接程序生成(一般是ld),其生成命令是:
- 隐含规则使用的变量
- 在隐含规则中基本上使用了一些预先设置的变量,可以在Makefile中改变这些变量的值,或者在make的命令行中传入这些值,或在你的环境变量中设置这些值
- 可以用 make -R 或者 make --no-builtin-variables 参数来取消所定义的变量对隐含规则的作用
- 隐含规则使用的变量可以分成:和命令相关的 或 和参数相关
- 关于命令的变量
- AR:函数库打包程序。默认命令是ar
- 关于命令参数的变量