target ... : prerequisites ...
command
...
...
target
可以是一个object file(目标文件),也可以是一个执行文件,还可以是一个标签(label)。对于标签这种特性,在后续的“伪目标”章节中会有叙述。
prerequisites
生成该target所依赖的文件和/或target
command
该target要执行的命令(任意的shell命令)
prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。
使用反斜杠\
作为换行符
make支持三个通配符:*
、?
、~
通配符代替了一系列文件,*.c
表示所有后缀为c的文件。如果文件名中有通配符,如*
,可以使用转义字符\
,如\*
来表示真实的*
字符,而不是任意长度的字符串。
clean:
cat main.c
rm -f *.o
这是shell命令中的通配符
print: *.c
lpr -p $?
touch print
上面这个例子说明了通配符也可以在我们的规则中,目标print依赖于所有的 .c 文件。其中的 $? 是一个自动化变量
object = *.o
上面这个例子,表示了通配符同样可以用在变量中,并不是说 *.o 会展开,objects的值就是*.o
。Makefile中的变量其实就是C/C++中的宏。如果你要让通配符在变量中展开,也就是让objects的值是所有 .o 的文件名的集合,那么,你可以这样:
objects := $(wildcard *.o)
.c
文件objects := $(wildcard *.c)
.o
文件objects := ( p a t s u b s t (patsubst %.c,%.o, (patsubst(wildcard *.c))
Makefile的关键字:wildcard
、patsubst
makefile中有特殊的变量VPATH
指定文件路径,可使make自动去寻找文件间的依赖关系。如果没有指定这个变量,make只会在当前的目录中去找寻依赖文件和目标文件。如果定义了这个变量,make就会在当前目录找不到的情况下,到指定的目录中去找寻文件了。
例如:
VPATH = src : …/headers
上面的定义指定两个目录,“src”和“…/headers”,make会按照这个顺序进行搜索。目录由“冒号”分隔。当前目录永远是最高优先搜索的地方
另一个设置文件搜索路径的方法是使用make的vpath
关键字,这是全小写的,这不是变量,是一个make的关键字。可以指定不同的文件在不同的搜索目录中。这个灵活的功能的使用方法有三种:
vpath
为符合模式的文件指定搜索目录
vpath
清除符合模式的文件的搜索目录
vpath
清除所有已被设置好了的文件搜索目录
vpath %.h ../headers
该语句表示,要求make在“…/headers”目录下搜索所有以 .h 结尾的文件
可以使用连续的语句,以指定不同搜索策略。如果连续的vpath语句中出现了相同的 ,或是被重复了的,那么,make会按照vpath语句的先后顺序来执行搜索
vpath %.c foo
vpath % blish
vpath %.c bar
其表示.c
结尾的文件,现在foo目录,然后是blish,最后是bar目录
vpath %.c foo:bar
vpath % blish
.c
结尾的文件,先在“foo”目录,然后是“bar”目录,最后才是“blish”目录
Makefile中的%标记和系统通配符 * 的区别在于, * 是应用在系统中的,%是应用在这个Makefile文件中的
clean:
rm *.o tmp
我们并不生成“clean”这个文件。“伪目标”并不是一个文件,只是一个标签,由于“伪目标”不是文件,所以make无法生成它的依赖关系和决定它是否要执行。我们只有通过显式地指明这个“目标”才能让其生效。当然,“伪目标”的取名不能和文件名重名,不然其就失去了“伪目标”的意义了。
当然,为了避免和文件重名的这种情况,我们可以使用一个特殊的标记“.PHONY”来显式地指明一个目标是“伪目标”,向make说明,不管是否有这个文件,这个目标就是“伪目标”。
.PHONY : clean
clean:
rm *.o temp
伪目标一般没有依赖的文件。但是,我们也可以为伪目标指定所依赖的文件。伪目标同样可以作为“默认目标”,只要将其放在第一个。一个示例就是,如果你的Makefile需要一口气生成若干个可执行文件,但你只想简单地敲一个make完事,并且,所有的目标文件都写在一个Makefile中,那么你可以使用“伪目标”这个特性:
all : prog1 prog2 prog3
.PHONY : all
prog1 : prog1.o utils.o
cc -o prog1 prog1.o utils.o
prog2 : prog2.o
cc -o prog2 prog2.o
prog3 : prog3.o sort.o utils.o
cc -o prog3 prog3.o sort.o utils.o
我们知道,Makefile中的第一个目标会被作为其默认目标。我们声明了一个“all”的伪目标,其依赖于其它三个目标。由于默认目标的特性是,总是被执行的,但由于“all”又是一个伪目标,伪目标只是一个标签不会生成文件,所以不会有“all”文件产生。于是,其它三个目标的规则总是会被决议。也就达到了我们一口气生成多个目标的目的。 .PHONY : all 声明了“all”这个目标为“伪目标”。(注:这里的显式“.PHONY : all” 不写的话一般情况也可以正确的执行,这样make可通过隐式规则推导出, “all” 是一个伪目标,执行make不会生成“all”文件,而执行后面的多个目标。建议:显式写出是一个好习惯。)
.PHONY : cleanall cleanobj cleandiff
cleanall : cleanobj cleandiff
rm program
cleanobj :
rm *.o
cleandiff :
rm *.diff
“make cleanall”将清除所有要被清除的文件。“cleanobj”和“cleandiff”这两个伪目标有点像“子程序”的意思。我们可以输入“make cleanall”和“make cleanobj”和“make cleandiff”命令来达到清除不同种类文件的目的。
Makefile的规则中的目标可以不止一个,其支持多目标,有可能我们的多个目标同时依赖于一个文件,并且其生成的命令大体类似。于是我们就能把其合并起来。当然,多个目标的生成规则的执行命令不是同一个,这可能会给我们带来麻烦,不过好在我们可以使用一个自动化变量 $@
,这个变量表示着目前规则中所有的目标的集合
bigoutput littleoutput : text.g
generate text.g -$(subst output,,$@) > $@
上述规则等价于
bigoutput : text.g
generate text.g -big > bigoutput
littleoutput : text.g
generate text.g -little > littleoutput
-$(subst output,,$@)
中的 $
表示执行一个Makefile的函数,函数名为subst,后面的为参数。关于函数,将在后面讲述。这里的这个函数是替换字符串的意思,$@
表示目标的集合,就像一个数组, $@
依次取出目标,并执于命令。
静态模式可以更加容易的定义多目标的规则,可以让我们的规则变的更加有弹性和灵活。
: :
...
targets 定义了一系列的目标文件,可以有通配符。是目标的集合。
target-pattern是指明了targets的模式,也就是的目标集模式。
prereq-patterns是目标的依赖模式,它对target-pattern形成的模式再进行一次依赖目标的定义。
如果我们的定义成 %.o ,意思是我们的;集合中都是以 .o 结尾的,而如果我们的定义成 %.c ,意思是对所形成的目标集进行二次定义,其计算方法是,取模式中的 % (也就是去掉了 .o 这个结尾),并为其加上 .c 这个结尾,形成的新集合。
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
files = foo.elc bar.o lose.o
$(filter %.o,$(files)): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
$(filter %.elc,$(files)): %.elc: %.el
emacs -f batch-byte-compile $<
$(filter %.o,$(files))
表示调用Makefile的filter函数,过滤“$files”集,只要其中模式为“%.o”的内容。
在大型的工程中,需要知道一些c文件中包含了那些头文件,并且在加入或者修改头文件时,也需要小心的修改makefile,这样很没有维护性。为了避免这个情况,可以使用C/C++编译器的功能,使用-M
的选项,自动找到源文件中包含的头文件,并形成依赖关系。GUN使用-M
参数会把一些标准库的头文件包含进来,使用-MM
则不会。
编译器为每一个源文件自动生成的依赖关系放到一个文件中,为每一个name.c
的文件都生成一个name.d
的Makefile文件,.d
文件就存放在对应.c
文件的依赖关系。
可以写出.c
文件和.d
文件的依赖关系,并让make自动更新或生成.d
文件,并把其包含在主makefile中,可自动化的生成每个文件的依赖关系。
一个模式规则来生成.d
文件
$@
–目标文件,$^
–所有的依赖文件,$<
–第一个依赖文件。
%.d: %.c
@set -e; rm -f $@; \
$(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
这个规则的意思是,所有的.d
文件依赖于.c
,rm -f $@
的意思是删除所有的目标,也就是.d
文件。第二行的意思,为每个依赖文件$<
,也就是.c
文件生成依赖文件,$@
表示模式%.d
文件,如果有一个C文件是name.c,那么%
就是name
,$$$$
是一个随机编号,第二行生成的文件有可能是name.d.12345
,第三行使用sed命令做了一个替换,关于sed命令的用法请参看相关的使用文档。第四行就是删除临时文件。
将这些规则放到主Makefile中,可以使用include
来引用别的Makefile文件
sources = foo.c bar.c
include $(sources: .c=.d)
上述语句中的 $(sources:.c=.d)
中的 .c=.d
的意思是做一个替换,把变量 $(sources)
所有 .c
的字串都替换成 .d
,关于这个“替换”的内容,在后面我会有更为详细的讲述。当然,你得注意次序,因为include是按次序来载入文件,最先载入的 .d
文件中的目标会成为默认目标。
### 显示命令
makefile中#
是注释符
make会把其要执行的命令行在命令执行前输出到屏幕上。当使用@
字符在命令行前,那么这个命令将不被make显示出来。
@echo 正在编译XXX模块......
当make执行时,会输出“正在编译xxx模块”,但是不会输出命令,如果没有@
,make将会输出
echo 正在编译XXX模块......
正在编译XXX模块......
如果make执行时,带入make参数-n
或者--just-print
,那么只是显示命令,但是不会执行命令,这个命令有利于调试makefile,make参数-s
或者--silent
或--quiet
则是全面禁止命令的显示。
### 命令执行
如果想让上一条命令的结果应用在下一条命令时,应该使用分号
分隔这两条命令。,并且这两条命令需要放在一行上。
exec:
cd /home/leo; pwd
### 命令出错
每当命令运行完成后,make会检查每个命令的返回码,如果命令返回成功,make会执行下一条命令,当规则中所有的命令都返回成功后,这个规则算是成功完成了。如果某个命令出错了,make会终止当前的规则,这有可能或终止所有规则的执行。
为了忽略命令出错,可以在Makefile的命令行前加一个-
(TAB之后),标记为不管命令是否出错都认为是成功的。
在make后加上-i
或者是-ignore-errors
,这是一种全局的方法,Makefile中所有命令都会忽略错误。
-k
或者-keep-going
表示如果某个命令出错了,那么就终止该命令的执行,但继续执行其他规则。
clean:
-rm -f *.o
在大的工程中,把不同模块或者是不同功能的源文件放在不同的目录中,可以在每个目录中都书写一个该目录的Makefile,有利于Makefile更加简洁,不至于把所有东西都写在一个Makefile中,对模块编译和分段编译非常有好处。
我们有一个子目录叫subdir,这个目录下有个Makefile文件,来指明了这个目录下文件的编译规则。那么我们总控的Makefile可以这样书写:
subsystem:
cd subdir && $(MAKE)
等价于
subsystem:
$(MAKE) -C subdir
总控的Makefile可以传递到下级的Makefile中,但是不会覆盖下层Makefile中所定义的变量,除非指定了-e
参数
如果要传递变量到下级的Makefile中,可以使用这样的声明:
export ;
如果不想要某些变量传递到下级的Makefile,可以这样声明
unexport ;
如果需要传递所有的变量,只需要使用export就行,后面什么也不用跟,表示传递所有的变量。
一个是SHELL一个是MAKEFLAGS,这俩个变量不管是否export,其总是要传递到下层的Makefile中,特别是MAKEFLAGS变量。
如果不想传,可以这样:
subsystem:
cd subdir && $(MAKE) MAKEFLAGS=
在嵌套执行的过程中,有一些比较有用的参数,-w
或者--print-directory
可以在make的过程中输出当前工作的目录。
进入目录:
make: Entering directory `/home/hchen/gnu/make'.
离开目录:
make: Leaving directory `/home/hchen/gnu/make'
如果Makefile中出现一些相同的命令序列,可以为这些命令序列定义一个变量。定义命令序列的语法以define
开始,以endef
结束
define run-yacc
yacc $(firstword &^)
mv y.tab.c &@
endef
run-yacc
是命令包的名称,不能和Makefile中的变量重名。
第一个命令是运行yacc程序,因为yacc程序总是生成y.tab.c
的文件,所以第二行的命令就是把这个文件改名字。
foo.c : foo.y
$(run-yacc)
命令包“run-yacc”中的 $^
就是 foo.y , $@
就是 foo.c (有关这种以 $ 开头的特殊变量,我们会在后面介绍),make在执行命令包时,命令包中的每个命令会被依次独立执行。
变量可以包含字符、数字、下划线,但是不应该包含:
、#
、=
或者空字符串。
变量大小写敏感
$<
、$@
是自动化变量
使用时,需要在变量名前加$
符号,最好使用小括号()或者是{}把变量包括起来。
使用真实的$
字符,可以使用$$
来表示。
变量可以使用在文件中的目标
、依赖
、命令
和新的变量中。
objects = program.o foo.o utils.o
program : $(objects)
cc -o program $(objects)
$(objects) : defs.h
foo = $(bar)
bar = $(ugh)
ugh = Huh
all:
echo $(foo)
Makefile中的变量是可以把真实性推到后面来定义
=
: 延时赋值,该变量只用在调用的时候才被赋值
:=
: 直接赋值,与延时赋值相反,使用直接赋值的话,变量的值定义时就已经确定
?=
: 若变量的值为空,则进行赋值,通常用于设置默认值
+=
: 追加赋值,可以往变量后增加新的内容
VAR_A = FILEA
VAR_B = $(VAR_A)
VAR_C := $(VAR_A)
VAR_A += FILEB
VAR_D ?= FILED
.PHONY:check
check:
@echo "VAR_A:"$(VAR_A)
@echo "VAR_B:"$(VAR_B)
@echo "VAR_C:"$(VAR_C)
@echo "VAR_D:"$(VAR_D)
$(var:a=b)
或者 ${var:a=b}
。把var变量中以a字串结尾的a换成b。foo := a.o b.o c.o
bar := $(foo:.o=.c)
通常在执行make时,如果通过命令行定义了一个变量,那么它将替代在Makefile中出现的同名变量的定义。就是说,对于一个在Makefile中使用常规方式(使用“=”、“:=”或者“define”)定义的变量,我们可以在执行make时通过命令行方式重新指定这个变量的值,命令行指定的值将替代出现在Makefile中此变量的值。如果不希望命令行指定的变量值替代在Makefile中的变量定义,那么我们需要在Makefile中使用指示符“override”来对这个变量进行声明
: ;
: overide
事例:
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
ifeq
、else
、endif
三个关键字
ifeq
: 表示条件语句的开始,并指定一个条件表达式,表达式包含两个参数,以逗号分隔,表达式以圆括号括起
endif
: 表示一个条件语句的结束
事例1:
libs_for_gcc = -lgnu
normal_libs =
foo: $(objects)
ifeq ($(CC),gcc)
$(CC) -o foo $(objects) $(libs_for_gcc)
else
$(CC) -o foo $(objects) $(normal_libs)
endif
事例2:
libs_for_gcc = -lgnu
normal_libs =
ifeq ($(CC),gcc)
libs=$(libs_for_gcc)
else
libs=$(normal_libs)
endif
foo: $(objects)
$(CC) -o foo $(objects) $(libs)
关键字
ifeq
ifeq (, )
ifeq '' ''
ifeq "" ""
ifeq "" ''
ifeq '' ""
ifneq
ifneq (, )
ifneq '' ''
ifneq "" ""
ifneq "" ''
ifneq '' ""
ifdef
事例一:
bar =
foo = $(bar)
ifdef foo
frobozz = yes
else
frobozz = no
endif
事例二:
foo =
ifdef foo
frobozz = yes
else
frobozz = no
endif
第一个例子中, $(frobozz)
值是 yes ,第二个则是 no。
ifndef
函数调用像变量的使用,是以$
来标识的
$(
或者 ${
是函数名,make支持的函数不多,是函数的参数,参数间以逗号
来分隔,而函数名和参数之间以空格
分隔。函数调用以$
开头,以圆括号或者花括号把函数名和参数括起。例如:$(subst a,b,$(x))
实例:
comma:= ,
empty:=
space:= $(empty) $(empty)
foo:= a b c
bar:= $(subst $(space),$(comma),$(foo))
在这个示例中, $(comma)
的值是一个逗号。 $(space)
使用了 $(empty)
定义了一个空格, $(foo)
的值是 a b c
, $(bar)
的定义用,调用了函数 subst
,这是一个替换函数,这个函数有三个参数,第一个参数是被替换字串,第二个参数是替换字串,第三个参数是替换操作作用的字串。这个函数也就是把 $(foo)
中的空格替换成逗号,所以 $(bar)
的值是 a,b,c
$(subst , , )
$(subst ee,EE,feet on the street)
fEEt on the strEEt
$(patsubst ,,)
中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式
,如果匹配的话,则以
替换。这里,
可以包括通配符 %
,表示任意长度的字串。如果
中也包含 %
,那么,
中的这个 %
将是
中的那个 %
所代表的字串。(可以用 \
来转义,以 \%
来表示真实含义的 %
字符)$(patsubst %.c,%.o,x.c.c bar.c)
$(var:=;)
相当于 $(patsubst ,,$(var))
$(strip )
字串中开头和结尾的空字符$(findstring ,)
中查找
字串
,否则返回空字符串$(findstring a,a b c)
$(filter ,)
模式过滤
字符串中的单词,保留符合模式
的单词。可以有多种模式。
的字串sources := foo.c bar.c baz.s ugh.h
foo: $(sources)
cc $(filter %.c %.s,$(sources)) -o foo
$(filter %.c %.s,$(sources))
返回的值是foo.c bar.c baz.s
filter
相反$(sort )
中的单词排序(升序),sort
会去掉list
中相同的单词$(sort foo bar lose)
返回bar foo lose
。$(word , )
中第
个单词(从1开始)$(word 2, foo bar baz)
返回值是bar
$(wordlist ,,)
中取从
开始到
的单词串$(wordlist 2, 3, foo bar baz)
返回值是 bar baz
$(words )
中字符串中的单词个数
中的单词数$(words, foo bar baz)
返回值是3
$(firstword )
$(dir )
中取出目录部分。目录部分是指最后一个反斜杠( / )之前的部分。如果没有反斜杠,那么返回 ./$(dir src/foo.c hacks)
返回值是 src/ ./
dir
相反,返回文件名序列
的非目录部分$(notdir src/foo.c hacks)
返回值是 foo.c hacks
$(suffix )
$(basename
中取出各个文件名的前缀部分$(basename src/foo.c src-1.0/bar.c hacks)
返回值是src/foo src-1.0/bar hacks
$(addsuffix ,)
加到
中的每个单词后面$(addsuffix .c,foo bar)
返回值是 foo.c bar.c
$(addprefix ,)
$(addprefix src/,foo bar)
返回值是 src/foo src/bar
$(join ,)
中的单词对应地加到
的单词后面。如果
的单词个数要比
的多,那么,
中的多出来的单词将保持原样。如果
的单词个数要比
多,那么,
多出来的单词将被复制到
中$(join aaa bbb , 111 222 333)
返回值是 aaa111 bbb222 333
$(foreach ,,)
中的单词逐一取出放到参数
所指定的变量中,然后再执行
所包含的表达式。每一次
会返回一个字符串,循环过程中,
的所返回的每个字符串会以空格分隔,最后当整个循环结束时,
所返回的每个字符串所组成的整个字符串(以空格分隔)将会是foreach函数的返回值。names := a b c d
files := $(foreach n,$(names),$(n).o)
$(name)
中的单词会被挨个取出,并存到变量 n
中, $(n).o
每次根据 $(n)
计算出一个值,这些值以空格分隔,最后作为foreach函数的返回,所以, $(files)
的值是 a.o b.o c.o d.o
$(if ,)
或者 $(if ,,)
contents := $(shell cat foo)
files := $(shell echo *.c)
$(error )
是错误信息。注意,error函数不会在一被使用就会产生错误信息,所以如果你把其定义在某个变量中,并在后续的脚本中使用这个变量,那么也是可以的。ERR = $(error found an error!)
.PHONY: err
err: $(ERR)
$(warning )
这个函数很像error函数,只是它并不会让make退出,只是输出一段警告信息,而make继续执行。自动化变量:这种变量会把模式中所定义的一系列的文件自动地挨个取出,直至所有的符合模式的文件都取完了。这种自动化变量只应出现在规则的命令中。
$@
: 表示规则中的目标文件集。在模式规则中,如果有多个目标,那么, $@ 就是匹配于目标中模式定义的集合。$%
: 仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是 foo.a(bar.o) ,那么, $%
就是 bar.o , $@
就是 foo.a 。如果目标不是函数库文件(Unix下是 .a ,Windows下是 .lib ),那么,其值为空。$<
: 依赖目标中的第一个目标名字。如果依赖目标是以模式(即 % )定义的,那么 $<
将是符合模式的一系列的文件集。注意,其是一个一个取出来的。$?
: 所有比目标新的依赖目标的集合。以空格分隔$^
: 所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那么这个变量会去除重复的依赖目标,只保留一份$+
: 这个变量很像$^
,也是所有依赖目标的集合。只是它不去除重复的依赖目标。$*
: 这个变量表示目标模式中 %
及其之前的部分。如果目标是 dir/a.foo.b
,并且目标的模式是 a.%.b ,那么, $*
的值就是 dir/a.foo 。这个变量对于构造有关联的文件名是比较有较。如果目标中没有模式的定义,那么 $*
也就不能被推导出,但是,如果目标文件的后缀是make所识别的,那么 $*
就是除了后缀的那一部分。例如:如果目标是 foo.c ,因为 .c 是make所能识别的后缀名,所以, $*
的值就是 foo 。这个特性是GNU make的,很有可能不兼容于其它版本的make,所以,你应该尽量避免使用 $*
,除非是在隐含规则或是静态模式中。如果目标中的后缀是make所不能识别的,那么 $*
就是空值。