目录
0 Make简介
Make是一个自动化编译工具,但是它本身并没有编译和链接的功能。其采用一种类似于批处理的方式,依据用户编写的规则文件Makefile中指定的命令来进行编译和链接。对于一个工程而言,我们使用make指令就能完全的编译整个工程的源文件,相比于使用GCC编译来说,效率就大大提高了。
1 Makefile规则语法
<target>: <prerequisites1> <prerequisites2>... [TAB] <command>
- target:目标,最终需要通过Make工具构建出的具体东西
- prerequisites:依赖,构建目标的先决条件。只有在满足先决条件的情况下,才能构建目标。在出现⽬标依赖关系时,Make⼯具会按从左到右的顺序构建规则中的所有依赖。
- command:命令,在依赖全部满足的条件下,调用命令来构建出目标
一个Makefile规则是由⽬标(targets)、先决条件(prerequisites)以及命令(commands)所组成的。
⽬标和先决条件之间表达的就是依赖关系(dependency),这种依赖关系指明在构建⽬标之前,必须保证先决条件先满⾜(或构建)。⽽先决条件可以是其它的⽬标,当先决条件是⽬标时,其必须先被构建出来。
⼀个规则中⽬标可以有多个,当存在多个⽬标,且这⼀规则是Makefile中的第⼀个规则时,如果我们运⾏ make命令不带任何⽬标,那么规则中的第⼀个⽬标将被视为是缺省⽬标。
规则的功能就是指明Make什么时候以及如何来为我们构建⽬标。
当Make在运⾏⼀个规则时,如果先决条件中相关的⽂件的时间戳⼤于⽬标的时间戳,则Make认为其发生了变化,那么就会在运⾏规则时根据命令重新构建⽬标。
1.1 命令书写
每条规则中的命令和操作系统Shell的命令行应该是一致的。Make会按照顺序一条一条的执行命令,每条命令的开头必须以TAB键开头,除非,命令是紧跟在依赖规则后面的分号后的。在命令行之间中的空格或是空行会被忽略,但是如果该空格或空行是以Tab键开头的,那么Make会认为其是一个空命令。
Make的命令默认是被 /bin/sh —— UNIX的标准Shell解释执行的(也可以特别指定一个其它的Shell)。 Makefile中,#是注释符,很像C/C++中的//,其后的本行字符都被注释。
1.2 命令显示
通常,Make会把其要执行的命令行在命令执行前输出到屏幕上。当我们用 @ 字符在命令行前,那么,这个命令将不被Make显示出来。
如果在执行make命令时,带入参数-n
或--just-print
,那么Make只显示命令,而不会执行命令,这个功能很有利于我们调试Makefile,观察我们书写的命令执行起来是什么样子的。
而make命令参数-s
或--silent
或--quiet
则是全面禁止命令的显示,但是Make仍然会静默执行命令。
1.3 命令前缀
- [没有前缀]:输出执行的命令以及命令执行的结果,出错的话停止执行
- [前缀@]:只输出命令执行的结果,出错的话停止执行,但不显示命令
- [前缀-]:命令执行有错的话,忽略错误,继续执行
1.4 命令出错
有些时候,某一条命令的出错不表示整个Makefile就是错误的。例如mkdir命令,我们需要建立一个目录,如果目录不存在,那么mkdir就成功执行,如果目录存在,那么就会报错。但是我们使用mkdir的意思就是一定要有这样的一个目录,于是我们不希望因为mkdir出错而终止规则的运行。
为了做到忽略命令的出错,我们可以在Makefile的命令行前加一个前缀“-” (在TAB键之后),指示Make即使执行命令出错,也忽略错误,继续执行下一条命令。
# 示例 clean: -rm -f *.o
还有一个全局的办法,就是在使用make命令时,带上-i
或是--ignore-errors
参数,那么,Makefile中所有命令都会忽略错误。
如果一个规则是以 .IGNORE作为目标的,那么这个规则中的所有命令将会忽略错误。这些不同级别的忽略命令出错的方法,使得我们可以根据实际情况进行设置。
2 关键字
.PHONY:
采用.PHONY关键字声明⼀个目标后,Make并不会将其当作⼀个文件来处理,而只是当作⼀个概念上的目标。对于伪目标,由于其并不与文件关联,所以每⼀次make这个伪目标时,其所在的规则中的命令都会被执行。
.PHONY: clean
override:
在设计Makefile时,我们并不希望用户将我们在Makefile中定义的某个变量覆盖掉,那就得用override指令声明该变量。
# override 用法 .PHONY: all override foo = x all: @echo "foo = $(foo)" # 运行结果 $ make foo=ewqqeq foo = x
include:
include在Makefile中的作用类似于#include在C/C++文件中的作用。
# 利用 include 指令在Makefile中包含依赖文件 include $(DEPS) # 若在include前加上“-”, 那么即使其包含的依赖文件不存在, make也会忽略这个错误 -include $(DEPS) # 利用 include 指令在Makefile中包含别的Makefile include <filenames>
vpath:
vpath关键字用于设置Make的文件搜索路径。它可以指定不同的文件在不同的搜索目录中,其使用方法有三种。
# 为符合模式<pattern>的文件指定搜索目录<directories> # vpath <pattern> <directories> vpath %.h ../headers # % 的意思是匹配零或若干字符,%.h 表示所有以 .h 结尾的文件 # 此命令指示make在../headers目录下搜索所有以 .h 结尾的文件 # 连续的使用vpath语句,以指定不同的搜索策略 vpath %.c OfilmSwc:GeelySwc vpath %.c src vpath % Application # 清除符合模式<pattern>的文件的搜索目录 # vpath <pattern> # 清除所有已被设置好了的文件搜索目录 # vpath
3 变量
# 采用了变量编写的makefile .PHONY: clean CC = gcc RM = rm EXE = simple OBJS = main.o foo.o $(EXE): $(OBJS) $(CC) -o $(EXE) $(OBJS) main.o: main.c $(CC) -o main.o -c main.c foo.o: foo.c $(CC) -o foo.o -c foo.c clean: $(RM) $(EXE) $(OBJS)
变量的使用可以提高Makefile的可维护性。⼀个变量的定义很简单,就是⼀个变量名后⾯跟上⼀个等号,然后在等号的后⾯放这个变量所期望的值。对于变量的引⽤,则需要采用$(变量名)或者${变量名}这种模式。在上述Makefile中,我们引入了CC和RM两个变量,⼀个用于保存编译器名,另⼀个用于指示删除文件的命令是什么。还有就是引入了EXE和OBJS两个变量,⼀个用于存放可执行文件名,可另⼀个则用于放置所有的目标文件名。采用变量的话,当我们需要更改编译器时,只需更改变量赋值的地方,非常方便,如果不采用变量,那我们得更改每⼀个使用编译器的地方,很是麻烦。
注意,变量是大小写敏感的,foo、Foo和FOO是三个不同的变量名。传统的Makefile的变量名是全大写的命名方式,但笔者推荐使用大小写搭配的变量名,如:MakeFlags。这样可以避免和系统的变量冲突,而发生意外的事情。
3.1 自动变量
对于每⼀个规则,目标和先决条件的名字会在规则的命令中多次出现,每⼀次出现都是⼀种麻烦,更为麻烦的是,如果改变了目标或是依赖的名,那得在命令中全部跟着改。因此为了简化这种更改方法,我们需要用到Makefile中的自动变量。
# $@:表示⼀个规则中的⽬标。当我们的⼀个规则中有多个⽬标时,$@所指的是其中任何能使命令可以被运⾏的⽬标 # $^:表示的是规则中的所有先决条件 # $<:表示的是规则中的第⼀个先决条件 # $?: 表示的是所有比目标新的依赖 # makefile如下: .PHONY:all all:first second third @echo "\$$@ = $@" @echo "\$$^ = $^" @echo "\$$< = $<" first second third: # 需要注意的是,在 Makefile 中‘$’具有特殊的意思,因此,如果想采⽤ echo 输出‘$’,则必需⽤两个连着的‘$’。还有就是,$@对于 Shell 也有特殊的意思,我们需要在“$$@”之前再加⼀个脱字符‘\’。 # 运行结果: $ make $@ = all $^ = first second third $< = first
3.2 自带变量
3.2.1 MAKE
表示make命令名是什么。当我们需要在Makefile中调用另⼀个Makefile时需要用到这个变量,采用这种方式,有利于写⼀个容易移植的Makefile。Makefile如下:
.PHONY: clean all: @echo "MAKE = $(MAKE)" # 运行结果: $ make MAKE = D:/1Software/Msys2/ucrt64/bin/make.exe
3.2.2 MAKECMDGOALS
表示的是当前用户所输入的make目标是什么,Makefile如下:
.PHONY: all clean all clean: @echo "\$$@ = $@" @echo "MAKECMDGOALS = $(MAKECMDGOALS)" # 运行结果: $ make $@ = all MAKECMDGOALS = $ make all $@ = all MAKECMDGOALS = all $ make clean $@ = clean MAKECMDGOALS = clean # 从测试结果看来,MAKECMDGOALS 指的是⽤户输⼊的⽬标,当我们只运⾏ make 命令时,虽然根据 Makefile 的语法,第⼀个⽬标将成为缺省⽬标,即 all ⽬标,但 MAKECMDGOALS 仍然是空,⽽不是 all
3.2.3 MAKEFLAGS
表示当前make指令的参数信息
# 为make指令加入 -w 参数 MAKEFLAGS = -w
3.2.4 MAKELEVEL
表示当前Makefile的嵌套调用层数
3.2.5 MAKEFILE_LIST
表示当前make解析的Makefile文件,以空格隔开,存储顺序为Makefile调用顺序。
MAKEFILE_LIST会在Makefile文件前加上相对于make工作目录的路径
3.2.6 VPATH
在一些大的工程中,有大量的源文件,我们通常的做法是把这许多的源文件分类,并存放在不同的目录中。所以,当利用Make去找寻具有依赖关系的文件时,我们可以在文件前加上路径,但最好的方法是把一个路径告诉Make,让Make自动去寻找。
Makefile文件中的特殊变量VPATH就是完成这个功能的,如果没有指明这个变量,Make只会在当前的目录中去找寻依赖文件和目标文件。如果定义了这个变量,那么,Make就会在当前目录找不到的情况下,到所指定的目录中去找寻文件。
# 指定两个目录,src 和 ../headers,目录由“:”分隔 VPATH = src:../headers
3.3 变量高级用法
3.3.1 变量值的替换
我们可以替换变量中的共有部分,其格式是 $(var:a=b) 或是 ${var:a=b} 。其意思是,把变量var中所有以a字串结尾的a替换成b字串。
# 示例1 foo := a.o b.o c.o bar := $(foo:.o=.c) # 把 $(foo) 中所有以 .o 字串结尾全部替换成 .c,所以 $(bar) 的值就是“a.c b.c c.c”
另外一种变量替换的技术是静态模式:
# 示例2 foo := a.o b.o c.o bar := $(foo:%.o=%.c) # 这依赖于被替换字串中有相同的模式,模式中必须包含一个 % 字符,这个例子同样让 $(bar) 变量的值为“a.c b.c c.c”
3.3.2 把变量的值再当成变量
# 示例1 x = y y = z a := $($(x)) # $(a)的值是“z” # 示例2 x = y y = z z = u a := $($($(x))) # $(a)的值是“u” # 示例3 x = $(y) y = z z = Hello a := $($(x)) # $(a)的值是“Hello” # 示例4 first_second = Hello a = first b = second all = $($a_$b) # $(all)的值是“Hello” # 示例5 a_objects := a.o b.o c.o 1_objects := 1.o 2.o 3.o sources := $($(a1)_objects:.o=.c) # 如果$(a1)的值是“a”的话,$(sources)的值就是“a.c b.c c.c”;如果$(a1)的值是“1”,$(sources)的值是“1.c 2.c 3.c” # 示例6 ifdef do_sort func := sort else func := strip endif bar := a d b g q c foo := $($(func) $(bar)) # 如果定义了do_sort,foo := $(sort a d b g q c) ,如果没有定义do_sort,foo := $(strip a d b g q c)
3.4 目标变量
我们可以为某个目标设置局部变量,这种变量被称为Target-specific Variable。它可以和全局变量同名,因为它的作用范围只在这条规则以及连带规则中,所以其值也只在作用范围内有效,而不会影响规则链以外的全局变量的值。
# 语法 <target ...> : <variable-assignment> <target ...> : override <variable-assignment> # <variable-assignment>可以是 = 、 := 、 += 或是 ?= 。第二个语法是针对于make命令行带入的变量,或是系统环境变量。
这个特性非常的有用,当我们设置了这样一个变量,这个变量会作用到由这个目标所引发的所有的规则中去。
# 示例: # 定义目标变量 CFLAGS prog : CFLAGS = -g prog : prog.o foo.o bar.o $(CC) $(CFLAGS) prog.o foo.o bar.o prog.o : prog.c $(CC) $(CFLAGS) prog.c foo.o : foo.c $(CC) $(CFLAGS) foo.c bar.o : bar.c $(CC) $(CFLAGS) bar.c # 不管全局$(CFLAGS)的值是什么,在prog目标及其所引发的连带规则中(prog.o foo.o bar.o的规则),$(CFLAGS)的值都是-g
3.5 模式变量
既然变量可以定义在目标上,同样,我们也可以把变量定义在模式上。这样,在符合这种模式的所有目标上,我们都可以使用这个变量。
# 语法 <pattern ...> : <variable-assignment> <pattern ...> : override <variable-assignment> # <variable-assignment>可以是 = 、 := 、 += 或是 ?= 。第二个语法是针对于make命令行带入的变量,或是系统环境变量。 # make的模式一般至少含有一个 % ,所以我们可用如下方式给所有以 .o 结尾的目标定义目标变量 %.o : CFLAGS = -O # 这样在符合该模式的所有目标上,其命令规则中的$(CFLAGS)的值都是-O
4 操作符
4.1 操作符 =
用”=“操作符定义的变量,称之为递归扩展变量(recursively expanded variable),其特点是可以把变量的真实值推到后面来定义。
# 使⽤“=”操作符 .PHONY: all foo = $(bar) bar = $(ugh) ugh = Huh? all: @echo $(foo) # 运行结果: $ make Huh?
4.2 操作符 :=
用”:=“操作符定义的变量,称之为简单扩展变量(simply expanded variables),其只能使用前面已定义好了的变量。
# 使⽤“:=”操作符 .PHONY: all x = foo y = $(x) b x = later xx := foo yy := $(xx) b xx := later all: @echo "x = $(y), xx = $(yy)" # 运行结果: $ make x = later b, xx = foo b
4.3 操作符 ?=
用”?=“条件赋值操作符定义变量。当变量以前没有定义时,就定义它并且将右边的值赋值给它,如果已经定义了那么就不再改变其值。条件赋值类似于提供了给变量赋缺省值的功能。
# 使用“?=”操作符 .PHONY: all foo = x foo ?= y bar ?= y all: @echo "foo = $(foo), bar = $(bar)" # 运行结果: $ make foo = x, bar = y
4.4 操作符 +=
# 使用“+=”操作符 .PHONY: all objects = main.o foo.o bar.o utils.o objects += another.o all: @echo $(objects) # 运行结果: $ make main.o foo.o bar.o utils.o another.o
5 静态模式
如果对于每⼀个目标文件都得写⼀个不同的规则来描述,那会是⼀种“体力活”。对于⼀个大型项目,采用Makefile中的静态模式可以更加容易地定义多目标的规则,可以让我们的规则变得更加的有弹性和灵活。
# 静态模式语法 <targets> : <target-pattern> : <prereq-patterns ...> <commands> # targets定义了一系列的目标文件,可以有通配符,是目标的一个集合 # target-pattern是指明了targets的模式,也就是的目标集模式 # prereq-patterns是目标的依赖模式,它对target-pattern形成的模式再进行一次依赖目标的定义
# 模式示例1 objects = foo.o bar.o all: $(objects) $(objects): %.o: %.c $(CC) -c $(CFLAGS) $< -o $@ # 上面的例子中,指明了我们的目标从$object中获取, %.o 表明要所有以 .o 结尾的目标,也就是 foo.o bar.o ,也就是变量 $object # 集合的模式,而依赖模式 %.c 则取模式 %.o 的 % ,也就是 foo bar ,并为其加下 .c 的后缀,于是,我们的依赖目标就是 foo.c # bar.c 。而命令中的 $< 和 $@ 则是自动化变量, $< 表示第一个依赖文件, $@ 表示目标集(也就是“foo.o bar.o”)。于是,上面的规# 则展开后等价于下面的规则: foo.o : foo.c $(CC) -c $(CFLAGS) foo.c -o foo.o bar.o : bar.c $(CC) -c $(CFLAGS) bar.c -o bar.o
# 模式示例2 files = foo.elc bar.o lose.o # $(filter %.o,$(files))表示调用Makefile的filter函数,过滤“$files”集,只要其中模式为“%.o”的内容。 $(filter %.o,$(files)): %.o: %.c $(CC) -c $(CFLAGS) $< -o $@ $(filter %.elc,$(files)): %.elc: %.el emacs -f batch-byte-compile $<
# 模式示例3 .PHONY: clean CC = gcc RM = rm EXE = simple OBJS = main.o foo.o $(EXE): $(OBJS) $(CC) -o $@ $^ %.o: %.c $(CC) -o $@ -c $^ clean: $(RM) $(EXE) $(OBJS) # 运行结果: $ make gcc -o main.o -c main.c gcc -o foo.o -c foo.c gcc -o simple main.o foo.o # 模式类似于我们在 Windows 操作系统中所使⽤的通配符,当然是⽤“%”⽽不是“*”。采⽤了模式以后,不论有多少个源⽂件要编译,我们都是应⽤同⼀个模式规则的,很显然,这⼤⼤的简化了我们的⼯作。
6 函数
6.1 字符串处理函数
subst
# Name:字符串替换函数 # brief:把字串 <text> 中的 <from> 字符串替换成 <to> # return:被替换过后的字符串 # 函数原型:$(subst <from>,<to>,<text>) # 示例: $(subst ee,EE,feet on the street) # 运行结果 fEEt on the strEEt
patsubst
# Name:模式字符串替换函数 # brief:查找 <text> 中的单词是否符合模式 <pattern> ,如果匹配的话,则以 <replacement> 替换 # return:被替换过后的字符串 # 函数原型:$(patsubst <pattern>,<replacement>,<text>) # 示例 .PHONY:all mixed = foo.c bar.c main.o objects:=$(patsubst %.c, %.o, $(mixed)) all: @echo $(objects) # 运行结果: $ make foo.o bar.o main.o
strip
# Name:去空格函数 # brief:去掉 <string> 字串中多余的空格 # return:被去掉空格的字符串 # 函数原型:$(strip <string>) # 示例: .PHONY: all original = foo.c bar.c stripped := $(strip $(original)) all: @echo "original = $(original)" @echo "stripped = $(stripped)" # 运行结果: $ make original = foo.c bar.c stripped = foo.c bar.c
findstring
# Name:查找字符串函数 # brief:在字串 <in> 中查找 <find> 字串 # return:如果找到,那么返回 <find> ,否则返回空字符串 # 函数原型:$(findstring <find>,<in>) # 示例: $(findstring a,a b c) $(findstring a,b c) # 运行结果: 第一个函数返回 a 字符串,第二个返回空字符串
filter
# Name:过滤函数 # brief:以 <pattern> 模式过滤 <text> 字符串中的单词,保留符合模式 <pattern> 的单词。可以有多个模式 # return:返回符合模式 <pattern> 的字串 # 函数原型:$(filter <pattern...>,<text>) # 示例 .PHONY: all sources = foo.c bar.c baz.s ugh.h sources := $(filter %.c %.s, $(sources)) all: @echo $(sources) # 运行结果: $ make foo.c bar.c baz.s
filter-out
# Name:反过滤函数 # brief:以 <pattern> 模式过滤 <text> 字符串中的单词,去除符合模式 <pattern> 的单词。可以有多个模式 # return:返回不符合模式 <pattern> 的字串 # 函数原型:$(filter-out <pattern...>,<text>) # 示例 .PHONY: all objects = main1.o foo.o main2.o bar.o result := $(filter-out main%.o, $(objects)) all: @echo $(result) # 运行结果: $ make foo.o bar.o
sort
# Name:排序函数 # brief:给字符串 <list> 中的单词排序(升序) # return:返回排序后的字符串 # note:sort 函数会去掉 <list> 中相同的单词 # 函数原型:$(sort <list>) # 示例: $(sort foo bar lose) # 运行结果: 返回 bar foo lose
word
# Name:取单词函数 # brief:取字符串 <text> 中第 <n> 个单词。(从1开始) # return:返回字符串 <text> 中第 <n> 个单词 # note:如果 <n> 溢出,那么返回空字符串 # 函数原型:$(word <n>,<text>) # 示例: $(word 2, foo bar baz) # 运行结果: 返回值是 bar
wordlist
# Name:取单词串函数 # brief:从字符串 <text> 中取从 <ss> 开始到 <e> 的单词串。 <ss> 和 <e> 是一个数字。 # return:返回字符串 <text> 中从 <ss> 到 <e> 的单词字串。 # note:如果 <ss> 溢出,那么返回空字符串。如果 <e> 溢出,那么返回从 <ss> 开始,到 <text> 结束的单词串。 # 函数原型:$(wordlist <ss>,<e>,<text>) # 示例: $(wordlist 2, 3, foo bar baz) # 运行结果: 返回值是 bar baz
words
# Name:单词个数统计函数 # brief:统计 <text> 中字符串中的单词个数。 # return:返回 <text> 中的单词数。 # note:如果我们要取 <text> 中最后的一个单词,我们可以这样: $(word $(words <text>),<text>) 。 # 函数原型:$(words <text>) # 示例: $(words, foo bar baz) # 运行结果: 返回值是 3
firstword
# Name:首单词函数 # brief:取字符串 <text> 中的第一个单词 # return:返回字符串 <text> 的第一个单词。 # note:这个函数可以用 word 函数来实现: $(word 1,<text>) # 函数原型:$(firstword <text>) # 示例: $(firstword foo bar) # 运行结果: 返回值是 foo
lastword
# Name:尾单词函数 # brief:取字符串 <text> 中的最后一个单词 # return:返回字符串 <text> 的最后一个单词。 # note: # 函数原型:$(lastword <text>)
6.2 文件名操作函数
dir
# Name:取目录函数 # brief:从文件名序列 <names> 中取出目录部分。 # return:返回文件名序列 <names> 的目录部分。 # note:目录部分是指最后一个反斜杠( / )之前的部分。如果没有反斜杠,那么返回 ./ 。 # 函数原型:$(dir <names...>) # 示例: $(dir src/foo.c hacks) # 运行结果: 返回值是 src/ ./
notdir
# Name:取文件函数 # brief:从文件名序列 <names> 中取出非目录部分。 # return:返回文件名序列 <names> 的非目录部分。 # note:非目录部分是指最後一个反斜杠( / )之后的部分。 # 函数原型:$(notdir <names...>) # 示例: $(notdir src/foo.c hacks) # 运行结果: 返回值是 foo.c hacks
suffix
# Name:取后缀函数 # brief:从文件名序列 <names> 中取出各个文件名的后缀。 # return:返回文件名序列 <names> 的后缀序列,如果文件没有后缀,则返回空字串。 # note: # 函数原型:$(suffix <names...>) # 示例: $(suffix src/foo.c src-1.0/bar.c hacks) # 运行结果: 返回值是 .c .c
basename
# Name:取前缀函数 # brief:从文件名序列 <names> 中取出各个文件名的前缀。 # return:返回文件名序列 <names> 的前缀序列,如果文件没有前缀,则返回空字串。 # note: # 函数原型:$(basename <names...>) # 示例: $(basename src/foo.c src-1.0/bar.c hacks) # 运行结果: 返回值是 src/foo src-1.0/bar hacks
addsuffix
# Name:加后缀函数 # brief:把后缀 <suffix> 加到 <names> 中的每个单词后面。 # return:返回加过后缀的文件名序列。 # note: # 函数原型:$(addsuffix <suffix>,<names...>) # 示例: $(addsuffix .c,foo bar) # 运行结果: 返回值是 foo.c bar.c
addprefix
# Name:加前缀函数 # brief:把前缀 <prefix> 加到 <names> 中的每个单词前面。 # return:返回加过前缀的文件名序列。 # note: # 函数原型:$(addprefix <prefix>,<names...>) # 示例: .PHONY:all without_dir = foo.c bar.c main.c with_dir := $(addprefix objs/, $(without_dir)) all: @echo $(with_dir) # 运行结果: $ make objs/foo.c objs/bar.c objs/main.c
join
# Name:连接函数 # brief:把 <list2> 中的单词对应地加到 <list1> 的单词后面。 # return:返回连接过后的字符串。 # note:若 <list1> 溢出,<list1> 中溢出的单词将保持原样。若 <list2> 溢出,<list2> 中溢出的单词将被复制到 <list1> 后。 # 函数原型:$(join <list1>,<list2>) # 示例: $(join aaa bbb , 111 222 333) # 运行结果: 返回值是 aaa111 bbb222 333
wildcard
# Name:通配符函数 # brief:在当前工作目录下,筛选出匹配模式的文件 # return:返回筛选出的文件 # note: # 函数原型:$(wildcard pattern) # 示例: .PHONY: all SRC = $(wildcard *.c) all: @echo "SRC = $(SRC)" # 运行结果: $ make SRC = bar.c foo.c main.c
6.3 其他函数
foreach
# Name:循环函数 # brief:把参数 <list> 中的单词逐一取出放到参数 <var> 所指定的变量中,然后再执行 <text> 所包含的表达式。 # return:当循环结束时,返回 <text> 所返回的每个字符串所组成的整个字符串(以空格分隔)。 # note:<var> 最好是一个变量名, <list> 可以是一个表达式,而 <text> 中一般会使用 <var> 这个参数来依次枚举 <list> 中的单词 # 函数原型:$(foreach <var>,<list>,<text>) # 示例: names := a b c d files := $(foreach n,$(names),$(n).o) # 运行结果: $(files) 的值是 a.o b.o c.o d.o
if
# Name:条件函数 # brief:若 <condition> 为真(非空字符串),返回 <then-part> ,否则,返回 <else-part>。 # return:返回 <then-part> 和 <else-part> 中的一个(只会有一个被计算)。 # note:如果 <else-part> 没有被定义,那么,整个函数返回空字串。 # 函数原型:$(if <condition>,<then-part>) 或 $(if <condition>,<then-part>,<else-part>) # 示例: # 运行结果:
call
# call函数是唯一一个可以用来创建新的参数化的函数。我们可以写一个非常复杂的表达式,这个表达式中,我们可以定义许多参数,然后利用call函数来向这个表达式传递参数 # 函数原型:$(call <expression>,<parm1>,<parm2>,...,<parmn>) # 当make执行这个函数时, <expression> 参数中的变量,如 $(1) 、 $(2) 等,会被参数 <parm1> 、 <parm2> 、 <parm3> 依次取代。# 而 <expression> 的返回值就是 call 函数的返回值。 # 示例1: reverse = $(1) $(2) foo = $(call reverse,a,b) # 运行结果: foo 的值就是 a b # 示例2: reverse = $(2) $(1) foo = $(call reverse,a,b) # 运行结果: foo 的值就是 b a # 在向 call 函数传递参数时要注意空格的使用。call 函数在处理参数时,第2个及其之后的参数中的空格会被保留,因而可能造成一些奇怪 # 的效果。因而在向call函数提供参数时,最安全的做法是去除所有多余的空格。
origin
# origin函数不像其它的函数,他并不操作变量的值,他只是告诉你你的这个变量是哪里来的 # 函数原型:$(origin <variable>) # 注意,<variable> 是变量的名字,不应该是引用。所以最好不要在 <variable> 中使用 $ 字符 # origin函数返回值: # undefined:如果 <variable> 从来没有定义过,origin函数返回这个值 undefined # default:如果 <variable> 是一个默认的定义,比如“CC”这个变量 # environment:如果 <variable> 是一个环境变量,并且当Makefile被执行时, -e 参数没有被打开 # file:如果 <variable> 这个变量被定义在Makefile中 # command line:如果 <variable> 这个变量是被命令行定义 # override:如果 <variable> 是被override指示符重新定义的 # automatic:如果 <variable> 是一个命令运行中的自动化变量 # 这些信息对于我们编写Makefile是非常有用的。例如,我们有一个Makefile包含一个定义文件 Make.def,在 Make.def中定义了一个变量 # “bletch”,而我们的环境中也有一个环境变量“bletch”,此时,我们想判断一下,如果变量来源于环境,那么我们就把之重定义,如果来源 # 于Make.def或是命令行等非环境变量,那么我们就不重新定义它。于是,在我们的Makefile中,我们可以这样写: ifdef bletch ifeq "$(origin bletch)" "environment" bletch = barf, gag, etc. endif endif # 虽然 override 关键字也可以重新定义环境变量,但是它同时也会把从命令行定义的变量也覆盖,而我们只想重新定义环境传来的变量,而不# 想重新定义命令行传来的变量
shell
# shell函数的参数就是操作系统Shell的命令。它和反引号“`”是相同的功能。shell函数把执行操作系统命令后的输出作为函数返回。因此, # 我们可以用操作系统命令以及字符串处理命令awk,sed等等来生成一个变量,如: contents := $(shell cat foo) files := $(shell echo *.c) # 注意:shell函数会新生成一个Shell程序来执行命令,若我们的Makefile中有一些比较复杂的规则,并大量使用了shell函数,那么会影响 # 我们的系统性能。特别是Makefile的隐式规则可能会让shell函数执行的次数比想像中多得多。
error
# 函数原型:$(error <text ...>) # brief:输出错误信息,make停止执行 # 示例1: ifdef ERROR_001 $(error error is $(ERROR_001)) endif # 运行结果: 在变量ERROR_001定义了后执行时产生error调用 # 示例2: ERR = $(error found an error!) .PHONY: err err: $(ERR) # 运行结果: 在目标err被执行时才发生error调用
warning
# 函数原型:$(warning <text ...>) # brief:输出警告信息,make继续执行
info
# 函数原型:$(warning <text ...>) # brief:输出调试信息,make继续执行
7 创建目录
毫无疑问,我们在编译项目之前希望先准备好用于存放文件的目录。虽然我们可以在编译之前手动创建所需的目录,但是利用Makefile可以自动的创建工程目录,会方便很多。
.PHONY: all MKDIR = mkdir DIRS = output bin RM = rm RMFLAGS = -fr all: $(DIRS) $(DIRS): $(MKDIR) $@ # 注意$@的用法 clean: $(RM) $(RMFLAGS) $(DIRS) # 运行结果: $ make mkdir output mkdir bin
8 归纳文件
为了将目标文件或是可执行程序分别放入上节所创建的output和bin目录中,我们需要用到Makefile中的⼀个函数 —— addprefix。
.PHONY:all clean MKDIR=mkdir RM=rm RMFLAGS=-fr CC=gcc # 确定要构建的目录 DIR_OBJS=output DIR_EXE=bin DIRS=$(DIR_OBJS) $(DIR_EXE) EXE=test # 为test加上前缀:bin/test,故最后的目标 test 生成在bin子目录下 EXE:=$(addprefix $(DIR_EXE)/, $(EXE)) # 找到工作目录下的所有.c文件 SRCS=$(wildcard *.c) # 将.c替换为.o,此时OBJS= foo.o main.o OBJS=$(SRCS:.c=.o) # 为OBJS加上前缀:output/foo.o output/main.o,故.o文件都生成在output子目录下 OBJS:=$(addprefix $(DIR_OBJS)/, $(OBJS)) all:$(DIRS) $(EXE) # 构建目录 $(DIRS): $(MKDIR) $@ # 构建目标:bin/test, 依赖:output/foo.o,output/main.o $(EXE):$(OBJS) $(CC) -o $@ $^ # 构建目标:output/foo.o,output/main.o,依赖:foo.c,main.c # 注意 “$(DIR_OBJS)/%.o”,为目标加上子目录前缀 $(DIR_OBJS)/%.o:%.c $(CC) -o $@ -c $^ clean: $(RM) $(RMFLAGS) $(DIRS) # 运行结果: $ make mkdir output mkdir bin gcc -o output/foo.o -c foo.c gcc -o output/main.o -c main.c gcc -o bin/test output/foo.o output/main.o
9 复杂的依赖关系
.PHONY: all clean MKDIR = mkdir RM = rm RMFLAGS = -fr CC = gcc # 确定要构建的目录 DIR_OBJS = output DIR_EXE = bin DIRS = $(DIR_OBJS) $(DIR_EXE) EXE = test.exe # 为test加上前缀:bin/test,故最后的目标 test 生成在bin子目录下 EXE := $(addprefix $(DIR_EXE)/, $(EXE)) # 找到工作目录下的所有.c文件 SRCS = $(wildcard *.c) # 将.c替换为.o,此时OBJS= foo.o main.o OBJS = $(SRCS:.c=.o) # 为OBJS加上前缀:output/foo.o output/main.o,故.o文件都生成在output子目录下 OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS)) all: $(DIRS) $(EXE) # 构建目录 $(DIRS): $(MKDIR) $@ # 构建目标:bin/test, 依赖:output/foo.o,output/main.o $(EXE): $(OBJS) $(CC) -o $@ $^ # 构建目标:output/foo.o,output/main.o,依赖:foo.c,main.c # 注意 “$(DIR_OBJS)/%.o”,为目标加上子目录前缀 # 注意 “foo.h”,此时目标的依赖项多了一个foo.h头文件, $(DIR_OBJS)/%.o: %.c foo.h $(CC) -o $@ -c $< # 此处采用$<而不是$^,因为不能用gcc编译.h文件 clean: $(RM) $(RMFLAGS) $(DIRS) # 运行结果: $ make mkdir output mkdir bin gcc -o output/foo.o -c foo.c gcc -o output/main.o -c main.c gcc -o bin/test.exe output/foo.o output/main.o
上述Makefile在构建.o目标时添加了对头文件foo.h的依赖,因此若foo.h文件有被修改,则在下次编译时,Make工具会重新编译该.o文件。若不包含对头文件的依赖,则即使头文件被修改了,Make也不会重新编译。
当项目复杂时,如果我们要将每⼀个头文件都写入到Makefile相对应的规则中,这将会是⼀个恶梦!所以我们需要找到另⼀种更好的方法来添加头文件的依赖。此时可以利用gcc -M
或gcc -MM
来动态的生成文件依赖关系。
.PHONY: all clean MKDIR = mkdir RM = rm RMFLAGS = -fr CC = gcc DIR_OBJS = output DIR_EXE = bin DIR_DEPS = deps DIRS = $(DIR_OBJS) $(DIR_EXE) $(DIR_DEPS) EXE = test EXE := $(addprefix $(DIR_EXE)/, $(EXE)) SRCS = $(wildcard *.c) OBJS = $(SRCS:.c=.o) OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS)) DEPS = $(SRCS:.c=.dep) DEPS := $(addprefix $(DIR_DEPS)/, $(DEPS)) all: $(EXE) # include关键字,用于包含依赖文件 # 当 make 看到 include 指令时,会先去查找包含的依赖⽂件是否存在。 # 若依赖文件存在,make 还会在 Makefile 中查找是否存在规则来更新它。若存在,则运⾏规则去更新需要被包含的依赖文件,更新完再将其包含进来 # 若依赖文件不存在,make 也会在 Makefile 中查找是否存在规则来构建它。若存在,则运⾏规则去构建需要被包含的依赖文件,构建完再将其包含进来 # 若依赖文件不存在,且在 Makefile 中不存在规则来构建它,则报错 include $(DEPS) $(DIRS): $(MKDIR) $@ # 构建目标:bin/test,依赖:子目录bin,output/*.o # 注意构建目标的依赖的第一个先决条件是子目录bin,所以当子目录不存在时,会先调用构建子目录的规则去建立该子目录 $(EXE): $(DIR_EXE) $(OBJS) $(CC) -o $@ $(filter %.o, $^) # 注意在命令中利用filter函数过滤出output/*.o,否则GCC编译时会连同子目录bin一起编译,这样编译会报错 # 构建目标:output/*.o,依赖:子目录output,*.c # 注意构建目标的依赖的第一个先决条件是子目录output,所以当子目录不存在时,会先调用构建子目录的规则去建立该子目录 $(DIR_OBJS)/%.o: $(DIR_OBJS) %.c $(CC) -o $@ -c $(filter %.c, $^) # 注意在命令中利用filter函数过滤出*.c,否则GCC编译时会连同子目录output一起编译,这样编译会报错 # 此规则就是include中包含的依赖文件的构建规则 # 构建目标:deps/*.dep,依赖:子目录deps,*.c # 注意构建目标的依赖的第一个先决条件是子目录deps,所以当子目录不存在时,会先调用构建子目录的规则去建立该子目录,然后再构建出目标deps/*.dep $(DIR_DEPS)/%.dep: $(DIR_DEPS) %.c @echo "Making $@ ..." @set -e; \ $(RM) $(RMFLAGS) $@.tmp ; \ $(CC) -E -MM $(filter %.c, $^) > $@.tmp ; \ sed 's,\(.*\)\.o[ :]*,objs/\1.o: ,g' < $@.tmp > $@ ; \ $(RM) $(RMFLAGS) $@.tmp clean: $(RM) $(RMFLAGS) $(DIRS) # 运行结果: $ make mkdir deps Making deps/main.dep ... Making deps/foo.dep ... mkdir bin mkdir output gcc -o output/foo.o -c foo.c gcc -o output/main.o -c main.c gcc -o bin/test output/foo.o output/main.o
10 条件语法
在Makefile中一共包含四种条件语法:ifdef、ifeq、ifndef 、ifneq。
特别注意:Make是在读取Makefile时就计算条件表达式的值,并根据条件表达式的值来选择语句,而自动变量($@、$^、$<)是在构建规则的命令处理阶段才被赋值,故自动变量在Makefile条件语句块中不能使用。
# Makefile中的条件语法有三种形式。其中 conditional-directive 可以是 ifdef、ifeq、ifndef 和 ifneq 中的任意⼀个 # 1 conditional-directive text-if-true endif # 2 conditional-directive text-if-true else text-if-false endif # 3 conditional-directive text-if-one-is-true else conditional-directive text-if-true else text-if-false endif
举例1: .PHONY: all sharp = square desk = square table = circle ifeq ($(sharp), $(desk)) result1 = "desk == sharp" else result1 = "desk != sharp" endif ifneq "$(table)" "square" result2 = "table != square" else result2 = "table == square" endif # 条件语法在构建规则之前被使用 all: @echo $(result1) @echo $(result2) # 运行结果: $ make desk == sharp table != square
举例2: .PHONY: all bar = defined ifdef foo result1 = "foo is defined" else result1 = "foo is not defined" endif ifndef bar result2 = "bar is not defined" else result2 = "bar is defined" endif # 条件语法在构建规则之前被使用 all: @echo $(result1) @echo $(result2) # 运行结果: $ make foo is not defined bar is defined
11 头文件依赖与.d文件
在复杂的依赖关系中,提到过头文件依赖,但是由于要用到sed命令,实现比较复杂,现在提供一种较简单的写法。
.PHONY: all clean MKDIR = mkdir RM = rm RMFLAGS = -fr CC = gcc DIR_OUT = tempOut DIR_EXE = bin DIRS = $(DIR_OUT) $(DIR_EXE) EXE = main.exe EXE := $(addprefix $(DIR_EXE)/, $(EXE)) SRCS = $(wildcard *.c) OBJS = $(SRCS:.c=.o) OBJS := $(addprefix $(DIR_OUT)/, $(OBJS)) DEPS = $(SRCS:.c=.d) DEPS := $(addprefix $(DIR_OUT)/, $(DEPS)) all: $(EXE) # 在include前加上“-”,那么即使其包含的依赖文件不存在,make也会忽略这个错误 # 在第一次编译时很有用,因为第一次编译时,往往.d文件都不存在 # 这种写法就不用先构建.d文件,才能构建可执行文件 -include $(DEPS) $(DIRS): $(MKDIR) $@ $(EXE): $(DIR_EXE) $(OBJS) $(CC) -o $@ $(filter %.o, $^) $(DIR_OUT)/%.o: $(DIR_OUT) %.c $(CC) -o $@ -c -MMD $(filter %.c, $^) clean: $(RM) $(RMFLAGS) $(DIRS) # 运行结果: $ make mkdir bin mkdir tempOut gcc -o tempOut/foo.o -c -MMD foo.c gcc -o tempOut/main.o -c -MMD main.c gcc -o bin/main.exe tempOut/foo.o tempOut/main.o
12 make的嵌套执行
在一些大的工程中,我们会把我们不同模块或是不同功能的源文件放在不同的目录中。因此我们可以在每个目录中都书写一个该目录的Makefile,这有利于让我们的Makefile变得更加地简洁,而不至于把所有的东西全部写在一个Makefile中,这样会很难维护我们的Makefile,这个技术对于我们模块编译和分段编译有着非常大的好处。
例如,在当前工作目录下有一个子目录subdir,subdir下有一个Makefile文件,其指明了subdir目录下文件的编译规则。那么我们的总控Makefile可以这样写:
# 利用cd命令进入subdir,并调用make执行subdir下的Makefile subsystem: cd subdir && $(MAKE) # 利用make参数-C进入subdir,并调用make执行subdir下的Makefile subsystem: $(MAKE) -C subdir # 以上两个例子完全等价
在总控Makefile中可能显示的定义了一些变量,那么这些变量会默认被传递到下级的Makefile中,但是不会覆盖下级Makefile中定义的变量,除非为make命令指定了 -e 参数。
# 如果要传递某些变量到下级Makefile中,我们可以这样声明: # export <variable ...> # 如果不要传递某些变量到下级Makefile中,我们可以这样声明: # unexport <variable ...>
# 示例1 export variable = value # 其完全等价于 variable = value export variable # 示例2 export variable := value # 其完全等价于 variable := value export variable # 实例3 export variable += value # 其完全等价于 variable += value export variable
如果需要传递所有的变量,那么我们只需要一个export就行了,后面什么都不要跟,表示传递所有变量。
需要注意MAKEFLAGS变量,其中包含了make指令的参数信息。如果我们的总控Makefile有make指令的参数或者在上层的Makefile中定义了这个变量,那么MAKEFLAGS变量将会是这些参数,并会传递到下层Makefile中。由于这是一个系统级的环境变量,所以不管我们是否export,其都会被传递到下层Makefile。但是make指令中的 -C , -f , -h, -o 和 -W 参数不会往下传递。
# 如果我们一定要让MAKEFLAGS不向下层传递,那么我们可以采取这种办法 subsystem: cd subdir && $(MAKE) MAKEFLAGS =
13 定义命令包
如果Makefile中出现一些相同命令序列,那么我们可以为这些相同的命令序列定义一个命令包。定义这种命令包的语法以 define 开始,以 endef 结束。命令包的名字不要和Makefile中的变量重名。
# 定义命令包 run-yacc define run-yacc yacc $(firstword $^) mv y.tab.c $@ endef # 使用命令包 run-yacc foo.c : foo.y $(run-yacc)
14 Make的运行
14.1 make的退出码
make命令执行后有三个退出码:
- 0:指示make命令执行成功
- 1:指示make命令执行时出现任何错误
- 2:指示使用make命令的-q参数选项,且make使得一些目标不需要更新
14.2 make的参数
# -n, --just-print, --dry-run, --recon 不执行参数,这些参数只是打印命令,不管目标是否更新,把规则和连带规则下的命令打印出来,但不执行,这些参数对于我们调试makefile很有用处。 # -t, --touch 这个参数的意思就是把目标文件的时间更新,但不更改目标文件。也就是说,make假装编译目标,但不是真正的编译目标,只是把目标变成已编译过的状态。 # -q, --question 这个参数的行为是找目标的意思,也就是说,如果目标存在,那么其什么也不会输出,当然也不会执行编译,如果目标不存在,其会打印出一条出错信息。 # -W <file>, --what-if=<file>, --assume-new=<file>, --new-file=<file> 这个参数需要指定一个文件。一般是是源文件(或依赖文件),Make会根据规则推导来运行依赖于这个文件的命令,一般来说,可以和“-n”参数一同使用,来查看这个依赖文件所发生的规则命令。 # -b, -m 这两个参数的作用是忽略和其它版本make的兼容性。 # -B, --always-make 认为所有的目标都需要更新(重编译)。 # -C <dir>, --directory=<dir> 指定读取makefile的目录。如果有多个“-C”参数,make的解释是后面的路径以前面的作为相对路径,并以最后的目录作为被指定目录。如:“make -C ~hchen/test -C prog”等价于“make -C ~hchen/test/prog”。 # -debug[=<options>] 输出make的调试信息。它有几种不同的级别可供选择,如果没有参数,那就是输出最简单的调试信息。下面是<options>的取值: a: 也就是all,输出所有的调试信息。(会非常的多) b: 也就是basic,只输出简单的调试信息。即输出不需要重编译的目标。 v: 也就是verbose,在b选项的级别之上。输出的信息包括哪个makefile被解析,不需要被重编译的依赖文件(或是依赖目标)等。 i: 也就是implicit,输出所有的隐含规则。 j: 也就是jobs,输出执行规则中命令的详细信息,如命令的PID、返回码等。 m: 也就是makefile,输出make读取makefile,更新makefile,执行makefile的信息。 # -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的搜索目标。可以使用多个“-I”参数来指定多个目录。 # -j [<jobsnum>], --jobs[=<jobsnum>] 指同时运行命令的个数。如果没有这个参数,make运行命令时能运行多少就运行多少。如果有一个以上的“-j”参数,那么仅最后一个“-j”才是有效的。(注意这个参数在MS-DOS中是无用的) # -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> 不重新生成的指定的<file>,即使这个目标的依赖文件新于它。 # -p, --print-data-base 输出makefile中的所有数据,包括所有的规则和变量。这个参数会让一个简单的makefile都会输出一堆信息。如果你只是想输出信息而不想执行makefile,你可以使用“make -qp”命令。如果你想查看执行makefile前的预设变量和规则,你可以使用 “make –p –f /dev/null”。这个参数输出的信息会包含着你的makefile文件的文件名和行号,所以,用这个参数来调试你的 makefile会是很有用的,特别是当你的环境变量很复杂的时候。 # -q, --question 不运行命令,也不输出。仅仅是检查所指定的目标是否需要更新。如果是0则说明要更新,如果是2则说明有错误发生。 # -r, --no-builtin-rules 禁止make使用任何隐含规则。 # -R, --no-builtin-variabes 禁止make使用任何作用于变量上的隐含规则。 # -s, --silent, --quiet 在命令运行时不输出命令的输出。 # -S, --no-keep-going, --stop 取消“-k”选项的作用。因为有些时候,make的选项是从环境变量“MAKEFLAGS”中继承下来的。所以你可以在命令行中使用这个参数来让环境变量中的“-k”选项失效。 # -t, --touch 相当于UNIX的touch命令,只是把目标的修改日期变成最新的,也就是阻止生成目标的命令运行。 # -v, --version 输出make程序的版本、版权等关于make的信息。 # -w, --print-directory 输出运行makefile之前和之后的信息。这个参数对于跟踪嵌套式调用make时很有用。 # --no-print-directory 禁止“-w”选项。 # -W <file>, --what-if=<file>, --new-file=<file>, --assume-file=<file> 假定目标<file>;需要更新,如果和“-n”选项使用,那么这个参数会输出该目标更新时的运行动作。如果没有“-n”那么就像运行UNIX的“touch”命令一样,使得<file>;的修改时间为当前时间。 # --warn-undefined-variables 只要make发现有未定义的变量,那么就输出警告信息。
14.3 指定Makefile
我们可以给make命令指定一些特殊的Makefile,只需要利用make的 -f 或是 –file 或是 –makefile 参数。
例如现在有个Makefile的名字是NXL.mk,那么我们可以这样让make执行这个文件:
make –f NXL.mk
14.4 指定目标
在一个Makefile中可能有多个目标,而Make中的MAKECMDGOALS变量则存放了我们make的最终目标列表。这个变量在某些特殊情况下可以使用。
sources = foo.c bar.c ifneq ($(MAKECMDGOALS), clean) include $(sources:.c=.d) endif # 运行结果: 只要我们输入的命令不是“make clean”,那么Makefile会自动包含“foo.d”和“bar.d”这两个依赖关系文件
由于Make可以指定所有Makefile中的目标,包括“伪目标”,因此我们可以根据这种性质来让Make根据指定的不同的目标来完成不同的事。在大型开源软件发布时,其Makefile都包含了编译、安装、打包等功能。我们可以参照这种规则来书写我们的Makefile中的目标。
- all:这个伪目标是所有目标的目标,其功能一般是编译所有的目标。
- clean:这个伪目标功能是删除所有被make创建的文件。
- install:这个伪目标功能是安装已编译好的程序,其实就是把目标执行文件拷贝到指定的目标中去。
- print:这个伪目标的功能是例出改变过的源文件。
- tar:这个伪目标功能是把源程序打包备份。也就是一个tar文件。
- dist:这个伪目标功能是创建一个压缩文件,一般是把tar文件压成Z文件。或是gz文件。
- TAGS:这个伪目标功能是更新所有的目标,以备完整地重编译使用。
- check和test:这两个伪目标一般用来测试Makefile的流程。
使用上述的规范写法一是很实用,二是可以显得我们的Makefile很专业。
15 Make隐含规则
15.1 隐含规则命令变量
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
15.2 隐含规则参数变量
ARFLAGS
: 函数库打包程序AR命令的参数。默认值是rv
ASFLAGS
: 汇编语言编译器参数。(当明显地调用.s
或.S
文件时)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文法分析器参数。OS
: 当前的操作系统(Operating System
)
16 总结
Makefile在一些简单的工程完全可以人工手写,但是当工程非常大的时候,手写Makefile也是非常麻烦的,同时Makefile是分平台的,也就是说当我们换了一个新的平台之后,又需要重新去编写Makefile文件,还是比较麻烦,因此,在这个时候就出现了CMake工具。关于CMake工具的用法,博主会在该专栏的下一篇文章讲解。