Makefile精简教程
Makefile介绍
一个大型项目的构建,以Makefile描述,从而自动化编译。
Makefile所需描述的就是编译规则和依赖关系。
代码编译和链接
高级语言所编写的代码(如C/C++)必须经过其编译器生成汇编代码,汇编代码经过汇编编译器生成机器代码(中间代码文件,Object
File),然後经过链接器生成可执行文件。
编译产生的中间代码文件有很多,为方便使用,可以进行打包以生成库文件,库文件分为静态库和动态库,区别是链接方式。
基本规则
1 | |
<target>[1] 目标,可以是执行文件或标签<prerequisites>依赖,所依赖的文件或目标<command>命令,此规则要执行的命令
如果prerequisites中有文件比target文件要新,则command将要被执行。
Makefile的内容
- 显式规则
是说明生成目标,由编写者明确指出 - 隐晦规则
是make自动推导,以便简略书写 - 变量定义
类似shell环境变量,一般都是字符串 - 文件指示
其一,引用其他Makefile文件,其二,根据情况指定有效部分,其三,定义多行命令 - 注释
只有行注释,使用'#'字符以注释一行其后内容
make的工作
执行当前目录下的"Makefile"或"makefile"的文件,此文件中的第一个目标作为最终目标,分析依赖关系,执行对应的命令以构建最终目标。
可以显式地指定要生成的最终目标:make target,例如:make clean,此命令通常用以清楚所有中间文件和目标文件。
伪目标和多目标
伪目标不会生成target文件,一般没有依赖文件,可以显式地声明伪目标,只要在伪目标规则之前插入一行:
.PHONY : <target>
注意:伪目标也可以作为第一个目标(默认目标)。
类似的特殊目标还有:
.SUFFIXES: <suffixes>
定义用于后缀规则的后缀列表.INTERMEDIATE : <target>
依赖作为中间文件(显式指定).SECONDARY : <target>
依赖作为次要目标,不会被make自动删除.PRECIOUS : <target>
依赖作为先决条件,make被终止也不会删除<target>文件,可用于保存中间文件
其他详见GNU make文档。当然,目标也可以有多个,可以配合自动化变量和模式,方便书写。
自动生成依赖关系
可以使用C/C++编译器的功能以自动输出源文件所需的依赖关系("-M"参数),可以将之保存到文件中(可以补充为完整的规则),然後用include关键字引入。
注:使用GNU的C/C++编译器,应使用"-MM"参数。
文件包含
make支持三个通配符:'*','?'和'~',这和shell一样。
文件搜寻
特殊变量VPATH用于指定make的搜寻目录,make先在当前目录寻找,然後到指定目录寻找文件。例如:
VPATH = src:../headers
指定了两个目录"src"和"../headers",make按此顺序搜索,目录之间以冒号分隔。
还可以使用vpath关键字指定搜寻目录:
vpath <pattern> <directories>
为符合模式<pattern>的文件指定搜索目录<directories>vpath <pattern>
清除符合模式<pattern>的文件的搜索目录vpath
清除所有已被设置好了的文件搜索目录
vpath关键字更加灵活,可以连续使用。
include关键字
使用include关键字可以引用其他Makefile文件,其后跟文件名,可使用路径通配符及变量。
make首先在当前目录下寻找文件,未找到,则寻找参数(--include-dir)指定的目录下,然後寻找<prefix>/include目录下。
MAKEFILES环境变量
此环境变量的值是其他Makefile文件名(以空格分隔),其会在执行make前引入MAKEFILES中的Makefile文件(其中的target无效,且忽略错误)。
不建议使用此环境变量,其会影响每一次make命令。
静态模式
静态模式可以更方便定义多目标规则,语法如下:
1 | |
- targets 多个目标,可以有通配符
- target-pattern 目标集模式
- prereq-patterns 目标的依赖模式
模式target-pattern和prereq-patterns中都应有'%'字符,需要真实的'%'字符则应使用'\'字符转义。
模式使用:
模式字符'%'表示匹配任意长度的非空字符串。
条件判断
条件判断让make根据情况选择不同分支,完整语法如下:
1 | |
<conditional-directive>是条件表达式,以条件关键字开头,後接两个字符串作参数,参数可以用单/双引号配合空格分隔,或者用小括号配合逗号分隔。其中条件关键字有:
- ifeq 是否相同
- ifneq 是否不同
- ifdef 是否已定义(空赋值会取消定义)
- ifndef 是否未定义
条件语句不能分成两部分放在两份文件中。
注意:<text-if-true>和<text-if-false>是可选分支,其可以空格开头,但不应以Tab开头(否则会被认为是命令)。
命令
命令必须以Tab键开头,和shell命令一致,默认以shell解释执行。
通常,make会把要执行的命令输出到终端,以'@'字符在命令前则此命令不会被显示到终端。
当规则目标需要被更新时,make会逐条执行其命令,如果要让下一条命令在上一条基础执行则应同一行上以分号间隔。
命令返回码
每当命令执行完,make会检查命令返回码,命令返回成功则执行下一条命令,若命令出错(命令返回码非零)则终止执行当前规则。
注:为了忽略命令出错,可以在命令前面加一个'-'字符。
嵌套make
make执行可以嵌套,以方便维护。
可以定义以下规则:
1 | |
此规则的命令是进入子目录并执行make(附带参数传递)。
$(MAKE)是特殊的宏变量,代表make及其传递参数。
总控Makefile文件的变量可以传递到下级Makefile中,但不会覆盖下级定义的变量(除非指定了"-e"参数)。
要传递变量到下级Makefile中,可以声明:
export <variable ...>;
当然也可以阻止变量的传递:
unexport <variable ...>;
而且使用export关键字同时,可以进行赋值。
如果要传递所有变量,只要一个export就行,有两个变量(SHELL,MAKEFLAGS)必须传递到下级Makefile中。
但是,"-C","-f","-h","-o","-W"参数不会往下传递。
可以在$(MAKE)命令後跟"MAKEFLAGS=..."来设置向下级Makefile文件传递的参数,或者紧接其他需要传递的参数。
"-w"参数会在make输出一些信息,即进入或离开子目录时打印目录信息,使用"-C"参数时,"-w"参数会自动打开,而若有"-s"或"--no-print-directory"参数则"-w"参数总是无效的。
定义命令包
可以定义命令包,调用时就像变量一样,语法如下:
1 | |
<command-package>是命令包的名字,可以在目标生成规则中像变量一样使用命令包。例如:
1 | |
变量
Makefile中定义的变量,和shell变量一样,变量名区分大小写。
变量引用方式:$(var)或${var},推荐使用前者(也可以不使用括号)。
变量定义
变量可以用以构建变量的值,变量构建语法如下:
<var> = <expr>
或者:
<var> := <expr>
未定义的变量可以直接使用(但是空值),使用'='赋值的变量的值可以延迟定义(其值引用未定义的变量可以在之後定义,其值也在之後确定),使用":="赋值的变量不能如此(其值引用的变量应在之前已经定义)。
注:<expr>中的通配符不会展开,如果需要展开,使用wildcard函数即可。
还有一个操作符"?="用于构建变量值,若变量未定义在进行赋值,否则什么也不做。
如果需要定义带空格的变量值,需要使用'#'字符,如下:
<var> = $(nullstring) # comment
$(nullstring)是空变量,结尾的'#'之前的空格也会在var的值中(这需要特别注意)。
多行变量: 使用define关键字,定义方式和命令包一样。
变量操作
变量替换,格式如下:
$(var:a=b)或${var:a=b}
即将var中的a替换为b.
变量引用可以嵌套,变量还可以直接拼接,可以用函数处理变量值。
追加变量的值,使用"+="操作符:
<var> += <expr>
追加变量的值,以空格分隔。
override指示符:
make命令行设置的参数是不能直接更改的,使用override指示符以设置这些参数的值,只需在变量赋值表达式(包括多行变量定义)之前加上override修饰符。
环境变量
系统环境变量在make运行时载入,make命令行或者Makefile文件中可以覆盖系统环境变量,若make指定了"-e"参数,则系统环境变量将覆盖Makefile中定义的变量。
当嵌套执行make时,上层Makefile中export的变量会以系统环境变量的方式传递到下级Makefile中。
目标变量
通常定义的是全局变量(作用于整个文件),可以为某个目标设置局部变量,其语法是:
<target ...> : <variable-assignment>
或者:
<target ...> : overide <variable-assignment>
注:<variable-assignment>可以是各种赋值表达式。
模式变量
模式变量是把变量定义在符合其模式的所有目标上,其语法和目标变量一样。
注:make的模式一般至少含有一个'%'的。
自动化变量
在模式规则中,目标和依赖都是一系列的文件,每次对模式规则的解析都是不同的目标和依赖文件,使用自动化变量以引用之。
$@模式规则中的目标文件(一般是一个,也可以有多个)$%目标中库文件的成员名(目标不是库文件则其值空)$<模式规则中第一个依赖文件$?所有比目标新的依赖文件的集合,以空格分隔$^所有依赖文件(去除重复),以空格分隔$+所有依赖文件(不去除重复)$*目标模式中'%'字符匹配的及之前的部分
如果目标没有模式定义,"$*"表示除后缀的部分(make所识别的),或者为空值(make不识别的),注意,此特性是GNU make的,应尽量不使用。
上述七个自动化变量可以加上'D'和'F',分别表示取目录(dir函数)和取文件名(notdir函数)功能,如:"$(@D)"等。
注:自动化变量也可以使用在一般规则中。
函数
函数使用和变量基本一样,函数的值是其返回值,语法如下:
$(<function> <arguments>)
或者:
${<function> <arguments>}
<function>是函数名,<arguments>是函数参数,参数以逗号分隔,推荐使用小括号形式的函数调用语法。
字符串函数
$(subst <from>,<to>,<text>)
字符串替换,<text>中的<from>替换为<to>$(patsubst <pattern>,<replacement>,<text>)
模式字符串替换,查找<text>中的单词(以空格、Tab、回车或换行分隔)符合模式<pattern>的以<replacement>(也可以是模式)替换$(strip <string>)
去除空字符,单词间隔的多个空格合并为一个空格且去除首尾的空字符(空格、Tab及不可显示字符)$(findstring <find>,<in>)
查找字符串,在字符串<in>中查找<find>字符串,成功则返回<find>,否则返回空字符串$(filter <pattern...>,<text>)
过滤字符串,以<pattern...>模式(可以有多个模式)过滤<text>中的单词,返回符合的字符串$(filter-out <pattern...>,<text>)
反过滤字符串,返回不符合模式的字符串$(sort <list>)
字符串排序,返回升序排序後的字符串(会去除相同的单词)$(word <n>,<text>)
取字符串,取字符串<text>中第<n>个单词,若单词数不足<n>则返回空字符串$(wordlist <ss>,<ee>,<text>)
取单词串,返回<text>中第<ss>到第<ee>的单词串,若单词数不足则补空字符串$(words <text>)
计算单词个数$(firstword <text>)
取首单词
文件名函数
$(dir <names...>)
取目录路径,返回路径(可以多个,依次处理)最后一个'/'及之前的部分,若无'/'字符则返回"./"$(notdir <names...>)
取文件名,返回路径的非目录部分,与dir函数相反$(suffix <names...>)
取后缀,返回路径(文件名)序列的后缀(包含'.'字符),若文件无后缀则返回空字符串$(basename <names...>)
取非后缀部分,返回文件名序列除后缀的部分,与suffix函数相反$(addsuffix <suffix>,<names...>)
加后缀,把后缀<suffix>加到<names>中的每个单词后面并返回$(addprefix <prefix>,<names...>)
加前缀,把前缀<prefix>加到<names>中的每个单词前面并返回$(join <list1>,<list2>)
连接函数,单词序列<list1>和<list2>按位置两两连接(list1的单词在list2前面)
其他函数
- foreach函数
foreach来源于shell的for语句,格式如下:
$(foreach <var>,<list>,<text>)
把参数<list>中的单词逐一取出放到参数<var>所指定的变量中,然后再计算<text>所包含的表达式,并将返回值连接(以空格隔开的)成一个字符串返回 - if函数
格式:$(if <condition>,<then-part>)或
$(if <condition>,<then-part>,<else-part>)
<condition>的值是字符串,若此值非空则计算<then-part>返回,否则计算<else-part>返回 - call函数
计算参数化表达式,格式如下:
$(call <expression>,<parm>...)
<expression>是复杂表达式,其中可以引用参数<parm...>,引用方式为"$(n)"(代表第n个参数)。
返回的是<expression>的计算结果。注意:<parm...>参数应该去除所有多余的空格(空格也会代入参数) - origin函数
格式:$(origin <variable>)
<variable>是变量的名字(不应使用引用'$'字符),此函数返回变量的类型(来源,不带引号):- "undefined" 未定义
- "default" 默认定义,如"CC"变量等
- "environment" 环境变量
- "file" 此Makefile文件中的变量
- "command line" 命令行变量
- "override" override指示符重定义
- "automatic" 自动化变量
- shell函数
shell函数很简单,其后跟shell命令及其参数即可,其和反引号'`'有着相同的功能,即把shell命令输出返回,需注意shell函数会影响性能 - 控制make的函数
$(error <text ...>)
执行此函数会引发错误以终止make的执行并显示错误信息,此函数可以赋值给变量,由于变量延迟机制,只有需要计算时才会引发错误$(warning <text ...>)
执行此函数引发警告信息,和error类似,但不会终止make执行
隐含规则
make支持许多隐含规则,越靠前的越是经常使用,如果不使用隐含规则,则应该明确指出生成目标的命令。
默认的后缀列表:.out, .a, .ln, .o, .c, .cc, .C, .p, .f, .F, .r, .y, .l, .s, .S, .mod, .sym, .def, .h, .info, .dvi, .tex, .texinfo, .texi, .txinfo, .w, .ch .web, .sh, .elc, .el
隐含规则一览
- 编译C程序的隐含规则
后缀".o"目标依赖".c"文件,生成命令为:
$(CC) –c $(CPPFLAGS) $(CFLAGS) - 编译C++程序的隐含规则
后缀".o"目标依赖".cc"或".C"文件,生成命令为:
$(CXX) –c $(CPPFLAGS) $(CFLAGS) - 编译Pascal程序的隐含规则
后缀".o"目标依赖".p"文件,生成命令为:
$(CXX) –c $(CPPFLAGS) $(CFLAGS) - 编译Fortran/Ratfor程序的隐含规则
后缀".o"目标依赖".r",".f"或".F"文件,生成命令为:
".r":$(FC) –c $(FFLAGS) $(RFLAGS)
".f":$(FC) –c $(FFLAGS)
".F":$(FC) –c $(FFLAGS) $(CPPFLAGS) - 预处理Fortran/Ratfor程序的隐含规则
后缀".f"目标依赖".r"或".F"文件,生成命令为:
".r":$(FC) –F $(FFLAGS) $(RFLAGS)
".F":$(FC) –F $(CPPFLAGS) $(FFLAGS) - 编译Modula-2程序的隐含规则
后缀".sym"目标依赖".def"文件,生成命令为:
$(M2C) $(M2FLAGS) $(DEFFLAGS)
后缀".o"目标依赖".mod"文件,生成命令为:
$(M2C) $(M2FLAGS) $(MODFLAGS) - 汇编编译和预处理的隐含规则
后缀".o"目标依赖".s"文件,生成命令为:
$(AS) $(ASFLAGS)
后缀".s"目标依赖".S"文件,默认使用C预编译器 - 链接Object文件的隐含规则
<n>目标依赖于"<n>.o",生成命令为:
$(CC) $(LDFLAGS) <n>.o $(LOADLIBES) $(LDLIBS)
此规则对于多个Object文件也有效 - Yacc C程序时的隐含规则
后缀".c"目标依赖".y"文件(Yacc文件),生成命令为:
$(YACC) $(YFALGS) - Lex C程序时的隐含规则
后缀".c"目标依赖".l"文件(Lex文件),生成命令为:
$(LEX) $(LFALGS) - Lex Ratfor程序时的隐含规则
后缀".r"目标依赖".l"文件(Lex文件),生成命令为:
$(LEX) $(LFALGS) - 从C程序、Yacc文件或Lex文件创建Lint库的隐含规则
后缀".ln"(lint文件)目标依赖".c"文件,生成命令为:
$(LINT) $(LINTFALGS) $(CPPFLAGS) -i
对于依赖为".y"和".l"后缀的文件也是类似规则
这些隐含规则(make内建规则),可以不明确写出规则,make自动寻找所需规则和命令。当然也可以只写出依赖关系而不写对应的命令(注意,模式规则和后缀规则要写命令,否则会取消此规则)。
make会推导生成目标的一切方法,而不管中间目标有多少。隐含规则涉及中间目标,当中间目标不存在时才会触发中间规则,产生最终目标後,所产生的中间目标文件都会被删除。
隐含规则的变量
隐含规则的生成命令使用预先设置的变量。
关于命令
- AR: 函数库打包,默认
"ar" - AS: 汇编编译,默认
"as" - CC: C语言编译,默认
"cc" - CXX: C++编译,默认
"g++" - CO: 从RCS文件中扩展文件,默认
"co" - CPP: C程序预处理器,默认
"$(CC) -E" - FC: Fortran和Ratfor的编译器和预处理器,默认
"f77" - GET: 从SCCS文件中扩展文件,默认
"get" - LEX: Lex方法分析器(针对C或Ratfor),默认
"lex" - PC: Pascal语言编译,默认
"pc" - YACC: Yacc文法分析器(针对C语言),默认
"yacc" - YACCR: Yacc文法分析器(针对Ratfor),默认
"yacc -r" - MAKEINFO:
转换Texinfo源文件(.texi)到Info文件,默认
"makeinfo" - TEX: 从TeX源文件创建TeX DVI文件,默认
"tex" - TEXI2DVI: 从Texinfo源文件创建TeX
DVI文件,默认
"texi2dvi" - WEAVE: 转换Web到TeX,默认
"weave" - CWEAVE: 转换C Web到TeX,默认
"cweave" - TANGLE: 转换Web到Pascal语言,默认
"tangle" - CTANGLE: 转换C Web到C语言,默认
"ctangle" - RM: 删除文件命令,默认
"rm -f"
关于参数
以下命令参数,若未指明默认值,则默认值为空。
- ARFLAGS: AR命令的参数,默认
"rv" - ASFLAGS: 汇编编译器参数
- CFLAGS: C语言编译器参数
- CXXFLAGS: C++语言编译器参数
- COFLAGS: RCS命令参数
- CPPFLAGS: C预处理器参数(C和Fortran编译器也会用到)
- FFLAGS: Fortran语言编译器参数
- GFLAGS: SCCS的get程序参数
- LDFLAGS: 链接器参数(如:
"ld") - LFLAGS: Lex文法分析器参数
- PFLAGS: Pascal语言编译器参数
- RFLAGS: Ratfor 程序的Fortran 编译器参数
- YFLAGS: Yacc文法分析器参数
模式规则
模式可用在目标规则上,便成了模式规则,格式如下:
1 | |
目标中的'%'表示对文件名匹配(任意非空字符串),其决定了依赖中的'%'的值。
模式规则配合自动化变量使用很方便。
模式规则中'%'匹配的内容称为"茎",依赖的"茎"会传递给目标,当模式匹配包含'/'的文件时,目录部分不参与匹配,文件名部分匹配成功后再把目录部分加回去,于是传递的"茎"也包含目录部分。
可以用模式规则覆盖隐含规则,或者取消内建规则(用空命令覆盖即可)。
后缀规则
后缀规则是老式的隐含规则定义方法,将被模式规则所取代。
后缀规则的后缀应该是make所识别的,有两种形式:
- 单后缀规则:如
".c"相当于"% : %.c" - 双后缀规则:如
".c.o"相当于"%.o : %.c"
格式如下:
1 | |
后缀规则不允许有依赖文件,<suffix>是后缀(包含句点),后缀规则应包含命令,否则将毫无意义。
可以定义自己的后缀列表,以让make识别之,详见.SUFFIXES伪目标。
补充
本文档的内容十分丰富,若需简单使用Makefile,只需阅读基本规则和少量其他内容。
make的运行
make执行可以指定目标,参考"make clean"形式。
环境变量"MAKECMDGOALS",存放指定的终极目标列表(命令行指定),若没有指定,则此值是空值。
make的退出码
- 0 表示成功执行
- 1 make运行出现错误
- 2
若使用make的
"-q"参数并且make使得一些目标不需要更新,在返回2
GNU Makefile功能
以下可以选择实现:
- all 此伪目标编译所有目标
- clean 删除所有make创建的文件
- install 安装已编译的程序(执行文件拷贝到指定目录)
- print 列出改变过的源文件
- tar 源程序打包备份(一般是tar文件)
- dist 创建压缩文件(一般是tar压缩为gz文件)
- TAGS 更新所有目标以完整地重编译
- check和test 用以测试makefile的流程
make的命令行参数
"-b","-m"
忽略和其它版本make的兼容性"-B","--always-make"
认为所有的目标都需要更新(重编译)"-C <dir>","--directory=<dir>"
指定Makefile文件目录"-debug[=<options>]"
输出make的调试信息"-d"
相当于"–debug=a",输出所有调试信息"-e","--environment-overrides"
指明环境变量的值覆盖Makefile文件中定义变量的值"-f=<file>","--file=<file>","--makefile=<file>"
指定执行的Makefile文件"-h","--help"
显示帮助信息"-i","--ignore-errors"
在执行时忽略所有的错误"-I <dir...>","--include-dir=<dir>"
指定搜寻Makefile文件的目录"-j [<jobsnum>]","--jobs[=<jobsnum>]"
指定并行执行的任务的最大个数"-k","--keep-going"
出错也不停止运行"-l <load>","--load-average=<load>","-max-load=<load>"
指定make的运行负载"-n","--just-print","--dry-run","--recon"
仅输出命令序列但不执行"-o <file>","--old-file=<file>","--assume-old=<file>"
不重新生成的指定的文件"-p","--print-data-base"
输出Makefile中的所有数据(包括所有的规则和变量)"-q","--question"
不运行命令也不输出,仅检查目标是否需要更新"-r","--no-builtin-rules"
禁止make使用任何隐含规则"-R","--no-builtin-variabes"
禁止make使用任何作用于变量上的隐含规则"-s","--silent","--quiet"
在命令运行时不输出命令的输出"-S","--no-keep-going","--stop"
取消"-k"选项的作用(用以覆盖MAKEFLAGS中选项)"-t","--touch"
相当于touch命令,仅把目标修改日期更新"-v","--version"
输出make程序的版本和版权等信息"-w","--print-directory"
输出运行makefile之前和之后的信息"--no-print-directory"
禁止“-w”选项"-W <file>","--what-if=<file>","--new-file=<file>","--assume-file=<file>"
假设目标文件需要更新"--warn-undefined-variables"
有未定义变量则警告
函数库文件
函数库文件是一个或多个Object文件的打包文件,格式:
archive(member)
多个member以空格分隔,例如:
foolib(hack.o kludge.o)
函数库文件名称上还可以使用通配符,可以使用模式,和普通文件名称基本一样。
注意:进行函数库打包时,小心使用make的并行机制("-j"参数),因为这可能损坏此函数库文件。
特殊符号
| 符号 | 说明 |
|---|---|
'\' |
转义(其后一个字符),或者换行(位于行末) |
'-' |
忽略命令返回码 |
'@' |
关闭命令回显 |
'~' |
表示当前用户目录,或者用以指定用户目录 |
'*','?' |
通配符 |
'%' |
模式字符 |
'$' |
变量或函数引用 |
- 尖括号代表可选项 ↩