深入Makefile.docx

上传人:b****6 文档编号:8186580 上传时间:2023-01-29 格式:DOCX 页数:10 大小:24.78KB
下载 相关 举报
深入Makefile.docx_第1页
第1页 / 共10页
深入Makefile.docx_第2页
第2页 / 共10页
深入Makefile.docx_第3页
第3页 / 共10页
深入Makefile.docx_第4页
第4页 / 共10页
深入Makefile.docx_第5页
第5页 / 共10页
点击查看更多>>
下载资源
资源描述

深入Makefile.docx

《深入Makefile.docx》由会员分享,可在线阅读,更多相关《深入Makefile.docx(10页珍藏版)》请在冰豆网上搜索。

深入Makefile.docx

深入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结束===

有几个地方值得解

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 人文社科 > 设计艺术

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1