深入Makefile.docx
《深入Makefile.docx》由会员分享,可在线阅读,更多相关《深入Makefile.docx(10页珍藏版)》请在冰豆网上搜索。
深入Makefile
GNUMake工具
~~~~~~~~~~~~~~~~
2.1基本makefile结构
GNUMake的主要工作是读进一个文本文件,makefile。
这个文件里主要是有关哪些文件(‘target’目的文件)是从哪些别的文件(‘dependencies’依靠文件)中产生的,用什么命令来进行这个产生过程。
有了这些信息,make会检查磁碟上的文件,如果目的文件的时间戳(该文件生成或被改动时的时间)比至少它的一个依靠文件旧的话,make就执行相应的命令,以便更新目的文件。
(目的文件不一定是最后的可执行档,它可以是任何一个文件。
)
makefile一般被叫做“makefile”或“Makefile”。
当然你可以在make的命令行指定别的文件名。
如果你不特别指定,它会寻找“makefile”或“Makefile”,因此使用这两个名字是最简单的。
一个makefile主要含有一系列的规则,如下:
:
...
(tab)
(tab)
.
.
.
例如,考虑以下的makefile:
===makefile开始===
myprog:
foo.obar.o
gccfoo.obar.o-omyprog
foo.o:
foo.cfoo.hbar.h
gcc-cfoo.c-ofoo.o
bar.o:
bar.cbar.h
gcc-cbar.c-obar.o
===makefile结束===
这是一个非常基本的makefile——make从最上面开始,把上面第一个目的,‘myprog’,做为它的主要目标(一个它需要保证其总是最新的最终目标)。
给出的规则说明只要文件‘myprog’比文件‘foo.o’或‘bar.o’中的任何一个旧,下一行的命令将会被执行。
但是,在检查文件foo.o和bar.o的时间戳之前,它会往下查找那些把foo.o或bar.o做为目标文件的规则。
它找到的关于foo.o的规则,该文件的依靠文件是foo.c,foo.h和bar.h。
它从下面再找不到生成这些依靠文件的规则,它就开始检查磁碟上这些依靠文件的时间戳。
如果这些文件中任何一个的时间戳比foo.o的新,命令'gcc-ofoo.ofoo.c'将会执行,从而更新文件foo.o。
接下来对文件bar.o做类似的检查,依靠文件在这里是文件bar.c和bar.h。
现在,make回到‘myprog’的规则。
如果刚才两个规则中的任何一个被执行,myprog就需要重建(因为其中一个.o档就会比‘myprog’新),因此连接命令将被执行。
希望到此,你可以看出使用make工具来建立程序的好处——前一章中所有繁琐的检查步骤都由make替你做了:
检查时间戳。
你的源码文件里一个简单改变都会造成那个文件被重新编译(因为.o文件依靠.c文件),进而可执行文件被重新连接(因为.o文件被改变了)。
其实真正的得益是在当你改变一个header档的时候——你不再需要记住那个源码文件依靠它,因为所有的资料都在makefile里。
make会很轻松的替你重新编译所有那些因依靠这个header文件而改变了的源码文件,如有需要,再进行重新连接。
当然,你要确定你在makefile中所写的规则是正确无误的,只列出那些在源码文件中被#include的header档……
2.2编写make规则(Rules)
最明显的(也是最简单的)编写规则的方法是一个一个的查看源码文件,把它们的目标文件做为目的,而C源码文件和被它#include的header档做为依靠文件。
但是你也要把其它被这些header档#include的header档也列为依靠文件,还有那些被包括的文件所包括的文件……然后你会发现要对越来越多的文件进行管理,然后你的头发开始脱落,你的脾气开始变坏,你的脸色变成菜色,你走在路上开始跟电线杆子碰撞,终于你捣毁你的电脑显示器,停止编程。
到低有没有些容易点儿的方法呢?
当然有!
向编译器要!
在编译每一个源码文件的时候,它实在应该知道应该包括什么样的header档。
使用gcc的时候,用-M开关,它会为每一个你给它的C文件输出一个规则,把目标文件做为目的,而这个C文件和所有应该被#include的header文件将做为依靠文件。
注意这个规则会加入所有header文件,包括被角括号(`<',`>')和双引号(`"')所包围的文件。
其实我们可以相当肯定系统header档(比如stdio.h,stdlib.h等等)不会被我们更改,如果你用-MM来代替-M传递给gcc,那些用角括号包围的header档将不会被包括。
(这会节省一些编译时间)
由gcc输出的规则不会含有命令部分;你可以自己写入你的命令或者什么也不写,而让make使用它的隐含的规则(参考下面的2.4节)。
2.3Makefile变量
上面提到makefiles里主要包含一些规则。
它们包含的其它的东西是变量定义。
makefile里的变量就像一个环境变量(environmentvariable)。
事实上,环境变量在make过程中被解释成make的变量。
这些变量是大小写敏感的,一般使用大写字母。
它们可以从几乎任何地方被引用,也可以被用来做很多事情,比如:
i)贮存一个文件名列表。
在上面的例子里,生成可执行文件的规则包含一些目标文件名做为依靠。
在这个规则的命令行里同样的那些文件被输送给gcc做为命令参数。
如果在这里使用一个变数来贮存所有的目标文件名,加入新的目标文件会变的简单而且较不易出错。
ii)贮存可执行文件名。
如果你的项目被用在一个非gcc的系统里,或者如果你想使用一个不同的编译器,你必须将所有使用编译器的地方改成用新的编译器名。
但是如果使用一个变量来代替编译器名,那么你只需要改变一个地方,其它所有地方的命令名就都改变了。
iii)贮存编译器旗标。
假设你想给你所有的编译命令传递一组相同的选项(例如-Wall-O-g);如果你把这组选项存入一个变量,那么你可以把这个变量放在所有呼叫编译器的地方。
而当你要改变选项的时候,你只需在一个地方改变这个变量的内容。
要设定一个变量,你只要在一行的开始写下这个变量的名字,后面跟一个=号,后面跟你要设定的这个变量的值。
以后你要引用这个变量,写一个$符号,后面是围在括号里的变量名。
比如在下面,我们把前面的makefile利用变量重写一遍:
===makefile开始===
OBJS=foo.obar.o
CC=gcc
CFLAGS=-Wall-O-g
myprog:
$(OBJS)
$(CC)$(OBJS)-omyprog
foo.o:
foo.cfoo.hbar.h
$(CC)$(CFLAGS)-cfoo.c-ofoo.o
bar.o:
bar.cbar.h
$(CC)$(CFLAGS)-cbar.c-obar.o
===makefile结束===
还有一些设定好的内部变量,它们根据每一个规则内容定义。
三个比较有用的变量是$@,$<和$^(这些变量不需要括号括住)。
$@扩展成当前规则的目的文件名,$<扩展成依靠列表中的第一个依靠文件,而$^扩展成整个依靠的列表(除掉了里面所有重复的文件名)。
利用这些变量,我们可以把上面的makefile写成:
===makefile开始===
OBJS=foo.obar.o
CC=gcc
CFLAGS=-Wall-O-g
myprog:
$(OBJS)
$(CC)$^-o$@
foo.o:
foo.cfoo.hbar.h
$(CC)$(CFLAGS)-c$<-o$@
bar.o:
bar.cbar.h
$(CC)$(CFLAGS)-c$<-o$@
===makefile结束===
你可以用变量做许多其它的事情,特别是当你把它们和函数混合使用的时候。
如果需要更进一步的了解,请参考GNUMake手册。
('manmake','manmakefile')
2.4隐含规则(ImplicitRules)
请注意,在上面的例子里,几个产生.o文件的命令都是一样的。
都是从.c文件和相关文件里产生.o文件,这是一个标准的步骤。
其实make已经知道怎么做——它有一些叫做隐含规则的内置的规则,这些规则告诉它当你没有给出某些命令的时候,应该怎么办。
如果你把生成foo.o和bar.o的命令从它们的规则中删除,make将会查找它的隐含规则,然后会找到一个适当的命令。
它的命令会使用一些变量,因此你可以按照你的想法来设定它:
它使用变量CC做为编译器(象我们在前面的例子),并且传递变量CFLAGS(给C编译器,C++编译器用CXXFLAGS),CPPFLAGS(C预处理器旗标),TARGET_ARCH(现在不用考虑这个),然后它加入旗标'-c',后面跟变量$<(第一个依靠名),然后是旗标'-o'跟变量$@(目的文件名)。
一个C编译的具体命令将会是:
$(CC)$(CFLAGS)$(CPPFLAGS)$(TARGET_ARCH)-c$<-o$@
当然你可以按照你自己的需要来定义这些变量。
这就是为什么用gcc的-M或-MM开关输出的码可以直接用在一个makefile里。
2.5假象目的(PhonyTargets)
假设你的一个项目最后需要产生两个可执行文件。
你的主要目标是产生两个可执行文件,但这两个文件是相互独立的——如果一个文件需要重建,并不影响另一个。
你可以使用“假象目的”来达到这种效果。
一个假象目的跟一个正常的目的几乎是一样的,只是这个目的文件是不存在的。
因此,make总是会假设它需要被生成,当把它的依赖文件更新后,就会执行它的规则里的命令行。
如果在我们的makefile开始处输入:
all:
exec1exec2
其中exec1和exec2是我们做为目的的两个可执行文件。
make把这个'all'做为它的主要目的,每次执行时都会尝试把'all'更新。
但既然这行规则里没有哪个命令来作用在一个叫'all'的实际文件(事实上all并不会在磁碟上实际产生),所以这个规则并不真的改变'all'的状态。
可既然这个文件并不存在,所以make会尝试更新all规则,因此就检查它的依靠exec1,exec2是否需要更新,如果需要,就把它们更新,从而达到我们的目的。
假象目的也可以用来描述一组非预设的动作。
例如,你想把所有由make产生的文件删除,你可以在makefile里设立这样一个规则:
veryclean:
rm*.o
rmmyprog
前提是没有其它的规则依靠这个'veryclean'目的,它将永远不会被执行。
但是,如果你明确的使用命令'makeveryclean',make会把这个目的做为它的主要目标,执行那些rm命令。
如果你的磁碟上存在一个叫veryclean文件,会发生什么事?
这时因为在这个规则里没有任何依靠文件,所以这个目的文件一定是最新的了(所有的依靠文件都已经是最新的了),所以既使用户明确命令make重新产生它,也不会有任何事情发生。
解决方法是标明所有的假象目的(用.PHONY),这就告诉make不用检查它们是否存在于磁碟上,也不用查找任何隐含规则,直接假设指定的目的需要被更新。
在makefile里加入下面这行包含上面规则的规则:
.PHONY:
veryclean
就可以了。
注意,这是一个特殊的make规则,make知道.PHONY是一个特殊目的,当然你可以在它的依靠里加入你想用的任何假象目的,而make知道它们都是假象目的。
2.6函数(Functions)
makefile里的函数跟它的变量很相似——使用的时候,你用一个$符号跟开括号,函数名,空格后跟一列由逗号分隔的参数,最后用关括号结束。
例如,在GNUMake里有一个叫'wildcard'的函数,它有一个参数,功能是展开成一列所有符合由其参数描述的文件名,文件间以空格间隔。
你可以像下面所示使用这个命令:
SOURCES=$(wildcard*.c)
这行会产生一个所有以'.c'结尾的文件的列表,然后存入变量SOURCES里。
当然你不需要一定要把结果存入一个变量。
另一个有用的函数是patsubst(pattensubstitude,匹配替换的缩写)函数。
它需要3个参数——第一个是一个需要匹配的式样,第二个表示用什么来替换它,第三个是一个需要被处理的由空格分隔的字列。
例如,处理那个经过上面定义后的变量,
OBJS=$(patsubst%.c,%.o,$(SOURCES))
这行将处理所有在SOURCES字列中的字(一列文件名),如果它的结尾是'.c',就用'.o'把'.c'取代。
注意这里的%符号将匹配一个或多个字符,而它每次所匹配的字串叫做一个‘柄’(stem)。
在第二个参数里,%被解读成用第一参数所匹配的那个柄。
2.7一个比较有效的makefile
利用我们现在所学的,我们可以建立一个相当有效的makefile。
这个makefile可以完成大部分我们需要的依靠检查,不用做太大的改变就可直接用在大多数的项目里。
首先我们需要一个基本的makefile来建我们的程序。
我们可以让它搜索当前目录,找到源码文件,并且假设它们都是属于我们的项目的,放进一个叫SOURCES的变量。
这里如果也包含所有的*.cc文件,也许会更保险,因为源码文件可能是C++码的。
SOURCES=$(wildcard*.c*.cc)
利用patsubst,我们可以由源码文件名产生目标文件名,我们需要编译出这些目标文件。
如果我们的源码文件既有.c文件,也有.cc文件,我们需要使用相嵌的patsubst函数呼叫:
OBJS=$(patsubst%.c,%.o,$(patsubst%.cc,%.o,$(SOURCES)))
最里面一层patsubst的呼叫会对.cc文件进行后缀替代,产生的结果被外层的patsubst呼叫处理,进行对.c文件后缀的替代。
现在我们可以设立一个规则来建可执行文件:
myprog:
$(OBJS)
gcc-omyprog$(OBJS)
进一步的规则不一定需要,gcc已经知道怎么去生成目标文件(objectfiles)。
下面我们可以设定产生依靠信息的规则:
depends:
$(SOURCES)
gcc-M$(SOURCES)>depends
在这里如果一个叫'depends'的文件不存在,或任何一个源码文件比一个已存在的depends文件新,那么一个depends文件会被生成。
depends文件将会含有由gcc产生的关于源码文件的规则(注意-M开关)。
现在我们要让make把这些规则当做makefile档的一部分。
这里使用的技巧很像C语言中的#include系统——我们要求make把这个文件include到makefile里,如下:
includedepends
GNUMake看到这个,检查'depends'目的是否更新了,如果没有,它用我们给它的命令重新产生depends档。
然后它会把这组(新)规则包含进来,继续处理最终目标'myprog'。
当看到有关myprog的规则,它会检查所有的目标文件是否更新——利用depends文件里的规则,当然这些规则现在已经是更新过的了。
这个系统其实效率很低,因为每当一个源码文件被改动,所有的源码文件都要被预处理以产生一个新的'depends'文件。
而且它也不是100%的安全,这是因为当一个header档被改动,依靠信息并不会被更新。
但就基本工作来说,它也算相当有用的了。
2.8一个更好的makefile
这是一个我为我大多数项目设计的makefile。
它应该可以不需要修改的用在大部分项目里。
我主要把它用在djgpp上,那是一个DOS版的gcc编译器。
因此你可以看到执行的命令名、'alleg'程序包、和RM-F变量都反映了这一点。
===makefile开始===
######################################
#
#Genericmakefile
#
#byGeorgeFoot
#email:
george.foot@merton.ox.ac.uk
#
#Copyright(c)1997GeorgeFoot
#Allrightsreserved.
#保留所有版权
#
#Nowarranty,noliability;
#youusethisatyourownrisk.
#没保险,不负责
#你要用这个,你自己担风险
#
#Youarefreetomodifyand
#distributethiswithoutgiving
#credittotheoriginalauthor.
#你可以随便更改和散发这个文件
#而不需要给原作者什么荣誉。
#(你好意思?
)
#
######################################
###Customising
#用户设定
#
#Adjustthefollowingifnecessary;EXECUTABLEisthetarget
#executable'sfilename,andLIBSisalistoflibrariestolinkin
#(e.g.alleg,stdcx,iostr,etc).Youcanoverridetheseonmake's
#commandlineofcourse,ifyouprefertodoitthatway.
#
#如果需要,调整下面的东西。
EXECUTABLE是目标的可执行文件名,LIBS
#是一个需要连接的程序包列表(例如alleg,stdcx,iostr等等)。
当然你
#可以在make的命令行覆盖它们,你愿意就没问题。
#
EXECUTABLE:
=mushroom.exe
LIBS:
=alleg
#Nowalteranyimplicitrules'variablesifyoulike,e.g.:
#
#现在来改变任何你想改动的隐含规则中的变量,例如
CFLAGS:
=-g-Wall-O3-m486
CXXFLAGS:
=$(CFLAGS)
#Thenextbitcheckstoseewhetherrmisinyourdjgppbin
#directory;ifnotitusesdelinstead,butthiscancause(harmless)
#`Filenotfound'errormessages.IfyouarenotusingDOSatall,
#setthevariabletosomethingwhichwillunquestioninglyremove
#files.
#
#下面先检查你的djgpp命令目录下有没有rm命令,如果没有,我们使用
#del命令来代替,但有可能给我们'Filenotfound'这个错误信息,这没
#什么大碍。
如果你不是用DOS,把它设定成一个删文件而不废话的命令。
#(其实这一步在UNIX类的系统上是多余的,只是方便DOS用户。
UNIX
#用户可以删除这5行命令。
)
ifneq($(wildcard$(DJDIR)/bin/rm.exe),)
RM-F:
=rm-f
else
RM-F:
=del
endif
#Youshouldn'tneedtochangeanythingbelowthispoint.
#
#从这里开始,你应该不需要改动任何东西。
(我是不太相信,太NB了!
)
SOURCE:
=$(wildcard*.c)$(wildcard*.cc)
OBJS:
=$(patsubst%.c,%.o,$(patsubst%.cc,%.o,$(SOURCE)))
DEPS:
=$(patsubst%.o,%.d,$(OBJS))
MISSING_DEPS:
=$(filter-out$(wildcard$(DEPS)),$(DEPS))
MISSING_DEPS_SOURCES:
=$(wildcard$(patsubst%.d,%.c,$(MISSING_DEPS))\
$(patsubst%.d,%.cc,$(MISSING_DEPS)))
CPPFLAGS+=-MD
.PHONY:
everythingdepsobjscleanverycleanrebuild
everything:
$(EXECUTABLE)
deps:
$(DEPS)
objs:
$(OBJS)
clean:
@$(RM-F)*.o
@$(RM-F)*.d
veryclean:
clean
@$(RM-F)$(EXECUTABLE)
rebuild:
verycleaneverything
ifneq($(MISSING_DEPS),)
$(MISSING_DEPS):
@$(RM-F)$(patsubst%.d,%.o,$@)
endif
-include$(DEPS)
$(EXECUTABLE):
$(OBJS)
gcc-o$(EXECUTABLE)$(OBJS)$(addprefix-l,$(LIBS))
===makefile结束===
有几个地方值得解