makefile的语法及写法⼆
也欢迎⼤家转载本篇⽂章。分享知识,造福⼈民,实现我们中华民族伟⼤复兴!
3 Makefile书写规则
--------------------------------------------------------------------------------
规则包含两个部分,⼀个是依赖关系,⼀个是⽣成⽬标的⽅法。
在Makefile中,规则的顺序是很重要的,因为,Makefile中只应该有⼀个最终⽬标,其它的⽬标都是被这个⽬标所连带出来的,所以⼀定要让make知道你的最终⽬标是什么。⼀般来说,定义在Makefile中的⽬标可能会有很多,但是第⼀条规则中的⽬标将被确⽴为最终的⽬标。如果第⼀条规则中的⽬标有很多个,那么,第⼀个⽬标会成为最终的⽬标。make所完成的也就是这个⽬标。
好了,还是让我们来看⼀看如何书写规则。
3.1 规则举例
foo.o : foo.c defs.h # foo模块
cc -c -g foo.c
看到这个例⼦,各位应该不是很陌⽣了,前⾯也已说过,foo.o是我们的⽬标,foo.c和defs.h是⽬标所依赖的源⽂件,⽽只有⼀个命令“cc -c -g foo.c”(以Tab键开头)。这个规则告诉我们两件事:
⽂件的依赖关系,foo.o依赖于foo.c和defs.h的⽂件,如果foo.c和defs.h的⽂件⽇期要⽐foo.o⽂件⽇期要新,或是foo.o不存在,那么依赖关系发⽣。
如果⽣成(或更新)foo.o⽂件。也就是那个cc命令,其说明了,如何⽣成foo.o这个⽂件。(当然foo.c⽂件include了defs.h⽂件)
3.2 规则的语法
targets : prerequisites
command
...
或是这样:
targets : prerequisites ; command
command
...
targets是⽂件名,以空格分开,可以使⽤通配符。⼀般来说,我们的⽬标基本上是⼀个⽂件,但也有可能是多个⽂件。
command是命令⾏,如果其不与“target:prerequisites”在⼀⾏,那么,必须以[Tab键]开头,如果和prerequisites在⼀⾏,那么可以⽤分号做为分隔。(见上)
prerequisites也就是⽬标所依赖的⽂件(或依赖⽬标)。如果其中的某个⽂件要⽐⽬标⽂件要新,那么,⽬标就被认为是“过时的”,被认为是需要重⽣成的。这个在前⾯已经讲过了。
如果命令太长,你可以使⽤反斜框(‘/’)作为换⾏符。make对⼀⾏上有多少个字符没有限制。规则告诉make两件事,⽂件的依赖关系和如何成成⽬标⽂件。
⼀般来说,make会以UNIX的标准Shell,也就是/bin/sh来执⾏命令。
3.3 在规则中使⽤通配符
如果我们想定义⼀系列⽐较类似的⽂件,我们很⾃然地就想起使⽤通配符。make⽀持三各通配符:“*”,“?”和“[...]”。这是和Unix的B-Shell是相同的。
波浪号(“~”)字符在⽂件名中也有⽐较特殊的⽤途。如果是“~/test”,这就表⽰当前⽤户的$HOME⽬录下的test⽬录。
⽽“~hchen/test”则表⽰⽤户hchen的宿主⽬录下的test⽬录。(这些都是Unix下的⼩知识了,make也⽀持)⽽在Windows或是MS-DOS下,⽤户没有宿主⽬录,那么波浪号所指的⽬录则根据环境变量“HOME”⽽定。
通配符代替了你⼀系列的⽂件,如“*.c”表⽰所以后缀为c的⽂件。⼀个需要我们注意的是,如果我们的⽂件名中有通配符,如:“*”,那么可以⽤转义字符“/”,如“/*”来表⽰真实的“*”字符,⽽不是任意长度的字符串。
好吧,还是先来看⼏个例⼦吧:
clean:
rm -f *.o
上⾯这个例⼦我不不多说了,这是操作系统Shell所⽀持的通配符。这是在命令中的通配符。
print: *.c
lpr -p $?
touch print
上⾯这个例⼦说明了通配符也可以在我们的规则中,⽬标print依赖于所有的[.c]⽂件。其中的“$?”是⼀个⾃动化变量,我会在后⾯给你讲述。
objects = *.o
上⾯这个例⼦,表⽰了,通符同样可以⽤在变量中。并不是说[*.o]会展开,不!objects的值就是“*.o”。Makefile中的变量其实就是
C/C++中的宏。如果你要让通配符在变量中展开,也就是让objects的值是所有[.o]的⽂件名的集合,那么,你可以这样:
objects := $(wildcard *.o)
这种⽤法由关键字“wildcard”指出,关于Makefile的关键字,我们将在后⾯讨论。
3.4 ⽂件搜寻
在⼀些⼤的⼯程中,有⼤量的源⽂件,我们通常的做法是把这许多的源⽂件分类,并存放在不同的⽬录中。所以,当make需要去寻⽂件的依赖关系时,你可以在⽂件前加上路径,但最好的⽅法是把⼀个路径告诉make,让make在⾃动去。
Makefile⽂件中的特殊变量“VPATH”就是完成这个功能的,如果没有指明这个变量,make只会在当前的⽬录中去寻依赖⽂件和⽬标⽂件。如果定义了这个变量,那么,make就会在当当前⽬录不到的情况下,到所指定的⽬录中去寻⽂件了。
VPATH = src:../headers
上⾯的的定义指定两个⽬录,“src”和“../headers”,make会按照这个顺序进⾏搜索。⽬录由“冒号”分隔。(当然,当前⽬录永远是最⾼优先搜索的地⽅)
另⼀个设置⽂件搜索路径的⽅法是使⽤make的“vpath”关键字(注意,它是全⼩写的),这不是变量,这是⼀个make的关键字,这和上⾯提到的那个VPATH变量很类似,但是它更为灵活。它可以指定不同的⽂件在不同的搜索⽬录中。这是⼀个很灵活的功能。它的使⽤⽅法有三种:
vpath < pattern> < directories>
为符合模式 < pattern>的⽂件指定搜索⽬录 < directories>。
vpath < pattern>
清除符合模式 < pattern>的⽂件的搜索⽬录。
vpath
清除所有已被设置好了的⽂件搜索⽬录。
vapth使⽤⽅法中的 < pattern>需要包含“%”字符。“%”的意思是匹配零或若⼲字符,例如,“%.h”表⽰所有以“.h”结尾的⽂件。 < pattern>指定了要搜索的⽂件集,⽽ < directories>则指定了的⽂件集的搜索的⽬录。例如:
vpath %.h ../headers
该语句表⽰,要求make在“../headers”⽬录下搜索所有以“.h”结尾的⽂件。(如果某⽂件在当前⽬录没有到的话)
我们可以连续地使⽤vpath语句,以指定不同搜索策略。如果连续的vpath语句中出现了相同的 < pattern>,或是被重复了的 < pattern>,那么,make会按照vpath语句的先后顺序来执⾏搜索。如:
vpath %.c foo
vpath % blish
vpath %.c bar
其表⽰“.c”结尾的⽂件,先在“foo”⽬录,然后是“blish”,最后是“bar”⽬录。
vpath %.c foo:bar
vpath % blish
⽽上⾯的语句则表⽰“.c”结尾的⽂件,先在“foo”⽬录,然后是“bar”⽬录,最后才是“blish”⽬录。
3.5 伪⽬标
最早先的⼀个例⼦中,我们提到过⼀个“clean”的⽬标,这是⼀个“伪⽬标”,
clean:
rm *.o temp
正像我们前⾯例⼦中的“clean”⼀样,即然我们⽣成了许多⽂件编译⽂件,我们也应该提供⼀个清除它们的“⽬标”以备完整地重编译⽽⽤。 (以“make clean”来使⽤该⽬标)
因为,我们并不⽣成“clean”这个⽂件。“伪⽬标”并不是⼀个⽂件,只是⼀个标签,由于“伪⽬标”不是⽂件,所以make⽆法⽣成它的依赖关系和决定它是否要执⾏。我们只有通过显⽰地指明这个“⽬标”才能让其⽣效。当然,“伪⽬标”的取名不能和⽂件名重名,不然其就失去了“伪⽬标”的意义了。
当然,为了避免和⽂件重名的这种情况,我们可以使⽤⼀个特殊的标记“.PHONY”来显⽰地指明⼀个⽬标是“伪⽬标”,向make说明,不管是否有这个⽂件,这个⽬标就是“伪⽬标”。
.PHONY : clean
只要有这个声明,不管是否有“clean”⽂件,要运⾏“clean”这个⽬标,只有“make clean”这样。于是整个过程可以这样写:
.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”这个⽬标新。所以,其它三个⽬标的规则总是会被决议。也就达到了我们⼀⼝⽓⽣成多个⽬标的⽬的。“.PHONY : all”声明了“all”这个⽬标为“伪⽬标”。
随便提⼀句,从上⾯的例⼦我们可以看出,⽬标也可以成为依赖。所以,伪⽬标同样也可成为依赖。看下⾯的例⼦:
.PHONY: cleanall cleanobj cleandiff
cleanall : cleanobj cleandiff
rm program
cleanobj :
rm *.o
cleandiff :
rm *.diff
“make clean”将清除所有要被清除的⽂件。“cleanobj”和“cleandiff”这两个伪⽬标有点像“⼦程序”的意思。我们可以输
⼊“make cleanall”和“make cleanobj”和“make cleandiff”命令来达到清除不同种类⽂件的⽬的
3.6 多⽬标
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,后⾯的为参数。关于函数,将在后⾯讲述。这⾥的这个函数是截取字符串的意思,“$@”表⽰⽬标的集合,就像⼀个数组,“$@”依次取出⽬标,并执于命令。
3.7 静态模式
静态模式可以更加容易地定义多⽬标的规则,可以让我们的规则变得更加的有弹性和灵活。我们还是先来看⼀下语法:
<targets ...>: <target-pattern>: <prereq-patterns ...>
<commands>
...targets定义了⼀系列的⽬标⽂件,可以有通配符。是⽬标的⼀个集合。
target-parrtern是指明了targets的模式,也就是的⽬标集模式。
prereq-parrterns是⽬标的依赖模式,它对target-parrtern形成的模式再进⾏⼀次依赖⽬标的定义。
这样描述这三个东西,可能还是没有说清楚,还是举个例⼦来说明⼀下吧。如果我们的 <target-parrtern>定义成“%.o”,意思是我们的集合中都是以“.o”结尾的,⽽如果我们的 <prereq-parrterns>定义成“%.c”,意思是对 <target-parrtern>所形成的⽬标集进⾏⼆次定义,其计算⽅法是,取 <target-parrtern>模式中的“%”(也就是去掉了[.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.c
bar.c”),“$@”表⽰⽬标集(也褪恰癴oo.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
试想,如果我们的“%.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函数,过滤“$filter”集,只要其中模式为“%.o”的内容。其的它内容,我就不⽤多说了
吧。这个例字展⽰了Makefile中更⼤的弹性。
3.8 ⾃动⽣成依赖性
在Makefile中,我们的依赖关系可能会需要包含⼀系列的头⽂件,⽐如,如果我们的main.c中有⼀句“#include "defs.h"”,那么我们的依赖关系应该是:
main.o : main.c defs.h
但是,如果是⼀个⽐较⼤型的⼯程,你必需清楚哪些C⽂件包含了哪些头⽂件,并且,你在加⼊或删
除头⽂件时,也需要⼩⼼地修改Makefile,这是⼀个很没有维护性的⼯作。为了避免这种繁重⽽⼜容易出错的事情,我们可以使⽤C/C++编译的⼀个功能。⼤多数的
C/C++编译器都⽀持⼀个“-M”的选项,即⾃动寻源⽂件中包含的头⽂件,并⽣成⼀个依赖关系。例如,如果我们执⾏下⾯的命令:
cc -M main.c
其输出是:
main.o : main.c defs.h
于是由编译器⾃动⽣成的依赖关系,这样⼀来,你就不必再⼿动书写若⼲⽂件的依赖关系,⽽由编译器⾃动⽣成了。需要提醒⼀句的是,如果你使⽤GNU的C/C++编译器,你得⽤“-MM”参数,不然,“-M”参数会把⼀些标准库的头⽂件也包含进来。
gcc -M main.c的输出是:
main.o: main.c defs.h /usr/include/stdio.h /usr/include/features.h /
/usr/include/sys/cdefs.h /usr/include/gnu/stubs.h /
/usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stddef.h /
/usr/include/bits/types.h /usr/include/bits/pthreadtypes.h /
/usr/include/bits/sched.h /usr/include/libio.h /
/usr/include/_G_config.h /usr/include/wchar.h /
/usr/include/bits/wchar.h /usr/include/gconv.h /
/usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stdarg.h /
/usr/include/bits/stdio_lim.h
gcc -MM main.c的输出则是:
main.o: main.c defs.h
那么,编译器的这个功能如何与我们的Makefile联系在⼀起呢。因为这样⼀来,我们的Makefile也要根据这些源⽂件重新⽣成,让Makefile⾃已依赖于源⽂件?这个功能并不现实,不过我们可以有其它⼿段来迂回地实现这⼀功能。GNU组织建议把编译器为每⼀个源⽂件的⾃动⽣成的依赖关系放到⼀个⽂
件中,为每⼀个“name.c”的⽂件都⽣成⼀个“name.d”的Makefile⽂件,[.d]⽂件中就存放对应[.c]⽂件的依赖关系。
于是,我们可以写出[.c]⽂件和[.d]⽂件的依赖关系,并让make⾃动更新或⾃成[.d]⽂件,并把其包含在我们的主Makefile中,这样,我们就可以⾃动化地⽣成每个⽂件的依赖关系了。
这⾥,我们给出了⼀个模式规则来产⽣[.d]⽂件:
makefile phony%.d: %.c
@set -e; rm -f $@; /
$(CC) -M $(CPPFLAGS) $ < > $@.
”意为⼀个随机编号,第⼆⾏⽣成的⽂件有可能是“name.d.12345”,第三⾏使⽤sed命令做了⼀个替换,关于sed命令的⽤法请参看相关的使⽤⽂档。第四⾏就是删除临时⽂件。
总⽽⾔之,这个模式要做的事就是在编译器⽣成的依赖关系中加⼊[.d]⽂件的依赖,即把依赖关系:
main.o : main.c defs.h
转成:
main.o main.d : main.c defs.h
于是,我们的[.d]⽂件也会⾃动更新了,并会⾃动⽣成了,当然,你还可以在这个[.d]⽂件中加⼊的不只是依赖关系,包括⽣成的命令也可⼀并加⼊,让每个[.d]⽂件都包含⼀个完赖的规则。⼀旦我们完成这个⼯作,接下来,我们就要把这些⾃动⽣成的规则放进我们的主Makefile 中。我们可以使⽤Makefile的“include”命令,来引⼊别的Makefile⽂件(前⾯讲过),例如:
sources = foo.c bar.c
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论