共计 118014 个字符,预计需要花费 296 分钟才能阅读完成。
什么是 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 进行讲述,我的环境是 RedHat Linux 8.0,make 的版本是 3.80。必竟,这个 make 是利用最为宽泛的,也是用得最多的。而且其还是最遵循于 IEEE 1003.2-1992 规范的(POSIX.2)。
在这篇文档中,将以 C /C++ 的源码作为咱们根底,所以必然波及一些对于 C /C++ 的编译的常识,相干于这方面的内容,还请各位查看相干的编译器的文档。这里所默认的编译器是 UNIX 下的 GCC 和 CC。
0.1 对于程序的编译和链接
在此,我想多说对于程序编译的一些标准和办法,一般来说,无论是 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 谬误,意思说是说,链接器未能找到函数的实现。你须要指定函数的 ObjectFile.
好,言归正传,GNU 的 make 有许多的内容,闲言少叙,还是让咱们开始吧。
1 Makefile 介绍
make 命令执行时,须要一个 Makefile 文件,以通知 make 命令须要怎么样的去编译和链接程序。
首先,咱们用一个示例来阐明 Makefile 的书写规定。以便给大家一个感兴意识。这个示例来源于 GNU 的 make 使用手册,在这个示例中,咱们的工程有 8 个 C 文件,和 3 个头文件,咱们要写一个 Makefile 来通知 make 命令如何编译和链接这几个文件。咱们的规定是:
1. 如果这个工程没有编译过,那么咱们的所有 C 文件都要编译并被链接。
2. 如果这个工程的某几个 C 文件被批改,那么咱们只编译被批改的 C 文件,并链接目标程序。
3. 如果这个工程的头文件被扭转了,那么咱们须要编译援用了这几个头文件的 C 文件,并链接目标程序。
只有咱们的 Makefile 写得够好,所有的这所有,咱们只用一个 make 命令就能够实现,make 命令会主动智能地依据以后的文件批改的状况来确定哪些文件须要重编译,从而本人编译所须要的文件和链接目标程序。
1.1 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 还不够,我会以前面一点一点地联合我的工作教训给你缓缓到来。内容还多着呢。:)
1.2 一个示例
正如后面所说的,如果一个工程有 3 个头文件,和 8 个 C 文件,咱们为了实现后面所述的那三个规定,咱们的 Makefile 应该是上面的这个样子的。
edit : main.o kbd.o command.o display.o
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o
insert.o search.o files.o utils.o
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
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 中定义不必的编译或是和编译无关的命令,比方程序的打包,程序的备份,等等。
1.3 make 是如何工作的
在默认的形式下,也就是咱们只输出 make 命令。那么,
- make 会在当前目录下找名字叫“Makefile”或“makefile”的文件。
- 如果找到,它会找文件中的第一个指标文件(target),在下面的例子中,他会找到“edit”这个文件,并把这个文件作为最终的指标文件。
- 如果 edit 文件不存在,或是 edit 所依赖的前面的 .o 文件的文件批改工夫要比 edit 这个文件新,那么,他就会执行前面所定义的命令来生成 edit 这个文件。
- 如果 edit 所依赖的.o 文件也存在,那么 make 会在以后文件中找指标为.o 文件的依赖性,如果找到则再依据那一个规定生成.o 文件。(这有点像一个堆栈的过程)
- 当然,你的 C 文件和 H 文件是存在的啦,于是 make 会生成 .o 文件,而后再用 .o 文件申明 make 的终极工作,也就是执行文件 edit 了。
这就是整个 make 的依赖性,make 会一层又一层地去找文件的依赖关系,直到最终编译出第一个指标文件。在找寻的过程中,如果呈现谬误,比方最初被依赖的文件找不到,那么 make 就会间接退出,并报错,而对于所定义的命令的谬误,或是编译不胜利,make 基本不理。make 只管文件的依赖性,即,如果在我找了依赖关系之后,冒号前面的文件还是不在,那么对不起,我就不工作啦。
通过上述剖析,咱们晓得,像 clean 这种,没有被第一个指标文件间接或间接关联,那么它前面所定义的命令将不会被主动执行,不过,咱们能够显示要 make 执行。即命令——“make clean”,以此来革除所有的指标文件,以便重编译。
于是在咱们编程中,如果这个工程已被编译过了,当咱们批改了其中一个源文件,比方 file.c,那么依据咱们的依赖性,咱们的指标 file.o 会被重编译(也就是在这个依性关系前面所定义的命令),于是 file.o 的文件也是最新的啦,于是 file.o 的文件批改工夫要比 edit 要新,所以 edit 也会被从新链接了(详见 edit 指标文件后定义的命令)。
而如果咱们扭转了“command.h”,那么,kdb.o、command.o 和 files.o 都会被重编译,并且,edit 会被重链接。
1.4 makefile 中应用变量
在下面的例子中,先让咱们看看 edit 的规定:
edit : main.o kbd.o command.o display.o
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o
insert.o search.o files.o utils.o
咱们能够看到 [.o] 文件的字符串被反复了两次,如果咱们的工程须要退出一个新的 [.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.osearch.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit $(objects)
于是如果有新的 .o 文件退出,咱们只需简略地批改一下 objects 变量就能够了。
对于变量更多的话题,我会在后续给你一一道来。
1.5 让 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.o
edit : $(objects)
cc -o edit $(objects)
main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h
.PHONY : clean
clean :
rm edit $(objects)
这种办法,也就是 make 的“费解规定”。下面文件内容中,“.PHONY”示意,clean 是个伪指标文件。
对于更为具体的“费解规定”和“伪指标文件”,我会在后续给你一一道来。
1.6 另类格调的 makefile
即然咱们的 make 能够主动推导命令,那么我看到那堆 [.o] 和[.h]的依赖就有点不爽,那么多的反复的[.h],能不能把其收拢起来,好吧,没有问题,这个对于 make 来说很容易,谁叫它提供了主动推导命令和文件的性能呢?来看看最新格调的 makefile 吧。
objects = main.o kbd.o command.o display.o
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
$(objects) : defs.h
kbd.o command.o files.o : command.h
display.o insert.o search.o files.o : buffer.h
.PHONY : clean
clean :
rm edit $(objects)
这种格调,让咱们的 makefile 变得很简略,但咱们的文件依赖关系就显得有点凌乱了。鱼和熊掌不可兼得。还看你的爱好了。我是不喜爱这种格调的,一是文件的依赖关系看不清楚,二是如果文件一多,要退出几个新的.o 文件,那就理不分明了。
1.7 清空指标文件的规定
每个 Makefile 中都应该写一个清空指标文件(.o 和执行文件)的规定,这不仅便于重编译,也很利于放弃文件的清洁。这是一个“涵养”(呵呵,还记得我的《编程涵养》吗)。个别的格调都是:
clean:
rm edit $(objects)
更为持重的做法是:
.PHONY : clean
clean :
-rm edit $(objects)
后面说过,.PHONY 意思示意 clean 是一个“伪指标”,。而在 rm 命令后面加了一个小减号的意思就是,兴许某些文件呈现问题,但不要管,持续做前面的事。当然,clean 的规定不要放在文件的结尾,不然,这就会变成 make 的默认指标,置信谁也不违心这样。不成文的规矩是——“clean 素来都是放在文件的最初”。
下面就是一个 makefile 的概貌,也是 makefile 的根底,上面还有很多 makefile 的相干细节,筹备好了吗?筹备好了就来。
2 Makefile 总述
2.1 Makefile 里有什么?
Makefile 里次要蕴含了 五个货色:显式规定、费解规定、变量定义、文件批示和正文。
- 显式规定。显式规定阐明了,如何生成一个或多的的指标文件。这是由 Makefile 的书写者显著指出,要生成的文件,文件的依赖文件,生成的命令。
- 费解规定。因为咱们的 make 有主动推导的性能,所以费解的规定能够让咱们比拟毛糙地简略地书写 Makefile,这是由 make 所反对的。
- 变量的定义。在 Makefile 中咱们要定义一系列的变量,变量个别都是字符串,这个有点你 C 语言中的宏,当 Makefile 被执行时,其中的变量都会被扩大到相应的援用地位上。
- 文件批示。其包含了三个局部,一个是在一个 Makefile 中援用另一个 Makefile,就像 C 语言中的 include 一样;另一个是指依据某些状况指定 Makefile 中的无效局部,就像 C 语言中的预编译 #if 一样;还有就是定义一个多行的命令。无关这一部分的内容,我会在后续的局部中讲述。
- 正文。Makefile 中只有行正文,和 UNIX 的 Shell 脚本一样,其正文是用“#”字符,这个就像 C /C++ 中的“//”一样。如果你要在你的 Makefile 中应用“#”字符,能够用反斜框进行本义,如:“#”。
最初,还值得一提的是,在 Makefile 中的命令,必须要以 [Tab] 键开始。
2.2Makefile 的文件名
默认的状况下,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。
2.3 援用其它的 Makefile
在 Makefile 应用 include 关键字能够把别的 Makefile 蕴含进来,这很像 C 语言的 #include,被蕴含的文件会原模原样的放在以后文件的蕴含地位。include 的语法是:
include<filename>filename 能够是以后操作系统 Shell 的文件模式(能够保含门路和通配符)
在 include 后面能够有一些空字符,然而绝不能是 [Tab] 键开始。include 和能够用一个或多个空格隔开。举个例子,你有这样几个 Makefile:a.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.mk
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<filename>
其示意,无论 include 过程中呈现什么谬误,都不要报错继续执行。和其它版本 make 兼容的相干命令是 sinclude,其作用和这一个是一样的。
2.4 环境变量 MAKEFILES
如果你的以后环境中定义了环境变量 MAKEFILES,那么,make 会把这个变量中的值做一个相似于 include 的动作。这个变量中的值是其它的 Makefile,用空格分隔。只是,它和 include 不同的是,从这个环境变中引入的 Makefile 的“指标”不会起作用,如果环境变量中定义的文件发现错误,make 也会不理。
然而在这里我还是倡议不要应用这个环境变量,因为只有这个变量一被定义,那么当你应用 make 时,所有的 Makefile 都会受到它的影响,这绝不是你想看到的。在这里提这个事,只是为了通知大家,兴许有时候你的 Makefile 呈现了怪事,那么你能够看看以后环境中有没有定义这个变量。
2.5 make 的工作形式
GNU 的 make 工作时的执行步骤入下:(想来其它的 make 也是相似)
1. 读入所有的 Makefile。
2. 读入被 include 的其它 Makefile。
3. 初始化文件中的变量。
4. 推导费解规定,并剖析所有规定。
5. 为所有的指标文件创建依赖关系链。
6. 依据依赖关系,决定哪些指标要从新生成。
7. 执行生成命令。
1- 5 步为第一个阶段,6- 7 为第二个阶段。第一个阶段中,如果定义的变量被应用了,那么,make 会把其开展在应用的地位。但 make 并不会齐全马上开展,make 应用的是拖延战术,如果变量呈现在依赖关系的规定中,那么仅当这条依赖被决定要应用了,变量才会在其外部开展。
当然,这个工作形式你不肯定要分明,然而晓得这个形式你也会对 make 更为相熟。有了这个根底,后续局部也就容易看懂了。
3 Makefile 书写规定
规定蕴含两个局部,一个是 依赖关系 ,一个是 生成指标的办法。
在 Makefile 中,规定的程序是很重要的,因为,Makefile 中只应该有一个最终目标,其它的指标都是被这个指标所连带进去的,所以肯定要让 make 晓得你的最终目标是什么。一般来说,定义在 Makefile 中的指标可能会有很多,然而第一条规定中的指标将被确立为最终的指标。如果第一条规定中的指标有很多个,那么,第一个指标会成为最终的指标。make 所实现的也就是这个指标。
好了,还是让咱们来看一看如何书写规定。
3.1 规定举例
foo.o: foo.c defs.h # foo 模块
cc -c -g foo.c
看到这个例子,各位应该不是很生疏了,后面也已说过,foo.o 是咱们的指标,foo.c 和 defs.h 是指标所依赖的源文件,而只有一个命令“cc -c -g foo.c”(以 Tab 键结尾)。这个规定通知咱们两件事:
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 文件)
3.2 规定的语法
targets : prerequisites
command
…
或是这样:
targets : prerequisites ; command
command
…
targets 是文件名,以空格离开,能够应用通配符。一般来说,咱们的指标基本上是一个文件,但也有可能是多个文件。
command 是命令行,如果其不与“target:prerequisites”在一行,那么,必须以 [Tab 键] 结尾,如果和 prerequisites 在一行,那么能够用分号做为分隔。(见上)
prerequisites 也就是指标所依赖的文件(或依赖指标)。如果其中的某个文件要比指标文件要新,那么,指标就被认为是“过期的”,被认为是须要重生成的。这个在后面曾经讲过了。
如果命令太长,你能够应用反斜框(‘’)作为换行符。make 对一行上有多少个字符没有限度。规定通知 make 两件事,文件的依赖关系和如何成成指标文件。
一般来说,make 会以 UNIX 的规范 Shell,也就是 /bin/sh 来执行命令。
3.3 在规定中应用通配符
如果咱们想定义一系列比拟相似的文件,咱们很天然地就想起应用通配符。make 反对三各通配符:“*”,“?”和“[…]”。这是和 Unix 的 B -Shell 是雷同的。
“~”
波浪号(“~”)字符在文件名中也有比拟非凡的用处。如果是“~/test”,这就示意以后用户的 $HOME 目录下的 test 目录。而“~hchen/test”则示意用户 hchen 的宿主目录下的 test 目录。(这些都是 Unix 下的小常识了,make 也反对)而在 Windows 或是 MS-DOS 下,用户没有宿主目录,那么波浪号所指的目录则依据环境变量“HOME”而定。
“*”
通配符代替了你一系列的文件,如“.c”示意所以后缀为 c 的文件。一个须要咱们留神的是,如果咱们的文件名中有通配符,如:“”,那么能够用转义字符“”,如“”来示意实在的“”字符,而不是任意长度的字符串。
好吧,还是先来看几个例子吧:
clean:
rm -f *.o
下面这个例子我不不多说了,这是操作系统 Shell 所反对的通配符。这是在命令中的通配符。
print: *.c
lpr -p $?
touch print
下面这个例子阐明了通配符也能够在咱们的规定中,指标 print 依赖于所有的 [.c] 文件。其中的“$?”是一个自动化变量,我会在前面给你讲述。
objects = *.o
下面这个例子,示意了,通符同样能够用在变量中。并不是说 [.o] 会开展,不!objects 的值就是“.o”。Makefile 中的变量其实就是 C /C++ 中的宏。如果你要让通配符在变量中开展,也就是让 objects 的值是所有 [.o] 的文件名的汇合,那么,你能够这样:
objects := $(wildcard *.o)
这种用法由关键字“wildcard”指出,对于 Makefile 的关键字,咱们将在前面探讨。
3.4 文件搜查
在一些大的工程中,有大量的源文件,咱们通常的做法是把这许多的源文件分类,并存放在不同的目录中。所以,当 make 须要去找寻文件的依赖关系时,你能够在文件前加上门路,但最好的办法是把一个门路通知 make,让 make 在主动去找。
Makefile 文件中的非凡变量“VPATH”就是实现这个性能的,如果没有指明这个变量,make 只会在以后的目录中去找寻依赖文件和指标文件。如果定义了这个变量,那么,make 就会在当当前目录找不到的状况下,到所指定的目录中去找寻文件了。
VPATH = src:../headers
下面的的定义指定两个目录,“src”和“../headers”,make 会依照这个程序进行搜寻。目录由“冒号”分隔。(当然,当前目录永远是最高优先搜寻的中央)
另一个设置文件搜寻门路的办法是应用 make 的“vpath”关键字(留神,它是全小写的),这不是变量,这是一个 make 的关键字,这和下面提到的那个 VPATH 变量很相似,然而它更为灵便。它能够指定不同的文件在不同的搜寻目录中。这是一个很灵便的性能。它的应用办法有三种:
1. vpath < pattern> < directories> 为合乎模式 < pattern> 的文件指定搜寻目录 <directories>。
2. vpath < pattern> 革除合乎模式 < pattern> 的文件的搜寻目录。
3. vpath 革除所有已被设置好了的文件搜寻目录。
vapth 应用办法中的 < pattern> 须要蕴含“%”字符。“%”的意思是匹配零或若干字符,例如,“%.h”示意所有以“.h”结尾的文件。< pattern> 指定了要搜寻的文件集,而 < directories> 则指定了的文件集的搜寻的目录。例如:
vpath %.h ../headers
该语句示意,要求 make 在“../headers”目录下搜寻所有以“.h”结尾的文件。(如果某文件在当前目录没有找到的话)
咱们能够间断地应用 vpath 语句,以指定不同搜寻策略。如果间断的 vpath 语句中呈现了雷同的 < pattern>,或是被反复了的 < pattern>,那么,make 会依照 vpath 语句的先后顺序来执行搜寻。如:
vpath %.c foo
vpath % blish
vpath %.c bar
其示意“.c”结尾的文件,先在“foo”目录,而后是“blish”,最初是“bar”目录。
vpath %.c foo:bar
vpath % blish
而下面的语句则示意“.c”结尾的文件,先在“foo”目录,而后是“bar”目录,最初才是“blish”目录。
3.5 伪指标
最早先的一个例子中,咱们提到过一个“clean”的指标,这是一个“伪指标”,
clean:
rm *.o temp
正像咱们后面例子中的“clean”一样,即然咱们生成了许多文件编译文件,咱们也应该提供一个革除它们的“指标”以备残缺地重编译而用。(以“make clean”来应用该指标)
因为,咱们并不生成“clean”这个文件。“伪指标”并不是一个文件,只是一个标签,因为“伪指标”不是文件,所以 make 无奈生成它的依赖关系和决定它是否要执行。咱们只有通过显示地指明这个“指标”能力让其失效。当然,“伪指标”的取名不能和文件名重名,不然其就失去了“伪指标”的意义了。
当然,为了防止和文件重名的这种状况,咱们能够应用一个非凡的标记“.PHONY”来显示地指明一个指标是“伪指标”,向 make 阐明,不论是否有这个文件,这个指标就是“伪指标”。
.PHONY : clean
只有有这个申明,不论是否有“clean”文件,要运行“clean”这个指标,只有“make clean”这样。于是整个过程能够这样写:
.PHONY: clean
clean:
rm *.o temp
伪指标个别没有依赖的文件。然而,咱们也能够为伪指标指定所依赖的文件。伪指标同样能够作为“默认指标”,只有将其放在第一个。一个示例就是,如果你的 Makefile 须要一口气生成若干个可执行文件,但你只想简略地敲一个 make 完事,并且,所有的指标文件都写在一个 Makefile 中,那么你能够应用“伪指标”这个个性:
all : prog1 prog2 prog3
.PHONY : all
prog1 : prog1.o utils.o
cc -o prog1 prog1.o utils.o
prog2 : prog2.o
cc -o prog2 prog2.o
prog3 : prog3.o sort.o utils.o
cc -o prog3 prog3.o sort.o utils.o
咱们晓得,Makefile 中的第一个指标会被作为其默认指标。咱们申明了一个“all”的伪指标,其依赖于其它三个指标。因为伪指标的个性是,总是被执行的,所以其依赖的那三个指标就总是不如“all”这个指标新。所以,其它三个指标的规定总是会被决定。也就达到了咱们一口气生成多个指标的目标。“.PHONY : all”申明了“all”这个指标为“伪指标”。
轻易提一句,从下面的例子咱们能够看出,指标也能够成为依赖。所以,伪指标同样也可成为依赖。看上面的例子:
.PHONY: cleanall cleanobj cleandiff
cleanall : cleanobj cleandiff
rm program
cleanobj :
rm *.o
cleandiff :
rm *.diff
“makeclean”将革除所有要被革除的文件。“cleanobj”和“cleandiff”这两个伪指标有点像“子程序”的意思。咱们能够输出“makecleanall”和“make cleanobj”和“makecleandiff”命令来达到革除不同品种文件的目标
3.6 多指标
Makefile 的规定中的指标能够不止一个,其反对多指标,有可能咱们的多个指标同时依赖于一个文件,并且其生成的命令大体相似。于是咱们就能把其合并起来。当然,多个指标的生成规定的执行命令是同一个,这可能会可咱们带来麻烦,不过好在咱们的能够应用一个自动化变量“$@”(对于自动化变量,将在前面讲述),这个变量示意着目前规定中所有的指标的汇合,这样说可能很形象,还是看一个例子吧。
bigoutput littleoutput : text.g
generate text.g -$(subst output,,$@) > $@
上述规定等价于:
bigoutput : text.g
generate text.g -big > bigoutput
littleoutput : text.g
generate text.g -little > littleoutput
其中,-$(subst output,,$@)中的“$”示意执行一个 Makefile 的函数,函数名为 subst,前面的为参数。对于函数,将在前面讲述。这里的这个函数是截取字符串的意思,“$@”示意指标的汇合,就像一个数组,“$@”顺次取出指标,并执于命令。
3.7 动态模式
动态模式能够更加容易地定义多指标的规定,能够让咱们的规定变得更加的有弹性和灵便。咱们还是先来看一下语法:
<targets…>: <target-pattern>: <prereq-patterns …>
<commands>
…
targets 定义了一系列的指标文件,能够有通配符。是指标的一个汇合。
target-parrtern 是指明了 targets 的模式,也就是的指标集模式。
prereq-parrterns 是指标的依赖模式,它对 target-parrtern 造成的模式再进行一次依赖指标的定义。
这样形容这三个货色,可能还是没有说分明,还是举个例子来阐明一下吧。如果咱们的 <target-parrtern> 定义成“%.o”,意思是咱们的汇合中都是以“.o”结尾的,而如果咱们的 <prereq-parrterns> 定义成“%.c”,意思是对 <target-parrtern> 所造成的指标集进行二次定义,其计算方法是,取 <target-parrtern> 模式中的“%”(也就是去掉了 [.o] 这个结尾),并为其加上 [.c] 这个结尾,造成的新汇合。
所以,咱们的“指标模式”或是“依赖模式”中都应该有“%”这个字符,如果你的文件名中有“%”那么你能够应用反斜杠“”进行本义,来表明实在的“%”字符。
看一个例子:
objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
下面的例子中,指明了咱们的指标从 $object 中获取,“%.o”表明要所有以“.o”结尾的指标,也就是“foo.o bar.o”,也就是变量 $object 汇合的模式,而依赖模式“%.c”则取模式“%.o”的“%”,也就是“foobar”,并为其加下“.c”的后缀,于是,咱们的依赖指标就是“foo.cbar.c”。而命令中的“$<”和“$@”则是自动化变量,“$<”示意所有的依赖指标集(也就是“foo.c bar.c”),“$@”示意指标集(也褪恰癴 oo.o bar.o”)。于是,下面的规定开展后等价于上面的规定:
foo.o : foo.c
$(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
$(CC) -c $(CFLAGS) bar.c -o bar.o
试想,如果咱们的“%.o”有几百个,那种咱们只有用这种很简略的“动态模式规定”就能够写完一堆规定,切实是太有效率了。“动态模式规定”的用法很灵便,如果用得好,那会一个很弱小的性能。再看一个例子:
files = foo.elc bar.o lose.o
$(filter %.o,$(files)): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
$(filter %.elc,$(files)): %.elc: %.el
emacs -f batch-byte-compile $<
$(filter%.o,$(files))示意调用 Makefile 的 filter 函数,过滤“$filter”集,只有其中模式为“%.o”的内容。其的它内容,我就不必多说了吧。这个例字展现了 Makefile 中更大的弹性。
3.8 主动生成依赖性
在 Makefile 中,咱们的依赖关系可能会须要蕴含一系列的头文件,比方,如果咱们的 main.c 中有一句“#include “defs.h””,那么咱们的依赖关系应该是:
main.o : main.c defs.h
然而,如果是一个比拟大型的工程,你必须分明哪些 C 文件蕴含了哪些头文件,并且,你在退出或删除头文件时,也须要小心地批改 Makefile,这是一个很没有维护性的工作。为了防止这种沉重而又容易出错的事件,咱们能够应用 C /C++ 编译的一个性能。大多数的 C /C++ 编译器都反对一个“-M”的选项,即主动找寻源文件中蕴含的头文件,并生成一个依赖关系。例如,如果咱们执行上面的命令:
cc -M main.c
其输入是:
main.o : main.c defs.h
于是由编译器主动生成的依赖关系,这样一来,你就不用再手动书写若干文件的依赖关系,而由编译器主动生成了。须要揭示一句的是,如果你应用 GNU 的 C /C++ 编译器,你得用“-MM”参数,不然,“-M”参数会把一些规范库的头文件也蕴含进来。
gcc-M main.c 的输入是:
main.o: main.c defs.h /usr/include/stdio.h /usr/include/features.h
/usr/include/sys/cdefs.h /usr/include/gnu/stubs.h
/usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stddef.h
/usr/include/bits/types.h /usr/include/bits/pthreadtypes.h
/usr/include/bits/sched.h /usr/include/libio.h
/usr/include/_G_config.h /usr/include/wchar.h
/usr/include/bits/wchar.h /usr/include/gconv.h
/usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stdarg.h
/usr/include/bits/stdio_lim.h
gcc-MM main.c 的输入则是:
main.o: main.c defs.h
那么,编译器的这个性能如何与咱们的 Makefile 分割在一起呢。因为这样一来,咱们的 Makefile 也要依据这些源文件从新生成,让 Makefile 自已依赖于源文件?这个性能并不事实,不过咱们能够有其它伎俩来曲折地实现这一性能。GNU 组织倡议把编译器为每一个源文件的主动生成的依赖关系放到一个文件中,为每一个“name.c”的文件都生成一个“name.d”的 Makefile 文件,[.d]文件中就寄存对应 [.c] 文件的依赖关系。
于是,咱们能够写出 [.c] 文件和 [.d] 文件的依赖关系,并让 make 自动更新或自成 [.d] 文件,并把其蕴含在咱们的主 Makefile 中,这样,咱们就能够自动化地生成每个文件的依赖关系了。
这里,咱们给出了一个模式规定来产生 [.d] 文件:
%.d: %.c
@set -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.c
include $(sources:.c=.d)
上述语句中的“$(sources:.c=.d)”中的“.c=.d”的意思是做一个替换,把变量 $(sources)所有 [.c] 的字串都替换成 [.d],对于这个“替换”的内容,在前面我会有更为具体的讲述。当然,你得留神秩序,因为 include 是按次来载入文件,最先载入的[.d] 文件中的指标会成为默认指标
4 Makefile 书写命令
每条规定中的命令和操作系统 Shell 的命令行是统一的。make 会一按程序一条一条的执行命令,每条命令的结尾必须以 [Tab] 键结尾,除非,命令是紧跟在依赖规定前面的分号后的。在命令行之间中的空格或是空行会被疏忽,然而如果该空格或空行是以 Tab 键结尾的,那么 make 会认为其是一个空命令。
咱们在 UNIX 下可能会应用不同的 Shell,然而 make 的命令默认是被“/bin/sh”——UNIX 的规范 Shell 解释执行的。除非你特地指定一个其它的 Shell。Makefile 中,“#”是正文符,很像 C /C++ 中的“//”,其后的本行字符都被正文。
4.1 显示命令
通常,make 会把其要执行的命令行在命令执行前输入到屏幕上。当咱们用“@”字符在命令行前,那么,这个命令将不被 make 显示进去,最具代表性的例子是,咱们用这个性能来像屏幕显示一些信息。如:
@echo 正在编译 XXX 模块 ……
当 make 执行时,会输入“正在编译 XXX 模块 ……”字串,但不会输入命令,如果没有“@”,那么,make 将输入:
echo 正在编译 XXX 模块 ……
正在编译 XXX 模块 ……
如果 make 执行时,带入 make 参数“-n”或“–just-print”,那么其只是显示命令,但不会执行命令,这个性能很有利于咱们调试咱们的 Makefile,看看咱们书写的命令是执行起来是什么样子的或是什么程序的。
而 make 参数“-s”或“–slient”则是全面禁止命令的显示。
4.2 命令执行
当依赖指标新于指标时,也就是当规定的指标须要被更新时,make 会一条一条的执行其后的命令。须要留神的是,如果你要让上一条命令的后果利用在下一条命令时,你应该应用分号分隔这两条命令。比方你的第一条命令是 cd 命令,你心愿第二条命令得在 cd 之后的根底上运行,那么你就不能把这两条命令写在两行上,而应该把这两条命令写在一行上,用分号分隔。如:
示例一:
exec:
cd /home/hchen
pwd
示例二:
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”等后缀。
4.3 命令出错
每当命令运行完后,make 会检测每个命令的返回码,如果命令返回胜利,那么 make 会执行下一条命令,当规定中所有的命令胜利返回后,这个规定就算是胜利实现了。如果一个规定中的某个命令出错了(命令退出码非零),那么 make 就会终止执行以后规定,这将有可能终止所有规定的执行。
有些时候,命令的出错并不示意就是谬误的。例如 mkdir 命令,咱们肯定须要建设一个目录,如果目录不存在,那么 mkdir 就胜利执行,高枕无忧,如果目录存在,那么就出错了。咱们之所以应用 mkdir 的意思就是肯定要有这样的一个目录,于是咱们就不心愿 mkdir 出错而终止规定的运行。
为了做到这一点,疏忽命令的出错,咱们能够在 Makefile 的命令行前加一个减号“-”(在 Tab 键之后),标记为不论命令出不出错都认为是胜利的。如:
clean:
-rm -f *.o
还有一个全局的方法是,给 make 加上“-i”或是“–ignore-errors”参数,那么,Makefile 中所有命令都会疏忽谬误。而如果一个规定是以“.IGNORE”作为指标的,那么这个规定中的所有命令将会疏忽谬误。这些是不同级别的避免命令出错的办法,你能够依据你的不同喜爱设置。
还有一个要提一下的 make 的参数的是“-k”或是“–keep-going”,这个参数的意思是,如果某规定中的命令出错了,那么就终目该规定的执行,但继续执行其它规定。
4.4 嵌套执行 make
在一些大的工程中,咱们会把咱们不同模块或是不同性能的源文件放在不同的目录中,咱们能够在每个目录中都书写一个该目录的 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 = value
export variable
其等价于:
export variable := value
其等价于:
variable := value
export variable
示例二:
export variable += value
其等价于:
variable += value
export 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”总是生效的。
4.5 定义命令包
如果 Makefile 中呈现一些雷同命令序列,那么咱们能够为这些雷同的命令序列定义一个变量。定义这种命令序列的语法以“define”开始,以“endef”完结,如:
define run-yacc
yacc $(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.o
program : $(objects)
cc -o program $(objects)
$(objects) : defs.h
变量会在应用它的中央准确地开展,就像 C /C++ 中的宏一样,例如:
foo = c
prog.o : prog.$(foo)
$(foo)$(foo) -$(foo) prog.$(foo)
开展后失去:
prog.o : prog.c
cc -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) -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 := foo bar
x := later
值得一提的是,这种办法,后面的变量不能应用前面的变量,只能应用后面已定义好了的变量。如果是这样:
y := $(x) bar
x := 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 line
nullstring 是一个 Empty 变量,其中什么也没有,而咱们的 space 的值是一个空格。因为在操作符的左边是很难形容一个空格的,这里采纳的技术很管用,先用一个 Empty 变量来表明变量的值开始了,而前面采纳“#”正文符来示意变量定义的终止,这样,咱们能够定义出其值是一个空格的变量。请留神这里对于“#”的应用,正文符“#”的这种个性值得咱们留神,如果咱们这样定义一个变量:
dir := /foo/bar # directory to put the frobs in
dir 这个变量的值是“/foo/bar”,前面还跟了 4 个空格,如果咱们这样应用这样变量来指定别的目录——“$(dir)/file”那么就完蛋了。
还有一个比拟有用的操作符是“?=”,先看示例:
FOO ?= bar
其含意是,如果 FOO 没有被定义过,那么变量 FOO 的值就是“bar”,如果 FOO 先前被定义过,那么这条语将什么也不做,其等价于:
ifeq ($(origin FOO), undefined)
FOO = bar
endif
三、变量高级用法
这里介绍两种变量的高级应用办法,第一种是变量值的替换。
咱们能够替换变量中的共有的局部,其格局是“$(var:a=b)”或是“${var:a=b}”,其意思是,把变量“var”中所有以“a”字串“结尾”的“a”替换成“b”字串。这里的“结尾”意思是“空格”或是“结束符”。
还是看一个示例吧:
foo := a.o b.o c.o
bar := $(foo:.o=.c)
这个示例中,咱们先定义了一个“$(foo)”变量,而第二行的意思是把“$(foo)”中所有以“.o”字串“结尾”全副替换成“.c”,所以咱们的“$(bar)”的值就是“a.c b.c c.c”。
另外一种变量替换的技术是以“动态模式”(参见后面章节)定义的,如:
foo := a.o b.o c.o
bar := $(foo:%.o=%.c)
这依赖于被替换字串中的有雷同的模式,模式中必须蕴含一个“%”字符,这个例子同样让 $(bar)变量的值为“a.c b.c c.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 = $(subst 1,2,$(x))
z = y
a := $($($(z)))
这个例子中,“$($($(z)))”扩大为“$($(y))”,而其再次被扩大为“$($(subst 1,2,$(x)))”。$(x)的值是“variable1”,subst 函数把“variable1”中的所有“1”字串替换成“2”字串,于是,“variable1”变成“variable2”,再取其值,所以,最终,$(a)的值就是 $(variable2)的值——“Hello”。(喔,好不容易)
在这种形式中,或要能够应用多个变量来组成一个变量的名字,而后再取其值:
first_second = Hello
a = first
b = second
all = $($a_$b)
这里的“$a_$b”组成了“first_second”,于是,$(all)的值就是“Hello”。
再来看看联合第一种技术的例子:
a_objects := a.o b.o c.o
1_objects := 1.o 2.o 3.o
sources := $($(a1)_objects:.o=.c)
这个例子中,如果 $(a1)的值是“a”的话,那么,$(sources)的值就是“a.c b.c c.c”;如果 $(a1)的值是“1”,那么 $(sources)的值是“1.c 2.c 3.c”。
再来看一个这种技术和“函数”与“条件语句”一起应用的例子:
ifdef do_sort
func := sort
else
func := strip
endif
bar := a d b g q c
foo := $($(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 bg q c),调用的就是 strip 函数。
当然,“把变量的值再当成变量”这种技术,同样能够用在操作符的右边:
dir = foo
$(dir)_sources := $(wildcard $(dir)/*.c)
define $(dir)_print
lpr $($(dir)_sources)
endef
这个例子中定义了三个变量:“dir”,“foo_sources”和“foo_print”。
**
四、追加变量值 **
咱们能够应用“+=”操作符给变量追加值,如:
objects = main.o foo.o bar.o utils.o
objects += another.o
于是,咱们的 $(objects)值变成:“main.o foo.o bar.o utils.o another.o”(another.o 被追加进去了)
应用“+=”操作符,能够模仿为上面的这种例子:
objects = main.o foo.o bar.o utils.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 <variable> = <value>
override <variable> := <value>
当然,你还能够追加:
override <variable> += <more text>
对于多行的变量定义,咱们用 define 批示符,在 define 批示符前,也同样能够应用 ovveride 批示符,如:
override define foo
bar
endef
六、多行变量
还有一种设置变量值的办法是应用 define 关键字。应用 define 关键字设置变量的值能够有换行,这有利于定义一系列的命令(后面咱们讲过“命令包”的技术就是利用这个关键字)。
define 批示符前面跟的是变量的名字,而重起一行定义变量的值,定义是以 endef 关键字完结。其工作形式和“=”操作符一样。变量的值能够蕴含函数、命令、文字,或是其它变量。因为命令须要以 [Tab] 键结尾,所以如果你用 define 定义的命令变量中没有以 [Tab] 键结尾,那么 make 就不会把其认为是命令。
上面的这个示例展现了 define 的用法:
define two-lines
echo foo
echo $(bar)
endef
七、环境变量
make 运行时的零碎环境变量能够在 make 开始运行时被载入到 Makefile 文件中,然而如果 Makefile 中已定义了这个变量,或是这个变量由 make 命令行带入,那么零碎的环境变量的值将被笼罩。(如果 make 指定了“-e”参数,那么,零碎环境变量将笼罩 Makefile 中定义的变量)
因而,如果咱们在环境变量中设置了“CFLAGS”环境变量,那么咱们就能够在所有的 Makefile 中应用这个变量了。这对于咱们应用对立的编译参数有比拟大的益处。如果 Makefile 中定义了 CFLAGS,那么则会应用 Makefile 中的这个变量,如果没有定义则应用零碎环境变量的值,一个共性和共性的对立,很像“全局变量”和“局部变量”的个性。当 make 嵌套调用时(参见后面的“嵌套调用”章节),下层 Makefile 中定义的变量会以零碎环境变量的形式传递到上层的 Makefile 中。当然,默认状况下,只有通过命令行设置的变量会被传递。而定义在文件中的变量,如果要向上层 Makefile 传递,则须要应用 exprot 关键字来申明。(参见后面章节)
当然,我并不举荐把许多的变量都定义在零碎环境中,这样,在咱们执行不必的 Makefile 时,领有的是同一套零碎变量,这可能会带来更多的麻烦。
**
八、指标变量 **
后面咱们所讲的在 Makefile 中定义的变量都是“全局变量”,在整个文件,咱们都能够拜访这些变量。当然,“自动化变量”除外,如“$<”等这品种量的自动化变量就属于“规定型变量”,这种变量的值依赖于规定的指标和依赖指标的定义。
当然,我样同样能够为某个指标设置局部变量,这种变量被称为“Target-specific Variable”,它能够和“全局变量”同名,因为它的作用范畴只在这条规定以及连带规定中,所以其值也只在作用范畴内无效。而不会影响规定链以外的全局变量的值。
其语法是:
<target …> : <variable-assignment>
<target …> : overide <variable-assignment>
<variable-assignment> 能够是后面讲过的各种赋值表达式,如“=”、“:=”、“+=”或是“?=”。第二个语法是针对于 make 命令行带入的变量,或是零碎环境变量。
这个个性十分的有用,当咱们设置了这样一个变量,这个变量会作用到由这个指标所引发的所有的规定中去。如:
prog : CFLAGS = -g
prog : prog.o foo.o bar.o
$(CC) $(CFLAGS) prog.o foo.o bar.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 指标,以及其所引发的所有规定中(prog.o foo.o bar.o 的规定),$(CFLAGS)的值都是“-g”
九、模式变量
在 GNU 的 make 中,还反对模式变量(Pattern-specific Variable),通过下面的指标变量中,咱们晓得,变量能够定义在某个指标上。模式变量的益处就是,咱们能够给定一种“模式”,能够把变量定义在合乎这种模式的所有指标上。
咱们晓得,make 的“模式”个别是至多含有一个“%”的,所以,咱们能够以如下形式给所有以 [.o] 结尾的指标定义指标变量:
%.o : CFLAGS = -O
同样,模式变量的语法和“指标变量”一样:
<pattern …> : <variable-assignment>
<pattern …> : override <variable-assignment>
override 同样是针对于零碎环境传入的变量,或是 make 命令行指定的变量。
应用条件判断
——————
应用条件判断,能够让 make 依据运行时的不同状况抉择不同的执行分支。条件表达式能够是比拟变量的值,或是比拟变量和常量的值。
一、示例
上面的例子,判断 $(CC)变量是否“gcc”,如果是的话,则应用 GNU 函数编译指标。
libs_for_gcc = -lgnu
normal_libs =
foo: $(objects)
ifeq ($(CC),gcc)
$(CC) -o foo $(objects) $(libs_for_gcc)
else
$(CC) -o foo $(objects) $(normal_libs)
endif
可见,在下面示例的这个规定中,指标“foo”能够依据变量“$(CC)”值来选取不同的函数库来编译程序。
咱们能够从下面的示例中看到三个关键字:ifeq、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 = -lgnu
normal_libs =
ifeq ($(CC),gcc)
libs=$(libs_for_gcc)
else
libs=$(normal_libs)
endif
foo: $(objects)
$(CC) -o foo $(objects) $(libs)
二、语法
条件表达式的语法为:
<conditional-directive>
<text-if-true>
endif
以及:
<conditional-directive>
<text-if-true>
else
<text-if-false>
endif
其中 <conditional-directive> 示意条件关键字,如“ifeq”。这个关键字有四个。
第一个是咱们后面所见过的“ifeq”
ifeq (<arg1>, <arg2>)
ifeq ‘<arg1>’ ‘<arg2>’
ifeq “<arg1>” “<arg2>”
ifeq “<arg1>” ‘<arg2>’
ifeq ‘<arg1>’ “<arg2>”
比拟参数“arg1”和“arg2”的值是否雷同。当然,参数中咱们还能够应用 make 的函数。如:
ifeq ($(strip $(foo)),)
<text-if-empty>
endif
这个示例中应用了“strip”函数,如果这个函数的返回值是空(Empty),那么 <text-if-empty> 就失效。
第二个条件关键字是“ifneq”。语法是:
ifneq (<arg1>, <arg2>)
ifneq ‘<arg1>’ ‘<arg2>’
ifneq “<arg1>” “<arg2>”
ifneq “<arg1>” ‘<arg2>’
ifneq ‘<arg1>’ “<arg2>”
其比拟参数“arg1”和“arg2”的值是否雷同,如果不同,则为真。和“ifeq”相似。
第三个条件关键字是“ifdef”。语法是:
ifdef <variable-name>
如果变量 <variable-name> 的值非空,那到表达式为真。否则,表达式为假。当然,<variable-name> 同样能够是一个函数的返回值。留神,ifdef 只是测试一个变量是否有值,其并不会把变量扩大到以后地位。还是来看两个例子:
示例一:
bar =
foo = $(bar)
ifdef foo
frobozz = yes
else
frobozz = no
endif
示例二:
foo =
ifdef foo
frobozz = yes
else
frobozz = no
endif
第一个例子中,“$(frobozz)”值是“yes”,第二个则是“no”。
第四个条件关键字是“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 c
bar:= $(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:<pattern>=<replacement>)”
相当于
“$(patsubst <pattern>,<replacement>,$(var))”,
而“$(var: <suffix>=<replacement>)”
则相当于
“$(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.h
foo: $(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> 的字串。
示例:
objects=main1.o foo.o main2.o bar.o
mains=main1.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 h
acks”。
$(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 d
files := $(foreach n,$(names),$(n).o)
下面的例子中,$(name)中的单词会被挨个取出,并存到变量“n”中,“$(n).o”每次依据“$(n)”计算出一个值,这些值以空格分隔,最初作为 foreach 函数的返回,所以,$(f
iles)的值是“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)等,会被参数 <parm1>,<parm2>,<parm3> 顺次取代。而 <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 bletch
ifeq “$(origin bletch)” “environment”
bletch = barf, gag, etc.
endif
endif
当然,你兴许会说,应用 override 关键字不就能够从新定义环境中的变量了吗?为什么须要应用这样的步骤?是的,咱们用 override 是能够达到这样的成果,可是 override 过于粗
暴,它同时会把从命令行定义的变量也笼罩了,而咱们只想从新定义环境传来的,而不想从新定义命令行传来的。
八、shell 函数
shell 函数也不像其它的函数。顾名思义,它的参数应该就是操作系统 Shell 的命令。它和反引号“`”是雷同的性能。这就是说,shell 函数把执行操作系统命令后的输入作为函数
返回。于是,咱们能够用操作系统命令以及字符串解决命令 awk,sed 等等命令来生成一个变量,如:
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: err
err: ; $(ERR)
示例一会在变量 ERROR_001 定义了后执行时产生 error 调用,而示例二则在目录 err 被执行时才产生 error 调用。
$(warning <text …>)
这个函数很像 error 函数,只是它并不会让 make 退出,只是输入一段正告信息,而 make 继续执行。
**make 的运行
——————**
一般来说,最简略的就是间接在命令行下输出 make 命令,make 命令会找当前目录的 makefile 来执行,一切都是主动的。但也有时你兴许只想让 make 重编译某些文件,而不是整个工程,而又有的时候你有几套编译规定,你想在不同的时候应用不同的编译规定,等等。本章节就是讲述如何应用 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”参数也行)。例如,咱们有个 mak
efile 的名字是“hchen.mk”,那么,咱们能够这样来让 make 来执行这个文件:
make –f hchen.mk
如果在 make 的命令行是,你不只一次地应用了“-f”参数,那么,所有指定的 makefile 将会被连在一起传递给 make 执行。
三、指定指标
一般来说,make 的最终目标是 makefile 中的第一个指标,而其它指标个别是由这个指标连带进去的。这是 make 的默认行为。当然,一般来说,你的 makefile 中的第一个指标是由许多个指标组成,你能够批示 make,让其实现你所指定的指标。要达到这一目标很简略,需在 make 命令后间接跟指标的名字就能够实现(如后面提到的“make clean”模式)任何在 makefile 中的指标都能够被指定成终极目标,然而除了以“-”打头,或是蕴含了“=”的指标,因为有这些字符的指标,会被解析成命令行参数或是变量。甚至没有被咱们明确写进去的指标也能够成为 make 的终极目标,也就是说,只有 make 能够找到其隐含规定推导规定,那么这个隐含指标同样能够被指定成终极目标。
有一个 make 的环境变量叫“MAKECMDGOALS”,这个变量中会寄存你所指定的终极目标的列表,如果在命令行上,你没有指定指标,那么,这个变量是空值。这个变量能够让你应用在一些比拟非凡的情景下。比方上面的例子:
sources = foo.c bar.c
ifneq ($(MAKECMDGOALS),clean)
include $(sources:.c=.d)
endif
基于下面的这个例子,只有咱们输出的命令不是“make clean”,那么 makefile 会主动蕴含“foo.d”和“bar.d”这两个 makefile。
应用指定终极目标的办法能够很不便地让咱们编译咱们的程序,例如上面这个例子:
.PHONY: all
all: 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-if=<file>”
“–assume-new=<file>”
“–new-file=<file>”
这个参数须要指定一个文件。个别是是源文件(或依赖文件),Make 会依据规定推导来运行依赖于这个文件的命令,一般来说,能够和“-n”参数一起应用,来查看这个依赖文件
所产生的规定命令。
另外一个很有意思的用法是联合“-p”和“-v”来输入 makefile 被执行时的信息(这个将在前面讲述)。
五、make 的参数
上面列举了所有 GNU make 3.80 版的参数定义。其它版本和产商的 make 大同小异,不过其它产商的 make 的具体参数还是请参考各自的产品文档。
“-b”
“-m”
这两个参数的作用是疏忽和其它版本 make 的兼容性。
“-B”
“–always-make”
认为所有的指标都须要更新(重编译)。
“-C <dir>”
“–directory=<dir>”
指定读取 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”
相当于“–debug=a”。
“-e”
“–environment-overrides”
指明环境变量的值笼罩 makefile 中定义的变量的值。
“-f=<file>”
“–file=<file>”
“–makefile=<file>”
指定须要执行的 makefile。
“-h”
“–help”
显示帮忙信息。
“-i”
“–ignore-errors”
在执行时疏忽所有的谬误。
“-I <dir>”
“–include-dir=<dir>”
指定一个被蕴含 makefile 的搜寻指标。能够应用多个“-I”参数来指定多个目录。
“-j [<jobsnum>]”
“–jobs[=<jobsnum>]”
指同时运行命令的个数。如果没有这个参数,make 运行命令时能运行多少就运行多少。如果有一个以上的“-j”参数,那么仅最初一个“-j”才是无效的。(留神这个参数在 MS-D
OS 中是无用的)
“-k”
“–keep-going”
出错也不进行运行。如果生成一个指标失败了,那么依赖于其上的指标就不会被执行了。
“-l <load>”
“–load-average[=<load]”
“—max-load[=<load>]”
指定 make 运行命令的负载。
“-n”
“–just-print”
“–dry-run”
“–recon”
仅输入执行过程中的命令序列,但并不执行。
“-o <file>”
“–old-file=<file>”
“–assume-old=<file>”
不从新生成的指定的 <file>,即便这个指标的依赖文件新于它。
“-p”
“–print-data-base”
输入 makefile 中的所有数据,包含所有的规定和变量。这个参数会让一个简略的 makefile 都会输入一堆信息。如果你只是想输入信息而不想执行 makefile,你能够应用“make -q
p”命令。如果你想查看执行 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-if=<file>”
“–new-file=<file>”
“–assume-file=<file>”
假设指标 <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 当时约定好的一些货色。例如,咱们有上面的一个 Makefile:
foo : foo.o bar.o
cc –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.c
cc –c foo.c $(CFLAGS)
bar.o : bar.c
cc –c bar.c $(CFLAGS)
因为,这曾经是“约定”好了的事了,make 和咱们约定好了用 C 编译器“cc”生成 [.o] 文件的规定,这就是隐含规定。
当然,如果咱们为 [.o] 文件书写了本人的规定,那么 make 就不会主动推导并调用隐含规定,它会依照咱们写好的规定忠诚地执行。
还有,在 make 的“隐含规定库”中,每一条隐含规定都在库中有其程序,越靠前的则是越被常常应用的,所以,这会导致咱们有些时候即便咱们显示地指定了指标依赖,make 也不会管。如上面这条规定(没有命令):
foo.o : foo.p
依赖文件“foo.p”(Pascal 程序的源文件)有可能变得没有意义。如果目录下存在了“foo.c”文件,那么咱们的隐含规定一样会失效,并会通过“foo.c”调用 C 的编译器生成 f
oo.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)”。这个规定对
于只有一个源文件的工程无效,同时也对多个 Object 文件(由不同的源文件生成)的也无效。例如如下规定:
x : y.o z.o
并且“x.c”、“y.c”和“z.c”都存在时,隐含规定将执行如下命令:
cc -c x.c -o x.o
cc -c y.c -o y.o
cc -c z.c -o z.o
cc x.o y.o z.o -o x
rm -f x.o
rm -f y.o
rm -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: %.y
bison -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.o
ar 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.o”)
2、创立所有匹配于 T 或是 N 的模式规定列表。
3、如果在模式规定列表中有匹配所有文件的模式,如 ”%”,那么从列表中移除其它的模式。
4、移除列表中没有命令的规定。
5、对于第一个在列表中的模式规定:
1)推导其 ” 茎 ”S,S 应该是 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.o
ar 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.o
ar r foo.a bar.o
rm -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、什么是 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 进行讲述,我的环境是 RedHat Linux 8.0,make 的版本是 3.80。必竟,这个 make 是利用最为宽泛的,也是用得最多的。而且其还是最遵循于 IEEE 1003.2-1992 规范的(POSIX.2)。
在这篇文档中,将以 C /C++ 的源码作为咱们根底,所以必然波及一些对于 C /C++ 的编译的常识,相干于这方面的内容,还请各位查看相干的编译器的文档。这里所默认的编译器是 UNIX 下的 GCC 和 CC。
0.1 对于程序的编译和链接
在此,我想多说对于程序编译的一些标准和办法,一般来说,无论是 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 谬误,意思说是说,链接器未能找到函数的实现。你须要指定函数的 ObjectFile.
好,言归正传,GNU 的 make 有许多的内容,闲言少叙,还是让咱们开始吧。
1 Makefile 介绍
make 命令执行时,须要一个 Makefile 文件,以通知 make 命令须要怎么样的去编译和链接程序。
首先,咱们用一个示例来阐明 Makefile 的书写规定。以便给大家一个感兴意识。这个示例来源于 GNU 的 make 使用手册,在这个示例中,咱们的工程有 8 个 C 文件,和 3 个头文件,咱们要写一个 Makefile 来通知 make 命令如何编译和链接这几个文件。咱们的规定是:
1. 如果这个工程没有编译过,那么咱们的所有 C 文件都要编译并被链接。
2. 如果这个工程的某几个 C 文件被批改,那么咱们只编译被批改的 C 文件,并链接目标程序。
3. 如果这个工程的头文件被扭转了,那么咱们须要编译援用了这几个头文件的 C 文件,并链接目标程序。
只有咱们的 Makefile 写得够好,所有的这所有,咱们只用一个 make 命令就能够实现,make 命令会主动智能地依据以后的文件批改的状况来确定哪些文件须要重编译,从而本人编译所须要的文件和链接目标程序。
1.1 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 还不够,我会以前面一点一点地联合我的工作教训给你缓缓到来。内容还多着呢。:)
1.2 一个示例
正如后面所说的,如果一个工程有 3 个头文件,和 8 个 C 文件,咱们为了实现后面所述的那三个规定,咱们的 Makefile 应该是上面的这个样子的。
edit : main.o kbd.o command.o display.o
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o
insert.o search.o files.o utils.o
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
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 中定义不必的编译或是和编译无关的命令,比方程序的打包,程序的备份,等等。
1.3 make 是如何工作的
在默认的形式下,也就是咱们只输出 make 命令。那么,
- make 会在当前目录下找名字叫“Makefile”或“makefile”的文件。
- 如果找到,它会找文件中的第一个指标文件(target),在下面的例子中,他会找到“edit”这个文件,并把这个文件作为最终的指标文件。
- 如果 edit 文件不存在,或是 edit 所依赖的前面的 .o 文件的文件批改工夫要比 edit 这个文件新,那么,他就会执行前面所定义的命令来生成 edit 这个文件。
- 如果 edit 所依赖的.o 文件也存在,那么 make 会在以后文件中找指标为.o 文件的依赖性,如果找到则再依据那一个规定生成.o 文件。(这有点像一个堆栈的过程)
- 当然,你的 C 文件和 H 文件是存在的啦,于是 make 会生成 .o 文件,而后再用 .o 文件申明 make 的终极工作,也就是执行文件 edit 了。
这就是整个 make 的依赖性,make 会一层又一层地去找文件的依赖关系,直到最终编译出第一个指标文件。在找寻的过程中,如果呈现谬误,比方最初被依赖的文件找不到,那么 make 就会间接退出,并报错,而对于所定义的命令的谬误,或是编译不胜利,make 基本不理。make 只管文件的依赖性,即,如果在我找了依赖关系之后,冒号前面的文件还是不在,那么对不起,我就不工作啦。
通过上述剖析,咱们晓得,像 clean 这种,没有被第一个指标文件间接或间接关联,那么它前面所定义的命令将不会被主动执行,不过,咱们能够显示要 make 执行。即命令——“make clean”,以此来革除所有的指标文件,以便重编译。
于是在咱们编程中,如果这个工程已被编译过了,当咱们批改了其中一个源文件,比方 file.c,那么依据咱们的依赖性,咱们的指标 file.o 会被重编译(也就是在这个依性关系前面所定义的命令),于是 file.o 的文件也是最新的啦,于是 file.o 的文件批改工夫要比 edit 要新,所以 edit 也会被从新链接了(详见 edit 指标文件后定义的命令)。
而如果咱们扭转了“command.h”,那么,kdb.o、command.o 和 files.o 都会被重编译,并且,edit 会被重链接。
1.4 makefile 中应用变量
在下面的例子中,先让咱们看看 edit 的规定:
edit : main.o kbd.o command.o display.o
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o
insert.o search.o files.o utils.o
咱们能够看到 [.o] 文件的字符串被反复了两次,如果咱们的工程须要退出一个新的 [.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.osearch.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit $(objects)
于是如果有新的 .o 文件退出,咱们只需简略地批改一下 objects 变量就能够了。
对于变量更多的话题,我会在后续给你一一道来。
1.5 让 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.o
edit : $(objects)
cc -o edit $(objects)
main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h
.PHONY : clean
clean :
rm edit $(objects)
这种办法,也就是 make 的“费解规定”。下面文件内容中,“.PHONY”示意,clean 是个伪指标文件。
对于更为具体的“费解规定”和“伪指标文件”,我会在后续给你一一道来。
1.6 另类格调的 makefile
即然咱们的 make 能够主动推导命令,那么我看到那堆 [.o] 和[.h]的依赖就有点不爽,那么多的反复的[.h],能不能把其收拢起来,好吧,没有问题,这个对于 make 来说很容易,谁叫它提供了主动推导命令和文件的性能呢?来看看最新格调的 makefile 吧。
objects = main.o kbd.o command.o display.o
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
$(objects) : defs.h
kbd.o command.o files.o : command.h
display.o insert.o search.o files.o : buffer.h
.PHONY : clean
clean :
rm edit $(objects)
这种格调,让咱们的 makefile 变得很简略,但咱们的文件依赖关系就显得有点凌乱了。鱼和熊掌不可兼得。还看你的爱好了。我是不喜爱这种格调的,一是文件的依赖关系看不清楚,二是如果文件一多,要退出几个新的.o 文件,那就理不分明了。
1.7 清空指标文件的规定
每个 Makefile 中都应该写一个清空指标文件(.o 和执行文件)的规定,这不仅便于重编译,也很利于放弃文件的清洁。这是一个“涵养”(呵呵,还记得我的《编程涵养》吗)。个别的格调都是:
clean:
rm edit $(objects)
更为持重的做法是:
.PHONY : clean
clean :
-rm edit $(objects)
后面说过,.PHONY 意思示意 clean 是一个“伪指标”,。而在 rm 命令后面加了一个小减号的意思就是,兴许某些文件呈现问题,但不要管,持续做前面的事。当然,clean 的规定不要放在文件的结尾,不然,这就会变成 make 的默认指标,置信谁也不违心这样。不成文的规矩是——“clean 素来都是放在文件的最初”。
下面就是一个 makefile 的概貌,也是 makefile 的根底,上面还有很多 makefile 的相干细节,筹备好了吗?筹备好了就来。
2 Makefile 总述
2.1 Makefile 里有什么?
Makefile 里次要蕴含了 五个货色:显式规定、费解规定、变量定义、文件批示和正文。
- 显式规定。显式规定阐明了,如何生成一个或多的的指标文件。这是由 Makefile 的书写者显著指出,要生成的文件,文件的依赖文件,生成的命令。
- 费解规定。因为咱们的 make 有主动推导的性能,所以费解的规定能够让咱们比拟毛糙地简略地书写 Makefile,这是由 make 所反对的。
- 变量的定义。在 Makefile 中咱们要定义一系列的变量,变量个别都是字符串,这个有点你 C 语言中的宏,当 Makefile 被执行时,其中的变量都会被扩大到相应的援用地位上。
- 文件批示。其包含了三个局部,一个是在一个 Makefile 中援用另一个 Makefile,就像 C 语言中的 include 一样;另一个是指依据某些状况指定 Makefile 中的无效局部,就像 C 语言中的预编译 #if 一样;还有就是定义一个多行的命令。无关这一部分的内容,我会在后续的局部中讲述。
- 正文。Makefile 中只有行正文,和 UNIX 的 Shell 脚本一样,其正文是用“#”字符,这个就像 C /C++ 中的“//”一样。如果你要在你的 Makefile 中应用“#”字符,能够用反斜框进行本义,如:“#”。
最初,还值得一提的是,在 Makefile 中的命令,必须要以 [Tab] 键开始。
2.2Makefile 的文件名
默认的状况下,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。
2.3 援用其它的 Makefile
在 Makefile 应用 include 关键字能够把别的 Makefile 蕴含进来,这很像 C 语言的 #include,被蕴含的文件会原模原样的放在以后文件的蕴含地位。include 的语法是:
include<filename>filename 能够是以后操作系统 Shell 的文件模式(能够保含门路和通配符)
在 include 后面能够有一些空字符,然而绝不能是 [Tab] 键开始。include 和能够用一个或多个空格隔开。举个例子,你有这样几个 Makefile:a.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.mk
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<filename>
其示意,无论 include 过程中呈现什么谬误,都不要报错继续执行。和其它版本 make 兼容的相干命令是 sinclude,其作用和这一个是一样的。
2.4 环境变量 MAKEFILES
如果你的以后环境中定义了环境变量 MAKEFILES,那么,make 会把这个变量中的值做一个相似于 include 的动作。这个变量中的值是其它的 Makefile,用空格分隔。只是,它和 include 不同的是,从这个环境变中引入的 Makefile 的“指标”不会起作用,如果环境变量中定义的文件发现错误,make 也会不理。
然而在这里我还是倡议不要应用这个环境变量,因为只有这个变量一被定义,那么当你应用 make 时,所有的 Makefile 都会受到它的影响,这绝不是你想看到的。在这里提这个事,只是为了通知大家,兴许有时候你的 Makefile 呈现了怪事,那么你能够看看以后环境中有没有定义这个变量。
2.5 make 的工作形式
GNU 的 make 工作时的执行步骤入下:(想来其它的 make 也是相似)
1. 读入所有的 Makefile。
2. 读入被 include 的其它 Makefile。
3. 初始化文件中的变量。
4. 推导费解规定,并剖析所有规定。
5. 为所有的指标文件创建依赖关系链。
6. 依据依赖关系,决定哪些指标要从新生成。
7. 执行生成命令。
1- 5 步为第一个阶段,6- 7 为第二个阶段。第一个阶段中,如果定义的变量被应用了,那么,make 会把其开展在应用的地位。但 make 并不会齐全马上开展,make 应用的是拖延战术,如果变量呈现在依赖关系的规定中,那么仅当这条依赖被决定要应用了,变量才会在其外部开展。
当然,这个工作形式你不肯定要分明,然而晓得这个形式你也会对 make 更为相熟。有了这个根底,后续局部也就容易看懂了。
3 Makefile 书写规定
规定蕴含两个局部,一个是 依赖关系 ,一个是 生成指标的办法。
在 Makefile 中,规定的程序是很重要的,因为,Makefile 中只应该有一个最终目标,其它的指标都是被这个指标所连带进去的,所以肯定要让 make 晓得你的最终目标是什么。一般来说,定义在 Makefile 中的指标可能会有很多,然而第一条规定中的指标将被确立为最终的指标。如果第一条规定中的指标有很多个,那么,第一个指标会成为最终的指标。make 所实现的也就是这个指标。
好了,还是让咱们来看一看如何书写规定。
3.1 规定举例
foo.o: foo.c defs.h # foo 模块
cc -c -g foo.c
看到这个例子,各位应该不是很生疏了,后面也已说过,foo.o 是咱们的指标,foo.c 和 defs.h 是指标所依赖的源文件,而只有一个命令“cc -c -g foo.c”(以 Tab 键结尾)。这个规定通知咱们两件事:
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 文件)
3.2 规定的语法
targets : prerequisites
command
…
或是这样:
targets : prerequisites ; command
command
…
targets 是文件名,以空格离开,能够应用通配符。一般来说,咱们的指标基本上是一个文件,但也有可能是多个文件。
command 是命令行,如果其不与“target:prerequisites”在一行,那么,必须以 [Tab 键] 结尾,如果和 prerequisites 在一行,那么能够用分号做为分隔。(见上)
prerequisites 也就是指标所依赖的文件(或依赖指标)。如果其中的某个文件要比指标文件要新,那么,指标就被认为是“过期的”,被认为是须要重生成的。这个在后面曾经讲过了。
如果命令太长,你能够应用反斜框(‘’)作为换行符。make 对一行上有多少个字符没有限度。规定通知 make 两件事,文件的依赖关系和如何成成指标文件。
一般来说,make 会以 UNIX 的规范 Shell,也就是 /bin/sh 来执行命令。
3.3 在规定中应用通配符
如果咱们想定义一系列比拟相似的文件,咱们很天然地就想起应用通配符。make 反对三各通配符:“*”,“?”和“[…]”。这是和 Unix 的 B -Shell 是雷同的。
“~”
波浪号(“~”)字符在文件名中也有比拟非凡的用处。如果是“~/test”,这就示意以后用户的 $HOME 目录下的 test 目录。而“~hchen/test”则示意用户 hchen 的宿主目录下的 test 目录。(这些都是 Unix 下的小常识了,make 也反对)而在 Windows 或是 MS-DOS 下,用户没有宿主目录,那么波浪号所指的目录则依据环境变量“HOME”而定。
“*”
通配符代替了你一系列的文件,如“.c”示意所以后缀为 c 的文件。一个须要咱们留神的是,如果咱们的文件名中有通配符,如:“”,那么能够用转义字符“”,如“”来示意实在的“”字符,而不是任意长度的字符串。
好吧,还是先来看几个例子吧:
clean:
rm -f *.o
下面这个例子我不不多说了,这是操作系统 Shell 所反对的通配符。这是在命令中的通配符。
print: *.c
lpr -p $?
touch print
下面这个例子阐明了通配符也能够在咱们的规定中,指标 print 依赖于所有的 [.c] 文件。其中的“$?”是一个自动化变量,我会在前面给你讲述。
objects = *.o
下面这个例子,示意了,通符同样能够用在变量中。并不是说 [.o] 会开展,不!objects 的值就是“.o”。Makefile 中的变量其实就是 C /C++ 中的宏。如果你要让通配符在变量中开展,也就是让 objects 的值是所有 [.o] 的文件名的汇合,那么,你能够这样:
objects := $(wildcard *.o)
这种用法由关键字“wildcard”指出,对于 Makefile 的关键字,咱们将在前面探讨。
3.4 文件搜查
在一些大的工程中,有大量的源文件,咱们通常的做法是把这许多的源文件分类,并存放在不同的目录中。所以,当 make 须要去找寻文件的依赖关系时,你能够在文件前加上门路,但最好的办法是把一个门路通知 make,让 make 在主动去找。
Makefile 文件中的非凡变量“VPATH”就是实现这个性能的,如果没有指明这个变量,make 只会在以后的目录中去找寻依赖文件和指标文件。如果定义了这个变量,那么,make 就会在当当前目录找不到的状况下,到所指定的目录中去找寻文件了。
VPATH = src:../headers
下面的的定义指定两个目录,“src”和“../headers”,make 会依照这个程序进行搜寻。目录由“冒号”分隔。(当然,当前目录永远是最高优先搜寻的中央)
另一个设置文件搜寻门路的办法是应用 make 的“vpath”关键字(留神,它是全小写的),这不是变量,这是一个 make 的关键字,这和下面提到的那个 VPATH 变量很相似,然而它更为灵便。它能够指定不同的文件在不同的搜寻目录中。这是一个很灵便的性能。它的应用办法有三种:
1. vpath < pattern> < directories> 为合乎模式 < pattern> 的文件指定搜寻目录 <directories>。
2. vpath < pattern> 革除合乎模式 < pattern> 的文件的搜寻目录。
3. vpath 革除所有已被设置好了的文件搜寻目录。
vapth 应用办法中的 < pattern> 须要蕴含“%”字符。“%”的意思是匹配零或若干字符,例如,“%.h”示意所有以“.h”结尾的文件。< pattern> 指定了要搜寻的文件集,而 < directories> 则指定了的文件集的搜寻的目录。例如:
vpath %.h ../headers
该语句示意,要求 make 在“../headers”目录下搜寻所有以“.h”结尾的文件。(如果某文件在当前目录没有找到的话)
咱们能够间断地应用 vpath 语句,以指定不同搜寻策略。如果间断的 vpath 语句中呈现了雷同的 < pattern>,或是被反复了的 < pattern>,那么,make 会依照 vpath 语句的先后顺序来执行搜寻。如:
vpath %.c foo
vpath % blish
vpath %.c bar
其示意“.c”结尾的文件,先在“foo”目录,而后是“blish”,最初是“bar”目录。
vpath %.c foo:bar
vpath % blish
而下面的语句则示意“.c”结尾的文件,先在“foo”目录,而后是“bar”目录,最初才是“blish”目录。
3.5 伪指标
最早先的一个例子中,咱们提到过一个“clean”的指标,这是一个“伪指标”,
clean:
rm *.o temp
正像咱们后面例子中的“clean”一样,即然咱们生成了许多文件编译文件,咱们也应该提供一个革除它们的“指标”以备残缺地重编译而用。(以“make clean”来应用该指标)
因为,咱们并不生成“clean”这个文件。“伪指标”并不是一个文件,只是一个标签,因为“伪指标”不是文件,所以 make 无奈生成它的依赖关系和决定它是否要执行。咱们只有通过显示地指明这个“指标”能力让其失效。当然,“伪指标”的取名不能和文件名重名,不然其就失去了“伪指标”的意义了。
当然,为了防止和文件重名的这种状况,咱们能够应用一个非凡的标记“.PHONY”来显示地指明一个指标是“伪指标”,向 make 阐明,不论是否有这个文件,这个指标就是“伪指标”。
.PHONY : clean
只有有这个申明,不论是否有“clean”文件,要运行“clean”这个指标,只有“make clean”这样。于是整个过程能够这样写:
.PHONY: clean
clean:
rm *.o temp
伪指标个别没有依赖的文件。然而,咱们也能够为伪指标指定所依赖的文件。伪指标同样能够作为“默认指标”,只有将其放在第一个。一个示例就是,如果你的 Makefile 须要一口气生成若干个可执行文件,但你只想简略地敲一个 make 完事,并且,所有的指标文件都写在一个 Makefile 中,那么你能够应用“伪指标”这个个性:
all : prog1 prog2 prog3
.PHONY : all
prog1 : prog1.o utils.o
cc -o prog1 prog1.o utils.o
prog2 : prog2.o
cc -o prog2 prog2.o
prog3 : prog3.o sort.o utils.o
cc -o prog3 prog3.o sort.o utils.o
咱们晓得,Makefile 中的第一个指标会被作为其默认指标。咱们申明了一个“all”的伪指标,其依赖于其它三个指标。因为伪指标的个性是,总是被执行的,所以其依赖的那三个指标就总是不如“all”这个指标新。所以,其它三个指标的规定总是会被决定。也就达到了咱们一口气生成多个指标的目标。“.PHONY : all”申明了“all”这个指标为“伪指标”。
轻易提一句,从下面的例子咱们能够看出,指标也能够成为依赖。所以,伪指标同样也可成为依赖。看上面的例子:
.PHONY: cleanall cleanobj cleandiff
cleanall : cleanobj cleandiff
rm program
cleanobj :
rm *.o
cleandiff :
rm *.diff
“makeclean”将革除所有要被革除的文件。“cleanobj”和“cleandiff”这两个伪指标有点像“子程序”的意思。咱们能够输出“makecleanall”和“make cleanobj”和“makecleandiff”命令来达到革除不同品种文件的目标
3.6 多指标
Makefile 的规定中的指标能够不止一个,其反对多指标,有可能咱们的多个指标同时依赖于一个文件,并且其生成的命令大体相似。于是咱们就能把其合并起来。当然,多个指标的生成规定的执行命令是同一个,这可能会可咱们带来麻烦,不过好在咱们的能够应用一个自动化变量“$@”(对于自动化变量,将在前面讲述),这个变量示意着目前规定中所有的指标的汇合,这样说可能很形象,还是看一个例子吧。
bigoutput littleoutput : text.g
generate text.g -$(subst output,,$@) > $@
上述规定等价于:
bigoutput : text.g
generate text.g -big > bigoutput
littleoutput : text.g
generate text.g -little > littleoutput
其中,-$(subst output,,$@)中的“$”示意执行一个 Makefile 的函数,函数名为 subst,前面的为参数。对于函数,将在前面讲述。这里的这个函数是截取字符串的意思,“$@”示意指标的汇合,就像一个数组,“$@”顺次取出指标,并执于命令。
3.7 动态模式
动态模式能够更加容易地定义多指标的规定,能够让咱们的规定变得更加的有弹性和灵便。咱们还是先来看一下语法:
<targets…>: <target-pattern>: <prereq-patterns …>
<commands>
…
targets 定义了一系列的指标文件,能够有通配符。是指标的一个汇合。
target-parrtern 是指明了 targets 的模式,也就是的指标集模式。
prereq-parrterns 是指标的依赖模式,它对 target-parrtern 造成的模式再进行一次依赖指标的定义。
这样形容这三个货色,可能还是没有说分明,还是举个例子来阐明一下吧。如果咱们的 <target-parrtern> 定义成“%.o”,意思是咱们的汇合中都是以“.o”结尾的,而如果咱们的 <prereq-parrterns> 定义成“%.c”,意思是对 <target-parrtern> 所造成的指标集进行二次定义,其计算方法是,取 <target-parrtern> 模式中的“%”(也就是去掉了 [.o] 这个结尾),并为其加上 [.c] 这个结尾,造成的新汇合。
所以,咱们的“指标模式”或是“依赖模式”中都应该有“%”这个字符,如果你的文件名中有“%”那么你能够应用反斜杠“”进行本义,来表明实在的“%”字符。
看一个例子:
objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
下面的例子中,指明了咱们的指标从 $object 中获取,“%.o”表明要所有以“.o”结尾的指标,也就是“foo.o bar.o”,也就是变量 $object 汇合的模式,而依赖模式“%.c”则取模式“%.o”的“%”,也就是“foobar”,并为其加下“.c”的后缀,于是,咱们的依赖指标就是“foo.cbar.c”。而命令中的“$<”和“$@”则是自动化变量,“$<”示意所有的依赖指标集(也就是“foo.c bar.c”),“$@”示意指标集(也褪恰癴 oo.o bar.o”)。于是,下面的规定开展后等价于上面的规定:
foo.o : foo.c
$(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
$(CC) -c $(CFLAGS) bar.c -o bar.o
试想,如果咱们的“%.o”有几百个,那种咱们只有用这种很简略的“动态模式规定”就能够写完一堆规定,切实是太有效率了。“动态模式规定”的用法很灵便,如果用得好,那会一个很弱小的性能。再看一个例子:
files = foo.elc bar.o lose.o
$(filter %.o,$(files)): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
$(filter %.elc,$(files)): %.elc: %.el
emacs -f batch-byte-compile $<
$(filter%.o,$(files))示意调用 Makefile 的 filter 函数,过滤“$filter”集,只有其中模式为“%.o”的内容。其的它内容,我就不必多说了吧。这个例字展现了 Makefile 中更大的弹性。
3.8 主动生成依赖性
在 Makefile 中,咱们的依赖关系可能会须要蕴含一系列的头文件,比方,如果咱们的 main.c 中有一句“#include “defs.h””,那么咱们的依赖关系应该是:
main.o : main.c defs.h
然而,如果是一个比拟大型的工程,你必须分明哪些 C 文件蕴含了哪些头文件,并且,你在退出或删除头文件时,也须要小心地批改 Makefile,这是一个很没有维护性的工作。为了防止这种沉重而又容易出错的事件,咱们能够应用 C /C++ 编译的一个性能。大多数的 C /C++ 编译器都反对一个“-M”的选项,即主动找寻源文件中蕴含的头文件,并生成一个依赖关系。例如,如果咱们执行上面的命令:
cc -M main.c
其输入是:
main.o : main.c defs.h
于是由编译器主动生成的依赖关系,这样一来,你就不用再手动书写若干文件的依赖关系,而由编译器主动生成了。须要揭示一句的是,如果你应用 GNU 的 C /C++ 编译器,你得用“-MM”参数,不然,“-M”参数会把一些规范库的头文件也蕴含进来。
gcc-M main.c 的输入是:
main.o: main.c defs.h /usr/include/stdio.h /usr/include/features.h
/usr/include/sys/cdefs.h /usr/include/gnu/stubs.h
/usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stddef.h
/usr/include/bits/types.h /usr/include/bits/pthreadtypes.h
/usr/include/bits/sched.h /usr/include/libio.h
/usr/include/_G_config.h /usr/include/wchar.h
/usr/include/bits/wchar.h /usr/include/gconv.h
/usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stdarg.h
/usr/include/bits/stdio_lim.h
gcc-MM main.c 的输入则是:
main.o: main.c defs.h
那么,编译器的这个性能如何与咱们的 Makefile 分割在一起呢。因为这样一来,咱们的 Makefile 也要依据这些源文件从新生成,让 Makefile 自已依赖于源文件?这个性能并不事实,不过咱们能够有其它伎俩来曲折地实现这一性能。GNU 组织倡议把编译器为每一个源文件的主动生成的依赖关系放到一个文件中,为每一个“name.c”的文件都生成一个“name.d”的 Makefile 文件,[.d]文件中就寄存对应 [.c] 文件的依赖关系。
于是,咱们能够写出 [.c] 文件和 [.d] 文件的依赖关系,并让 make 自动更新或自成 [.d] 文件,并把其蕴含在咱们的主 Makefile 中,这样,咱们就能够自动化地生成每个文件的依赖关系了。
这里,咱们给出了一个模式规定来产生 [.d] 文件:
%.d: %.c
@set -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.c
include $(sources:.c=.d)
上述语句中的“$(sources:.c=.d)”中的“.c=.d”的意思是做一个替换,把变量 $(sources)所有 [.c] 的字串都替换成 [.d],对于这个“替换”的内容,在前面我会有更为具体的讲述。当然,你得留神秩序,因为 include 是按次来载入文件,最先载入的[.d] 文件中的指标会成为默认指标
4 Makefile 书写命令
每条规定中的命令和操作系统 Shell 的命令行是统一的。make 会一按程序一条一条的执行命令,每条命令的结尾必须以 [Tab] 键结尾,除非,命令是紧跟在依赖规定前面的分号后的。在命令行之间中的空格或是空行会被疏忽,然而如果该空格或空行是以 Tab 键结尾的,那么 make 会认为其是一个空命令。
咱们在 UNIX 下可能会应用不同的 Shell,然而 make 的命令默认是被“/bin/sh”——UNIX 的规范 Shell 解释执行的。除非你特地指定一个其它的 Shell。Makefile 中,“#”是正文符,很像 C /C++ 中的“//”,其后的本行字符都被正文。
4.1 显示命令
通常,make 会把其要执行的命令行在命令执行前输入到屏幕上。当咱们用“@”字符在命令行前,那么,这个命令将不被 make 显示进去,最具代表性的例子是,咱们用这个性能来像屏幕显示一些信息。如:
@echo 正在编译 XXX 模块 ……
当 make 执行时,会输入“正在编译 XXX 模块 ……”字串,但不会输入命令,如果没有“@”,那么,make 将输入:
echo 正在编译 XXX 模块 ……
正在编译 XXX 模块 ……
如果 make 执行时,带入 make 参数“-n”或“–just-print”,那么其只是显示命令,但不会执行命令,这个性能很有利于咱们调试咱们的 Makefile,看看咱们书写的命令是执行起来是什么样子的或是什么程序的。
而 make 参数“-s”或“–slient”则是全面禁止命令的显示。
4.2 命令执行
当依赖指标新于指标时,也就是当规定的指标须要被更新时,make 会一条一条的执行其后的命令。须要留神的是,如果你要让上一条命令的后果利用在下一条命令时,你应该应用分号分隔这两条命令。比方你的第一条命令是 cd 命令,你心愿第二条命令得在 cd 之后的根底上运行,那么你就不能把这两条命令写在两行上,而应该把这两条命令写在一行上,用分号分隔。如:
示例一:
exec:
cd /home/hchen
pwd
示例二:
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”等后缀。
4.3 命令出错
每当命令运行完后,make 会检测每个命令的返回码,如果命令返回胜利,那么 make 会执行下一条命令,当规定中所有的命令胜利返回后,这个规定就算是胜利实现了。如果一个规定中的某个命令出错了(命令退出码非零),那么 make 就会终止执行以后规定,这将有可能终止所有规定的执行。
有些时候,命令的出错并不示意就是谬误的。例如 mkdir 命令,咱们肯定须要建设一个目录,如果目录不存在,那么 mkdir 就胜利执行,高枕无忧,如果目录存在,那么就出错了。咱们之所以应用 mkdir 的意思就是肯定要有这样的一个目录,于是咱们就不心愿 mkdir 出错而终止规定的运行。
为了做到这一点,疏忽命令的出错,咱们能够在 Makefile 的命令行前加一个减号“-”(在 Tab 键之后),标记为不论命令出不出错都认为是胜利的。如:
clean:
-rm -f *.o
还有一个全局的方法是,给 make 加上“-i”或是“–ignore-errors”参数,那么,Makefile 中所有命令都会疏忽谬误。而如果一个规定是以“.IGNORE”作为指标的,那么这个规定中的所有命令将会疏忽谬误。这些是不同级别的避免命令出错的办法,你能够依据你的不同喜爱设置。
还有一个要提一下的 make 的参数的是“-k”或是“–keep-going”,这个参数的意思是,如果某规定中的命令出错了,那么就终目该规定的执行,但继续执行其它规定。
4.4 嵌套执行 make
在一些大的工程中,咱们会把咱们不同模块或是不同性能的源文件放在不同的目录中,咱们能够在每个目录中都书写一个该目录的 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 = value
export variable
其等价于:
export variable := value
其等价于:
variable := value
export variable
示例二:
export variable += value
其等价于:
variable += value
export 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”总是生效的。
4.5 定义命令包
如果 Makefile 中呈现一些雷同命令序列,那么咱们能够为这些雷同的命令序列定义一个变量。定义这种命令序列的语法以“define”开始,以“endef”完结,如:
define run-yacc
yacc $(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.o
program : $(objects)
cc -o program $(objects)
$(objects) : defs.h
变量会在应用它的中央准确地开展,就像 C /C++ 中的宏一样,例如:
foo = c
prog.o : prog.$(foo)
$(foo)$(foo) -$(foo) prog.$(foo)
开展后失去:
prog.o : prog.c
cc -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) -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 := foo bar
x := later
值得一提的是,这种办法,后面的变量不能应用前面的变量,只能应用后面已定义好了的变量。如果是这样:
y := $(x) bar
x := 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 line
nullstring 是一个 Empty 变量,其中什么也没有,而咱们的 space 的值是一个空格。因为在操作符的左边是很难形容一个空格的,这里采纳的技术很管用,先用一个 Empty 变量来表明变量的值开始了,而前面采纳“#”正文符来示意变量定义的终止,这样,咱们能够定义出其值是一个空格的变量。请留神这里对于“#”的应用,正文符“#”的这种个性值得咱们留神,如果咱们这样定义一个变量:
dir := /foo/bar # directory to put the frobs in
dir 这个变量的值是“/foo/bar”,前面还跟了 4 个空格,如果咱们这样应用这样变量来指定别的目录——“$(dir)/file”那么就完蛋了。
还有一个比拟有用的操作符是“?=”,先看示例:
FOO ?= bar
其含意是,如果 FOO 没有被定义过,那么变量 FOO 的值就是“bar”,如果 FOO 先前被定义过,那么这条语将什么也不做,其等价于:
ifeq ($(origin FOO), undefined)
FOO = bar
endif
三、变量高级用法
这里介绍两种变量的高级应用办法,第一种是变量值的替换。
咱们能够替换变量中的共有的局部,其格局是“$(var:a=b)”或是“${var:a=b}”,其意思是,把变量“var”中所有以“a”字串“结尾”的“a”替换成“b”字串。这里的“结尾”意思是“空格”或是“结束符”。
还是看一个示例吧:
foo := a.o b.o c.o
bar := $(foo:.o=.c)
这个示例中,咱们先定义了一个“$(foo)”变量,而第二行的意思是把“$(foo)”中所有以“.o”字串“结尾”全副替换成“.c”,所以咱们的“$(bar)”的值就是“a.c b.c c.c”。
另外一种变量替换的技术是以“动态模式”(参见后面章节)定义的,如:
foo := a.o b.o c.o
bar := $(foo:%.o=%.c)
这依赖于被替换字串中的有雷同的模式,模式中必须蕴含一个“%”字符,这个例子同样让 $(bar)变量的值为“a.c b.c c.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 = $(subst 1,2,$(x))
z = y
a := $($($(z)))
这个例子中,“$($($(z)))”扩大为“$($(y))”,而其再次被扩大为“$($(subst 1,2,$(x)))”。$(x)的值是“variable1”,subst 函数把“variable1”中的所有“1”字串替换成“2”字串,于是,“variable1”变成“variable2”,再取其值,所以,最终,$(a)的值就是 $(variable2)的值——“Hello”。(喔,好不容易)
在这种形式中,或要能够应用多个变量来组成一个变量的名字,而后再取其值:
first_second = Hello
a = first
b = second
all = $($a_$b)
这里的“$a_$b”组成了“first_second”,于是,$(all)的值就是“Hello”。
再来看看联合第一种技术的例子:
a_objects := a.o b.o c.o
1_objects := 1.o 2.o 3.o
sources := $($(a1)_objects:.o=.c)
这个例子中,如果 $(a1)的值是“a”的话,那么,$(sources)的值就是“a.c b.c c.c”;如果 $(a1)的值是“1”,那么 $(sources)的值是“1.c 2.c 3.c”。
再来看一个这种技术和“函数”与“条件语句”一起应用的例子:
ifdef do_sort
func := sort
else
func := strip
endif
bar := a d b g q c
foo := $($(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 bg q c),调用的就是 strip 函数。
当然,“把变量的值再当成变量”这种技术,同样能够用在操作符的右边:
dir = foo
$(dir)_sources := $(wildcard $(dir)/*.c)
define $(dir)_print
lpr $($(dir)_sources)
endef
这个例子中定义了三个变量:“dir”,“foo_sources”和“foo_print”。
**
四、追加变量值 **
咱们能够应用“+=”操作符给变量追加值,如:
objects = main.o foo.o bar.o utils.o
objects += another.o
于是,咱们的 $(objects)值变成:“main.o foo.o bar.o utils.o another.o”(another.o 被追加进去了)
应用“+=”操作符,能够模仿为上面的这种例子:
objects = main.o foo.o bar.o utils.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 <variable> = <value>
override <variable> := <value>
当然,你还能够追加:
override <variable> += <more text>
对于多行的变量定义,咱们用 define 批示符,在 define 批示符前,也同样能够应用 ovveride 批示符,如:
override define foo
bar
endef
六、多行变量
还有一种设置变量值的办法是应用 define 关键字。应用 define 关键字设置变量的值能够有换行,这有利于定义一系列的命令(后面咱们讲过“命令包”的技术就是利用这个关键字)。
define 批示符前面跟的是变量的名字,而重起一行定义变量的值,定义是以 endef 关键字完结。其工作形式和“=”操作符一样。变量的值能够蕴含函数、命令、文字,或是其它变量。因为命令须要以 [Tab] 键结尾,所以如果你用 define 定义的命令变量中没有以 [Tab] 键结尾,那么 make 就不会把其认为是命令。
上面的这个示例展现了 define 的用法:
define two-lines
echo foo
echo $(bar)
endef
七、环境变量
make 运行时的零碎环境变量能够在 make 开始运行时被载入到 Makefile 文件中,然而如果 Makefile 中已定义了这个变量,或是这个变量由 make 命令行带入,那么零碎的环境变量的值将被笼罩。(如果 make 指定了“-e”参数,那么,零碎环境变量将笼罩 Makefile 中定义的变量)
因而,如果咱们在环境变量中设置了“CFLAGS”环境变量,那么咱们就能够在所有的 Makefile 中应用这个变量了。这对于咱们应用对立的编译参数有比拟大的益处。如果 Makefile 中定义了 CFLAGS,那么则会应用 Makefile 中的这个变量,如果没有定义则应用零碎环境变量的值,一个共性和共性的对立,很像“全局变量”和“局部变量”的个性。当 make 嵌套调用时(参见后面的“嵌套调用”章节),下层 Makefile 中定义的变量会以零碎环境变量的形式传递到上层的 Makefile 中。当然,默认状况下,只有通过命令行设置的变量会被传递。而定义在文件中的变量,如果要向上层 Makefile 传递,则须要应用 exprot 关键字来申明。(参见后面章节)
当然,我并不举荐把许多的变量都定义在零碎环境中,这样,在咱们执行不必的 Makefile 时,领有的是同一套零碎变量,这可能会带来更多的麻烦。
**
八、指标变量 **
后面咱们所讲的在 Makefile 中定义的变量都是“全局变量”,在整个文件,咱们都能够拜访这些变量。当然,“自动化变量”除外,如“$<”等这品种量的自动化变量就属于“规定型变量”,这种变量的值依赖于规定的指标和依赖指标的定义。
当然,我样同样能够为某个指标设置局部变量,这种变量被称为“Target-specific Variable”,它能够和“全局变量”同名,因为它的作用范畴只在这条规定以及连带规定中,所以其值也只在作用范畴内无效。而不会影响规定链以外的全局变量的值。
其语法是:
<target …> : <variable-assignment>
<target …> : overide <variable-assignment>
<variable-assignment> 能够是后面讲过的各种赋值表达式,如“=”、“:=”、“+=”或是“?=”。第二个语法是针对于 make 命令行带入的变量,或是零碎环境变量。
这个个性十分的有用,当咱们设置了这样一个变量,这个变量会作用到由这个指标所引发的所有的规定中去。如:
prog : CFLAGS = -g
prog : prog.o foo.o bar.o
$(CC) $(CFLAGS) prog.o foo.o bar.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 指标,以及其所引发的所有规定中(prog.o foo.o bar.o 的规定),$(CFLAGS)的值都是“-g”
九、模式变量
在 GNU 的 make 中,还反对模式变量(Pattern-specific Variable),通过下面的指标变量中,咱们晓得,变量能够定义在某个指标上。模式变量的益处就是,咱们能够给定一种“模式”,能够把变量定义在合乎这种模式的所有指标上。
咱们晓得,make 的“模式”个别是至多含有一个“%”的,所以,咱们能够以如下形式给所有以 [.o] 结尾的指标定义指标变量:
%.o : CFLAGS = -O
同样,模式变量的语法和“指标变量”一样:
<pattern …> : <variable-assignment>
<pattern …> : override <variable-assignment>
override 同样是针对于零碎环境传入的变量,或是 make 命令行指定的变量。
应用条件判断
——————
应用条件判断,能够让 make 依据运行时的不同状况抉择不同的执行分支。条件表达式能够是比拟变量的值,或是比拟变量和常量的值。
一、示例
上面的例子,判断 $(CC)变量是否“gcc”,如果是的话,则应用 GNU 函数编译指标。
libs_for_gcc = -lgnu
normal_libs =
foo: $(objects)
ifeq ($(CC),gcc)
$(CC) -o foo $(objects) $(libs_for_gcc)
else
$(CC) -o foo $(objects) $(normal_libs)
endif
可见,在下面示例的这个规定中,指标“foo”能够依据变量“$(CC)”值来选取不同的函数库来编译程序。
咱们能够从下面的示例中看到三个关键字:ifeq、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 = -lgnu
normal_libs =
ifeq ($(CC),gcc)
libs=$(libs_for_gcc)
else
libs=$(normal_libs)
endif
foo: $(objects)
$(CC) -o foo $(objects) $(libs)
二、语法
条件表达式的语法为:
<conditional-directive>
<text-if-true>
endif
以及:
<conditional-directive>
<text-if-true>
else
<text-if-false>
endif
其中 <conditional-directive> 示意条件关键字,如“ifeq”。这个关键字有四个。
第一个是咱们后面所见过的“ifeq”
ifeq (<arg1>, <arg2>)
ifeq ‘<arg1>’ ‘<arg2>’
ifeq “<arg1>” “<arg2>”
ifeq “<arg1>” ‘<arg2>’
ifeq ‘<arg1>’ “<arg2>”
比拟参数“arg1”和“arg2”的值是否雷同。当然,参数中咱们还能够应用 make 的函数。如:
ifeq ($(strip $(foo)),)
<text-if-empty>
endif
这个示例中应用了“strip”函数,如果这个函数的返回值是空(Empty),那么 <text-if-empty> 就失效。
第二个条件关键字是“ifneq”。语法是:
ifneq (<arg1>, <arg2>)
ifneq ‘<arg1>’ ‘<arg2>’
ifneq “<arg1>” “<arg2>”
ifneq “<arg1>” ‘<arg2>’
ifneq ‘<arg1>’ “<arg2>”
其比拟参数“arg1”和“arg2”的值是否雷同,如果不同,则为真。和“ifeq”相似。
第三个条件关键字是“ifdef”。语法是:
ifdef <variable-name>
如果变量 <variable-name> 的值非空,那到表达式为真。否则,表达式为假。当然,<variable-name> 同样能够是一个函数的返回值。留神,ifdef 只是测试一个变量是否有值,其并不会把变量扩大到以后地位。还是来看两个例子:
示例一:
bar =
foo = $(bar)
ifdef foo
frobozz = yes
else
frobozz = no
endif
示例二:
foo =
ifdef foo
frobozz = yes
else
frobozz = no
endif
第一个例子中,“$(frobozz)”值是“yes”,第二个则是“no”。
第四个条件关键字是“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 c
bar:= $(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:<pattern>=<replacement>)”
相当于
“$(patsubst <pattern>,<replacement>,$(var))”,
而“$(var: <suffix>=<replacement>)”
则相当于
“$(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.h
foo: $(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> 的字串。
示例:
objects=main1.o foo.o main2.o bar.o
mains=main1.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 h
acks”。
$(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 d
files := $(foreach n,$(names),$(n).o)
下面的例子中,$(name)中的单词会被挨个取出,并存到变量“n”中,“$(n).o”每次依据“$(n)”计算出一个值,这些值以空格分隔,最初作为 foreach 函数的返回,所以,$(f
iles)的值是“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)等,会被参数 <parm1>,<parm2>,<parm3> 顺次取代。而 <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 bletch
ifeq “$(origin bletch)” “environment”
bletch = barf, gag, etc.
endif
endif
当然,你兴许会说,应用 override 关键字不就能够从新定义环境中的变量了吗?为什么须要应用这样的步骤?是的,咱们用 override 是能够达到这样的成果,可是 override 过于粗
暴,它同时会把从命令行定义的变量也笼罩了,而咱们只想从新定义环境传来的,而不想从新定义命令行传来的。
八、shell 函数
shell 函数也不像其它的函数。顾名思义,它的参数应该就是操作系统 Shell 的命令。它和反引号“`”是雷同的性能。这就是说,shell 函数把执行操作系统命令后的输入作为函数
返回。于是,咱们能够用操作系统命令以及字符串解决命令 awk,sed 等等命令来生成一个变量,如:
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: err
err: ; $(ERR)
示例一会在变量 ERROR_001 定义了后执行时产生 error 调用,而示例二则在目录 err 被执行时才产生 error 调用。
$(warning <text …>)
这个函数很像 error 函数,只是它并不会让 make 退出,只是输入一段正告信息,而 make 继续执行。
**make 的运行
——————**
一般来说,最简略的就是间接在命令行下输出 make 命令,make 命令会找当前目录的 makefile 来执行,一切都是主动的。但也有时你兴许只想让 make 重编译某些文件,而不是整个工程,而又有的时候你有几套编译规定,你想在不同的时候应用不同的编译规定,等等。本章节就是讲述如何应用 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”参数也行)。例如,咱们有个 mak
efile 的名字是“hchen.mk”,那么,咱们能够这样来让 make 来执行这个文件:
make –f hchen.mk
如果在 make 的命令行是,你不只一次地应用了“-f”参数,那么,所有指定的 makefile 将会被连在一起传递给 make 执行。
三、指定指标
一般来说,make 的最终目标是 makefile 中的第一个指标,而其它指标个别是由这个指标连带进去的。这是 make 的默认行为。当然,一般来说,你的 makefile 中的第一个指标是由许多个指标组成,你能够批示 make,让其实现你所指定的指标。要达到这一目标很简略,需在 make 命令后间接跟指标的名字就能够实现(如后面提到的“make clean”模式)任何在 makefile 中的指标都能够被指定成终极目标,然而除了以“-”打头,或是蕴含了“=”的指标,因为有这些字符的指标,会被解析成命令行参数或是变量。甚至没有被咱们明确写进去的指标也能够成为 make 的终极目标,也就是说,只有 make 能够找到其隐含规定推导规定,那么这个隐含指标同样能够被指定成终极目标。
有一个 make 的环境变量叫“MAKECMDGOALS”,这个变量中会寄存你所指定的终极目标的列表,如果在命令行上,你没有指定指标,那么,这个变量是空值。这个变量能够让你应用在一些比拟非凡的情景下。比方上面的例子:
sources = foo.c bar.c
ifneq ($(MAKECMDGOALS),clean)
include $(sources:.c=.d)
endif
基于下面的这个例子,只有咱们输出的命令不是“make clean”,那么 makefile 会主动蕴含“foo.d”和“bar.d”这两个 makefile。
应用指定终极目标的办法能够很不便地让咱们编译咱们的程序,例如上面这个例子:
.PHONY: all
all: 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-if=<file>”
“–assume-new=<file>”
“–new-file=<file>”
这个参数须要指定一个文件。个别是是源文件(或依赖文件),Make 会依据规定推导来运行依赖于这个文件的命令,一般来说,能够和“-n”参数一起应用,来查看这个依赖文件
所产生的规定命令。
另外一个很有意思的用法是联合“-p”和“-v”来输入 makefile 被执行时的信息(这个将在前面讲述)。
五、make 的参数
上面列举了所有 GNU make 3.80 版的参数定义。其它版本和产商的 make 大同小异,不过其它产商的 make 的具体参数还是请参考各自的产品文档。
“-b”
“-m”
这两个参数的作用是疏忽和其它版本 make 的兼容性。
“-B”
“–always-make”
认为所有的指标都须要更新(重编译)。
“-C <dir>”
“–directory=<dir>”
指定读取 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”
相当于“–debug=a”。
“-e”
“–environment-overrides”
指明环境变量的值笼罩 makefile 中定义的变量的值。
“-f=<file>”
“–file=<file>”
“–makefile=<file>”
指定须要执行的 makefile。
“-h”
“–help”
显示帮忙信息。
“-i”
“–ignore-errors”
在执行时疏忽所有的谬误。
“-I <dir>”
“–include-dir=<dir>”
指定一个被蕴含 makefile 的搜寻指标。能够应用多个“-I”参数来指定多个目录。
“-j [<jobsnum>]”
“–jobs[=<jobsnum>]”
指同时运行命令的个数。如果没有这个参数,make 运行命令时能运行多少就运行多少。如果有一个以上的“-j”参数,那么仅最初一个“-j”才是无效的。(留神这个参数在 MS-D
OS 中是无用的)
“-k”
“–keep-going”
出错也不进行运行。如果生成一个指标失败了,那么依赖于其上的指标就不会被执行了。
“-l <load>”
“–load-average[=<load]”
“—max-load[=<load>]”
指定 make 运行命令的负载。
“-n”
“–just-print”
“–dry-run”
“–recon”
仅输入执行过程中的命令序列,但并不执行。
“-o <file>”
“–old-file=<file>”
“–assume-old=<file>”
不从新生成的指定的 <file>,即便这个指标的依赖文件新于它。
“-p”
“–print-data-base”
输入 makefile 中的所有数据,包含所有的规定和变量。这个参数会让一个简略的 makefile 都会输入一堆信息。如果你只是想输入信息而不想执行 makefile,你能够应用“make -q
p”命令。如果你想查看执行 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-if=<file>”
“–new-file=<file>”
“–assume-file=<file>”
假设指标 <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 当时约定好的一些货色。例如,咱们有上面的一个 Makefile:
foo : foo.o bar.o
cc –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.c
cc –c foo.c $(CFLAGS)
bar.o : bar.c
cc –c bar.c $(CFLAGS)
因为,这曾经是“约定”好了的事了,make 和咱们约定好了用 C 编译器“cc”生成 [.o] 文件的规定,这就是隐含规定。
当然,如果咱们为 [.o] 文件书写了本人的规定,那么 make 就不会主动推导并调用隐含规定,它会依照咱们写好的规定忠诚地执行。
还有,在 make 的“隐含规定库”中,每一条隐含规定都在库中有其程序,越靠前的则是越被常常应用的,所以,这会导致咱们有些时候即便咱们显示地指定了指标依赖,make 也不会管。如上面这条规定(没有命令):
foo.o : foo.p
依赖文件“foo.p”(Pascal 程序的源文件)有可能变得没有意义。如果目录下存在了“foo.c”文件,那么咱们的隐含规定一样会失效,并会通过“foo.c”调用 C 的编译器生成 f
oo.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)”。这个规定对
于只有一个源文件的工程无效,同时也对多个 Object 文件(由不同的源文件生成)的也无效。例如如下规定:
x : y.o z.o
并且“x.c”、“y.c”和“z.c”都存在时,隐含规定将执行如下命令:
cc -c x.c -o x.o
cc -c y.c -o y.o
cc -c z.c -o z.o
cc x.o y.o z.o -o x
rm -f x.o
rm -f y.o
rm -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: %.y
bison -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.o
ar 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.o”)
2、创立所有匹配于 T 或是 N 的模式规定列表。
3、如果在模式规定列表中有匹配所有文件的模式,如 ”%”,那么从列表中移除其它的模式。
4、移除列表中没有命令的规定。
5、对于第一个在列表中的模式规定:
1)推导其 ” 茎 ”S,S 应该是 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.o
ar 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.o
ar r foo.a bar.o
rm -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 语言中用 ”EXECSQL” 的样子执行 SQL 语句,在用 cc/gcc 编译之前,须要应用 ” 扩大 C ” 的翻译程序,如 cpre,把其翻译成规范 C)。如果
你在应用 make 时有一些更为绝妙的办法,请记得通知我啊。
回头看看整篇文档,不觉记起几年前刚刚开始在 Unix 下做开发的时候,有人问我会不会写 Makefile 时,我两眼发直,基本不晓得在说什么。一开始看到他人在 vi 中写完程序后输出 ”!make” 时,还认为是 vi 的性能,起初才晓得有一个 Makefile 在作怪,于是上网查啊查,那时又不违心看英文,发现就基本没有中文的文档介绍 Makefile,只得看他人写的 Makefile,本人瞎碰瞎搞才积攒了一点常识,但在很多中央齐全是知其然不知所以然。起初开始从事 UNIX 下产品软件的开发,看到一个 400 人年,近 200 万行代码的大工程,发现要编译这样一个硕大无朋,如果没有 Makefile,那会是如许恐怖的一样事啊。于是横下心来,狠命地读了一堆英文文档,才感觉对其把握了。但发现目前网上对 Makefile 介绍的文章还是少得那么的可怜,所以想写这样一篇文章,共享给大家,心愿能对各位有所帮忙。ls、rm、yacc、rpm、ftp……等等,等等,来实现诸如 ” 程序打包 ”、” 程序备份 ”、” 制作程序安装包 ”、” 提交代码 ”、” 应用程序模板 ”、” 合并文件 ” 等等形形色色的性能,文件操作,文件治理,编程开发设计,或是其它一些胡思乱想的货色。比方,以前在书写银行交易程序时,因为银行的交易程序根本一样,就见到有人书写了一些交易的通用程序模板,在该模板中把一些网络通讯、数据库操作的、业务操作共性的货色写在一个文件中,在这些文件中用些诸如 ”@@@N、###N” 奇怪字串标注一些地位,而后书写交易时,只需依照一种特定的规定书写特定的解决,最初在 make 时,应用 awk 和 sed,把模板中的 ”@@@N、###N” 等字串代替成特定的程序,造成 C 文件,而后再编译。这个动作很像数据库的 ” 扩大 C ” 语言(即在 C 语言中用 ”EXECSQL” 的样子执行 SQL 语句,在用 cc/gcc 编译之前,须要应用 ” 扩大 C ” 的翻译程序,如 cpre,把其翻译成规范 C)。如果
你在应用 make 时有一些更为绝妙的办法,请记得通知我啊。
回头看看整篇文档,不觉记起几年前刚刚开始在 Unix 下做开发的时候,有人问我会不会写 Makefile 时,我两眼发直,基本不晓得在说什么。一开始看到他人在 vi 中写完程序后输出 ”!make” 时,还认为是 vi 的性能,起初才晓得有一个 Makefile 在作怪,于是上网查啊查,那时又不违心看英文,发现就基本没有中文的文档介绍 Makefile,只得看他人写的 Makefile,本人瞎碰瞎搞才积攒了一点常识,但在很多中央齐全是知其然不知所以然。起初开始从事 UNIX 下产品软件的开发,看到一个 400 人年,近 200 万行代码的大工程,发现要编译这样一个硕大无朋,如果没有 Makefile,那会是如许恐怖的一样事啊。于是横下心来,狠命地读了一堆英文文档,才感觉对其把握了。但发现目前网上对 Makefile 介绍的文章还是少得那么的可怜,所以想写这样一篇文章,共享给大家,心愿能对各位有所帮忙。