当前位置: 首页 > news >正文

微信网站建设报价单网络营销方案策划书

微信网站建设报价单,网络营销方案策划书,凡客诚品商城,公关公司是做什么的近期在学习Linux下的C编程#xff0c;买了一本叫《Linux环境下的C编程指南》读到makefile就越看越迷糊#xff0c;可能是我的理解能不行。 于是google到了下面这篇文章。通俗易懂。然后把它贴出来#xff0c;方便学习。 后记#xff0c;看完发现这篇文章和《Linux环境下的C…             近期在学习Linux下的C编程买了一本叫《Linux环境下的C编程指南》读到makefile就越看越迷糊可能是我的理解能不行。             于是google到了下面这篇文章。通俗易懂。然后把它贴出来方便学习。            后记看完发现这篇文章和《Linux环境下的C编程指南》的makefile一章所讲述的惊人的类似仅仅是这篇文章从一个实例切入在有些地方比較好理解。能让人看懂就是好文章。                        跟我一起写 Makefile陈皓 (CSDN)概述——什么是makefile也许非常多Winodws的程序猿都不知道这个东西由于那些Windows的IDE都为你做了这个工作但我认为要作一个好的和professional的程序猿makefile还是要懂。这就好像如今有这么多的HTML的编辑器但假设你想成为一个专业人士你还是要了解HTML的标识的含义。特别在Unix下的软件编译你就不能不自己写makefile了会不会写makefile从一个側面说明了一个人是否具备完毕大型project的能力。由于makefile关系到了整个project的编译规则。一个project中的源文件不计数其按类型、功能、模块分别放在若干个文件夹中makefile定义了一系列的规则来指定哪些文件须要先编译哪些文件须要后编译哪些文件须要又一次编译甚至于进行更复杂的功能操作由于makefile就像一个Shell脚本一样当中也能够运行操作系统的命令。makefile带来的优点就是——“自己主动化编译”一旦写好仅仅须要一个make命令整个project全然自己主动编译极大的提高了软件开发的效率。make是一个命令工具是一个解释makefile中指令的命令工具一般来说大多数的IDE都有这个命令比方Delphi的makeVisual C的nmakeLinux下GNU的make。可见makefile都成为了一种在project方面的编译方法。如今讲述怎样写makefile的文章比較少这是我想写这篇文章的原因。当然不同产商的make各不同样也有不同的语法但其本质都是在“文件依赖性”上做文章这里我仅对GNU的make进行讲述我的环境是RedHat Linux 8.0make的版本号是3.80。必竟这个make是应用最为广泛的也是用得最多的。而且其还是最遵循于IEEE 1003.2-1992 标准的POSIX.2。在这篇文档中将以C/C的源代码作为我们基础所以必定涉及一些关于C/C的编译的知识相关于这方面的内容还请各位查看相关的编译器的文档。这里所默认的编译器是UNIX下的GCC和CC。关于程序的编译和链接——————————在此我想多说关于程序编译的一些规范和方法一般来说无论是C、C、还是pas首先要把源文件编译成中间代码文件在Windows下也就是 .obj 文件UNIX下是 .o 文件即 Object File这个动作叫做编译compile。然后再把大量的Object File合成运行文件这个动作叫作链接link。编译时编译器须要的是语法的正确函数与变量的声明的正确。对于后者通常是你须要告诉编译器头文件的所在位置头文件里应该仅仅是声明而定义应该放在C/C文件里仅仅要全部的语法正确编译器就能够编译出中间目标文件。一般来说每个源文件都应该相应于一个中间目标文件O文件或是OBJ文件。链接时主要是链接函数和全局变量所以我们能够使用这些中间目标文件O文件或是OBJ文件来链接我们的应用程序。链接器并无论函数所在的源文件仅仅管函数的中间目标文件Object File在大多数时候由于源文件太多编译生成的中间目标文件太多而在链接时须要明显地指出中间目标文件名称这对于编译非常不方便所以我们要给中间目标文件打个包在Windows下这样的包叫“库文件”Library File)也就是 .lib 文件在UNIX下是Archive File也就是 .a 文件。总结一下源文件首先会生成中间目标文件再由中间目标文件生成运行文件。在编译时编译器仅仅检測程序语法和函数、变量是否被声明。假设函数未被声明编译器会给出一个警告但能够生成Object File。而在链接程序时链接器会在全部的Object File中找寻函数的实现假设找不到那到就会报链接错误码Linker Error在VC下这样的错误通常是Link 2001错误意思说是说链接器未能找到函数的实现。你须要指定函数的Object File.好言归正传GNU的make有很多的内容闲言少叙还是让我们開始吧。Makefile 介绍———————make命令运行时须要一个 Makefile 文件以告诉make命令须要怎么样的去编译和链接程序。首先我们用一个演示样例来说明Makefile的书写规则。以便给大家一个感兴认识。这个演示样例来源于GNU的make使用手冊在这个演示样例中我们的project有8个C文件和3个头文件我们要写一个Makefile来告诉make命令怎样编译和链接这几个文件。我们的规则是1假设这个project没有编译过那么我们的全部C文件都要编译并被链接。2假设这个project的某几个C文件被改动那么我们仅仅编译被改动的C文件并链接目标程序。3假设这个project的头文件被改变了那么我们须要编译引用了这几个头文件的C文件并链接目标程序。仅仅要我们的Makefile写得够好全部的这一切我们仅仅用一个make命令就能够完毕make命令会自己主动智能地依据当前的文件改动的情况来确定哪些文件须要重编译从而自己编译所须要的文件和链接目标程序。一、Makefile的规则在讲述这个Makefile之前还是让我们先来粗略地看一看Makefile的规则。target ... : prerequisites ...command......target也就是一个目标文件能够是Object File也能够是运行文件。还能够是一个标签Label对于标签这样的特性在兴许的“伪目标”章节中会有叙述。prerequisites就是要生成那个target所须要的文件或是目标。command也就是make须要运行的命令。随意的Shell命令这是一个文件的依赖关系也就是说target这一个或多个的目标文件依赖于prerequisites中的文件其生成规则定义在command中。说白一点就是说prerequisites中假设有一个以上的文件比target文件要新的话command所定义的命令就会被运行。这就是Makefile的规则。也就是Makefile中最核心的内容。说究竟Makefile的东西就是这样一点好像我的这篇文档也该结束了。呵呵。还不尽然这是Makefile的主线和核心但要写好一个Makefile还不够我会以后面一点一点地结合我的工作经验给你慢慢到来。内容还多着呢。二、一个演示样例正如前面所说的假设一个project有3个头文件和8个C文件我们为了完毕前面所述的那三个规则我们的Makefile应该是以下的这个样子的。edit : main.o kbd.o command.o display.o /insert.o search.o files.o utils.occ -o edit main.o kbd.o command.o display.o /insert.o search.o files.o utils.omain.o : main.c defs.hcc -c main.ckbd.o : kbd.c defs.h command.hcc -c kbd.ccommand.o : command.c defs.h command.hcc -c command.cdisplay.o : display.c defs.h buffer.hcc -c display.cinsert.o : insert.c defs.h buffer.hcc -c insert.csearch.o : search.c defs.h buffer.hcc -c search.cfiles.o : files.c defs.h buffer.h command.hcc -c files.cutils.o : utils.c defs.hcc -c utils.cclean :rm edit main.o kbd.o command.o display.o /insert.o search.o files.o utils.o反斜杠/是换行符的意思。这样比較便于Makefile的易读。我们能够把这个内容保存在文件为“Makefile”或“makefile”的文件里然后在该文件夹下直接输入命令“make”就能够生成运行文件edit。假设要删除运行文件和全部的中间目标文件那么仅仅要简单地运行一下“make clean”就能够了。在这个makefile中目标文件target包括运行文件edit和中间目标文件*.o依赖文件prerequisites就是冒号后面的那些 .c 文件和 .h文件。每个 .o 文件都有一组依赖文件而这些 .o 文件又是运行文件 edit 的依赖文件。依赖关系的实质上就是说明了目标文件是由哪些文件生成的换言之目标文件是哪些文件更新的。在定义好依赖关系后兴许的那一行定义了怎样生成目标文件的操作系统命令一定要以一个Tab键作为开头。记住make并无论命令是怎么工作的他仅仅管运行所定义的命令。make会比較targets文件和prerequisites文件的改动日期假设prerequisites文件的日期要比targets文件的日期要新或者target不存在的话那么make就会运行兴许定义的命令。这里要说明一点的是clean不是一个文件它仅仅只是是一个动作名字有点像C语言中的lable一样其冒号后什么也没有那么make就不会自己主动去找文件的依赖性也就不会自己主动运行其后所定义的命令。要运行其后的命令就要在make命令后明显得指出这个lable的名字。这样的方法非常实用我们能够在一个makefile中定义不用的编译或是和编译无关的命令比方程序的打包程序的备份等等。三、make是怎样工作的在默认的方式下也就是我们仅仅输入make命令。那么1、make会在当前文件夹下找名字叫“Makefile”或“makefile”的文件。2、假设找到它会找文件里的第一个目标文件target在上面的样例中他会找到“edit”这个文件并把这个文件作为终于的目标文件。3、假设edit文件不存在或是edit所依赖的后面的 .o 文件的文件改动时间要比edit这个文件新那么他就会运行后面所定义的命令来生成edit这个文件。4、假设edit所依赖的.o文件也不存在那么make会在当前文件里找目标为.o文件的依赖性假设找到则再依据那一个规则生成.o文件。这有点像一个堆栈的过程5、当然你的C文件和H文件是存在的啦于是make会生成 .o 文件然后再用 .o 文件生命make的终极任务也就是运行文件edit了。这就是整个make的依赖性make会一层又一层地去找文件的依赖关系直到终于编译出第一个目标文件。在找寻的过程中假设出现错误比方最后被依赖的文件找不到那么make就会直接退出并报错而对于所定义的命令的错误或是编译不成功make根本不理。make仅仅管文件的依赖性即假设在我找了依赖关系之后冒号后面的文件还是不在那么对不起我就不工作啦。通过上述分析我们知道像clean这样的没有被第一个目标文件直接或间接关联那么它后面所定义的命令将不会被自己主动运行只是我们能够显示要make运行。即命令——“make clean”以此来清除全部的目标文件以便重编译。于是在我们编程中假设这个project已被编译过了当我们改动了当中一个源文件比方file.c那么依据我们的依赖性我们的目标file.o会被重编译也就是在这个依性关系后面所定义的命令于是file.o的文件也是最新的啦于是file.o的文件改动时间要比edit要新所以edit也会被又一次链接了详见edit目标文件后定义的命令。而假设我们改变了“command.h”那么kdb.o、command.o和files.o都会被重编译而且edit会被重链接。四、makefile中使用变量在上面的样例中先让我们看看edit的规则edit : main.o kbd.o command.o display.o /insert.o search.o files.o utils.occ -o edit main.o kbd.o command.o display.o /insert.o search.o files.o utils.o我们能够看到[.o]文件的字符串被反复了两次假设我们的project须要增加一个新的[.o]文件那么我们须要在两个地方加应该是三个地方另一个地方在clean中。当然我们的makefile并不复杂所以在两个地方加也不累但假设makefile变得复杂那么我们就有可能会忘掉一个须要增加的地方而导致编译失败。所以为了makefile的易维护在makefile中我们能够使用变量。makefile的变量也就是一个字符串理解成C语言中的宏可能会更好。比方我们声明一个变量叫objects, OBJECTS, objs, OBJS, obj, 或是 OBJ反正无论什么啦仅仅要能够表示obj文件即可了。我们在makefile一開始就这样定义objects main.o kbd.o command.o display.o /insert.o search.o files.o utils.o于是我们就能够非常方便地在我们的makefile中以“$(objects)”的方式来使用这个变量了于是我们的改良版makefile就变成以下这个样子objects main.o kbd.o command.o display.o /insert.o search.o files.o utils.oedit : $(objects)cc -o edit $(objects)main.o : main.c defs.hcc -c main.ckbd.o : kbd.c defs.h command.hcc -c kbd.ccommand.o : command.c defs.h command.hcc -c command.cdisplay.o : display.c defs.h buffer.hcc -c display.cinsert.o : insert.c defs.h buffer.hcc -c insert.csearch.o : search.c defs.h buffer.hcc -c search.cfiles.o : files.c defs.h buffer.h command.hcc -c files.cutils.o : utils.c defs.hcc -c utils.cclean :rm edit $(objects)于是假设有新的 .o 文件增加我们仅仅需简单地改动一下 objects 变量就能够了。关于变量很多其它的话题我会在兴许给你一一道来。五、让make自己主动推导GNU的make非常强大它能够自己主动推导文件以及文件依赖关系后面的命令于是我们就不是必需去在每个[.o]文件后都写上相似的命令由于我们的make会自己主动识别并自己推导命令。仅仅要make看到一个[.o]文件它就会自己主动的把[.c]文件加在依赖关系中假设make找到一个whatever.o那么whatever.c就会是whatever.o的依赖文件。而且 cc -c whatever.c 也会被推导出来于是我们的makefile再也不用写得这么复杂。我们的是新的makefile又出炉了。objects main.o kbd.o command.o display.o /insert.o search.o files.o utils.oedit : $(objects)cc -o edit $(objects)main.o : defs.hkbd.o : defs.h command.hcommand.o : defs.h command.hdisplay.o : defs.h buffer.hinsert.o : defs.h buffer.hsearch.o : defs.h buffer.hfiles.o : defs.h buffer.h command.hutils.o : defs.h.PHONY : cleanclean :rm edit $(objects)这样的方法也就是make的“隐晦规则”。上面文件内容中“.PHONY”表示clean是个伪目标文件。关于更为具体的“隐晦规则”和“伪目标文件”我会在兴许给你一一道来。六、另类风格的makefile即然我们的make能够自己主动推导命令那么我看到那堆[.o]和[.h]的依赖就有点不爽那么多的反复的[.h]能不能把其收拢起来好吧没有问题这个对于make来说非常easy谁叫它提供了自己主动推导命令和文件的功能呢来看看最新风格的makefile吧。objects main.o kbd.o command.o display.o /insert.o search.o files.o utils.oedit : $(objects)cc -o edit $(objects)$(objects) : defs.hkbd.o command.o files.o : command.hdisplay.o insert.o search.o files.o : buffer.h.PHONY : cleanclean :rm edit $(objects)这样的风格让我们的makefile变得非常简单但我们的文件依赖关系就显得有点凌乱了。鱼和熊掌不可兼得。还看你的喜好了。我是不喜欢这样的风格的一是文件的依赖关系看不清楚二是假设文件一多要增加几个新的.o文件那就理不清楚了。 七、清空目标文件的规则每一个Makefile中都应该写一个清空目标文件.o和运行文件的规则这不仅便于重编译也非常利于保持文件的清洁。这是一个“修养”呵呵还记得我的《编程修养》吗。一般的风格都是clean:rm edit $(objects)更为稳健的做法是.PHONY : cleanclean :-rm edit $(objects)前面说过.PHONY意思表示clean是一个“伪目标”。而在rm命令前面加了一个小减号的意思就是或许某些文件出现故障但不要管继续做后面的事。当然clean的规则不要放在文件的开头不然这就会变成make的默认目标相信谁也不愿意这样。不成文的规矩是——“clean从来都是放在文件的最后”。上面就是一个makefile的概貌也是makefile的基础以下还有非常多makefile的相关细节准备好了吗准备好了就来。Makefile 总述———————一、Makefile里有什么Makefile里主要包括了五个东西显式规则、隐晦规则、变量定义、文件指示和凝视。1、显式规则。显式规则说明了怎样生成一个或多的的目标文件。这是由Makefile的书写者明显指出要生成的文件文件的依赖文件生成的命令。2、隐晦规则。由于我们的make有自己主动推导的功能所以隐晦的规则能够让我们比較粗糙地简略地书写Makefile这是由make所支持的。3、变量的定义。在Makefile中我们要定义一系列的变量变量一般都是字符串这个有点你C语言中的宏当Makefile被运行时当中的变量都会被扩展到相应的引用位置上。4、文件指示。其包括了三个部分一个是在一个Makefile中引用还有一个Makefile就像C语言中的include一样还有一个是指依据某些情况指定Makefile中的有效部分就像C语言中的预编译#if一样还有就是定义一个多行的命令。有关这一部分的内容我会在兴许的部分中讲述。5、凝视。Makefile中仅仅有行凝视和UNIX的Shell脚本一样其凝视是用“#”字符这个就像C/C中的“//”一样。假设你要在你的Makefile中使用“#”字符能够用反斜框进行转义如“/#”。最后还值得一提的是在Makefile中的命令必须要以[Tab]键開始。二、Makefile的文件名称默认的情况下make命令会在当前文件夹下按顺序找寻文件名称为“GNUmakefile”、“makefile”、“Makefile”的文件找到了解释这个文件。在这三个文件名称中最好使用“Makefile”这个文件名称由于这个文件名称第一个字符为大写这样有一种显目的感觉。最好不要用“GNUmakefile”这个文件是GNU的make识别的。有另外一些make仅仅对全小写的“makefile”文件名称敏感可是基本上来说大多数的make都支持“makefile”和“Makefile”这两种默认文件名称。当然你能够使用别的文件名称来书写Makefile比方“Make.Linux”“Make.Solaris”“Make.AIX”等假设要指定特定的Makefile你能够使用make的“-f”和“--file”參数如make -f Make.Linux或make --file Make.AIX。三、引用其他的Makefile在Makefile使用includekeyword能够把别的Makefile包括进来这非常像C语言的#include被包括的文件会原模原样的放在当前文件的包括位置。include的语法是include filenamefilename能够是当前操作系统Shell的文件模式能够保含路径和通配符在include前面能够有一些空字符可是绝不能是[Tab]键開始。include和filename能够用一个或多个空格隔开。举个样例你有这样几个Makefilea.mk、b.mk、c.mk还有一个文件叫foo.make以及一个变量$(bar)其包括了e.mk和f.mk那么以下的语句include foo.make *.mk $(bar)等价于include foo.make a.mk b.mk c.mk e.mk f.mkmake命令開始时会把找寻include所指出的其他Makefile并把其内容安置在当前的位置。就好像C/C的#include指令一样。假设文件都没有指定绝对路径或是相对路径的话make会在当前文件夹下首先寻找假设当前文件夹下没有找到那么make还会在以下的几个文件夹下找1、假设make运行时有“-I”或“--include-dir”參数那么make就会在这个參数所指定的文件夹下去寻找。2、假设文件夹prefix/include通常是/usr/local/bin或/usr/include存在的话make也会去找。假设有文件没有找到的话make会生成一条警告信息但不会立即出现致命错误。它会继续加载其他的文件一旦完毕makefile的读取make会再重试这些没有找到或是不能读取的文件假设还是不行make才会出现一条致命信息。假设你想让make不理那些无法读取的文件而继续运行你能够在include前加一个减号“-”。如-include filename其表示无论include过程中出现什么错误都不要报错继续运行。和其他版本号make兼容的相关命令是sinclude其作用和这一个是一样的。四、环境变量 MAKEFILES 假设你的当前环境中定义了环境变量MAKEFILES那么make会把这个变量中的值做一个相似于include的动作。这个变量中的值是其他的Makefile用空格分隔。仅仅是它和include不同的是从这个环境变中引入的Makefile的“目标”不会起作用假设环境变量中定义的文件发现错误make也会不理。可是在这里我还是建议不要使用这个环境变量由于仅仅要这个变量一被定义那么当你使用make时全部的Makefile都会受到它的影响这绝不是你想看到的。在这里提这个事仅仅是为了告诉大家或许有时候你的Makefile出现了怪事那么你能够看看当前环境中有未定义这个变量。五、make的工作方式GNU的make工作时的运行步骤入下想来其他的make也是相似1、读入全部的Makefile。2、读入被include的其他Makefile。3、初始化文件里的变量。4、推导隐晦规则并分析全部规则。5、为全部的目标文件创建依赖关系链。6、依据依赖关系决定哪些目标要又一次生成。7、运行生成命令。1-5步为第一个阶段6-7为第二个阶段。第一个阶段中假设定义的变量被使用了那么make会把其展开在使用的位置。但make并不会全然立即展开make使用的是迟延战术假设变量出如今依赖关系的规则中那么仅当这条依赖被决定要使用了变量才会在其内部展开。当然这个工作方式你不一定要清楚可是知道这个方式你也会对make更为熟悉。有了这个基础兴许部分也就easy看懂了。书写规则————规则包括两个部分一个是依赖关系一个是生成目标的方法。在Makefile中规则的顺序是非常重要的由于Makefile中仅仅应该有一个终于目标其他的目标都是被这个目标所连带出来的所以一定要让make知道你的终于目标是什么。一般来说定义在Makefile中的目标可能会有非常多可是第一条规则中的目标将被确立为终于的目标。假设第一条规则中的目标有非常多个那么第一个目标会成为终于的目标。make所完毕的也就是这个目标。好了还是让我们来看一看怎样书写规则。一、规则举例foo.o : foo.c defs.h # foo模块cc -c -g foo.c看到这个样例各位应该不是非常陌生了前面也已说过foo.o是我们的目标foo.c和defs.h是目标所依赖的源文件而仅仅有一个命令“cc -c -g foo.c”以Tab键开头。这个规则告诉我们两件事1、文件的依赖关系foo.o依赖于foo.c和defs.h的文件假设foo.c和defs.h的文件日期要比foo.o文件日期要新或是foo.o不存在那么依赖关系发生。2、假设生成或更新foo.o文件。也就是那个cc命令其说明了怎样生成foo.o这个文件。当然foo.c文件include了defs.h文件二、规则的语法targets : prerequisitescommand...或是这样 targets : prerequisites ; commandcommand...targets是文件名称以空格分开能够使用通配符。一般来说我们的目标基本上是一个文件但也有可能是多个文件。command是命令行假设其不与“targetrerequisites”在一行那么必须以[Tab键]开头假设和prerequisites在一行那么能够用分号做为分隔。见上prerequisites也就是目标所依赖的文件或依赖目标。假设当中的某个文件要比目标文件要新那么目标就被觉得是“过时的”被觉得是须要重生成的。这个在前面已经讲过了。假设命令太长你能够使用反斜框‘/’作为换行符。make对一行上有多少个字符没有限制。规则告诉make两件事文件的依赖关系和怎样成成目标文件。一般来说make会以UNIX的标准Shell也就是/bin/sh来运行命令。三、在规则中使用通配符假设我们想定义一系列比較相似的文件我们非常自然地就想起使用通配符。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: *.clpr -p $?touch print上面这个样例说明了通配符也能够在我们的规则中目标print依赖于全部的[.c]文件。当中的“$?”是一个自己主动化变量我会在后面给你讲述。objects *.o上面这个样例表示了通符相同能够用在变量中。并非说[*.o]会展开不objects的值就是“*.o”。Makefile中的变量事实上就是C/C中的宏。假设你要让通配符在变量中展开也就是让objects的值是全部[.o]的文件名称的集合那么你能够这样objects : $(wildcard *.o)这样的使用方法由keyword“wildcard”指出关于Makefile的keyword我们将在后面讨论。四、文件搜寻在一些大的project中有大量的源文件我们通常的做法是把这很多的源文件分类并存放在不同的文件夹中。所以当make须要去找寻文件的依赖关系时你能够在文件前加上路径但最好的方法是把一个路径告诉make让make在自己主动去找。Makefile文件里的特殊变量“VPATH”就是完毕这个功能的假设没有指明这个变量make仅仅会在当前的文件夹中去找寻依赖文件和目标文件。假设定义了这个变量那么make就会在当当前文件夹找不到的情况下到所指定的文件夹中去找寻文件了。VPATH src:../headers上面的的定义指定两个文件夹“src”和“../headers”make会依照这个顺序进行搜索。文件夹由“冒号”分隔。当然当前文件夹永远是最高优先搜索的地方还有一个设置文件搜索路径的方法是使用make的“vpath”keyword注意它是全小写的这不是变量这是一个make的keyword这和上面提到的那个VPATH变量非常相似可是它更为灵活。它能够指定不同的文件在不同的搜索文件夹中。这是一个非常灵活的功能。它的使用方法有三种1、vpath pattern directories为符合模式pattern的文件指定搜索文件夹directories。2、vpath pattern清除符合模式pattern的文件的搜索文件夹。3、vpath清除全部已被设置好了的文件搜索文件夹。vapth使用方法中的pattern须要包括“%”字符。“%”的意思是匹配零或若干字符比如“%.h”表示全部以“.h”结尾的文件。pattern指定了要搜索的文件集而directories则指定了pattern的文件集的搜索的文件夹。比如vpath %.h ../headers该语句表示要求make在“../headers”文件夹下搜索全部以“.h”结尾的文件。假设某文件在当前文件夹没有找到的话我们能够连续地使用vpath语句以指定不同搜索策略。假设连续的vpath语句中出现了相同的pattern或是被反复了的pattern那么make会依照vpath语句的先后顺序来运行搜索。如vpath %.c foovpath % blishvpath %.c bar其表示“.c”结尾的文件先在“foo”文件夹然后是“blish”最后是“bar”文件夹。vpath %.c foo:barvpath % blish而上面的语句则表示“.c”结尾的文件先在“foo”文件夹然后是“bar”文件夹最后才是“blish”文件夹。五、伪目标最早先的一个样例中我们提到过一个“clean”的目标这是一个“伪目标”clean:rm *.o temp正像我们前面样例中的“clean”一样即然我们生成了很多文件编译文件我们也应该提供一个清除它们的“目标”以备完整地重编译而用。 以“make clean”来使用该目标由于我们并不生成“clean”这个文件。“伪目标”并非一个文件仅仅是一个标签由于“伪目标”不是文件所以make无法生成它的依赖关系和决定它是否要运行。我们仅仅有通过显示地指明这个“目标”才干让其生效。当然“伪目标”的取名不能和文件名称重名不然其就失去了“伪目标”的意义了。当然为了避免和文件重名的这样的情况我们能够使用一个特殊的标记“.PHONY”来显示地指明一个目标是“伪目标”向make说明无论是否有这个文件这个目标就是“伪目标”。.PHONY : clean仅仅要有这个声明无论是否有“clean”文件要运行“clean”这个目标仅仅有“make clean”这样。于是整个过程能够这样写.PHONY: cleanclean:rm *.o temp伪目标一般没有依赖的文件。可是我们也能够为伪目标指定所依赖的文件。伪目标相同能够作为“默认目标”仅仅要将其放在第一个。一个演示样例就是假设你的Makefile须要一口气生成若干个可运行文件但你仅仅想简单地敲一个make完事而且全部的目标文件都写在一个Makefile中那么你能够使用“伪目标”这个特性all : prog1 prog2 prog3.PHONY : allprog1 : prog1.o utils.occ -o prog1 prog1.o utils.oprog2 : prog2.occ -o prog2 prog2.oprog3 : prog3.o sort.o utils.occ -o prog3 prog3.o sort.o utils.o我们知道Makefile中的第一个目标会被作为其默认目标。我们声明了一个“all”的伪目标其依赖于其他三个目标。由于伪目标的特性是总是被运行的所以其依赖的那三个目标就总是不如“all”这个目标新。所以其他三个目标的规则总是会被决议。也就达到了我们一口气生成多个目标的目的。“.PHONY : all”声明了“all”这个目标为“伪目标”。随便提一句从上面的样例我们能够看出目标也能够成为依赖。所以伪目标相同也可成为依赖。看以下的样例.PHONY: cleanall cleanobj cleandiffcleanall : cleanobj cleandiffrm programcleanobj :rm *.ocleandiff :rm *.diff“make clean”将清除全部要被清除的文件。“cleanobj”和“cleandiff”这两个伪目标有点像“子程序”的意思。我们能够输入“make cleanall”和“make cleanobj”和“make cleandiff”命令来达到清除不同种类文件的目的。六、多目标Makefile的规则中的目标能够不止一个其支持多目标有可能我们的多个目标同一时候依赖于一个文件而且其生成的命令大体相似。于是我们就能把其合并起来。当然多个目标的生成规则的运行命令是同一个这可能会可我们带来麻烦只是好在我们的能够使用一个自己主动化变量“$”关于自己主动化变量将在后面讲述这个变量表示着眼下规则中全部的目标的集合这样说可能非常抽象还是看一个样例吧。bigoutput littleoutput : text.ggenerate text.g -$(subst output,,$) $上述规则等价于bigoutput : text.ggenerate text.g -big bigoutputlittleoutput : text.ggenerate text.g -little littleoutput当中-$(subst output,,$)中的“$”表示运行一个Makefile的函数函数名为subst后面的为參数。关于函数将在后面讲述。这里的这个函数是截取字符串的意思“$”表示目标的集合就像一个数组“$”依次取出目标并执于命令。七、静态模式静态模式能够更加easy地定义多目标的规则能够让我们的规则变得更加的有弹性和灵活。我们还是先来看一下语法targets ...: target-pattern: prereq-patterns ...commands...targets定义了一系列的目标文件能够有通配符。是目标的一个集合。target-parrtern是指明了targets的模式也就是的目标集模式。prereq-parrterns是目标的依赖模式它对target-parrtern形成的模式再进行一次依赖目标的定义。这样描写叙述这三个东西可能还是没有说清楚还是举个样例来说明一下吧。假设我们的target-parrtern定义成“%.o”意思是我们的target集合中都是以“.o”结尾的而假设我们的prereq-parrterns定义成“%.c”意思是对target-parrtern所形成的目标集进行二次定义其计算方法是取target-parrtern模式中的“%”也就是去掉了[.o]这个结尾并为其加上[.c]这个结尾形成的新集合。所以我们的“目标模式”或是“依赖模式”中都应该有“%”这个字符假设你的文件名称中有“%”那么你能够使用反斜杠“/”进行转义来标明真实的“%”字符。看一个样例objects foo.o bar.oall: $(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”“$”表示目标集也就是“foo.o bar.o”。于是上面的规则展开后等价于以下的规则foo.o : foo.c$(CC) -c $(CFLAGS) foo.c -o foo.obar.o : bar.c$(CC) -c $(CFLAGS) bar.c -o bar.o试想假设我们的“%.o”有几百个那种我们仅仅要用这样的非常easy的“静态模式规则”就能够写完一堆规则实在是太有效率了。“静态模式规则”的使用方法非常灵活假设用得好那会一个非常强大的功能。再看一个样例files foo.elc bar.o lose.o$(filter %.o,$(files)): %.o: %.c$(CC) -c $(CFLAGS) $ -o $$(filter %.elc,$(files)): %.elc: %.elemacs -f batch-byte-compile $$(filter %.o,$(files))表示调用Makefile的filter函数过滤“$filter”集仅仅要当中模式为“%.o”的内容。其的它内容我就不用多说了吧。这个例字展示了Makefile中更大的弹性。八、自己主动生成依赖性在Makefile中我们的依赖关系可能会须要包括一系列的头文件比方假设我们的main.c中有一句“#include defs.h”那么我们的依赖关系应该是main.o : main.c defs.h可是假设是一个比較大型的project你必需清楚哪些C文件包括了哪些头文件而且你在增加或删除头文件时也须要小心地改动Makefile这是一个非常没有维护性的工作。为了避免这样的繁重而又easy出错的事情我们能够使用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.hgcc -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]文件%.d: %.cset -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命令的使用方法请參看相关的使用文档。第四行就是删除暂时文件。总而言之这个模式要做的事就是在编译器生成的依赖关系中增加[.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.cinclude $(sources:.c.d)上述语句中的“$(sources:.c.d)”中的“.c.d”的意思是做一个替换把变量$(sources)全部[.c]的字串都替换成[.d]关于这个“替换”的内容在后面我会有更为具体的讲述。当然你得注意次序由于include是按次来加载文件最先加载的[.d]文件里的目标会成为默认目标。   书写命令————每条规则中的命令和操作系统Shell的命令行是一致的。make会一按顺序一条一条的运行命令每条命令的开头必须以[Tab]键开头除非命令是紧跟在依赖规则后面的分号后的。在命令行之间中的空格或是空行会被忽略可是假设该空格或空行是以Tab键开头的那么make会觉得其是一个空命令。我们在UNIX下可能会使用不同的Shell可是make的命令默认是被“/bin/sh”——UNIX的标准Shell解释运行的。除非你特别指定一个其他的Shell。Makefile中“#”是凝视符非常像C/C中的“//”其后的本行字符都被凝视。一、显示命令通常make会把其要运行的命令行在命令运行前输出到屏幕上。当我们用“”字符在命令行前那么这个命令将不被make显示出来最具代表性的样例是我们用这个功能来像屏幕显示一些信息。如echo 正在编译XXX模块......当make运行时会输出“正在编译XXX模块......”字串但不会输出命令假设没有“”那么make将输出echo 正在编译XXX模块......正在编译XXX模块......假设make运行时带入make參数“-n”或“--just-print”那么其仅仅是显示命令但不会运行命令这个功能非常有利于我们调试我们的Makefile看看我们书写的命令是运行起来是什么样子的或是什么顺序的。而make參数“-s”或“--slient”则是全面禁止命令的显示。二、命令运行当依赖目标新于目标时也就是当规则的目标须要被更新时make会一条一条的运行其后的命令。须要注意的是假设你要让上一条命令的结果应用在下一条命令时你应该使用分号分隔这两条命令。比方你的第一条命令是cd命令你希望第二条命令得在cd之后的基础上运行那么你就不能把这两条命令写在两行上而应该把这两条命令写在一行上用分号分隔。如演示样例一exec:cd /home/hchenpwd演示样例二exec:cd /home/hchen; pwd当我们运行“make exec”时第一个样例中的cd没有作用pwd会打印出当前的Makefile文件夹而第二个样例中cd就起作用了pwd会打印出“/home/hchen”。make通常是使用环境变量SHELL中所定义的系统Shell来运行命令默认情况下使用UNIX的标准Shell——/bin/sh来运行命令。但在MS-DOS下有点特殊由于MS-DOS下没有SHELL环境变量当然你也能够指定。假设你指定了UNIX风格的文件夹形式首先make会在SHELL所指定的路径中找寻命令解释器假设找不到其会在当前盘符中的当前文件夹中寻找假设再找不到其会在PATH环境变量中所定义的全部路径中寻找。MS-DOS中假设你定义的命令解释器没有找到其会给你的命令解释器加上诸如“.exe”、“.com”、“.bat”、“.sh”等后缀。三、命令出错每当命令运行完后make会检測每一个命令的返回码假设命令返回成功那么make会运行下一条命令当规则中全部的命令成功返回后这个规则就算是成功完毕了。假设一个规则中的某个命令出错了命令退出码非零那么make就会终止运行当前规则这将有可能终止全部规则的运行。有些时候命令的出错并不表示就是错误的。比如mkdir命令我们一定须要建立一个文件夹假设文件夹不存在那么mkdir就成功运行万事大吉假设文件夹存在那么就出错了。我们之所以使用mkdir的意思就是一定要有这样的一个文件夹于是我们就不希望mkdir出错而终止规则的运行。为了做到这一点忽略命令的出错我们能够在Makefile的命令行前加一个减号“-”在Tab键之后标记为无论命令出不出错都觉得是成功的。如clean:-rm -f *.o另一个全局的办法是给make加上“-i”或是“--ignore-errors”參数那么Makefile中全部命令都会忽略错误。而假设一个规则是以“.IGNORE”作为目标的那么这个规则中的全部命令将会忽略错误。这些是不同级别的防止命令出错的方法你能够依据你的不同喜欢设置。另一个要提一下的make的參数的是“-k”或是“--keep-going”这个參数的意思是假设某规则中的命令出错了那么就终目该规则的运行但继续运行其他规则。四、嵌套运行make在一些大的project中我们会把我们不同模块或是不同功能的源文件放在不同的文件夹中我们能够在每一个文件夹中都书写一个该文件夹的Makefile这有利于让我们的Makefile变得更加地简洁而不至于把全部的东西全部写在一个Makefile中这样会非常难维护我们的Makefile这个技术对于我们模块编译和分段编译有着非常大的优点。比如我们有一个子文件夹叫subdir这个文件夹下有个Makefile文件来指明了这个文件夹下文件的编译规则。那么我们总控的Makefile能够这样书写subsystem:cd subdir $(MAKE)其等价于subsystem:$(MAKE) -C subdir定义$(MAKE)宏变量的意思是也许我们的make须要一些參数所以定义成一个变量比較利于维护。这两个样例的意思都是先进入“subdir”文件夹然后运行make命令。我们把这个Makefile叫做“总控Makefile”总控Makefile的变量能够传递到下级的Makefile中假设你显示的声明可是不会覆盖下层的Makefile中所定义的变量除非指定了“-e”參数。假设你要传递变量到下级Makefile中那么你能够使用这样的声明export variable ...假设你不想让某些变量传递到下级Makefile中那么你能够这样声明 unexport variable ...如演示样例一export variable value其等价于variable valueexport variable其等价于export variable : value其等价于variable : valueexport variable演示样例二export variable value其等价于variable valueexport variable假设你要传递全部的变量那么仅仅要一个export即可了。后面什么也不用跟表示传递全部的变量。须要注意的是有两个变量一个是SHELL一个是MAKEFLAGS这两个变量无论你是否export其总是要传递到下层Makefile中特别是MAKEFILES变量当中包括了make的參数信息假设我们运行“总控Makefile”时有make參数或是在上层Makefile中定义了这个变量那么MAKEFILES变量将会是这些參数并会传递到下层Makefile中这是一个系统级的环境变量。可是make命令中的有几个參数并不往下传递它们是“-C”,“-f”,“-h”“-o”和“-W”有关Makefile參数的细节将在后面说明假设你不想往下层传递參数那么你能够这样来subsystem:cd subdir $(MAKE) MAKEFLAGS假设你定义了环境变量MAKEFLAGS那么你得确信当中的选项是大家都会用到的假设当中有“-t”,“-n”,和“-q”參数那么将会有让你意想不到的结果也许会让你异常地恐慌。另一个在“嵌套运行”中比較实用的參数“-w”或是“--print-directory”会在make的过程中输出一些信息让你看到眼下的工作文件夹。比方假设我们的下级make文件夹是“/home/hchen/gnu/make”假设我们使用“make -w”来运行那么当进入该文件夹时我们会看到make: Entering directory /home/hchen/gnu/make.而在完毕下层make后离开文件夹时我们会看到make: Leaving directory /home/hchen/gnu/make当你使用“-C”參数来指定make下层Makefile时“-w”会被自己主动打开的。假设參数中有“-s”“--slient”或是“--no-print-directory”那么“-w”总是失效的。五、定义命令包假设Makefile中出现一些相同命令序列那么我们能够为这些相同的命令序列定义一个变量。定义这样的命令序列的语法以“define”開始以“endef”结束如define run-yaccyacc $(firstword $^)mv y.tab.c $endef这里“run-yacc”是这个命令包的名字其不要和Makefile中的变量重名。在“define”和“endef”中的两行就是命令序列。这个命令包中的第一个命令是运行Yacc程序由于Yacc程序总是生成“y.tab.c”的文件所以第二行的命令就是把这个文件改改名字。还是把这个命令包放到一个演示样例中来看看吧。foo.c : foo.y$(run-yacc)我们能够看见要使用这个命令包我们就好像使用变量一样。在这个命令包的使用中命令包“run-yacc”中的“$^”就是“foo.y”“$”就是“foo.c”有关这样的以“$”开头的特殊变量我们会在后面介绍make在运行命令包时命令包中的每一个命令会被依次独立运行。使用变量————在Makefile中的定义的变量就像是C/C语言中的宏一样他代表了一个文本字串在Makefile中运行的时候其会自己主动原模原样地展开在所使用的地方。其与C/C所不同的是你能够在Makefile中改变其值。在Makefile中变量能够使用在“目标”“依赖目标”“命令”或是Makefile的其他部分中。变量的命名字能够包括字符、数字下划线能够是数字开头但不应该含有“:”、“#”、“”或是空字符空格、回车等。变量是大写和小写敏感的“foo”、“Foo”和“FOO”是三个不同的变量名。传统的Makefile的变量名是全大写的命名方式但我推荐使用大写和小写搭配的变量名如MakeFlags。这样能够避免和系统的变量冲突而发生意外的事情。有一些变量是非常奇怪字串如“$”、“$”等这些是自己主动化变量我会在后面介绍。一、变量的基础变量在声明时须要给予初值而在使用时须要给在变量名前加上“$”符号但最好用小括号“”或是大括号“{}”把变量给包括起来。假设你要使用真实的“$”字符那么你须要用“$$”来表示。变量能够使用在很多地方如规则中的“目标”、“依赖”、“命令”以及新的变量中。先看一个样例objects program.o foo.o utils.oprogram : $(objects)cc -o program $(objects)$(objects) : defs.h变量会在使用它的地方精确地展开就像C/C中的宏一样比如foo cprog.o : prog.$(foo)$(foo)$(foo) -$(foo) prog.$(foo)展开后得到prog.o : prog.ccc -c prog.c当然千万不要在你的Makefile中这样干这里仅仅是举个样例来表明Makefile中的变量在使用处展开的真实样子。可见其就是一个“替代”的原理。另外给变量加上括号全然是为了更加安全地使用这个变量在上面的样例中假设你不想给变量加上括号那也能够但我还是强烈建议你给变量加上括号。二、变量中的变量在定义变量的值时我们能够使用其他变量来构造变量的值在Makefile中有两种方式来在用变量定义变量的值。先看第一种方式也就是简单的使用“”号在“”左側是变量右側是变量的值右側变量的值能够定义在文件的不论什么一处也就是说右側中的变量不一定非要是已定义好的值其也能够使用后面定义的值。如foo $(bar)bar $(ugh)ugh Huh?all:echo $(foo)我们运行“make all”将会打出变量$(foo)的值是“Huh?” $(foo)的值是$(bar)$(bar)的值是$(ugh)$(ugh)的值是“Huh?”可见变量是能够使用后面的变量来定义的。这个功能有好的地方也有不好的地方好的地方是我们能够把变量的真实值推到后面来定义如CFLAGS $(include_dirs) -Oinclude_dirs -Ifoo -Ibar当“CFLAGS”在命令中被展开时会是“-Ifoo -Ibar -O”。但这样的形式也有不好的地方那就是递归定义如CFLAGS $(CFLAGS) -O或A $(B)B $(A)这会让make陷入无限的变量展开过程中去当然我们的make是有能力检測这样的定义并会报错。还有就是假设在变量中使用函数那么这样的方式会让我们的make运行时非常慢更糟糕的是他会使用得两个make的函数“wildcard”和“shell”发生不可预知的错误。由于你不会知道这两个函数会被调用多少次。为了避免上面的这样的方法我们能够使用make中的另一种用变量来定义变量的方法。这样的方法使用的是“:”操作符如x : fooy : $(x) barx : later其等价于y : foo barx : later值得一提的是这样的方法前面的变量不能使用后面的变量仅仅能使用前面已定义好了的变量。假设是这样y : $(x) barx : foo那么y的值是“bar”而不是“foo bar”。上面都是一些比較简单的变量使用了让我们来看一个复杂的样例当中包括了make的函数、条件表达式和一个系统变量“MAKELEVEL”的使用ifeq (0,${MAKELEVEL})cur-dir : $(shell pwd)whoami : $(shell whoami)host-type : $(shell arch)MAKE : ${MAKE} host-type${host-type} whoami${whoami}endif关于条件表达式和函数我们在后面再说对于系统变量“MAKELEVEL”其意思是假设我们的make有一个嵌套运行的动作參见前面的“嵌套使用make”那么这个变量会记录了我们的当前Makefile的调用层数。以下再介绍两个定义变量时我们须要知道的请先看一个样例假设我们要定义一个变量其值是一个空格那么我们能够这样来nullstring :space : $(nullstring) # end of the linenullstring是一个Empty变量当中什么也没有而我们的space的值是一个空格。由于在操作符的右边是非常难描写叙述一个空格的这里採用的技术非常管用先用一个Empty变量来标明变量的值開始了而后面採用“#”凝视符来表示变量定义的终止这样我们能够定义出其值是一个空格的变量。请注意这里关于“#”的使用凝视符“#”的这样的特性值得我们注意假设我们这样定义一个变量dir : /foo/bar # directory to put the frobs indir这个变量的值是“/foo/bar”后面还跟了4个空格假设我们这样使用这样变量来指定别的文件夹——“$(dir)/file”那么就完蛋了。另一个比較实用的操作符是“?”先看演示样例FOO ? bar其含义是假设FOO没有被定义过那么变量FOO的值就是“bar”假设FOO先前被定义过那么这条语将什么也不做其等价于ifeq ($(origin FOO), undefined)FOO barendif三、变量高级使用方法这里介绍两种变量的高级使用方法第一种是变量值的替换。我们能够替换变量中的共同拥有的部分其格式是“$(var:ab)”或是“${var:ab}”其意思是把变量“var”中全部以“a”字串“结尾”的“a”替换成“b”字串。这里的“结尾”意思是“空格”或是“结束符”。还是看一个演示样例吧foo : a.o b.o c.obar : $(foo:.o.c)这个演示样例中我们先定义了一个“$(foo)”变量而第二行的意思是把“$(foo)”中全部以“.o”字串“结尾”全部替换成“.c”所以我们的“$(bar)”的值就是“a.c b.c c.c”。第二种变量替换的技术是以“静态模式”參见前面章节定义的如foo : a.o b.o c.obar : $(foo:%.o%.c)这依赖于被替换字串中的有相同的模式模式中必须包括一个“%”字符这个样例相同让$(bar)变量的值为“a.c b.c c.c”。 第二种高级使用方法是——“把变量的值再当成变量”。先看一个样例x yy za : $($(x))在这个样例中$(x)的值是“y”所以$($(x))就是$(y)于是$(a)的值就是“z”。注意是“xy”而不是“x$(y)”我们还能够使用很多其他的层次x yy zz ua : $($($(x)))这里的$(a)的值是“u”相关的推导留给读者自己去做吧。让我们再复杂一点使用上“在变量定义中使用变量”的第一个方式来看一个样例x $(y)y zz Helloa : $($(x))这里的$($(x))被替换成了$($(y))由于$(y)值是“z”所以终于结果是a:$(z)也就是“Hello”。再复杂一点我们再加上函数x variable1variable2 : Helloy $(subst 1,2,$(x))z ya : $($($(z)))这个样例中“$($($(z)))”扩展为“$($(y))”而其再次被扩展为“$($(subst 1,2,$(x)))”。$(x)的值是“variable1”subst函数把“variable1”中的全部“1”字串替换成“2”字串于是“variable1”变成“variable2”再取其值所以终于$(a)的值就是$(variable2)的值——“Hello”。喔好不easy在这样的方式中或要能够使用多个变量来组成一个变量的名字然后再取其值first_second Helloa firstb secondall $($a_$b)这里的“$a_$b”组成了“first_second”于是$(all)的值就是“Hello”。再来看看结合第一种技术的样例a_objects : a.o b.o c.o1_objects : 1.o 2.o 3.osources : $($(a1)_objects:.o.c)这个样例中假设$(a1)的值是“a”的话那么$(sources)的值就是“a.c b.c c.c”假设$(a1)的值是“1”那么$(sources)的值是“1.c 2.c 3.c”。再来看一个这样的技术和“函数”与“条件语句”一同使用的样例ifdef do_sortfunc : sortelsefunc : stripendifbar : a d b g q cfoo : $($(func) $(bar))这个演示样例中假设定义了“do_sort”那么foo : $(sort a d b g q c)于是$(foo)的值就是“a b c d g q”而假设未定义“do_sort”那么foo : $(sort a d b g q c)调用的就是strip函数。当然“把变量的值再当成变量”这样的技术相同能够用在操作符的左边dir foo$(dir)_sources : $(wildcard $(dir)/*.c)define $(dir)_printlpr $($(dir)_sources)endef这个样例中定义了三个变量“dir”“foo_sources”和“foo_print”。四、追加变量值我们能够使用“”操作符给变量追加值如objects main.o foo.o bar.o utils.oobjects another.o于是我们的$(objects)值变成“main.o foo.o bar.o utils.o another.o”another.o被追加进去了使用“”操作符能够模拟为以下的这样的样例objects main.o foo.o bar.o utils.oobjects : $(objects) another.o所不同的是用“”更为简洁。假设变量之前未定义过那么“”会自己主动变成“”假设前面有变量定义那么“”会继承于前次操作的赋值符。假设前一次的是“:”那么“”会以“:”作为其赋值符如variable : valuevariable more等价于variable : valuevariable : $(variable) more但假设是这样的情况 variable valuevariable more由于前次的赋值符是“”所以“”也会以“”来做为赋值那么岂不会发生变量的递补归定义这是非常不好的所以make会自己主动为我们解决问题我们不必操心这个问题。五、override 指示符假设有变量是通常make的命令行參数设置的那么Makefile中对这个变量的赋值会被忽略。假设你想在Makefile中设置这类參数的值那么你能够使用“override”指示符。其语法是override variable valueoverride variable : value当然你还能够追加override variable more text对于多行的变量定义我们用define指示符在define指示符前也相同能够使用ovveride指示符如override define foobarendef六、多行变量另一种设置变量值的方法是使用definekeyword。使用definekeyword设置变量的值能够有换行这有利于定义一系列的命令前面我们讲过“命令包”的技术就是利用这个keyword。define指示符后面跟的是变量的名字而重起一行定义变量的值定义是以endefkeyword结束。其工作方式和“”操作符一样。变量的值能够包括函数、命令、文字或是其他变量。由于命令须要以[Tab]键开头所以假设你用define定义的命令变量中没有以[Tab]键开头那么make就不会把其觉得是命令。以下的这个演示样例展示了define的使用方法define two-linesecho fooecho $(bar)endef七、环境变量make运行时的系统环境变量能够在make開始运行时被加载到Makefile文件里可是假设Makefile中已定义了这个变量或是这个变量由make命令行带入那么系统的环境变量的值将被覆盖。假设make指定了“-e”參数那么系统环境变量将覆盖Makefile中定义的变量因此假设我们在环境变量中设置了“CFLAGS”环境变量那么我们就能够在全部的Makefile中使用这个变量了。这对于我们使用统一的编译參数有比較大的优点。假设Makefile中定义了CFLAGS那么则会使用Makefile中的这个变量假设未定义则使用系统环境变量的值一个共性和个性的统一非常像“全局变量”和“局部变量”的特性。当make嵌套调用时參见前面的“嵌套调用”章节上层Makefile中定义的变量会以系统环境变量的方式传递到下层的Makefile中。当然默认情况下仅仅有通过命令行设置的变量会被传递。而定义在文件里的变量假设要向下层Makefile传递则须要使用exprotkeyword来声明。參见前面章节当然我并不推荐把很多的变量都定义在系统环境中这样在我们运行不用的Makefile时拥有的是同一套系统变量这可能会带来很多其他的麻烦。八、目标变量前面我们所讲的在Makefile中定义的变量都是“全局变量”在整个文件我们都能够訪问这些变量。当然“自己主动化变量”除外如“$”等这样的类量的自己主动化变量就属于“规则型变量”这样的变量的值依赖于规则的目标和依赖目标的定义。当然我样相同能够为某个目标设置局部变量这样的变量被称为“Target-specific Variable”它能够和“全局变量”同名由于它的作用范围仅仅在这条规则以及连带规则中所以其值也仅仅在作用范围内有效。而不会影响规则链以外的全局变量的值。其语法是target ... : variable-assignmenttarget ... : overide variable-assignmentvariable-assignment能够是前面讲过的各种赋值表达式如“”、“:”、“”或是“”。第二个语法是针对于make命令行带入的变量或是系统环境变量。这个特性非常的实用当我们设置了这样一个变量这个变量会作用到由这个目标所引发的全部的规则中去。如prog : CFLAGS -gprog : prog.o foo.o bar.o$(CC) $(CFLAGS) prog.o foo.o bar.oprog.o : prog.c$(CC) $(CFLAGS) prog.cfoo.o : foo.c$(CC) $(CFLAGS) foo.cbar.o : bar.c$(CC) $(CFLAGS) bar.c在这个演示样例中无论全局的$(CFLAGS)的值是什么在prog目标以及其所引发的全部规则中prog.o foo.o bar.o的规则$(CFLAGS)的值都是“-g”九、模式变量在GNU的make中还支持模式变量Pattern-specific Variable通过上面的目标变量中我们知道变量能够定义在某个目标上。模式变量的优点就是我们能够给定一种“模式”能够把变量定义在符合这样的模式的全部目标上。我们知道make的“模式”通常是至少含有一个“%”的所以我们能够以例如以下方式给全部以[.o]结尾的目标定义目标变量%.o : CFLAGS -O相同模式变量的语法和“目标变量”一样pattern ... : variable-assignmentpattern ... : override variable-assignmentoverride相同是针对于系统环境传入的变量或是make命令行指定的变量。 使用条件推断——————使用条件推断能够让make依据运行时的不同情况选择不同的运行分支。条件表达式能够是比較变量的值或是比較变量和常量的值。一、演示样例以下的样例推断$(CC)变量是否“gcc”假设是的话则使用GNU函数编译目标。libs_for_gcc -lgnunormal_libs foo: $(objects)ifeq ($(CC),gcc)$(CC) -o foo $(objects) $(libs_for_gcc)else$(CC) -o foo $(objects) $(normal_libs)endif可见在上面演示样例的这个规则中目标“foo”能够依据变量“$(CC)”值来选取不同的函数库来编译程序。我们能够从上面的演示样例中看到三个keywordifeq、else和endif。ifeq的意思表示条件语句的開始并指定一个条件表达式表达式包括两个參数以逗号分隔表达式以圆括号括起。else表示条件表达式为假的情况。endif表示一个条件语句的结束不论什么一个条件表达式都应该以endif结束。当我们的变量$(CC)值是“gcc”时目标foo的规则是foo: $(objects)$(CC) -o foo $(objects) $(libs_for_gcc)而当我们的变量$(CC)值不是“gcc”时比方“cc”目标foo的规则是foo: $(objects)$(CC) -o foo $(objects) $(normal_libs)当然我们还能够把上面的那个样例写得更简洁一些libs_for_gcc -lgnunormal_libs ifeq ($(CC),gcc)libs$(libs_for_gcc)elselibs$(normal_libs)endiffoo: $(objects)$(CC) -o foo $(objects) $(libs)二、语法条件表达式的语法为conditional-directivetext-if-trueendif以及conditional-directivetext-if-trueelsetext-if-falseendif其中conditional-directive表示条件keyword如“ifeq”。这个keyword有四个。第一个是我们前面所见过的“ifeq”ifeq (arg1, arg2 ) ifeq arg1 arg2 ifeq arg1 arg2 ifeq arg1 arg2 ifeq arg1 arg2 比較參数“arg1”和“arg2”的值是否同样。当然參数中我们还能够使用make的函数。如ifeq ($(strip $(foo)),)text-if-emptyendif这个演示样例中使用了“strip”函数假设这个函数的返回值是空Empty那么text-if-empty就生效。第二个条件keyword是“ifneq”。语法是ifneq (arg1, arg2 ) ifneq arg1 arg2 ifneq arg1 arg2 ifneq arg1 arg2 ifneq arg1 arg2 其比較參数“arg1”和“arg2”的值是否同样假设不同则为真。和“ifeq”类似。第三个条件keyword是“ifdef”。语法是ifdef variable-name 假设变量variable-name的值非空那到表达式为真。否则表达式为假。当然variable-name同样能够是一个函数的返回值。注意ifdef仅仅是測试一个变量是否有值其并不会把变量扩展到当前位置。还是来看两个样例演示样例一bar foo $(bar)ifdef foofrobozz yeselsefrobozz noendif演示样例二foo ifdef foofrobozz yeselsefrobozz noendif第一个样例中“$(frobozz)”值是“yes”第二个则是“no”。第四个条件keyword是“ifndef”。其语法是ifndef variable-name这个我就不多说了和“ifdef”是相反的意思。在conditional-directive这一行上多余的空格是被同意的可是不能以[Tab]键做为開始不然就被觉得是命令。而凝视符“#”同样也是安全的。“else”和“endif”也一样仅仅要不是以[Tab]键開始即可了。特别注意的是make是在读取Makefile时就计算条件表达式的值并依据条件表达式的值来选择语句所以你最好不要把自己主动化变量如“$”等放入条件表达式中由于自己主动化变量是在运行时才有的。而且为了避免混乱make不同意把整个条件语句分成两部分放在不同的文件里。使用函数————在Makefile中能够使用函数来处理变量从而让我们的命令或是规则更为的灵活和具有智能。make所支持的函数也不算非常多只是已经足够我们的操作了。函数调用后函数的返回值能够当做变量来使用。一、函数的调用语法函数调用非常像变量的使用也是以“$”来标识的其语法例如以下$(function arguments )或是${function arguments}这里function就是函数名make支持的函数不多。arguments是函数的參数參数间以逗号“,”分隔而函数名和參数之间以“空格”分隔。函数调用以“$”开头以圆括号或花括号把函数名和參数括起。感觉非常像一个变量是不是函数中的參数能够使用变量为了风格的统一函数和变量的括号最好一样如使用“$(subst a,b,$(x))”这样的形式而不是“$(subst a,b,${x})”的形式。由于统一会更清楚也会降低一些不必要的麻烦。还是来看一个演示样例comma: ,empty:space: $(empty) $(empty)foo: a b cbar: $(subst $(space),$(comma),$(foo))在这个演示样例中$(comma)的值是一个逗号。$(space)使用了$(empty)定义了一个空格$(foo)的值是“a b c”$(bar)的定义用调用了函数“subst”这是一个替换函数这个函数有三个參数第一个參数是被替换字串第二个參数是替换字串第三个參数是替换操作作用的字串。这个函数也就是把$(foo)中的空格替换成逗号所以$(bar)的值是“a,b,c”。二、字符串处理函数$(subst from,to,text ) 名称字符串替换函数——subst。功能把字串text中的from字符串替换成to。返回函数返回被替换过后的字符串。演示样例$(subst ee,EE,feet on the street)把“feet on the street”中的“ee”替换成“EE”返回结果是“fEEt on the strEEt”。$(patsubst pattern,replacement,text ) 名称模式字符串替换函数——patsubst。功能查找text中的单词单词以“空格”、“Tab”或“回车”“换行”分隔是否符合模式pattern假设匹配的话则以replacement替换。这里pattern能够包括通配符“%”表示随意长度的字串。假设replacement中也包括“%”那么replacement中的这个“%”将是pattern中的那个“%”所代表的字串。能够用“/”来转义以“/%”来表示真实含义的“%”字符返回函数返回被替换过后的字符串。演示样例$(patsubst %.c,%.o,x.c.c bar.c)把字串“x.c.c bar.c”符合模式[%.c]的单词替换成[%.o]返回结果是“x.c.o bar.o”备注这和我们前面“变量章节”说过的相关知识有点类似。如“$(var:patternreplacement )”相当于“$(patsubst pattern,replacement,$(var))”而“$(var: suffixreplacement )”则相当于“$(patsubst %suffix,%replacement,$(var))”。比如有objects foo.o bar.o baz.o那么“$(objects:.o.c)”和“$(patsubst %.o,%.c,$(objects))”是一样的。$(strip string )名称去空格函数——strip。功能去掉string字串中开头和结尾的空字符。返回返回被去掉空格的字符串值。演示样例$(strip a b c )把字串“a b c ”去到开头和结尾的空格结果是“a b c”。$(findstring find,in )名称查找字符串函数——findstring。功能在字串in中查找find字串。返回假设找到那么返回find否则返回空字符串。演示样例$(findstring a,a b c)$(findstring a,b c)第一个函数返回“a”字符串第二个返回“”字符串空字符串$(filter pattern...,text )名称过滤函数——filter。功能以pattern模式过滤text字符串中的单词保留符合模式pattern的单词。能够有多个模式。返回返回符合模式pattern的字串。演示样例sources : foo.c bar.c baz.s ugh.hfoo: $(sources)cc $(filter %.c %.s,$(sources)) -o foo$(filter %.c %.s,$(sources))返回的值是“foo.c bar.c baz.s”。$(filter-out pattern...,text )名称反过滤函数——filter-out。功能以pattern模式过滤text字符串中的单词去除符合模式pattern的单词。能够有多个模式。返回返回不符合模式pattern的字串。演示样例objectsmain1.o foo.o main2.o bar.omainsmain1.o main2.o$(filter-out $(mains),$(objects)) 返回值是“foo.o bar.o”。$(sort list )名称排序函数——sort。功能给字符串list中的单词排序升序。返回返回排序后的字符串。演示样例$(sort foo bar lose)返回“bar foo lose” 。备注sort函数会去掉list中同样的单词。$(word n,text )名称取单词函数——word。功能取字符串text中第n个单词。从一開始返回返回字符串text中第n个单词。假设n比text中的单词数要大那么返回空字符串。演示样例$(word 2, foo bar baz)返回值是“bar”。$(wordlist s,e,text ) 名称取单词串函数——wordlist。功能从字符串text中取从s開始到e的单词串。s和e是一个数字。返回返回字符串text中从s到e的单词字串。假设s比text中的单词数要大那么返回空字符串。假设e大于text的单词数那么返回从s開始到text结束的单词串。演示样例 $(wordlist 2, 3, foo bar baz)返回值是“bar baz”。$(words text )名称单词个数统计函数——words。功能统计text中字符串中的单词个数。返回返回text中的单词数。演示样例$(words, foo bar baz)返回值是“3”。备注假设我们要取text中最后的一个单词我们能够这样$(word $(words text ),text )。$(firstword text )名称首单词函数——firstword。功能取字符串text中的第一个单词。返回返回字符串text的第一个单词。演示样例$(firstword foo bar)返回值是“foo”。备注这个函数能够用word函数来实现$(word 1,text )。以上是全部的字符串操作函数假设搭配混合使用能够完毕比較复杂的功能。这里举一个现实中应用的样例。我们知道make使用“VPATH”变量来指定“依赖文件”的搜索路径。于是我们能够利用这个搜索路径来指定编译器对头文件的搜索路径參数CFLAGS如override CFLAGS $(patsubst %,-I%,$(subst :, ,$(VPATH)))假设我们的“$(VPATH)”值是“src:../headers”那么“$(patsubst %,-I%,$(subst :, ,$(VPATH)))”将返回“-Isrc -I../headers”这正是cc或gcc搜索头文件路径的參数。三、文件名称操作函数以下我们要介绍的函数主要是处理文件名称的。每一个函数的參数字符串都会被当做一个或是一系列的文件名称来对待。$(dir names... ) 名称取文件夹函数——dir。功能从文件名称序列names中取出文件夹部分。文件夹部分是指最后一个反斜杠“/”之前的部分。假设没有反斜杠那么返回“./”。返回返回文件名称序列names的文件夹部分。演示样例 $(dir src/foo.c hacks)返回值是“src/ ./”。$(notdir names... ) 名称取文件函数——notdir。功能从文件名称序列names中取出非文件夹部分。非文件夹部分是指最后一个反斜杠“/”之后的部分。返回返回文件名称序列names的非文件夹部分。演示样例 $(notdir src/foo.c hacks)返回值是“foo.c hacks”。$(suffix names... ) 名称取后缀函数——suffix。功能从文件名称序列names中取出各个文件名称的后缀。返回返回文件名称序列names的后缀序列假设文件没有后缀则返回空字串。演示样例$(suffix src/foo.c src-1.0/bar.c hacks)返回值是“.c .c”。$(basename names... )名称取前缀函数——basename。功能从文件名称序列names中取出各个文件名称的前缀部分。返回返回文件名称序列names的前缀序列假设文件没有前缀则返回空字串。演示样例$(basename src/foo.c src-1.0/bar.c hacks)返回值是“src/foo src-1.0/bar hacks”。$(addsuffix suffix,names... ) 名称加后缀函数——addsuffix。功能把后缀suffix加到names中的每一个单词后面。返回返回加过后缀的文件名称序列。演示样例$(addsuffix .c,foo bar)返回值是“foo.c bar.c”。$(addprefix prefix,names... ) 名称加前缀函数——addprefix。功能把前缀prefix加到names中的每一个单词后面。返回返回加过前缀的文件名称序列。演示样例$(addprefix src/,foo bar)返回值是“src/foo src/bar”。$(join list1,list2 )名称连接函数——join。功能把list2中的单词相应地加到list1的单词后面。假设list1的单词个数要比list2的多那么list1中的多出来的单词将保持原样。假设list2的单词个数要比list1多那么list2多出来的单词将被复制到list2中。返回返回连接过后的字符串。演示样例$(join aaa bbb , 111 222 333)返回值是“aaa111 bbb222 333”。四、foreach 函数foreach函数和别的函数非常的不一样。由于这个函数是用来做循环用的Makefile中的foreach函数差点儿是仿照于Unix标准Shell/bin/sh中的for语句或是C-Shell/bin/csh中的foreach语句而构建的。它的语法是$(foreach var,list,text )这个函数的意思是把參数list中的单词逐一取出放到參数var所指定的变量中然后再运行text所包括的表达式。每一次text会返回一个字符串循环过程中text的所返回的每一个字符串会以空格分隔最后当整个循环结束时text所返回的每一个字符串所组成的整个字符串以空格分隔将会是foreach函数的返回值。所以var最好是一个变量名list能够是一个表达式而text中通常会使用var这个參数来依次枚举list中的单词。举个样例names : a b c dfiles : $(foreach n,$(names),$(n).o)上面的样例中$(name)中的单词会被挨个取出并存到变量“n”中“$(n).o”每次依据“$(n)”计算出一个值这些值以空格分隔最后作为foreach函数的返回所以$(files)的值是“a.o b.o c.o d.o”。注意foreach中的var參数是一个暂时的局部变量foreach函数运行完后參数var的变量将不在作用其作用域仅仅在foreach函数其中。五、if 函数if函数非常像GNU的make所支持的条件语句——ifeq參见前面所述的章节if函数的语法是$(if condition,then-part ) 或是$(if condition,then-part,else-part )可见if函数能够包括“else”部分或是不含。即if函数的參数能够是两个也能够是三个。condition參数是if的表达式假设其返回的为非空字符串那么这个表达式就相当于返回真于是then-part会被计算否则else-part会被计算。而if函数的返回值是假设condition为真非空字符串那个then-part会是整个函数的返回值假设condition为假空字符串那么else-part会是整个函数的返回值此时假设else-part没有被定义那么整个函数返回空字串。所以then-part和else-part仅仅会有一个被计算。六、call函数call函数是唯一一个能够用来创建新的參数化的函数。你能够写一个非常复杂的表达式这个表达式中你能够定义很多參数然后你能够用call函数来向这个表达式传递參数。其语法是$(call expression,parm1,parm2,parm3...)当make运行这个函数时expression參数中的变量如$(1)$(2)$(3)等会被參数parm1parm2parm3依次代替。而expression的返回值就是call函数的返回值。比如reverse $(1) $(2)foo $(call reverse,a,b)那么foo的值就是“a b”。当然參数的次序是能够自己定义的不一定是顺序的如reverse $(2) $(1)foo $(call reverse,a,b)此时的foo的值就是“b a”。七、origin函数origin函数不像其他的函数他并不操作变量的值他仅仅是告诉你你的这个变量是哪里来的其语法是$(origin variable )注意variable是变量的名字不应该是引用。所以你最好不要在variable中使用“$”字符。Origin函数会以其返回值来告诉你这个变量的“出生情况”以下是origin函数的返回值:“undefined”假设variable从来未定义过origin函数返回这个值“undefined”。“default”假设variable是一个默认的定义比方“CC”这个变量这样的变量我们将在后面讲述。“environment”假设variable是一个环境变量而且当Makefile被运行时“-e”參数没有被打开。“file”假设variable这个变量被定义在Makefile中。“command line”假设variable这个变量是被命令行定义的。“override”假设variable是被override指示符又一次定义的。“automatic”假设variable是一个命令运行中的自己主动化变量。关于自己主动化变量将在后面讲述。这些信息对于我们编写Makefile是非常实用的比如假设我们有一个Makefile其包了一个定义文件Make.def在Make.def中定义了一个变量“bletch”而我们的环境中也有一个环境变量“bletch”此时我们想推断一下假设变量来源于环境那么我们就把之重定义了假设来源于Make.def或是命令行等非环境的那么我们就不又一次定义它。于是在我们的Makefile中我们能够这样写ifdef bletchifeq $(origin bletch) environmentbletch barf, gag, etc.endifendif当然你或许会说使用overridekeyword不就能够又一次定义环境中的变量了吗为什么须要使用这样的步骤是的我们用override是能够达到这样的效果可是override过于粗暴它同一时候会把从命令行定义的变量也覆盖了而我们仅仅想又一次定义环境传来的而不想又一次定义命令行传来的。八、shell函数shell函数也不像其他的函数。顾名思义它的參数应该就是操作系统Shell的命令。它和反引號“”是同样的功能。这就是说shell函数把运行操作系统命令后的输出作为函数返回。于是我们能够用操作系统命令以及字符串处理命令awksed等等命令来生成一个变量如contents : $(shell cat foo)files : $(shell echo *.c)注意这个函数会新生成一个Shell程序来运行命令所以你要注意其运行性能假设你的Makefile中有一些比較复杂的规则并大量使用了这个函数那么对于你的系统性能是有害的。特别是Makefile的隐晦的规则可能会让你的shell函数运行的次数比你想像的多得多。九、控制make的函数make提供了一些函数来控制make的运行。通常你须要检測一些运行Makefile时的运行时信息而且依据这些信息来决定你是让make继续运行还是停止。$(error text ... )产生一个致命的错误text ...是错误信息。注意error函数不会在一被使用就会产生错误信息所以假设你把其定义在某个变量中并在兴许的脚本中使用这个变量那么也是能够的。比如演示样例一ifdef ERROR_001$(error error is $(ERROR_001))endif演示样例二ERR $(error found an error!).PHONY: errerr: ; $(ERR)演示样例一会在变量ERROR_001定义了后运行时产生error调用而演示样例二则在文件夹err被运行时才发生error调用。$(warning text ... )这个函数非常像error函数仅仅是它并不会让make退出仅仅是输出一段警告信息而make继续运行。make 的运行——————一般来说最简单的就是直接在命令行下输入make命令make命令会找当前文件夹的makefile来运行一切都是自己主动的。但也有时你或许仅仅想让make重编译某些文件而不是整个project而又有的时候你有几套编译规则你想在不同的时候使用不同的编译规则等等。本章节就是讲述怎样使用make命令的。一、make的退出码make命令运行后有三个退出码0 —— 表示成功运行。1 —— 假设make运行时出现不论什么错误其返回1。2 —— 假设你使用了make的“-q”选项而且make使得一些目标不须要更新那么返回2。Make的相关參数我们会在兴许章节中讲述。二、指定Makefile前面我们说过GNU make找寻默认的Makefile的规则是在当前文件夹下依次找三个文件——“GNUmakefile”、“makefile”和“Makefile”。其按顺序找这三个文件一旦找到就開始读取这个文件并运行。当前我们也能够给make命令指定一个特殊名字的Makefile。要达到这个功能我们要使用make的“-f”或是“--file”參数“--makefile”參数也行。比如我们有个makefile的名字是“hchen.mk”那么我们能够这样来让make来运行这个文件make –f hchen.mk假设在make的命令行是你不仅仅一次地使用了“-f”參数那么全部指定的makefile将会被连在一起传递给make运行。三、指定目标一般来说make的终于目标是makefile中的第一个目标而其他目标通常是由这个目标连带出来的。这是make的默认行为。当然一般来说你的makefile中的第一个目标是由很多个目标组成你能够指示make让其完毕你所指定的目标。要达到这一目的非常easy需在make命令后直接跟目标的名字就能够完毕如前面提到的“make clean”形式不论什么在makefile中的目标都能够被指定成终极目标可是除了以“-”打头或是包括了“”的目标由于有这些字符的目标会被解析成命令行參数或是变量。甚至没有被我们明白写出来的目标也能够成为make的终极目标也就是说仅仅要make能够找到其隐含规则推导规则那么这个隐含目标同样能够被指定成终极目标。有一个make的环境变量叫“MAKECMDGOALS”这个变量中会存放你所指定的终极目标的列表假设在命令行上你没有指定目标那么这个变量是空值。这个变量能够让你使用在一些比較特殊的情形下。比方以下的样例sources foo.c bar.cifneq ( $(MAKECMDGOALS),clean)include $(sources:.c.d)endif基于上面的这个样例仅仅要我们输入的命令不是“make clean”那么makefile会自己主动包括“foo.d”和“bar.d”这两个makefile。使用指定终极目标的方法能够非常方便地让我们编译我们的程序比如以下这个样例.PHONY: allall: prog1 prog2 prog3 prog4从这个样例中我们能够看到这个makefile中有四个须要编译的程序——“prog1” “prog2” “prog3”和 “prog4”我们能够使用“make all”命令来编译全部的目标假设把all置成第一个目标那么仅仅需运行“make”我们也能够使用“make prog2”来单独编译目标“prog2”。即然make能够指定全部makefile中的目标那么也包括“伪目标”于是我们能够依据这样的性质来让我们的makefile依据指定的不同的目标来完毕不同的事。在Unix世界中软件公布时特别是GNU这样的开源软件的公布时其makefile都包括了编译、安装、打包等功能。我们能够參照这样的规则来书写我们的makefile中的目标。“all”这个伪目标是全部目标的目标其功能通常是编译全部的目标。“clean”这个伪目标功能是删除全部被make创建的文件。“install”这个伪目标功能是安装已编译好的程序事实上就是把目标运行文件复制到指定的目标中去。“print”这个伪目标的功能是例出改变过的源文件。“tar”这个伪目标功能是把源程序打包备份。也就是一个tar文件。“dist”这个伪目标功能是创建一个压缩文件通常是把tar文件压成Z文件。或是gz文件。“TAGS”这个伪目标功能是更新全部的目标以备完整地重编译使用。“check”和“test”这两个伪目标一般用来測试makefile的流程。当然一个项目的makefile中也不一定要书写这样的目标这些东西都是GNU的东西可是我想GNU搞出这些东西一定有其可取之处等你的UNIX下的程序文件一多时你就会发现这些功能非常实用了这里仅仅只是是说明了假设你要书写这样的功能最好使用这样的名字命名你的目标这样规范一些规范的优点就是——不用解释大家都明白。而且假设你的makefile中有这些功能一是非常实用二是能够显得你的makefile非常专业不是那种刚開始学习的人的作品。四、检查规则有时候我们不想让我们的makefile中的规则运行起来我们仅仅想检查一下我们的命令或是运行的序列。于是我们能够使用make命令的下述參数“-n”“--just-print”“--dry-run”“--recon”不运行參数这些參数仅仅是打印命令无论目标是否更新把规则和连带规则下的命令打印出来但不运行这些參数对于我们调试makefile非常实用处。“-t”“--touch”这个參数的意思就是把目标文件的时间更新但不更改目标文件。也就是说make假装编译目标但不是真正的编译目标仅仅是把目标变成已编译过的状态。“-q”“--question”这个參数的行为是找目标的意思也就是说假设目标存在那么其什么也不会输出当然也不会运行编译假设目标不存在其会打印出一条出错信息。“-W file”“--what-iffile”“--assume-newfile”“--new-filefile”这个參数须要指定一个文件。通常是是源文件或依赖文件Make会依据规则推导来运行依赖于这个文件的命令一般来说能够和“-n”參数一同使用来查看这个依赖文件所发生的规则命令。另外一个非常有意思的使用方法是结合“-p”和“-v”来输出makefile被运行时的信息这个将在后面讲述。五、make的參数以下列举了全部GNU make 3.80版的參数定义。其他版本号和产商的make大同小异只是其他产商的make的具体參数还是请參考各自的产品文档。“-b”“-m”这两个參数的作用是忽略和其他版本号make的兼容性。“-B”“--always-make”觉得全部的目标都须要更新重编译。“-C dir”“--directorydir”指定读取makefile的文件夹。假设有多个“-C”參数make的解释是后面的路径曾经面的作为相对路径并以最后的文件夹作为被指定文件夹。如“make –C ~hchen/test –C prog”等价于“make –C ~hchen/test/prog”。“—debug[options]”输出make的调试信息。它有几种不同的级别可供选择假设没有參数那就是输出最简单的调试信息。以下是options的取值a —— 也就是all输出全部的调试信息。会非常的多b —— 也就是basic仅仅输出简单的调试信息。即输出不须要重编译的目标。v —— 也就是verbose在b选项的级别之上。输出的信息包括哪个makefile被解析不须要被重编译的依赖文件或是依赖目标等。i —— 也就是implicit输出所以的隐含规则。j —— 也就是jobs输出运行规则中命令的具体信息如命令的PID、返回码等。m —— 也就是makefile输出make读取makefile更新makefile运行makefile的信息。“-d”相当于“--debuga”。“-e”“--environment-overrides”指明环境变量的值覆盖makefile中定义的变量的值。“-ffile”“--filefile”“--makefilefile”指定须要运行的makefile。“-h”“--help”显示帮助信息。“-i”“--ignore-errors”在运行时忽略全部的错误。“-I dir”“--include-dirdir”指定一个被包括makefile的搜索目标。能够使用多个“-I”參数来指定多个文件夹。“-j [jobsnum]”“--jobs[jobsnum]”指同一时候运行命令的个数。假设没有这个參数make运行命令时能运行多少就运行多少。假设有一个以上的“-j”參数那么仅最后一个“-j”才是有效的。注意这个參数在MS-DOS中是没用的“-k”“--keep-going”出错也不停止运行。假设生成一个目标失败了那么依赖于其上的目标就不会被运行了。“-l load”“--load-average[load]”“—max-load[load]”指定make运行命令的负载。“-n”“--just-print”“--dry-run”“--recon”仅输出运行过程中的命令序列但并不运行。“-o file”“--old-filefile”“--assume-oldfile”不又一次生成的指定的file即使这个目标的依赖文件新于它。“-p”“--print-data-base”输出makefile中的全部数据包括全部的规则和变量。这个參数会让一个简单的makefile都会输出一堆信息。假设你仅仅是想输出信息而不想运行makefile你能够使用“make -qp”命令。假设你想查看运行makefile前的预设变量和规则你能够使用“make –p –f /dev/null”。这个參数输出的信息会包括着你的makefile文件的文件名称和行号所以用这个參数来调试你的makefile会是非常实用的特别是当你的环境变量非常复杂的时候。“-q”“--question”不运行命令也不输出。仅仅是检查所指定的目标是否须要更新。假设是0则说明要更新假设是2则说明有发生错误。“-r”“--no-builtin-rules”禁止make使用不论什么隐含规则。“-R”“--no-builtin-variabes”禁止make使用不论什么作用于变量上的隐含规则。“-s”“--silent”“--quiet”在命令运行时不输出命令的输出。“-S”“--no-keep-going”“--stop”取消“-k”选项的作用。由于有些时候make的选项是从环境变量“MAKEFLAGS”中继承下来的。所以你能够在命令行中使用这个參数来让环境变量中的“-k”选项失效。“-t”“--touch”相当于UNIX的touch命令仅仅是把目标的改动日期变成最新的也就是阻止生成目标的命令运行。“-v”“--version”输出make程序的版本号、版权等关于make的信息。“-w”“--print-directory”输出运行makefile之前和之后的信息。这个參数对于跟踪嵌套式调用make时非常实用。“--no-print-directory”禁止“-w”选项。“-W file”“--what-iffile”“--new-filefile”“--assume-filefile”假定目标file须要更新假设和“-n”选项使用那么这个參数会输出该目标更新时的运行动作。假设没有“-n”那么就像运行UNIX的“touch”命令一样使得file的改动时间为当前时间。“--warn-undefined-variables”仅仅要make发现有未定义的变量那么就输出警告信息。 隐含规则————在我们使用Makefile时有一些我们会经常使用而且使用频率非常高的东西比方我们编译C/C的源程序为中间目标文件Unix下是[.o]文件Windows下是[.obj]文件。本章讲述的就是一些在Makefile中的“隐含的”早先约定了的不须要我们再写出来的规则。“隐含规则”也就是一种惯例make会依照这样的“惯例”心照不喧地来运行那怕我们的Makefile中没有书写这样的规则。比如把[.c]文件编译成[.o]文件这一规则你根本就不用写出来make会自己主动推导出这样的规则并生成我们须要的[.o]文件。“隐含规则”会使用一些我们系统变量我们能够改变这些系统变量的值来定制隐含规则的运行时的參数。如系统变量“CFLAGS”能够控制编译时的编译器參数。我们还能够通过“模式规则”的方式写下自己的隐含规则。用“后缀规则”来定义隐含规则会有很多的限制。使用“模式规则”会更回得智能和清楚但“后缀规则”能够用来保证我们Makefile的兼容性。我们了解了“隐含规则”能够让其为我们更好的服务也会让我们知道一些“约定俗成”了的东西而不至于使得我们在运行Makefile时出现一些我们认为莫名其妙的东西。当然不论什么事物都是矛盾的水能载舟亦可覆舟所以有时候“隐含规则”也会给我们造成不小的麻烦。仅仅有了解了它我们才干更好地使用它。一、使用隐含规则假设要使用隐含规则生成你须要的目标你所须要做的就是不要写出这个目标的规则。那么make会试图去自己主动推导产生这个目标的规则和命令假设make能够自己主动推导生成这个目标的规则和命令那么这个行为就是隐含规则的自己主动推导。当然隐含规则是make事先约定好的一些东西。比如我们有以下的一个Makefilefoo : foo.o bar.occ –o foo foo.o bar.o $(CFLAGS) $(LDFLAGS)我们能够注意到这个Makefile中并没有写下怎样生成foo.o和bar.o这两目标的规则和命令。由于make的“隐含规则”功能会自己主动为我们自己主动去推导这两个目标的依赖目标和生成命令。make会在自己的“隐含规则”库中寻找能够用的规则假设找到那么就会使用。假设找不到那么就会报错。在上面的那个样例中make调用的隐含规则是把[.o]的目标的依赖文件置成[.c]并使用C的编译命令“cc –c $(CFLAGS) [.c]”来生成[.o]的目标。也就是说我们全然没有必要写下以下的两条规则foo.o : foo.ccc –c foo.c $(CFLAGS)bar.o : bar.ccc –c bar.c $(CFLAGS)由于这已经是“约定”好了的事了make和我们约定好了用C编译器“cc”生成[.o]文件的规则这就是隐含规则。当然假设我们为[.o]文件书写了自己的规则那么make就不会自己主动推导并调用隐含规则它会依照我们写好的规则忠实地运行。还有在make的“隐含规则库”中每一条隐含规则都在库中有其顺序越靠前的则是越被经常使用的所以这会导致我们有些时候即使我们显示地指定了目标依赖make也不会管。如以下这条规则没有命令foo.o : foo.p依赖文件“foo.p”Pascal程序的源文件有可能变得没有意义。假设文件夹下存在了“foo.c”文件那么我们的隐含规则一样会生效并会通过“foo.c”调用C的编译器生成foo.o文件。由于在隐含规则中Pascal的规则出如今C的规则之后所以make找到能够生成foo.o的C的规则就不再寻找下一条规则了。假设你确实不希望不论什么隐含规则推导那么你就不要仅仅写出“依赖规则”而不写命令。二、隐含规则一览这里我们将讲述全部预先设置也就是make内建的隐含规则假设我们不明白地写下规则那么make就会在这些规则中寻找所须要规则和命令。当然我们也能够使用make的參数“-r”或“--no-builtin-rules”选项来取消全部的预设置的隐含规则。当然即使是我们指定了“-r”參数某些隐含规则还是会生效由于有很多的隐含规则都是使用了“后缀规则”来定义的所以仅仅要隐含规则中有“后缀列表”也就一系统定义在目标.SUFFIXES的依赖目标那么隐含规则就会生效。默认的后缀列表是.out, .a, .ln, .o, .c, .cc, .C, .p, .f, .F, .r, .y, .l, .s, .S, .mod, .sym, .def, .h, .info, .dvi, .tex, .texinfo, .texi, .txinfo, .w, .ch .web, .sh, .elc, .el。具体的细节我们会在后面讲述。还是先来看一看经常使用的隐含规则吧。1、编译C程序的隐含规则。“n.o”的目标的依赖目标会自己主动推导为“n.c”而且其生成命令是“$(CC) –c $(CPPFLAGS) $(CFLAGS)”2、编译C程序的隐含规则。“n.o”的目标的依赖目标会自己主动推导为“n.cc”或是“n.C”而且其生成命令是“$(CXX) –c $(CPPFLAGS) $(CFLAGS)”。建议使用“.cc”作为C源文件的后缀而不是“.C”3、编译Pascal程序的隐含规则。“n.o”的目标的依赖目标会自己主动推导为“n.p”而且其生成命令是“$(PC) –c $(PFLAGS)”。4、编译Fortran/Ratfor程序的隐含规则。“n.o”的目标的依赖目标会自己主动推导为“n.r”或“n.F”或“n.f”而且其生成命令是:“.f” “$(FC) –c $(FFLAGS)”“.F” “$(FC) –c $(FFLAGS) $(CPPFLAGS)”“.f” “$(FC) –c $(FFLAGS) $(RFLAGS)”5、预处理Fortran/Ratfor程序的隐含规则。“n.f”的目标的依赖目标会自己主动推导为“n.r”或“n.F”。这个规则仅仅是转换Ratfor或有预处理的Fortran程序到一个标准的Fortran程序。其使用的命令是“.F” “$(FC) –F $(CPPFLAGS) $(FFLAGS)”“.r” “$(FC) –F $(FFLAGS) $(RFLAGS)”6、编译Modula-2程序的隐含规则。“n.sym”的目标的依赖目标会自己主动推导为“n.def”而且其生成命令是“$(M2C) $(M2FLAGS) $(DEFFLAGS)”。“n.o” 的目标的依赖目标会自己主动推导为“n.mod”而且其生成命令是“$(M2C) $(M2FLAGS) $(MODFLAGS)”。7、汇编和汇编预处理的隐含规则。“n.o” 的目标的依赖目标会自己主动推导为“n.s”默认使用编译品“as”而且其生成命令是“$(AS) $(ASFLAGS)”。“n.s” 的目标的依赖目标会自己主动推导为“n.S”默认使用C预编译器“cpp”而且其生成命令是“$(AS) $(ASFLAGS)”。8、链接Object文件的隐含规则。“n”目标依赖于“n.o”通过运行C的编译器来运行链接程序生成通常是“ld”其生成命令是“$(CC) $(LDFLAGS) n.o $(LOADLIBES) $(LDLIBS)”。这个规则对于仅仅有一个源文件的project有效同一时候也对多个Object文件由不同的源文件生成的也有效。比如例如以下规则x : y.o z.o而且“x.c”、“y.c”和“z.c”都存在时隐含规则将运行例如以下命令cc -c x.c -o x.occ -c y.c -o y.occ -c z.c -o z.occ x.o y.o z.o -o xrm -f x.orm -f y.orm -f z.o假设没有一个源文件如上例中的x.c和你的目标名字如上例中的x相关联那么你最好写出自己的生成规则不然隐含规则会报错的。9、Yacc C程序时的隐含规则。“n.c”的依赖文件被自己主动推导为“n.y”Yacc生成的文件其生成命令是“$(YACC) $(YFALGS)”。“Yacc”是一个语法分析器关于其细节请查看相关资料10、Lex C程序时的隐含规则。“n.c”的依赖文件被自己主动推导为“n.l”Lex生成的文件其生成命令是“$(LEX) $(LFALGS)”。关于“Lex”的细节请查看相关资料11、Lex Ratfor程序时的隐含规则。“n.r”的依赖文件被自己主动推导为“n.l”Lex生成的文件其生成命令是“$(LEX) $(LFALGS)”。12、从C程序、Yacc文件或Lex文件创建Lint库的隐含规则。“n.ln” lint生成的文件的依赖文件被自己主动推导为“n.c”其生成命令是“$(LINT) $(LINTFALGS) $(CPPFLAGS) -i”。对于“n.y”和“n.l”也是相同的规则。三、隐含规则使用的变量在隐含规则中的命令中基本上都是使用了一些预先设置的变量。你能够在你的makefile中改变这些变量的值或是在make的命令行中传入这些值或是在你的环境变量中设置这些值不管怎么样仅仅要设置了这些特定的变量那么其就会对隐含规则起作用。当然你也能够利用make的“-R”或“--no–builtin-variables”參数来取消你所定义的变量对隐含规则的作用。比如第一条隐含规则——编译C程序的隐含规则的命令是“$(CC) –c $(CFLAGS) $(CPPFLAGS)”。Make默认的编译命令是“cc”假设你把变量“$(CC)”重定义成“gcc”把变量“$(CFLAGS)”重定义成“-g”那么隐含规则中的命令全部会以“gcc –c -g $(CPPFLAGS)”的样子来运行了。我们能够把隐含规则中使用的变量分成两种一种是命令相关的如“CC”一种是參数相的关如“CFLAGS”。以下是全部隐含规则中会用到的变量1、关于命令的变量。AR 函数库打包程序。默认命令是“ar”。 AS 汇编语言编译程序。默认命令是“as”。CC C语言编译程序。默认命令是“cc”。CXX C语言编译程序。默认命令是“g”。CO 从 RCS文件里扩展文件程序。默认命令是“co”。CPP C程序的预处理器输出是标准输出设备。默认命令是“$(CC) –E”。FC Fortran 和 Ratfor 的编译器和预处理程序。默认命令是“f77”。GET 从SCCS文件里扩展文件的程序。默认命令是“get”。 LEX Lex方法分析器程序针对于C或Ratfor。默认命令是“lex”。PC Pascal语言编译程序。默认命令是“pc”。YACC Yacc文法分析器针对于C程序。默认命令是“yacc”。YACCR Yacc文法分析器针对于Ratfor程序。默认命令是“yacc –r”。MAKEINFO 转换Texinfo源文件.texi到Info文件程序。默认命令是“makeinfo”。TEX 从TeX源文件创建TeX DVI文件的程序。默认命令是“tex”。TEXI2DVI 从Texinfo源文件创建军TeX DVI 文件的程序。默认命令是“texi2dvi”。WEAVE 转换Web到TeX的程序。默认命令是“weave”。CWEAVE 转换C Web 到 TeX的程序。默认命令是“cweave”。TANGLE 转换Web到Pascal语言的程序。默认命令是“tangle”。CTANGLE 转换C Web 到 C。默认命令是“ctangle”。RM 删除文件命令。默认命令是“rm –f”。2、关于命令參数的变量以下的这些变量都是相关上面的命令的參数。假设没有指明其默认值那么其默认值都是空。ARFLAGS 函数库打包程序AR命令的參数。默认值是“rv”。ASFLAGS 汇编语言编译器參数。当明显地调用“.s”或“.S”文件时。 CFLAGS C语言编译器參数。CXXFLAGS C语言编译器參数。COFLAGS RCS命令參数。 CPPFLAGS C预处理器參数。 C 和 Fortran 编译器也会用到。FFLAGS Fortran语言编译器參数。GFLAGS SCCS “get”程序參数。LDFLAGS 链接器參数。如“ld”LFLAGS Lex文法分析器參数。PFLAGS Pascal语言编译器參数。RFLAGS Ratfor 程序的Fortran 编译器參数。YFLAGS Yacc文法分析器參数。 四、隐含规则链有些时候一个目标可能被一系列的隐含规则所作用。比如一个[.o]的文件生成可能会是先被Yacc的[.y]文件先成[.c]然后再被C的编译器生成。我们把这一系列的隐含规则叫做“隐含规则链”。在上面的样例中假设文件[.c]存在那么就直接调用C的编译器的隐含规则假设没有[.c]文件但有一个[.y]文件那么Yacc的隐含规则会被调用生成[.c]文件然后再调用C编译的隐含规则最终由[.c]生成[.o]文件达到目标。我们把这样的[.c]的文件或是目标叫做中间目标。不管怎么样make会努力自己主动推导生成目标的一切方法不管中间目标有多少其都会执着地把全部的隐含规则和你书写的规则全部合起来分析努力达到目标所以有些时候可能会让你认为奇怪怎么我的目标会这样生成怎么我的makefile发疯了在默认情况下对于中间目标它和一般的目标有两个地方所不同第一个不同是除非中间的目标不存在才会引发中间规则。第二个不同的是仅仅要目标成功产生那么产生最终目标过程中所产生的中间目标文件会被以“rm -f”删除。通常一个被makefile指定成目标或是依赖目标的文件不能被当作中介。然而你能够明显地说明一个文件或是目标是中介目标你能够使用伪目标“.INTERMEDIATE”来强制声明。如.INTERMEDIATE mid 你也能够阻止make自己主动删除中间目标要做到这一点你能够使用伪目标“.SECONDARY”来强制声明如.SECONDARY : sec。你还能够把你的目标以模式的方式来指定如%.o成伪目标“.PRECIOUS”的依赖目标以保存被隐含规则所生成的中间文件。在“隐含规则链”中禁止同一个目标出现两次或两次以上这样一来就可防止在make自己主动推导时出现无限递归的情况。Make会优化一些特殊的隐含规则而不生成中间文件。如从文件“foo.c”生成目标程序“foo”按道理make会编译生成中间文件“foo.o”然后链接成“foo”但在实际情况下这一动作能够被一条“cc”的命令完毕cc –o foo foo.c于是优化过的规则就不会生成中间文件。五、定义模式规则你能够使用模式规则来定义一个隐含规则。一个模式规则就好像一个一般的规则仅仅是在规则中目标的定义须要有%字符。%的意思是表示一个或多个随意字符。在依赖目标中相同能够使用%仅仅是依赖目标中的%的取值取决于其目标。有一点须要注意的是%的展开发生在变量和函数的展开之后变量和函数的展开发生在make加载Makefile时而模式规则中的%则发生在运行时。1、模式规则介绍模式规则中至少在规则的目标定义中要包括%否则就是一般的规则。目标中的%定义表示对文件名称的匹配%表示长度随意的非空字符串。比如%.c表示以.c结尾的文件名称文件名称的长度至少为3而s.%.c则表示以s.开头.c结尾的文件名称文件名称的长度至少为5。假设%定义在目标中那么目标中的%的值决定了依赖目标中的%的值也就是说目标中的模式的%决定了依赖目标中%的样子。比如有一个模式规则例如以下%.o : %.c ; command ......其含义是指出了怎么从全部的[.c]文件生成对应的[.o]文件的规则。假设要生成的目标是a.o b.o那么%c就是a.c b.c。一旦依赖目标中的%模式被确定那么make会被要求去匹配当前文件夹下全部的文件名称一旦找到make就会规则下的命令所以在模式规则中目标可能会是多个的假设有模式匹配出多个目标make就会产生全部的模式目标此时make关心的是依赖的文件名称和生成目标的命令这两件事。2、模式规则演示样例以下这个样例表示了,把全部的[.c]文件都编译成[.o]文件.%.o : %.c$(CC) -c $(CFLAGS) $(CPPFLAGS) $ -o $当中$表示全部的目标的挨个值$表示了全部依赖目标的挨个值。这些奇怪的变量我们叫自己主动化变量后面会具体讲述。以下的这个样例中有两个目标是模式的%.tab.c %.tab.h: %.ybison -d $这条规则告诉make把全部的[.y]文件都以bison -d n.y运行然后生成n.tab.c和n.tab.h文件。当中n表示一个随意字符串。假设我们的运行程序foo依赖于文件parse.tab.o和scan.o而且文件scan.o依赖于文件parse.tab.h假设parse.y文件被更新了那么依据上述的规则bison -d parse.y就会被运行一次于是parse.tab.o和scan.o的依赖文件就齐了。假设parse.tab.o由parse.tab.c生成和scan.o由scan.c生成而foo由parse.tab.o和scan.o链接生成而且foo和其[.o]文件的依赖关系也写好那么全部的目标都会得到满足3、自己主动化变量在上述的模式规则中目标和依赖文件都是一系例的文件那么我们怎样书写一个命令来完毕从不同的依赖文件生成对应的目标由于在每一次的对模式规则的解析时都会是不同的目标和依赖文件。自己主动化变量就是完毕这个功能的。在前面我们已经对自己主动化变量有所提涉相信你看到这里已对它有一个感性认识了。所谓自己主动化变量就是这样的变量会把模式中所定义的一系列的文件自己主动地挨个取出直至全部的符合模式的文件都取完了。这样的自己主动化变量仅仅应出如今规则的命令中。以下是全部的自己主动化变量及其说明$表示规则中的目标文件集。在模式规则中假设有多个目标那么$就是匹配于目标中模式定义的集合。$%仅当目标是函数库文件里表示规则中的目标成员名。比如假设一个目标是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所不能识别的那么$*就是空值。当你希望仅仅对更新过的依赖文件进行操作时$?在显式规则中非常实用比如假设有一个函数库文件叫lib其由其他几个object文件更新。那么把object文件打包的比較有效率的Makefile规则是lib : foo.o bar.o lose.o win.oar r lib $?在上述所列出来的自己主动量变量中。四个变量$、$、$%、$*在扩展时仅仅会有一个文件而另三个的值是一个文件列表。这七个自己主动化变量还能够取得文件的文件夹名或是在当前文件夹下的符合模式的文件名称仅仅须要搭配上D或F字样。这是GNU make中老版本号的特性在新版本号中我们使用函数dir或notdir就能够做到了。D的含义就是Directory就是文件夹F的含义就是File就是文件。以下是对于上面的七个变量分别加上D或是F的含义$(D)表示$的文件夹部分不以斜杠作为结尾假设$值是dir/foo.o那么$(D)就是dir而假设$中没有包括斜杠的话其值就是.当前文件夹。$(F)表示$的文件部分假设$值是dir/foo.o那么$(F)就是foo.o$(F)相当于函数$(notdir $)。$(*D)$(*F)和上面所述的同理也是取文件的文件夹部分和文件部分。对于上面的那个样例$(*D)返回dir而$(*F)返回foo$(%D)$(%F)分别表示了函数包文件成员的文件夹部分和文件部分。这对于形同archive(member)形式的目标中的member中包括了不同的文件夹非常实用。$(D)$(F)分别表示依赖文件的文件夹部分和文件部分。$(^D)$(^F)分别表示全部依赖文件的文件夹部分和文件部分。无相同的$(D)$(F)分别表示全部依赖文件的文件夹部分和文件部分。能够有相同的$(?D)$(?F)分别表示被更新的依赖文件的文件夹部分和文件部分。最后想提醒一下的是对于$为了避免产生不必要的麻烦我们最好给$后面的那个特定字符都加上圆括号比方$( )就要比$要好一些。还得要注意的是这些变量仅仅使用在规则的命令中而且一般都是显式规则和静态模式规则參见前面书写规则一章。其在隐含规则中并没有意义。4、模式的匹配一般来说一个目标的模式有一个有前缀或是后缀的%或是没有前后缀直接就是一个%。由于%代表一个或多个字符所以在定义好了的模式中我们把%所匹配的内容叫做茎比如%.c所匹配的文件test.c中test就是茎。由于在目标和依赖目标中同一时候有%时依赖目标的茎会传给目标当做目标中的茎。当一个模式匹配包括有斜杠实际也不经常包括的文件时那么在进行模式匹配时文件夹部分会首先被移开然后进行匹配成功后再把文件夹加回去。在进行茎的传递时我们须要知道这个步骤。比如有一个模式e%t文件src/eat匹配于该模式于是src/a就是其茎假设这个模式定义在依赖目标中而被依赖于这个模式的目标中又有个模式c%r那么目标就是src/car。茎被传递5、重载内建隐含规则你能够重载内建的隐含规则或是定义一个全新的比如你能够又一次构造和内建隐含规则不同的命令如%.o : %.c$(CC) -c $(CPPFLAGS) $(CFLAGS) -D$(date)你能够取消内建的隐含规则仅仅要不在后面写命令即可。如%.o : %.s相同你也能够又一次定义一个全新的隐含规则其在隐含规则中的位置取决于你在哪里写下这个规则。朝前的位置就靠前。六、老式风格的后缀规则后缀规则是一个比較老式的定义隐含规则的方法。后缀规则会被模式规则逐步地代替。由于模式规则更强更清楚。为了和老版本号的Makefile兼容GNU make相同兼容于这些东西。后缀规则有两种方式双后缀和单后缀。双后缀规则定义了一对后缀目标文件的后缀和依赖目标源文件的后缀。如.c.o相当于%o : %c。单后缀规则仅仅定义一个后缀也就是源文件的后缀。如.c相当于% : %.c。后缀规则中所定义的后缀应该是make所认识的假设一个后缀是make所认识的那么这个规则就是单后缀规则而假设两个连在一起的后缀都被make所认识那就是双后缀规则。比如.c和.o都是make所知道。因而假设你定义了一个规则是.c.o那么其就是双后缀规则意义就是.c是源文件的后缀.o是目标文件的后缀。例如以下演示样例.c.o:$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $ $后缀规则不同意不论什么的依赖文件假设有依赖文件的话那就不是后缀规则那些后缀统统被认为是文件名称如.c.o: foo.h$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $ $这个样例就是说文件.c.o依赖于文件foo.h而不是我们想要的这样%.o: %.c foo.h$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $ $后缀规则中假设没有命令那是毫无意义的。由于他也不会移去内建的隐含规则。而要让make知道一些特定的后缀我们能够使用伪目标.SUFFIXES来定义或是删除如.SUFFIXES: .hack .win把后缀.hack和.win增加后缀列表中的末尾。.SUFFIXES: # 删除默认的后缀.SUFFIXES: .c .o .h # 定义自己的后缀先清楚默认后缀后定义自己的后缀列表。make的參数-r或-no-builtin-rules也会使用得默认的后缀列表为空。而变量SUFFIXE被用来定义默认的后缀列表你能够用.SUFFIXES来改变后缀列表但请不要改变变量SUFFIXE的值。七、隐含规则搜索算法比方我们有一个目标叫 T。以下是搜索目标T的规则的算法。请注意在以下我们没有提到后缀规则原因是全部的后缀规则在Makefile被加载内存时会被转换成模式规则。假设目标是archive(member)的函数库文件模式那么这个算法会被运行两次第一次是找目标T假设没有找到的话那么进入第二次第二次会把member当作T来搜索。1、把T的文件夹部分分离出来。叫D而剩余部分叫N。如假设T是src/foo.o那么D就是src/N就是foo.o2、创建全部匹配于T或是N的模式规则列表。3、假设在模式规则列表中有匹配全部文件的模式如%那么从列表中移除其他的模式。4、移除列表中没有命令的规则。5、对于第一个在列表中的模式规则1推导其茎SS应该是T或是N匹配于模式中%非空的部分。2计算依赖文件。把依赖文件里的%都替换成茎S。假设目标模式中没有包括斜框字符而把D加在第一个依赖文件的开头。3測试是否全部的依赖文件都存在或是理当存在。假设有一个文件被定义成另外一个规则的目标文件或者是一个显式规则的依赖文件那么这个文件就叫理当存在4假设全部的依赖文件存在或是理当存在或是就没有依赖文件。那么这条规则将被採用退出该算法。6、假设经过第5步没有模式规则被找到那么就做更进一步的搜索。对于存在于列表中的第一个模式规则1假设规则是终止规则那就忽略它继续下一条模式规则。2计算依赖文件。同第5步3測试全部的依赖文件是否存在或是理当存在。4对于不存在的依赖文件递归调用这个算法查找他能否够被隐含规则找到。5假设全部的依赖文件存在或是理当存在或是就根本没有依赖文件。那么这条规则被採用退出该算法。7、假设没有隐含规则能够使用查看.DEFAULT规则假设有採用把.DEFAULT的命令给T使用。一旦规则被找到就会运行其相当的命令而此时我们的自己主动化变量的值才会生成。使用make更新函数库文件———————————函数库文件也就是对Object文件程序编译的中间文件的打包文件。在Unix下通常是由命令ar来完毕打包工作。一、函数库文件的成员一个函数库文件由多个文件组成。你能够以例如以下格式指定函数库文件及其组成archive(member)这个不是一个命令而一个目标和依赖的定义。一般来说这样的使用方法基本上就是为了ar命令来服务的。如foolib(hack.o) : hack.oar cr foolib hack.o假设要指定多个member那就以空格分开如foolib(hack.o kludge.o)其等价于foolib(hack.o) foolib(kludge.o)你还能够使用Shell的文件通配符来定义如foolib(*.o)二、函数库成员的隐含规则当make搜索一个目标的隐含规则时一个特殊的特性是假设这个目标是a(m)形式的其会把目标变成(m)。于是假设我们的成员是%.o的模式定义而且假设我们使用make foo.a(bar.o)的形式调用Makefile时隐含规则会去找bar.o的规则假设未定义bar.o的规则那么内建隐含规则生效make会去找bar.c文件来生成bar.o假设找得到的话make运行的命令大致例如以下cc -c bar.c -o bar.oar r foo.a bar.orm -f bar.o另一个变量要注意的是$%这是专属函数库文件的自己主动化变量有关其说明请參见自己主动化变量一节。三、函数库文件的后缀规则你能够使用后缀规则和隐含规则来生成函数库打包文件如.c.a:$(CC) $(CFLAGS) $(CPPFLAGS) -c $ -o $*.o$(AR) r $ $*.o$(RM) $*.o其等效于(%.o) : %.c$(CC) $(CFLAGS) $(CPPFLAGS) -c $ -o $*.o$(AR) r $ $*.o$(RM) $*.o四、注意事项在进行函数库打包文件生成时请小心使用make的并行机制-j參数。假设多个ar命令在同一时间运行在同一个函数库打包文件上就非常有能够损坏这个函数库文件。所以在make未来的版本号中应该提供一种机制来避免并行操作发生在函数打包文件上。但就眼下而言你还是应该不要尽量不要使用-j參数。后序——最终到写结束语的时候了以上基本上就是GNU make的Makefile的全部细节了。其他的产商的make基本上也就是这样的不管什么样的make都是以文件的依赖性为基础的其基本是都是遵循一个标准的。这篇文档中80%的技术细节都适用于不论什么的make我推測函数那一章的内容可能不是其他make所支持的而隐含规则方面我想不同的make会有不同的实现我没有精力来查看GNU的make和VC的nmake、BCB的make或是别的UNIX下的make有些什么样的区别一是时间精力不够二是由于我基本上都是在Unix下使用make曾经在SCO Unix和IBM的AIX如今在Linux、Solaris、HP-UX、AIX和Alpha下使用Linux和Solaris下很多其他一点。只是我能够肯定的是在Unix下的make不管是哪种平台差点儿都使用了Richard Stallman开发的make和cc/gcc的编译器而且基本上都是GNU的make公司里全部的UNIX机器上都被装上了GNU的东西所以使用GNU的程序也就多了一些。GNU的东西还是非常不错的特别是使用得深了以后越来越认为GNU的软件的强大也越来越认为GNU的在操作系统中主要是Unix甚至Windows杀伤力。对于上述全部的make的细节我们不但能够利用make这个工具来编译我们的程序还能够利用make来完毕其他的工作由于规则中的命令能够是不论什么Shell之下的命令所以在Unix下你不一定仅仅是使用程序语言的编译器你还能够在Makefile中书写其他的命令如tar、awk、mail、sed、cvs、compress、ls、rm、yacc、rpm、ftp……等等等等来完毕诸如程序打包、程序备份、制作程序安装包、提交代码、使用程序模板、合并文件等等五花八门的功能文件操作文件管理编程开发设计或是其他一些异想天开的东西。比方曾经在书写银行交易程序时由于银行的交易程序基本一样就见到有人书写了一些交易的通用程序模板在该模板中把一些网络通讯、数据库操作的、业务操作共性的东西写在一个文件里在这些文件里用些诸如N、###N奇怪字串标注一些位置然后书写交易时仅仅需依照一种特定的规则书写特定的处理最后在make时使用awk和sed把模板中的N、###N等字串替代成特定的程序形成C文件然后再编译。这个动作非常像数据库的扩展C语言即在C语言中用EXEC SQL的样子运行SQL语句在用cc/gcc编译之前须要使用扩展C的翻译程序如cpre把其翻译成标准C。假设你在使用make时有一些更为绝妙的方法请记得告诉我啊。回头看看整篇文档不觉记起几年前刚刚開始在Unix下做开发的时候有人问我会不会写Makefile时我两眼发直根本不知道在说什么。一開始看到别人在vi中写完程序后输入!make时还以为是vi的功能后来才知道有一个Makefile在作怪于是上网查啊查那时又不愿意看英文发现就根本没有中文的文档介绍Makefile仅仅得看别人写的Makefile自己瞎碰瞎搞才积累了一点知识但在非常多地方全然是知其然不知所以然。后来開始从事UNIX下产品软件的开发看到一个400人年近200万行代码的大project发现要编译这样一个庞然大物假设没有Makefile那会是多么恐怖的一样事啊。于是横下心来狠命地读了一堆英文文档才认为对其掌握了。但发现眼下网上对Makefile介绍的文章还是少得那么的可怜所以想写这样一篇文章共享给大家希望能对各位有所帮助。如今我最终写完了看了看文件的创建时间这篇技术文档也写了两个多月了。发现自己知道是一回事要写下来跟别人讲述又是另外一回事而且如今越来越没有时间专研技术细节所以在写作时发如今阐述一些细节问题时非常难做到严谨和精练而且对先讲什么后讲什么不是非常清楚所以还是參考了一些国外网站上的资料和题纲以及一些技术书籍的语言风格才得以完毕。整篇文档的提纲是基于GNU的Makefile技术手冊的提纲来书写的并结合了自己的工作经验以及自己的学习历程。由于从来没有写过这么长这么细的文档所以一定会有非常多地方存在表达问题语言歧义或是错误。因些我迫切地得等待各位给我指证和建议以及不论什么的反馈。最后还是利用这个后序介绍一下自己。我眼下从事于全部Unix平台下的软件研发主要是做分布式计算/网格计算方面的系统产品软件而且我对于下一代的计算机革命——网格计算非常地感兴趣对于分布式计算、P2P、Web Service、J2EE技术方向也非常感兴趣同一时候对于项目实施、团队管理、项目管理也小有心得希望相同和我战斗在“技术和管理并重”的阵线上的年轻一代能够和我多多地交流。我的MSN是haoelhotmail.com经常使用QQ是753640不经常使用。注请勿给我MSN的邮箱发信由于hotmail的垃圾邮件导致我拒收这个邮箱的全部来信我欢迎不论什么形式的交流不管是讨论技术还是管理或是其他海阔天空的东西。除了政治和娱乐新闻我不关心其他仅仅要积极向上的东西我都欢迎最最后我还想介绍一下make程序的设计开发人员。首当其冲的是 Richard Stallman 开源软件的领袖和先驱从来没有领过一天工资从来没有使用过Windows操作系统。对于他的事迹和他的软件以及他的思想我无需说过多的话相信大家对这个人并不比我陌生这是他的主页http://www.stallman.org/ 。第二位是Roland McGrath 个人主页是http://www.frob.com/~roland/ 以下是他的一些事迹1 合作编写了并维护GNU make。2 和Thomas Bushnell一同编写了GNU Hurd。3 编写并维护着GNU C library。 4 合作编写并维护着部分的GNU Emacs。 在此向这两位开源项目的斗士致以最真切的敬意。全文完 转载于:https://www.cnblogs.com/mengfanrong/p/4041398.html
http://www.yutouwan.com/news/351002/

相关文章:

  • 湖南网站建设公司上海前100强企业名单
  • 横峰县城乡建设网站嘉祥做网站
  • 龙岗网站的建设平台公司的定义
  • 蒙icp备网站建设网站建设项目简介
  • 做网站时如何写接口文档小型网站开发 论文
  • 做sorry动图的网站网站ui设计素材
  • 用wordpress开发网站模板wordpress大前端主题下载
  • 潍坊网站制作 熊掌号北京建机官网
  • 上海轨道交通建设查询网站北京建设网站哪家好
  • 赣州南康网站建设网站开发软件下载
  • 淄博高端网站建设公司网站页面在线设计
  • 深圳找个做网站平台的公司宣传册设计与制作公司
  • 网站备案查询工信网丽水建设部门网站
  • 网站正在建设中...徐州免费模板建站
  • 在线ppt制作网站有哪些万网科技
  • 建设集团公司网站做哪个软件网站app
  • 建站技术分享用c 建网站时怎么做导航菜单栏
  • 贵州成品网站网店推广运营
  • 电器类网站设计wordpress 生成图片不显示
  • 自己做网站怎么样wordpress dns解析
  • 高端网站建设专家评价WordPress京东淘宝主题
  • 网站开发模板下载苏州网站建设排名
  • 个人主页网站模板免费wordpress 评论框 提示
  • 建设网站培训wordpress菜单管理
  • 网站建设合同要交印花吗wordpress繁体
  • 网站建设比较合理的流程软件开发项目管理文档
  • 网站建设哪里有学网站推广120种方法
  • 购物网站开发的必要性给手机做网站的公司
  • 网站建设 项目书 框架提供设计网站效果图
  • 法语网站建站公司定制化网站一般价格