files是什么意思

更新时间:2022-11-22 17:41:50 阅读: 评论:0


2022年11月22日发(作者:小学三年级语文试卷分析)

Makefile的使⽤⽅法

什么是makefile?或许很多Winodws的程序员都不知道这个东西,因为那些Windows的IDE都为你做了这个⼯作,但我觉得要作⼀个好的和

professional的程序员,makefile还是要懂。这就好像现在有这么多的HTML的编辑器,但如果你想成为⼀个专业⼈⼠,你还是要了解HTML

的标识的含义。特别在Unix下的软件编译,你就不能不⾃⼰写makefile了,会不会写makefile,从⼀个侧⾯说明了⼀个⼈是否具备完成⼤型

⼯程的能⼒。

因为,makefile关系到了整个⼯程的编译规则。⼀个⼯程中的源⽂件不计数,其按类型、功能、模块分别放在若⼲个⽬录中,makefile定义

了⼀系列的规则来指定,哪些⽂件需要先编译,哪些⽂件需要后编译,哪些⽂件需要重新编译,甚⾄于进⾏更复杂的功能操作,因为

makefile就像⼀个Shell脚本⼀样,其中也可以执⾏操作系统的命令。

makefile带来的好处就是——“⾃动化编译”,⼀旦写好,只需要⼀个make命令,整个⼯程完全⾃动编译,极⼤的提⾼了软件开发的效率。

make是⼀个命令⼯具,是⼀个解释makefile中指令的命令⼯具,⼀般来说,⼤多数的IDE都有这个命令,⽐如:Delphi的make,Visual

C++的nmake,Linux下GNU的make。可见,makefile都成为了⼀种在⼯程⽅⾯的编译⽅法。

现在讲述如何写makefile的⽂章⽐较少,这是我想写这篇⽂章的原因。当然,不同产商的make各不相同,也有不同的语法,但其本质都是

在“⽂件依赖性”上做⽂章,这⾥,我仅对GNU的make进⾏讲述,我的环境是RedHatLinux8.0,make的版本是3.80。必竟,这个make是应

⽤最为⼴泛的,也是⽤得最多的。⽽且其还是最遵循于IEEE1003.2-1992标准的(POSIX.2)。

在这篇⽂档中,将以C/C++的源码作为我们基础,所以必然涉及⼀些关于C/C++的编译的知识,相关于这⽅⾯的内容,还请各位查看相关的

编译器的⽂档。这⾥所默认的编译器是UNIX下的GCC和CC。

关于程序的编译和链接

——————————

在此,我想多说关于程序编译的⼀些规范和⽅法,⼀般来说,⽆论是C、C++、还是pas,⾸先要把源⽂件编译成中间代码⽂件,在

Windows下也就是.obj⽂件,UNIX下是.o⽂件,即ObjectFile,这个动作叫做编译(compile)。然后再把⼤量的ObjectFile合成执⾏⽂

件,这个动作叫作链接(link)。

编译时,编译器需要的是语法的正确,函数与变量的声明的正确。对于后者,通常是你需要告诉编译器头⽂件的所在位置(头⽂件中应该只

是声明,⽽定义应该放在C/C++⽂件中),只要所有的语法正确,编译器就可以编译出中间⽬标⽂件。⼀般来说,每个源⽂件都应该对应于

⼀个中间⽬标⽂件(O⽂件或是OBJ⽂件)。

链接时,主要是链接函数和全局变量,所以,我们可以使⽤这些中间⽬标⽂件(O⽂件或是OBJ⽂件)来链接我们的应⽤程序。链接器并不

管函数所在的源⽂件,只管函数的中间⽬标⽂件(ObjectFile),在⼤多数时候,由于源⽂件太多,编译⽣成的中间⽬标⽂件太多,⽽在链

接时需要明显地指出中间⽬标⽂件名,这对于编译很不⽅便,所以,我们要给中间⽬标⽂件打个包,在Windows下这种包叫“库⽂

件”(LibraryFile),也就是.lib⽂件,在UNIX下,是ArchiveFile,也就是.a⽂件。

总结⼀下,源⽂件⾸先会⽣成中间⽬标⽂件,再由中间⽬标⽂件⽣成执⾏⽂件。在编译时,编译器只检测程序语法,和函数、变量是否被声

明。如果函数未被声明,编译器会给出⼀个警告,但可以⽣成ObjectFile。⽽在链接程序时,链接器会在所有的ObjectFile中找寻函数的实

现,如果找不到,那到就会报链接错误码(LinkerError),在VC下,这种错误⼀般是:Link2001错误,意思说是说,链接器未能找到函数

的实现。你需要指定函数的ObjectFile.

Makefile介绍

———————

make命令执⾏时,需要⼀个Makefile⽂件,以告诉make命令需要怎么样的去编译和链接程序。

⾸先,我们⽤⼀个⽰例来说明Makefile的书写规则。以便给⼤家⼀个感兴认识。这个⽰例来源于GNU的make使⽤⼿册,在这个⽰例中,我

们的⼯程有8个C⽂件,和3个头⽂件,我们要写⼀个Makefile来告诉make命令如何编译和链接这⼏个⽂件。我们的规则是:

1)如果这个⼯程没有编译过,那么我们的所有C⽂件都要编译并被链接。

2)如果这个⼯程的某⼏个C⽂件被修改,那么我们只编译被修改的C⽂件,并链接⽬标程序。

3)如果这个⼯程的头⽂件被改变了,那么我们需要编译引⽤了这⼏个头⽂件的C⽂件,并链接⽬标程序。

只要我们的Makefile写得够好,所有的这⼀切,我们只⽤⼀个make命令就可以完成,make命令会⾃动智能地根据当前的⽂件修改的情况来

确定哪些⽂件需要重编译,从⽽⾃⼰编译所需要的⽂件和链接⽬标程序。

⼀、Makefile的规则

在讲述这个Makefile之前,还是让我们先来粗略地看⼀看Makefile的规则。

target...:prerequisites...

command

...

...

target也就是⼀个⽬标⽂件,可以是ObjectFile,也可以是执⾏⽂件。还可以是⼀个标签(Label),对于标签这种特性,在后续的“伪⽬

标”章节中会有叙述。

prerequisites就是,要⽣成那个target所需要的⽂件或是⽬标。

command也就是make需要执⾏的命令。(任意的Shell命令)

这是⼀个⽂件的依赖关系,也就是说,target这⼀个或多个的⽬标⽂件依赖于prerequisites中的⽂件,其⽣成规则定义在command中。说⽩

⼀点就是说,prerequisites中如果有⼀个以上的⽂件⽐target⽂件要新的话,command所定义的命令就会被执⾏。这就是Makefile的规则。

也就是Makefile中最核⼼的内容。

⼆、⼀个⽰例

正如前⾯所说的,如果⼀个⼯程有3个头⽂件,和8个C⽂件,我们为了完成前⾯所述的那三个规则,我们的Makefile应该是下⾯的这个样⼦

的。

edit:ay.o

.o

ay.o

.o

main.o:.h

cc-cmain.c

kbd.o:nd.h

cc-ckbd.c

command.o:nd.h

cc-ccommand.c

display.o:r.h

cc-cdisplay.c

inrt.o:r.h

cc-cinrt.c

arch.o:r.h

cc-carch.c

files.o:nd.h

cc-cfiles.c

utils.o:.h

cc-cutils.c

clean:

ay.o

.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执⾏。即命令——“makeclean”,以此来清除所有的⽬标⽂件,以便重编译。

于是在我们编程中,如果这个⼯程已被编译过了,当我们修改了其中⼀个源⽂件,⽐如file.c,那么根据我们的依赖性,我们的⽬标file.o会被

重编译(也就是在这个依性关系后⾯所定义的命令),于是file.o的⽂件也是最新的啦,于是file.o的⽂件修改时间要⽐edit要新,所以edit也

会被重新链接了(详见edit⽬标⽂件后定义的命令)。

⽽如果我们改变了“command.h”,那么,kdb.o、command.o和files.o都会被重编译,并且,edit会被重链接。

四、makefile中使⽤变量

在上⾯的例⼦中,先让我们看看edit的规则:

edit:ay.o

.o

ay.o

.o

我们可以看到[.o]⽂件的字符串被重复了两次,如果我们的⼯程需要加⼊⼀个新的[.o]⽂件,那么我们需要在两个地⽅加(应该是三个地⽅,

还有⼀个地⽅在clean中)。当然,我们的makefile并不复杂,所以在两个地⽅加也不累,但如果makefile变得复杂,那么我们就有可能会忘

掉⼀个需要加⼊的地⽅,⽽导致编译失败。所以,为了makefile的易维护,在makefile中我们可以使⽤变量。makefile的变量也就是⼀个字符

串,理解成C语⾔中的宏可能会更好。

⽐如,我们声明⼀个变量,叫objects,OBJECTS,objs,OBJS,obj,或是OBJ,反正不管什么啦,只要能够表⽰obj⽂件就⾏了。我们在

makefile⼀开始就这样定义:

objects=ay.o

.o

于是,我们就可以很⽅便地在我们的makefile中以“$(objects)”的⽅式来使⽤这个变量了,于是我们的改良版makefile就变成下⾯这个样⼦:

objects=ay.o

.o

edit:$(objects)

cc-oedit$(objects)

main.o:.h

cc-cmain.c

kbd.o:nd.h

cc-ckbd.c

command.o:nd.h

cc-ccommand.c

display.o:r.h

cc-cdisplay.c

inrt.o:r.h

cc-cinrt.c

arch.o:r.h

cc-carch.c

files.o:nd.h

cc-cfiles.c

utils.o:.h

cc-cutils.c

clean:

rmedit$(objects)

于是如果有新的.o⽂件加⼊,我们只需简单地修改⼀下objects变量就可以了。

关于变量更多的话题,我会在后续给你⼀⼀道来。

五、让make⾃动推导

GNU的make很强⼤,它可以⾃动推导⽂件以及⽂件依赖关系后⾯的命令,于是我们就没必要去在每⼀个[.o]⽂件后都写上类似的命令,因

为,我们的make会⾃动识别,并⾃⼰推导命令。

只要make看到⼀个[.o]⽂件,它就会⾃动的把[.c]⽂件加在依赖关系中,如果make找到⼀个whatever.o,那么whatever.c,就会是whatever.o

的依赖⽂件。并且cc-cwhatever.c也会被推导出来,于是,我们的makefile再也不⽤写得这么复杂。我们的是新的makefile⼜出炉了。

objects=ay.o

.o

edit:$(objects)

cc-oedit$(objects)

main.o:defs.h

kbd.o:nd.h

command.o:nd.h

display.o:r.h

inrt.o:r.h

arch.o:r.h

files.o:nd.h

utils.o:defs.h

.PHONY:clean

clean:

rmedit$(objects)

这种⽅法,也就是make的“隐晦规则”。上⾯⽂件内容中,“.PHONY”表⽰,clean是个伪⽬标⽂件。

关于更为详细的“隐晦规则”和“伪⽬标⽂件”,我会在后续给你⼀⼀道来。

七、清空⽬标⽂件的规则

每个Makefile中都应该写⼀个清空⽬标⽂件(.o和执⾏⽂件)的规则,这不仅便于重编译,也很利于保持⽂件的清洁。这是⼀个“修养”(呵

呵,还记得我的《编程修养》吗)。⼀般的风格都是:

clean:

rmedit$(objects)

更为稳健的做法是:

.PHONY:clean

clean:

-rmedit$(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,⽐如:“”,“s”,“”等,如果要指定特定的Makefile,你可以

使⽤make的“-f”和“--file”参数,如:或。

三、引⽤其它的Makefile

在Makefile使⽤include关键字可以把别的Makefile包含进来,这很像C语⾔的#include,被包含的⽂件会原模原样的放在当前⽂件的包含位

置。include的语法是:

include

filename可以是当前操作系统Shell的⽂件模式(可以保含路径和通配符)

在include前⾯可以有⼀些空字符,但是绝不能是[Tab]键开始。include和可以⽤⼀个或多个空格隔开。举个例⼦,你有这样⼏个Makefile:

、、,还有⼀个⽂件叫,以及⼀个变量$(bar),其包含了和,那么,下⾯的语句:

*.mk$(bar)

等价于:

make命令开始时,会把找寻include所指出的其它Makefile,并把其内容安置在当前的位置。就好像C/C++的#include指令⼀样。如果⽂件

都没有指定绝对路径或是相对路径的话,make会在当前⽬录下⾸先寻找,如果当前⽬录下没有找到,那么,make还会在下⾯的⼏个⽬录下

找:

1、如果make执⾏时,有“-I”或“--include-dir”参数,那么make就会在这个参数所指定的⽬录下去寻找。

2、如果⽬录/include(⼀般是:/usr/local/bin或/usr/include)存在的话,make也会去找。

如果有⽂件没有找到的话,make会⽣成⼀条警告信息,但不会马上出现致命错误。它会继续载⼊其它的⽂件,⼀旦完成makefile的读

取,make会再重试这些没有找到,或是不能读取的⽂件,如果还是不⾏,make才会出现⼀条致命信息。如果你想让make不理那些⽆法读

取的⽂件,⽽继续执⾏,你可以在include前加⼀个减号“-”。如:

-include

其表⽰,⽆论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更为熟悉。有了这个基础,后续部分也就容易看懂了。

书写规则

————

规则包含两个部分,⼀个是依赖关系,⼀个是⽣成⽬标的⽅法。

在Makefile中,规则的顺序是很重要的,因为,Makefile中只应该有⼀个最终⽬标,其它的⽬标都是被这个⽬标所连带出来的,所以⼀定要

让make知道你的最终⽬标是什么。⼀般来说,定义在Makefile中的⽬标可能会有很多,但是第⼀条规则中的⽬标将被确⽴为最终的⽬标。如

果第⼀条规则中的⽬标有很多个,那么,第⼀个⽬标会成为最终的⽬标。make所完成的也就是这个⽬标。

好了,还是让我们来看⼀看如何书写规则。

⼀、规则举例

foo.o:.h#foo模块

cc-c-gfoo.c

看到这个例⼦,各位应该不是很陌⽣了,前⾯也已说过,foo.o是我们的⽬标,foo.c和defs.h是⽬标所依赖的源⽂件,⽽只有⼀个命令“cc-c-

gfoo.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:prerequisites

command

...

或是这样:

targets:prerequisites;command

command

...

targets是⽂件名,以空格分开,可以使⽤通配符。⼀般来说,我们的⽬标基本上是⼀个⽂件,但也有可能是多个⽂件。

command是命令⾏,如果其不与“target:prerequisites”在⼀⾏,那么,必须以[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:*.c

lpr-p$?

touchprint

上⾯这个例⼦说明了通配符也可以在我们的规则中,⽬标print依赖于所有的[.c]⽂件。其中的“$?”是⼀个⾃动化变量,我会在后⾯给你讲述。

objects=*.o

上⾯这个例⼦,表⽰了,通符同样可以⽤在变量中。并不是说[*.o]会展开,不!objects的值就是“*.o”。Makefile中的变量其实就是C/C++中

的宏。如果你要让通配符在变量中展开,也就是让objects的值是所有[.o]的⽂件名的集合,那么,你可以这样:

objects:=$(wildcard*.o)

这种⽤法由关键字“wildcard”指出,关于Makefile的关键字,我们将在后⾯讨论。

四、⽂件搜寻

在⼀些⼤的⼯程中,有⼤量的源⽂件,我们通常的做法是把这许多的源⽂件分类,并存放在不同的⽬录中。所以,当make需要去找寻⽂件

的依赖关系时,你可以在⽂件前加上路径,但最好的⽅法是把⼀个路径告诉make,让make在⾃动去找。

Makefile⽂件中的特殊变量“VPATH”就是完成这个功能的,如果没有指明这个变量,make只会在当前的⽬录中去找寻依赖⽂件和⽬标⽂件。

如果定义了这个变量,那么,make就会在当当前⽬录找不到的情况下,到所指定的⽬录中去找寻⽂件了。

VPATH=src:../headers

上⾯的的定义指定两个⽬录,“src”和“../headers”,make会按照这个顺序进⾏搜索。⽬录由“冒号”分隔。(当然,当前⽬录永远是最⾼优先搜

索的地⽅)

另⼀个设置⽂件搜索路径的⽅法是使⽤make的“vpath”关键字(注意,它是全⼩写的),这不是变量,这是⼀个make的关键字,这和上⾯提

到的那个VPATH变量很类似,但是它更为灵活。它可以指定不同的⽂件在不同的搜索⽬录中。这是⼀个很灵活的功能。它的使⽤⽅法有三

种:

1、vpath

为符合模式的⽂件指定搜索⽬录。

2、vpath

清除符合模式的⽂件的搜索⽬录。

3、vpath

清除所有已被设置好了的⽂件搜索⽬录。

vapth使⽤⽅法中的需要包含“%”字符。“%”的意思是匹配零或若⼲字符,例如,“%.h”表⽰所有以“.h”结尾的⽂件。指定了要搜索的⽂件集,⽽

则指定了的⽂件集的搜索的⽬录。例如:

vpath%.h../headers

该语句表⽰,要求make在“../headers”⽬录下搜索所有以“.h”结尾的⽂件。(如果某⽂件在当前⽬录没有找到的话)

我们可以连续地使⽤vpath语句,以指定不同搜索策略。如果连续的vpath语句中出现了相同的,或是被重复了的,那么,make会按照vpath

语句的先后顺序来执⾏搜索。如:

vpath%.cfoo

vpath%blish

vpath%.cbar

其表⽰“.c”结尾的⽂件,先在“foo”⽬录,然后是“blish”,最后是“bar”⽬录。

vpath%.cfoo:bar

vpath%blish

⽽上⾯的语句则表⽰“.c”结尾的⽂件,先在“foo”⽬录,然后是“bar”⽬录,最后才是“blish”⽬录。

五、伪⽬标

最早先的⼀个例⼦中,我们提到过⼀个“clean”的⽬标,这是⼀个“伪⽬标”,

clean:

rm*.otemp

正像我们前⾯例⼦中的“clean”⼀样,即然我们⽣成了许多⽂件编译⽂件,我们也应该提供⼀个清除它们的“⽬标”以备完整地重编译⽽⽤。

(以“makeclean”来使⽤该⽬标)

因为,我们并不⽣成“clean”这个⽂件。“伪⽬标”并不是⼀个⽂件,只是⼀个标签,由于“伪⽬标”不是⽂件,所以make⽆法⽣成它的依赖关系

和决定它是否要执⾏。我们只有通过显⽰地指明这个“⽬标”才能让其⽣效。当然,“伪⽬标”的取名不能和⽂件名重名,不然其就失去了“伪⽬

标”的意义了。

当然,为了避免和⽂件重名的这种情况,我们可以使⽤⼀个特殊的标记“.PHONY”来显⽰地指明⼀个⽬标是“伪⽬标”,向make说明,不管是

否有这个⽂件,这个⽬标就是“伪⽬标”。

.PHONY:clean

只要有这个声明,不管是否有“clean”⽂件,要运⾏“clean”这个⽬标,只有“makeclean”这样。于是整个过程可以这样写:

.PHONY:clean

clean:

rm*.otemp

伪⽬标⼀般没有依赖的⽂件。但是,我们也可以为伪⽬标指定所依赖的⽂件。伪⽬标同样可以作为“默认⽬标”,只要将其放在第⼀个。⼀个

⽰例就是,如果你的Makefile需要⼀⼝⽓⽣成若⼲个可执⾏⽂件,但你只想简单地敲⼀个make完事,并且,所有的⽬标⽂件都写在⼀个

Makefile中,那么你可以使⽤“伪⽬标”这个特性:

all:prog1prog2prog3

.PHONY:all

prog1:.o

.o

prog2:prog2.o

cc-oprog2prog2.o

prog3:.o

.o

我们知道,Makefile中的第⼀个⽬标会被作为其默认⽬标。我们声明了⼀个“all”的伪⽬标,其依赖于其它三个⽬标。由于伪⽬标的特性是,

总是被执⾏的,所以其依赖的那三个⽬标就总是不如“all”这个⽬标新。所以,其它三个⽬标的规则总是会被决议。也就达到了我们⼀⼝⽓⽣

成多个⽬标的⽬的。“.PHONY:all”声明了“all”这个⽬标为“伪⽬标”。

随便提⼀句,从上⾯的例⼦我们可以看出,⽬标也可以成为依赖。所以,伪⽬标同样也可成为依赖。看下⾯的例⼦:

.PHONY:cleanallcleanobjcleandiff

cleanall:cleanobjcleandiff

rmprogram

cleanobj:

rm*.o

cleandiff:

rm*.diff

“makeclean”将清除所有要被清除的⽂件。“cleanobj”和“cleandiff”这两个伪⽬标有点像“⼦程序”的意思。我们可以输⼊“make

cleanall”和“makecleanobj”和“makecleandiff”命令来达到清除不同种类⽂件的⽬的。

六、多⽬标

Makefile的规则中的⽬标可以不⽌⼀个,其⽀持多⽬标,有可能我们的多个⽬标同时依赖于⼀个⽂件,并且其⽣成的命令⼤体类似。于是我

们就能把其合并起来。当然,多个⽬标的⽣成规则的执⾏命令是同⼀个,这可能会可我们带来⿇烦,不过好在我们的可以使⽤⼀个⾃动化变

量“$@”(关于⾃动化变量,将在后⾯讲述),这个变量表⽰着⽬前规则中所有的⽬标的集合,这样说可能很抽象,还是看⼀个例⼦吧。

bigoutputlittleoutput:text.g

generatetext.g-$(substoutput,,$@)>$@

上述规则等价于:

bigoutput:text.g

generatetext.g-big>bigoutput

littleoutput:text.g

generatetext.g-little>littleoutput

其中,-$(substoutput,,$@)中的“$”表⽰执⾏⼀个Makefile的函数,函数名为subst,后⾯的为参数。关于函数,将在后⾯讲述。这⾥的这个函

数是截取字符串的意思,“$@”表⽰⽬标的集合,就像⼀个数组,“$@”依次取出⽬标,并执于命令。

七、静态模式

静态模式可以更加容易地定义多⽬标的规则,可以让我们的规则变得更加的有弹性和灵活。我们还是先来看⼀下语法:

::

...

targets定义了⼀系列的⽬标⽂件,可以有通配符。是⽬标的⼀个集合。

target-parrtern是指明了targets的模式,也就是的⽬标集模式。

prereq-parrterns是⽬标的依赖模式,它对target-parrtern形成的模式再进⾏⼀次依赖⽬标的定义。

这样描述这三个东西,可能还是没有说清楚,还是举个例⼦来说明⼀下吧。如果我们的定义成“%.o”,意思是我们的集合中都是以“.o”结尾

的,⽽如果我们的定义成“%.c”,意思是对所形成的⽬标集进⾏⼆次定义,其计算⽅法是,取模式中的“%”(也就是去掉了[.o]这个结尾),并

为其加上[.c]这个结尾,形成的新集合。

所以,我们的“⽬标模式”或是“依赖模式”中都应该有“%”这个字符,如果你的⽂件名中有“%”那么你可以使⽤反斜杠“”进⾏转义,来标明真实

的“%”字符。

看⼀个例⼦:

objects=.o

all:$(objects)

$(objects):%.o:%.c

$(CC)-c$(CFLAGS)$<-o$@

上⾯的例⼦中,指明了我们的⽬标从$object中获取,“%.o”表明要所有以“.o”结尾的⽬标,也就是“.o”,也就是变量$object集合的模

式,⽽依赖模式“%.c”则取模式“%.o”的“%”,也就是“foobar”,并为其加下“.c”的后缀,于是,我们的依赖⽬标就是“.c”。⽽命令中

的“$<”和“$@”则是⾃动化变量,“$<”表⽰所有的依赖⽬标集(也就是“.c”),“$@”表⽰⽬标集(也就是“.o”)。于是,上⾯

的规则展开后等价于下⾯的规则:

foo.o:foo.c

$(CC)-c$(CFLAGS)foo.c-ofoo.o

bar.o:bar.c

$(CC)-c$(CFLAGS)bar.c-obar.o

试想,如果我们的“%.o”有⼏百个,那种我们只要⽤这种很简单的“静态模式规则”就可以写完⼀堆规则,实在是太有效率了。“静态模式规

则”的⽤法很灵活,如果⽤得好,那会⼀个很强⼤的功能。再看⼀个例⼦:

files=.o

$(filter%.o,$(files)):%.o:%.c

$(CC)-c$(CFLAGS)$<-o$@

$(filter%.elc,$(files)):%.elc:%.el

emacs-fbatch-byte-compile$<

$(filter%.o,$(files))表⽰调⽤Makefile的filter函数,过滤“$filter”集,只要其中模式为“%.o”的内容。其的它内容,我就不⽤多说了吧。这个例字

展⽰了Makefile中更⼤的弹性。

⼋、⾃动⽣成依赖性

在Makefile中,我们的依赖关系可能会需要包含⼀系列的头⽂件,⽐如,如果我们的main.c中有⼀句“#include"defs.h"”,那么我们的依赖

关系应该是:

main.o:.h

但是,如果是⼀个⽐较⼤型的⼯程,你必需清楚哪些C⽂件包含了哪些头⽂件,并且,你在加⼊或删除头⽂件时,也需要⼩⼼地修改

Makefile,这是⼀个很没有维护性的⼯作。为了避免这种繁重⽽⼜容易出错的事情,我们可以使⽤C/C++编译的⼀个功能。⼤多数的

C/C++编译器都⽀持⼀个“-M”的选项,即⾃动找寻源⽂件中包含的头⽂件,并⽣成⼀个依赖关系。例如,如果我们执⾏下⾯的命令:

cc-Mmain.c

其输出是:

main.o:.h

于是由编译器⾃动⽣成的依赖关系,这样⼀来,你就不必再⼿动书写若⼲⽂件的依赖关系,⽽由编译器⾃动⽣成了。需要提醒⼀句的是,如

果你使⽤GNU的C/C++编译器,你得⽤“-MM”参数,不然,“-M”参数会把⼀些标准库的头⽂件也包含进来。

gcc-Mmain.c的输出是:

main.o:.h/usr/include/stdio.h/usr/include/features.h

/usr/include/sys/cdefs.h/usr/include/gnu/stubs.h

/usr/lib/gcc-lib/i486-su-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-su-linux/2.95.3/include/stdarg.h

/usr/include/bits/stdio_lim.h

gcc-MMmain.c的输出则是:

main.o:.h

那么,编译器的这个功能如何与我们的Makefile联系在⼀起呢。因为这样⼀来,我们的Makefile也要根据这些源⽂件重新⽣成,让Makefile⾃

已依赖于源⽂件?这个功能并不现实,不过我们可以有其它⼿段来迂回地实现这⼀功能。GNU组织建议把编译器为每⼀个源⽂件的⾃动⽣成

的依赖关系放到⼀个⽂件中,为每⼀个“name.c”的⽂件都⽣成⼀个“name.d”的Makefile⽂件,[.d]⽂件中就存放对应[.c]⽂件的依赖关系。

于是,我们可以写出[.c]⽂件和[.d]⽂件的依赖关系,并让make⾃动更新或⾃成[.d]⽂件,并把其包含在我们的主Makefile中,这样,我们就可

以⾃动化地⽣成每个⽂件的依赖关系了。

这⾥,我们给出了⼀个模式规则来产⽣[.d]⽂件:

%.d:%.c

@t-e;rm-f$@;

$(CC)-M$(CPPFLAGS)$<>$@.$$$$;

d's,($*).o[:]*,1.o$@:,g'<$@.$$$$>$@;

rm-f$@.$$$$

这个规则的意思是,所有的[.d]⽂件依赖于[.c]⽂件,“rm-f$@”的意思是删除所有的⽬标,也就是[.d]⽂件,第⼆⾏的意思是,为每个依赖⽂

件“$<”,也就是[.c]⽂件⽣成依赖⽂件,“$@”表⽰模式“%.d”⽂件,如果有⼀个C⽂件是name.c,那么“%”就是“name”,“$$$$”意为⼀个随机编

号,第⼆⾏⽣成的⽂件有可能是“name.d.12345”,第三⾏使⽤d命令做了⼀个替换,关于d命令的⽤法请参看相关的使⽤⽂档。第四⾏就

是删除临时⽂件。

总⽽⾔之,这个模式要做的事就是在编译器⽣成的依赖关系中加⼊[.d]⽂件的依赖,即把依赖关系:

main.o:.h

转成:

.d:.h

于是,我们的[.d]⽂件也会⾃动更新了,并会⾃动⽣成了,当然,你还可以在这个[.d]⽂件中加⼊的不只是依赖关系,包括⽣成的命令也可⼀

并加⼊,让每个[.d]⽂件都包含⼀个完赖的规则。⼀旦我们完成这个⼯作,接下来,我们就要把这些⾃动⽣成的规则放进我们的主Makefile

中。我们可以使⽤Makefile的“include”命令,来引⼊别的Makefile⽂件(前⾯讲过),例如:

sources=.c

include$(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/hchen

pwd

⽰例⼆:

exec:

cd/home/hchen;pwd

当我们执⾏“makeexec”时,第⼀个例⼦中的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

在⼀些⼤的⼯程中,我们会把我们不同模块或是不同功能的源⽂件放在不同的⽬录中,我们可以在每个⽬录中都书写⼀个该⽬录的

Makefile,这有利于让我们的Makefile变得更加地简洁,⽽不⾄于把所有的东西全部写在⼀个Makefile中,这样会很难维护我们的Makefile,

这个技术对于我们模块编译和分段编译有着⾮常⼤的好处。

例如,我们有⼀个⼦⽬录叫subdir,这个⽬录下有个Makefile⽂件,来指明了这个⽬录下⽂件的编译规则。那么我们总控的Makefile可以这样

书写:

subsystem:

cdsubdir&&$(MAKE)

其等价于:

subsystem:

$(MAKE)-Csubdir

定义$(MAKE)宏变量的意思是,也许我们的make需要⼀些参数,所以定义成⼀个变量⽐较利于维护。这两个例⼦的意思都是先进

⼊“subdir”⽬录,然后执⾏make命令。

我们把这个Makefile叫做“总控Makefile”,总控Makefile的变量可以传递到下级的Makefile中(如果你显⽰的声明),但是不会覆盖下层的

Makefile中所定义的变量,除⾮指定了“-e”参数。

如果你要传递变量到下级Makefile中,那么你可以使⽤这样的声明:

export

如果你不想让某些变量传递到下级Makefile中,那么你可以这样声明:

unexport

如:

⽰例⼀:

exportvariable=value

其等价于:

variable=value

exportvariable

其等价于:

exportvariable:=value

其等价于:

variable:=value

exportvariable

⽰例⼆:

exportvariable+=value

其等价于:

variable+=value

exportvariable

如果你要传递所有的变量,那么,只要⼀个export就⾏了。后⾯什么也不⽤跟,表⽰传递所有的变量。

需要注意的是,有两个变量,⼀个是SHELL,⼀个是MAKEFLAGS,这两个变量不管你是否export,其总是要传递到下层Makefile中,特别

是MAKEFILES变量,其中包含了make的参数信息,如果我们执⾏“总控Makefile”时有make参数或是在上层Makefile中定义了这个变量,那

么MAKEFILES变量将会是这些参数,并会传递到下层Makefile中,这是⼀个系统级的环境变量。

但是make命令中的有⼏个参数并不往下传递,它们是“-C”,“-f”,“-h”“-o”和“-W”(有关Makefile参数的细节将在后⾯说明),如果你不想往下层

传递参数,那么,你可以这样来:

subsystem:

cdsubdir&&$(MAKE)MAKEFLAGS=

如果你定义了环境变量MAKEFLAGS,那么你得确信其中的选项是⼤家都会⽤到的,如果其中有“-t”,“-n”,和“-q”参数,那么将会有让你意想不

到的结果,或许会让你异常地恐慌。

还有⼀个在“嵌套执⾏”中⽐较有⽤的参数,“-w”或是“--print-directory”会在make的过程中输出⼀些信息,让你看到⽬前的⼯作⽬录。⽐如,如

果我们的下级make⽬录是“/home/hchen/gnu/make”,如果我们使⽤“make-w”来执⾏,那么当进⼊该⽬录时,我们会看到:

make:Enteringdirectory`/home/hchen/gnu/make'.

⽽在完成下层make后离开⽬录时,我们会看到:

make:Leavingdirectory`/home/hchen/gnu/make'

当你使⽤“-C”参数来指定make下层Makefile时,“-w”会被⾃动打开的。如果参数中有“-s”(“--slient”)或是“--no-print-directory”,那么,“-w”总

是失效的。

五、定义命令包

如果Makefile中出现⼀些相同命令序列,那么我们可以为这些相同的命令序列定义⼀个变量。定义这种命令序列的语法以“define”开始,

以“endef”结束,如:

definerun-yacc

yacc$(firstword$^)

.c$@

endef

这⾥,“run-yacc”是这个命令包的名字,其不要和Makefile中的变量重名。在“define”和“endef”中的两⾏就是命令序列。这个命令包中的第⼀

个命令是运⾏Yacc程序,因为Yacc程序总是⽣成“.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=.o

program:$(objects)

cc-oprogram$(objects)

$(objects):defs.h

变量会在使⽤它的地⽅精确地展开,就像C/C++中的宏⼀样,例如:

foo=c

prog.o:prog.$(foo)

$(foo)$(foo)-$(foo)prog.$(foo)

展开后得到:

prog.o:prog.c

cc-cprog.c

当然,千万不要在你的Makefile中这样⼲,这⾥只是举个例⼦来表明Makefile中的变量在使⽤处展开的真实样⼦。可见其就是⼀个“替代”的原

理。

另外,给变量加上括号完全是为了更加安全地使⽤这个变量,在上⾯的例⼦中,如果你不想给变量加上括号,那也可以,但我还是强烈建议

你给变量加上括号。

⼆、变量中的变量

在定义变量的值时,我们可以使⽤其它变量来构造变量的值,在Makefile中有两种⽅式来在⽤变量定义变量的值。

先看第⼀种⽅式,也就是简单的使⽤“=”号,在“=”左侧是变量,右侧是变量的值,右侧变量的值可以定义在⽂件的任何⼀处,也就是说,右

侧中的变量不⼀定⾮要是已定义好的值,其也可以使⽤后⾯定义的值。如:

foo=$(bar)

bar=$(ugh)

ugh=Huh?

all:

echo$(foo)

我们执⾏“makeall”将会打出变量$(foo)的值是“Huh?”($(foo)的值是$(bar),$(bar)的值是$(ugh),$(ugh)的值是“Huh?”)可见,变量是可以

使⽤后⾯的变量来定义的。

这个功能有好的地⽅,也有不好的地⽅,好的地⽅是,我们可以把变量的真实值推到后⾯来定义,如:

CFLAGS=$(include_dirs)-O

include_dirs=-Ifoo-Ibar

当“CFLAGS”在命令中被展开时,会是“-Ifoo-Ibar-O”。但这种形式也有不好的地⽅,那就是递归定义,如:

CFLAGS=$(CFLAGS)-O

或:

A=$(B)

B=$(A)

这会让make陷⼊⽆限的变量展开过程中去,当然,我们的make是有能⼒检测这样的定义,并会报错。还有就是如果在变量中使⽤函数,那

么,这种⽅式会让我们的make运⾏时⾮常慢,更糟糕的是,他会使⽤得两个make的函数“wildcard”和“shell”发⽣不可预知的错误。因为你不

会知道这两个函数会被调⽤多少次。

为了避免上⾯的这种⽅法,我们可以使⽤make中的另⼀种⽤变量来定义变量的⽅法。这种⽅法使⽤的是“:=”操作符,如:

x:=foo

y:=$(x)bar

x:=later

其等价于:

y:=foobar

x:=later

值得⼀提的是,这种⽅法,前⾯的变量不能使⽤后⾯的变量,只能使⽤前⾯已定义好了的变量。如果是这样:

y:=$(x)bar

x:=foo

那么,y的值是“bar”,⽽不是“foobar”。

上⾯都是⼀些⽐较简单的变量使⽤了,让我们来看⼀个复杂的例⼦,其中包括了make的函数、条件表达式和⼀个系统变量“MAKELEVEL”的

使⽤:

ifeq(0,${MAKELEVEL})

cur-dir:=$(shellpwd)

whoami:=$(shellwhoami)

host-type:=$(shellarch)

MAKE:=${MAKE}host-type=${host-type}whoami=${whoami}

endif

关于条件表达式和函数,我们在后⾯再说,对于系统变量“MAKELEVEL”,其意思是,如果我们的make有⼀个嵌套执⾏的动作(参见前⾯

的“嵌套使⽤make”),那么,这个变量会记录了我们的当前Makefile的调⽤层数。

下⾯再介绍两个定义变量时我们需要知道的,请先看⼀个例⼦,如果我们要定义⼀个变量,其值是⼀个空格,那么我们可以这样来:

nullstring:=

space:=$(nullstring)#endoftheline

nullstring是⼀个Empty变量,其中什么也没有,⽽我们的space的值是⼀个空格。因为在操作符的右边是很难描述⼀个空格的,这⾥采⽤的

技术很管⽤,先⽤⼀个Empty变量来标明变量的值开始了,⽽后⾯采⽤“#”注释符来表⽰变量定义的终⽌,这样,我们可以定义出其值是⼀个

空格的变量。请注意这⾥关于“#”的使⽤,注释符“#”的这种特性值得我们注意,如果我们这样定义⼀个变量:

dir:=/foo/bar#directorytoputthefrobsin

dir这个变量的值是“/foo/bar”,后⾯还跟了4个空格,如果我们这样使⽤这样变量来指定别的⽬录——“$(dir)/file”那么就完蛋了。

还有⼀个⽐较有⽤的操作符是“?=”,先看⽰例:

FOO?=bar

其含义是,如果FOO没有被定义过,那么变量FOO的值就是“bar”,如果FOO先前被定义过,那么这条语将什么也不做,其等价于:

ifeq($(originFOO),undefined)

FOO=bar

endif

三、变量⾼级⽤法

这⾥介绍两种变量的⾼级使⽤⽅法,第⼀种是变量值的替换。

我们可以替换变量中的共有的部分,其格式是“$(var:a=b)”或是“${var:a=b}”,其意思是,把变量“var”中所有以“a”字串“结尾”的“a”替换成“b”字

串。这⾥的“结尾”意思是“空格”或是“结束符”。

还是看⼀个⽰例吧:

foo:=.o

bar:=$(foo:.o=.c)

这个⽰例中,我们先定义了⼀个“$(foo)”变量,⽽第⼆⾏的意思是把“$(foo)”中所有以“.o”字串“结尾”全部替换成“.c”,所以我们的“$(bar)”的值就

是“.c”。

另外⼀种变量替换的技术是以“静态模式”(参见前⾯章节)定义的,如:

foo:=.o

bar:=$(foo:%.o=%.c)

这依赖于被替换字串中的有相同的模式,模式中必须包含⼀个“%”字符,这个例⼦同样让$(bar)变量的值为“.c”。

第⼆种⾼级⽤法是——“把变量的值再当成变量”。先看⼀个例⼦:

x=y

y=z

a:=$($(x))

在这个例⼦中,$(x)的值是“y”,所以$($(x))就是$(y),于是$(a)的值就是“z”。(注意,是“x=y”,⽽不是“x=$(y)”)

我们还可以使⽤更多的层次:

x=y

y=z

z=u

a:=$($($(x)))

这⾥的$(a)的值是“u”,相关的推导留给读者⾃⼰去做吧。

让我们再复杂⼀点,使⽤上“在变量定义中使⽤变量”的第⼀个⽅式,来看⼀个例⼦:

x=$(y)

y=z

z=Hello

a:=$($(x))

这⾥的$($(x))被替换成了$($(y)),因为$(y)值是“z”,所以,最终结果是:a:=$(z),也就是“Hello”。

再复杂⼀点,我们再加上函数:

x=variable1

variable2:=Hello

y=$(subst1,2,$(x))

z=y

a:=$($($(z)))

这个例⼦中,“$($($(z)))”扩展为“$($(y))”,⽽其再次被扩展为“$($(subst1,2,$(x)))”。$(x)的值是“variable1”,subst函数把“variable1”中的所

有“1”字串替换成“2”字串,于是,“variable1”变成“variable2”,再取其值,所以,最终,$(a)的值就是$(variable2)的值——“Hello”。(喔,好

不容易)

在这种⽅式中,或要可以使⽤多个变量来组成⼀个变量的名字,然后再取其值:

first_cond=Hello

a=first

b=cond

all=$($a_$b)

这⾥的“$a_$b”组成了“first_cond”,于是,$(all)的值就是“Hello”。

再来看看结合第⼀种技术的例⼦:

a_objects:=.o

1_objects:=1.o2.o3.o

sources:=$($(a1)_objects:.o=.c)

这个例⼦中,如果$(a1)的值是“a”的话,那么,$(sources)的值就是“.c”;如果$(a1)的值是“1”,那么$(sources)的值是“1.c2.c3.c”。

再来看⼀个这种技术和“函数”与“条件语句”⼀同使⽤的例⼦:

ifdefdo_sort

func:=sort

el

func:=strip

endif

bar:=adbgqc

foo:=$($(func)$(bar))

这个⽰例中,如果定义了“do_sort”,那么:foo:=$(sortadbgqc),于是$(foo)的值就是“abcdgq”,⽽如果没有定义“do_sort”,那么:

foo:=$(sortadbgqc),调⽤的就是strip函数。

当然,“把变量的值再当成变量”这种技术,同样可以⽤在操作符的左边:

dir=foo

$(dir)_sources:=$(wildcard$(dir)/*.c)

define$(dir)_print

lpr$($(dir)_sources)

endef

这个例⼦中定义了三个变量:“dir”,“foo_sources”和“foo_print”。

四、追加变量值

我们可以使⽤“+=”操作符给变量追加值,如:

objects=.o

objects+=another.o

于是,我们的$(objects)值变成:“er.o”(another.o被追加进去了)

使⽤“+=”操作符,可以模拟为下⾯的这种例⼦:

objects=.o

objects:=$(objects)another.o

所不同的是,⽤“+=”更为简洁。

如果变量之前没有定义过,那么,“+=”会⾃动变成“=”,如果前⾯有变量定义,那么“+=”会继承于前次操作的赋值符。如果前⼀次的是“:=”,那

么“+=”会以“:=”作为其赋值符,如:

variable:=value

variable+=more

等价于:

variable:=value

variable:=$(variable)more

但如果是这种情况:

variable=value

variable+=more

由于前次的赋值符是“=”,所以“+=”也会以“=”来做为赋值,那么岂不会发⽣变量的递补归定义,这是很不好的,所以make会⾃动为我们解决

这个问题,我们不必担⼼这个问题。

五、override指⽰符

如果有变量是通常make的命令⾏参数设置的,那么Makefile中对这个变量的赋值会被忽略。如果你想在Makefile中设置这类参数的值,那

么,你可以使⽤“override”指⽰符。其语法是:

override=

override:=

当然,你还可以追加:

override+=

对于多⾏的变量定义,我们⽤define指⽰符,在define指⽰符前,也同样可以使⽤ovveride指⽰符,如:

overridedefinefoo

bar

endef

六、多⾏变量

还有⼀种设置变量值的⽅法是使⽤define关键字。使⽤define关键字设置变量的值可以有换⾏,这有利于定义⼀系列的命令(前⾯我们讲

过“命令包”的技术就是利⽤这个关键字)。

define指⽰符后⾯跟的是变量的名字,⽽重起⼀⾏定义变量的值,定义是以endef关键字结束。其⼯作⽅式和“=”操作符⼀样。变量的值可以

包含函数、命令、⽂字,或是其它变量。因为命令需要以[Tab]键开头,所以如果你⽤define定义的命令变量中没有以[Tab]键开头,那么

make就不会把其认为是命令。

下⾯的这个⽰例展⽰了define的⽤法:

definetwo-lines

echofoo

echo$(bar)

endef

七、环境变量

make运⾏时的系统环境变量可以在make开始运⾏时被载⼊到Makefile⽂件中,但是如果Makefile中已定义了这个变量,或是这个变量由

make命令⾏带⼊,那么系统的环境变量的值将被覆盖。(如果make指定了“-e”参数,那么,系统环境变量将覆盖Makefile中定义的变量)

因此,如果我们在环境变量中设置了“CFLAGS”环境变量,那么我们就可以在所有的Makefile中使⽤这个变量了。这对于我们使⽤统⼀的编

译参数有⽐较⼤的好处。如果Makefile中定义了CFLAGS,那么则会使⽤Makefile中的这个变量,如果没有定义则使⽤系统环境变量的值,

⼀个共性和个性的统⼀,很像“全局变量”和“局部变量”的特性。

当make嵌套调⽤时(参见前⾯的“嵌套调⽤”章节),上层Makefile中定义的变量会以系统环境变量的⽅式传递到下层的Makefile中。当然,

默认情况下,只有通过命令⾏设置的变量会被传递。⽽定义在⽂件中的变量,如果要向下层Makefile传递,则需要使⽤exprot关键字来声

明。(参见前⾯章节)

当然,我并不推荐把许多的变量都定义在系统环境中,这样,在我们执⾏不⽤的Makefile时,拥有的是同⼀套系统变量,这可能会带来更多

的⿇烦。

⼋、⽬标变量

前⾯我们所讲的在Makefile中定义的变量都是“全局变量”,在整个⽂件,我们都可以访问这些变量。当然,“⾃动化变量”除外,如“$<”等这种

类量的⾃动化变量就属于“规则型变量”,这种变量的值依赖于规则的⽬标和依赖⽬标的定义。

当然,我样同样可以为某个⽬标设置局部变量,这种变量被称为“Target-specificVariable”,它可以和“全局变量”同名,因为它的作⽤范围只

在这条规则以及连带规则中,所以其值也只在作⽤范围内有效。⽽不会影响规则链以外的全局变量的值。

其语法是:

:

:overide

可以是前⾯讲过的各种赋值表达式,如“=”、“:=”、“+=”或是“?=”。第⼆个语法是针对于make命令⾏带⼊的变量,或是系统环境变量。

这个特性⾮常的有⽤,当我们设置了这样⼀个变量,这个变量会作⽤到由这个⽬标所引发的所有的规则中去。如:

prog:CFLAGS=-g

prog:.o

$(CC)$(CFLAGS).o

prog.o:prog.c

$(CC)$(CFLAGS)prog.c

foo.o:foo.c

$(CC)$(CFLAGS)foo.c

bar.o:bar.c

$(CC)$(CFLAGS)bar.c

在这个⽰例中,不管全局的$(CFLAGS)的值是什么,在prog⽬标,以及其所引发的所有规则中(.o的规则),$(CFLAGS)的

值都是“-g”

九、模式变量

在GNU的make中,还⽀持模式变量(Pattern-specificVariable),通过上⾯的⽬标变量中,我们知道,变量可以定义在某个⽬标上。模式

变量的好处就是,我们可以给定⼀种“模式”,可以把变量定义在符合这种模式的所有⽬标上。

我们知道,make的“模式”⼀般是⾄少含有⼀个“%”的,所以,我们可以以如下⽅式给所有以[.o]结尾的⽬标定义⽬标变量:

%.o:CFLAGS=-O

同样,模式变量的语法和“⽬标变量”⼀样:

:

:override

override同样是针对于系统环境传⼊的变量,或是make命令⾏指定的变量。

使⽤条件判断

——————

使⽤条件判断,可以让make根据运⾏时的不同情况选择不同的执⾏分⽀。条件表达式可以是⽐较变量的值,或是⽐较变量和常量的值。

⼀、⽰例

下⾯的例⼦,判断$(CC)变量是否“gcc”,如果是的话,则使⽤GNU函数编译⽬标。

libs_for_gcc=-lgnu

normal_libs=

foo:$(objects)

ifeq($(CC),gcc)

$(CC)-ofoo$(objects)$(libs_for_gcc)

el

$(CC)-ofoo$(objects)$(normal_libs)

endif

可见,在上⾯⽰例的这个规则中,⽬标“foo”可以根据变量“$(CC)”值来选取不同的函数库来编译程序。

我们可以从上⾯的⽰例中看到三个关键字:ifeq、el和endif。ifeq的意思表⽰条件语句的开始,并指定⼀个条件表达式,表达式包含两个

参数,以逗号分隔,表达式以圆括号括起。el表⽰条件表达式为假的情况。endif表⽰⼀个条件语句的结束,任何⼀个条件表达式都应该以

endif结束。

当我们的变量$(CC)值是“gcc”时,⽬标foo的规则是:

foo:$(objects)

$(CC)-ofoo$(objects)$(libs_for_gcc)

⽽当我们的变量$(CC)值不是“gcc”时(⽐如“cc”),⽬标foo的规则是:

foo:$(objects)

$(CC)-ofoo$(objects)$(normal_libs)

当然,我们还可以把上⾯的那个例⼦写得更简洁⼀些:

libs_for_gcc=-lgnu

normal_libs=

ifeq($(CC),gcc)

libs=$(libs_for_gcc)

el

libs=$(normal_libs)

endif

foo:$(objects)

$(CC)-ofoo$(objects)$(libs)

⼆、语法

条件表达式的语法为:

endif

以及:

el

endif

其中表⽰条件关键字,如“ifeq”。这个关键字有四个。

第⼀个是我们前⾯所见过的“ifeq”

ifeq(,)

ifeq''''

ifeq""""

ifeq""''

ifeq''""

⽐较参数“arg1”和“arg2”的值是否相同。当然,参数中我们还可以使⽤make的函数。如:

ifeq($(strip$(foo)),)

endif

这个⽰例中使⽤了“strip”函数,如果这个函数的返回值是空(Empty),那么就⽣效。

第⼆个条件关键字是“ifneq”。语法是:

ifneq(,)

ifneq''''

ifneq""""

ifneq""''

ifneq''""

其⽐较参数“arg1”和“arg2”的值是否相同,如果不同,则为真。和“ifeq”类似。

第三个条件关键字是“ifdef”。语法是:

ifdef

如果变量的值⾮空,那到表达式为真。否则,表达式为假。当然,同样可以是⼀个函数的返回值。注意,ifdef只是测试⼀个变量是否有值,

其并不会把变量扩展到当前位置。还是来看两个例⼦:

⽰例⼀:

bar=

foo=$(bar)

ifdeffoo

frobozz=yes

el

frobozz=no

endif

⽰例⼆:

foo=

ifdeffoo

frobozz=yes

el

frobozz=no

endif

第⼀个例⼦中,“$(frobozz)”值是“yes”,第⼆个则是“no”。

第四个条件关键字是“ifndef”。其语法是:

ifndef

这个我就不多说了,和“ifdef”是相反的意思。

在这⼀⾏上,多余的空格是被允许的,但是不能以[Tab]键做为开始(不然就被认为是命令)。⽽注释符“#”同样也是安全的。

“el”和“endif”也⼀样,只要不是以[Tab]键开始就⾏了。

特别注意的是,make是在读取Makefile时就计算条件表达式的值,并根据条件表达式的值来选择语句,所以,你最好不要把⾃动化变量

(如“$@”等)放⼊条件表达式中,因为⾃动化变量是在运⾏时才有的。

⽽且,为了避免混乱,make不允许把整个条件语句分成两部分放在不同的⽂件中。

使⽤函数

————

在Makefile中可以使⽤函数来处理变量,从⽽让我们的命令或是规则更为的灵活和具有智能。make所⽀持的函数也不算很多,不过已经⾜够

我们的操作了。函数调⽤后,函数的返回值可以当做变量来使⽤。

⼀、函数的调⽤语法

函数调⽤,很像变量的使⽤,也是以“$”来标识的,其语法如下:

$()

或是

${}

这⾥,就是函数名,make⽀持的函数不多。是函数的参数,参数间以逗号“,”分隔,⽽函数名和参数之间以“空格”分隔。函数调⽤以“$”开头,

以圆括号或花括号把函数名和参数括起。感觉很像⼀个变量,是不是?函数中的参数可以使⽤变量,为了风格的统⼀,函数和变量的括号最

好⼀样,如使⽤“$(substa,b,$(x))”这样的形式,⽽不是“$(substa,b,${x})”的形式。因为统⼀会更清楚,也会减少⼀些不必要的⿇烦。

还是来看⼀个⽰例:

comma:=,

empty:=

space:=$(empty)$(empty)

foo:=abc

bar:=$(subst$(space),$(comma),$(foo))

在这个⽰例中,$(comma)的值是⼀个逗号。$(space)使⽤了$(empty)定义了⼀个空格,$(foo)的值是“abc”,$(bar)的定义⽤,调⽤了函

数“subst”,这是⼀个替换函数,这个函数有三个参数,第⼀个参数是被替换字串,第⼆个参数是替换字串,第三个参数是替换操作作⽤的字

串。这个函数也就是把$(foo)中的空格替换成逗号,所以$(bar)的值是“a,b,c”。

⼆、字符串处理函数

$(subst,,)

名称:字符串替换函数——subst。

功能:把字串中的字符串替换成。

返回:函数返回被替换过后的字符串。

⽰例:

$(substee,EE,feetonthestreet),

把“feetonthestreet”中的“ee”替换成“EE”,返回结果是“fEEtonthestrEEt”。

$(patsubst,,)

名称:模式字符串替换函数——patsubst。

功能:查找中的单词(单词以“空格”、“Tab”或“回车”“换⾏”分隔)是否符合模式,如果匹配的话,则以替换。这⾥,可以包括通配符“%”,表

⽰任意长度的字串。如果中也包含“%”,那么,中的这个“%”将是中的那个“%”所代表的字串。(可以⽤“”来转义,以“%”来表⽰真实含义

的“%”字符)

返回:函数返回被替换过后的字符串。

⽰例:

$(patsubst%.c,%.o,.c)

把字串“.c”符合模式[%.c]的单词替换成[%.o],返回结果是“.o”

备注:

这和我们前⾯“变量章节”说过的相关知识有点相似。如:

“$(var:=)”

相当于

“$(patsubst,,$(var))”,

⽽“$(var:=)”

则相当于

“$(patsubst%,%,$(var))”。

例如有:objects=.o,

那么,“$(objects:.o=.c)”和“$(patsubst%.o,%.c,$(objects))”是⼀样的。

$(strip)

名称:去空格函数——strip。

功能:去掉字串中开头和结尾的空字符。

返回:返回被去掉空格的字符串值。

⽰例:

$(stripabc)

把字串“abc”去到开头和结尾的空格,结果是“abc”。

$(findstring,)

名称:查找字符串函数——findstring。

功能:在字串中查找字串。

返回:如果找到,那么返回,否则返回空字符串。

⽰例:

$(findstringa,abc)

$(findstringa,bc)

第⼀个函数返回“a”字符串,第⼆个返回“”字符串(空字符串)

$(filter,)

名称:过滤函数——filter。

功能:以模式过滤字符串中的单词,保留符合模式的单词。可以有多个模式。

返回:返回符合模式的字串。

⽰例:

sources:=.h

foo:$(sources)

cc$(filter%.c%.s,$(sources))-ofoo

$(filter%.c%.s,$(sources))返回的值是“.s”。

$(filter-out,)

名称:反过滤函数——filter-out。

功能:以模式过滤字符串中的单词,去除符合模式的单词。可以有多个模式。

返回:返回不符合模式的字串。

⽰例:

objects=.o

mains=2.o

$(filter-out$(mains),$(objects))返回值是“.o”。

$(sort)

名称:排序函数——sort。

功能:给字符串中的单词排序(升序)。

返回:返回排序后的字符串。

⽰例:$(sortfoobarlo)返回“barfoolo”。

备注:sort函数会去掉中相同的单词。

$(word,)

名称:取单词函数——word。

功能:取字符串中第个单词。(从⼀开始)

返回:返回字符串中第个单词。如果⽐中的单词数要⼤,那么返回空字符串。

⽰例:$(word2,foobarbaz)返回值是“bar”。

$(wordlist,,)

名称:取单词串函数——wordlist。

功能:从字符串中取从开始到的单词串。和是⼀个数字。

返回:返回字符串中从到的单词字串。如果⽐中的单词数要⼤,那么返回空字符串。如果⼤于的单词数,那么返回从开始,到结束的单词

串。

⽰例:$(wordlist2,3,foobarbaz)返回值是“barbaz”。

$(words)

名称:单词个数统计函数——words。

功能:统计中字符串中的单词个数。

返回:返回中的单词数。

⽰例:$(words,foobarbaz)返回值是“3”。

备注:如果我们要取中最后的⼀个单词,我们可以这样:$(word$(words),)。

$(firstword)

名称:⾸单词函数——firstword。

功能:取字符串中的第⼀个单词。

返回:返回字符串的第⼀个单词。

⽰例:$(firstwordfoobar)返回值是“foo”。

备注:这个函数可以⽤word函数来实现:$(word1,)。

以上,是所有的字符串操作函数,如果搭配混合使⽤,可以完成⽐较复杂的功能。这⾥,举⼀个现实中应⽤的例⼦。我们知道,make使

⽤“VPATH”变量来指定“依赖⽂件”的搜索路径。于是,我们可以利⽤这个搜索路径来指定编译器对头⽂件的搜索路径参数CFLAGS,如:

overrideCFLAGS+=$(patsubst%,-I%,$(subst:,,$(VPATH)))

如果我们的“$(VPATH)”值是“src:../headers”,那么“$(patsubst%,-I%,$(subst:,,$(VPATH)))”将返回“-Isrc-I../headers”,这正是cc或gcc搜索头

⽂件路径的参数。

三、⽂件名操作函数

下⾯我们要介绍的函数主要是处理⽂件名的。每个函数的参数字符串都会被当做⼀个或是⼀系列的⽂件名来对待。

$(dir)

名称:取⽬录函数——dir。

功能:从⽂件名序列中取出⽬录部分。⽬录部分是指最后⼀个反斜杠(“/”)之前的部分。如果没有反斜杠,那么返回“./”。

返回:返回⽂件名序列的⽬录部分。

⽰例:$(dirsrc/)返回值是“src/./”。

$(notdir)

名称:取⽂件函数——notdir。

功能:从⽂件名序列中取出⾮⽬录部分。⾮⽬录部分是指最后⼀个反斜杠(“/”)之后的部分。

返回:返回⽂件名序列的⾮⽬录部分。

⽰例:$(notdirsrc/)返回值是“”。

$(suffix)

名称:取后缀函数——suffix。

功能:从⽂件名序列中取出各个⽂件名的后缀。

返回:返回⽂件名序列的后缀序列,如果⽂件没有后缀,则返回空字串。

⽰例:$(suffixsrc/-1.0/)返回值是“.c.c”。

$(baname)

名称:取前缀函数——baname。

功能:从⽂件名序列中取出各个⽂件名的前缀部分。

返回:返回⽂件名序列的前缀序列,如果⽂件没有前缀,则返回空字串。

⽰例:$(banamesrc/-1.0/)返回值是“src/foosrc-1.0/barhacks”。

$(addsuffix,)

名称:加后缀函数——addsuffix。

功能:把后缀加到中的每个单词后⾯。

返回:返回加过后缀的⽂件名序列。

⽰例:$(addsuffix.c,foobar)返回值是“.c”。

$(addprefix,)

名称:加前缀函数——addprefix。

功能:把前缀加到中的每个单词后⾯。

返回:返回加过前缀的⽂件名序列。

⽰例:$(addprefixsrc/,foobar)返回值是“src/foosrc/bar”。

$(join,)

名称:连接函数——join。

功能:把中的单词对应地加到的单词后⾯。如果的单词个数要⽐的多,那么,中的多出来的单词将保持原样。如果的单词个数要⽐多,那

么,多出来的单词将被复制到中。

返回:返回连接过后的字符串。

⽰例:$(joinaaabbb,111222333)返回值是“aaa111bbb222333”。

四、foreach函数

foreach函数和别的函数⾮常的不⼀样。因为这个函数是⽤来做循环⽤的,Makefile中的foreach函数⼏乎是仿照于Unix标准Shell(/bin/sh)

中的for语句,或是C-Shell(/bin/csh)中的foreach语句⽽构建的。它的语法是:

$(foreach,,)

这个函数的意思是,把参数中的单词逐⼀取出放到参数所指定的变量中,然后再执⾏所包含的表达式。每⼀次会返回⼀个字符串,循环过程

中,的所返回的每个字符串会以空格分隔,最后当整个循环结束时,所返回的每个字符串所组成的整个字符串(以空格分隔)将会是

foreach函数的返回值。

所以,最好是⼀个变量名,可以是⼀个表达式,⽽中⼀般会使⽤这个参数来依次枚举中的单词。举个例⼦:

names:=abcd

files:=$(foreachn,$(names),$(n).o)

上⾯的例⼦中,$(name)中的单词会被挨个取出,并存到变量“n”中,“$(n).o”每次根据“$(n)”计算出⼀个值,这些值以空格分隔,最后作为

foreach函数的返回,所以,$(files)的值是“.o”。

注意,foreach中的参数是⼀个临时的局部变量,foreach函数执⾏完后,参数的变量将不在作⽤,其作⽤域只在foreach函数当中。

本文发布于:2022-11-22 17:41:50,感谢您对本站的认可!

本文链接:http://www.wtabcd.cn/fanwen/fan/90/584.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

上一篇:雄伟的什么
下一篇:欢喜佛是什么
相关文章
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图