本文参考了一些网上资料,特别是《跟我一起写 Makefile》一文。命令make运行时,会在当前目录下找名字叫“Makefile”或“makefile”的文件。如果找到,它会找文件中的第一个目标文件(target),并把这个文件作为最终的目标文件,然后递归分析此目标文件的依赖目标,根据时间戳信息,决定哪些文件需要重新编译,最后得到最终的目标文件。运行make时,可跟参数,例如make –C /home/user1(等价于命令cd /home/user1 && make)即到user1这个目录下运行make命令。运行make时也可以跟变量定义,例如make ARCH=arm,即先在Makefile开始处定义一个变量ARCH=arm再运行。
Makefile的几个要点:
1) 命令不能顶格写,前面必须有TAB键;注释用#号;一行写不完,可以使用\进行换行;$$表示真实的$字符。其余一些特定字符如%(以及#、*)可通过转义符号\%表示真正的%符号,。
2) 使用=或:=定义变量。两者区别在于用一个变量来定义另一个变量时,=号后面的变量可在任何地方定义,:=号后面的变量只是在这条语句前的定义,这样可避免嵌套定义(例如CFLAGS = $(CFLAGS) -O导致嵌套,如果用:=则$(CFLAGS)会用之前的定义来替换即可)。使用+=来追加变量内容,使用?=表示如果变量没有定义过则用这条语句定义,否则不做任何操作。变量引用时用小括号或花括号包起来。引用变量时,变量会在引用它的地方精确地按照定义展开(替换),就像C/C++中的宏一样。例如定义变量AA=aa和aa=BB,则$($(AA))先展开为$(aa),即取变量aa的值,最终为BB。
3) 通配符展开。在规则中,通配符会被自动展开,例如命令rm *.c删除当前目录所有的.c结尾的文件。但是变量定义和函数引用时有通配符不会展开。例如变量定义filec=*.c,则变量filec的值就是字符*.c,通配符*不会展开,即filec的值不是当前目录下所有的.c文件名集合,下面有$(filec)的地方只会用*.c替换。可以使用函数wildcard(功能:通配符扩展然后获取文件名列表)使得变量定义和函数引用时进行通配符扩展,例如定义变量filec:=$(wildcard *.c)即得到当前目录下所有以.c结尾的文件名列表。wildcard用法是$(wildcard PATTERN),表示展开为匹配此模式的(且存在的)所有文件名列表,展开的文件名之间使用空格分开。如果当前目录下有test.c文件,则当前目录下运行make时,$(wildcard test.c)的值为test.c,如果没有test.c这个文件,其值为空,利用这个特性判断是否存在某文件,如if($(wildcard test.c), ,)。如果/home/s123/目录下有文件test.c,则$(wildcard /home/s123/test.c)的值为/home/s123/test.c,即找到匹配PATTERN的文件,且按照PATTERN的格式展开。注意:如果定义变量filec=*.c,下面有命令rm $(filec),则先替换为rm *.c,此时rm命令中通配符*会自动展开,删除所有.c结尾文件。
4) if函数很像GNU的make所支持的条件语句ifeq,语法:$(if <condition>,<then-part> ) 或是$(if <condition>,<then-part>,<else-part> )可见,if函数可以包含“else”部分,或是不含。即if函数的参数可以是两个,也可以是三个。<condition>参数是if的表达式,如果其返回的为非空字符串,那么这个表达式就相当于返回真,于是,<then-part>会被计算,否则<else-part>会被计算。
5) 隐晦规则。只要make看到一个[.o]文件,它就会自动的把同名的[.c]文件加在依赖关系中,例如如果make找到一个whatever.o,那么whatever.c,就会是whatever.o的依赖文件,并且 cc -c whatever.c 也会被推导出来,于是下面两段语句等效:
main.o : main.c defs.h
cc -c main.c
等价于:main.o : defs.h
6) 伪目标。Makefile文件中第一个目标文件为最终的目标,其它目标如果没有被第一个目标文件直接或间接关联,则为伪目标,当输入make命令时,伪目标后面所定义的命令将不会被执行。我们可以输入“make 伪目标”的命令形式仅执行伪目标后的命令。例如一个伪目标clean,后面的命令为清除所有的目标文件,以便重编译,因此我们可以使用命令make clean来执行clean后面的命令。如果目录下有clean这个文件,为避免混淆,使用.PHONY : clean显式定义下面一行的clean目标是伪指令。
7) 自动化变量。自动化变量,就是这种变量会把模式中所定义的一系列的文件自动地挨个取出,直至所有的符合模式的文件都取完了。这种自动化变量只应出现在规则的命令中,常用的自动化变量如下:
a) $@:规则中的目标文件集,在模式规则中,如果有多个目标,那么,"$@"就是匹配于目标中模式定义的集合。
b) $<:依赖目标中的第一个目标名字,如果依赖目标是以模式(即"%")定义的,那么"$<"将是符合模式的一系列的文件名集,注意文件名是一个一个取出来的。
c) $?:所有比目标新的依赖目标的集合,以空格分隔。
d) $^:所有的依赖目标的集合,以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。
e) $+:所有的依赖目标的集合,以空格分隔,但是不去除重复的依赖目标。
f) $%:
上述自动化变量还可以取得文件的目录名或文件名,只需要搭配上"D"或"F"字样,如$(@D)和$(@F)分别表示$@的目录部分和文件部分,如果"$@"值是"dir/foo.o",那么分别为dir和foo.o,其中$(@F)相当于函数"$(notdir $@)"。自动化变量还可以进行变量替换,例如$(@:a=b)把目标文件集中a结尾的字串替换为b。
8) 变量值的替换
替换变量中的共有的部分,其格式是$(var:a=b),即把变量var中所有以’a’结尾的字串进行替换,替换规则为把结尾的’a’替换成’b’。例如:相邻两条语句foo := a.o b.o和bar := $(foo:.o=.c) ,其含义是把$(foo)中所有以’.o’ 结尾的字串全部替换成’.c’结尾,所以 $(bar)的值就是’a.c b.c’。也可以使用静态模式的定义进行替换,此时要求被替换字串集有相同的模式,例如:foo := a.o b.o和bar := $(foo:%.o=%.c),和上面的两条语句执行结果相同。在Makefile中符号%的意思是匹配零或若干字符。
在u-boot的Makefile中有类似如下语句:
smdk2410_config : unconfig
@./mkconfig $(@:_config=) arm arm920t
此语句@./mkconfig表示执行当前目录下的mkconfig脚步文件,@符号表示不显示这个命令行,类似@echo命令。$(@:_config=)中的@代表目标文件smdk2410_config,因此$(@:_config=)等价于$(smdk2410_config:_config=),即把字串smdk2410_config结尾的_config替换为空,所以第二句等价于@./mkconfig smdk2410 arm arm920t,即运行脚本且传递给脚本的三个参数分别为smdk2410 arm arm920t。
9) 静态模式定义多目标规则,表达式如下:
<targets ...>: <target-pattern>: <prereq-patterns ...>
<commands>
targets,定义了一系列的我们想获取的目标文件,可以有通配符,是目标的一个集合。目标模式<target-pattern>指明了<targets ...>的模式,例如<target-pattern>定义成“%.o”,意思是我们的<targets…>集合中都是以“.o”结尾的文件,而符号%代表文件名中“.o”前的字符串。<prereq-patterns…>利用<target-pattern>形成的模式对目标的依赖目标进行定义,例如<prereq-parrterns…>定义成“%.c”,意思是对<target-pattern>所形成的目标集进行二次定义得到依赖模式的定义,其计算方法是,取<target-pattern>模式中的“%”(也就是去掉了“.o”这个结尾),并为其加上“.c”这个结尾,形成的新集合。例如:
objects=foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
第一行定义objects变量;第二行定义目标all依赖于变量objects,使用隐含规则得到all的获取方式(链接foo.o和bar.o);第三行和第四行利用静态模式定义多目标,其中命令中的“$<”和“$@”则是自动化变量,“$<”表示所有的依赖目标集中的目标(即“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