Makefile 随记

foo.o : foo.c defs.h # module for twiddling the frobs 
cc -c -g foo.c 
上面的规则告诉我们了两件事:
1.  如何确定目标文件是否过期(需要重建目标),过期是指目标文件不存在或者目标文件“foo.o”在时间戳上比依赖文件中的任何一个(“foo.c”或者“defs.h”)“老”。
2.  如何重建目标文件“foo.o”。这个规则中使用cc编译器。规则的命令中没有明确的使用到依赖文件“defs.h”。我们假设在源文件“foo.c”中已经包含了此头文件。这也是为什么它作为目标依赖出现的原因。




规则语法
通常规则的语法格式如下:
TARGETS : PREREQUISITES 
COMMAND 
... 
或者:
TARGETS : PREREQUISITES ; COMMAND 
COMMAND 
... 
1.  规则的命令部分有两种书写方式:
a. 命令可以和目标:依赖描述放在同一行。命令在依赖文件列表后并使用分号(;)和依赖文件列表分开。
b. 命令在目标:依赖的描述的下一行,作为独立的命令行。当作为独立的命令行时此行必须以[Tab]字符开始。在Makefile中,在第一个规则之后出现的所有以[Tab]字符开始的行都会被当作命令来处理。
2. Makefile中符号“$”有特殊的含义(表示变量或者函数的引用),在规则中需要使用符号“$”的地方,需要书写两个连续的(“$$”)。
3.  前边已提到过,对于Makefile中一个较长的行,我们可以使用反斜线“\”将其书写到几个独立的物理行上。虽然make对Makefile文本行的最大长度是没有限制的,但还是建议这样做。不仅书写方便而且更有利于别人的阅读(这也是一个程序员修养的体现)。
规则的中心思想是:目标文件的内容是由依赖文件文件决定,依赖文件的任何一处改动,将导致目前已经存在的目标文件的内容过期。规则的命令为重建目标提供了方法。这些命令运行在系统shell之上。


有时,需要定义一个这样的规则,在更新目标(目标文件已经存在)时只需要根据依赖文件中的部分来决定目标是否需要被重建,而不是在依赖文件的任何一个被修改后都重建目标。为了实现这一目的,相应的就需要对规则的依赖进行分类,一类是在这些依赖文件被更新后,需要更新规则的目标;另一类是更新这些依赖的,可不需要更新规则的目标。我们把第二类称为:“order-only”依赖。书写规则时,“order-only”依赖使用管道符号“|”开始,作为目标的一个依赖文件。规则依赖列表中管道符号“|”左边的是常规依赖,管道符号右边的就是“order-only”依赖。这样的规则书写格式如下:
TARGETS : NORMAL-PREREQUISITES | ORDER-ONLY-PREREQUISITES 
这样的规则中常规依赖文件可以是空;同样也可以对一个目标进行多次追加依赖。需要注意:规则依赖文件列表中如果一个文件同时出现在常规列表和“order-only”列表中,那么此文件被作为常规依赖处理(因为常规依赖所实现的动作是“order-only”依赖所实现的动作的一个超集)。
LIBS = libtest.a 
foo : foo.c | $(LIBS) 
 $(CC) $(CFLAGS) $< -o $@ $(LIBS) 
make在执行这个规则时,如果目标文件“foo”已经存在。当“foo.c”被修改以后,目标“foo”将会被重建,但是当“libtest.a”被修改以后。将不执行规则的命令来重建目标“foo”。就是说,规则中依赖文件$(LIBS)只有在目标文件不存在的情况下,才会参与规则的执行。当目标文件存在时此依赖不会参与规则的执行过程。




4.4 文件名使用通配符
Maekfile中表示文件名时可使用通配符。可使用的通配符有:“*”、“?”和“[…]”。在Makefile中通配符的用法和含义和Linux(unix)的Bourne shell完全相同。例如,“*.c”代表了当前工作目录下所有的以“.c”结尾的文件等。但是在Makefile中这些统配符并不是可以用在任何地方,Makefile中统配符可以出现在以下两种场合:
1.  可以用在规则的目标、依赖中,make在读取Makefile时会自动对其进行匹配处理(通配符展开);
2.  可出现在规则的命令中,通配符的通配处理是在shell在执行此命令时完成的。除这两种情况之外的其它上下文中,不能直接使用通配符。而是需要通过函数“wildcard”(可参考 8.3 文件名处理函数 一节)来实现。
如果规则的一个文件名包含统配字符(“*”、“.”等字符),在使用这样的文件时需要对文件名中的统配字符使用反斜线(\)进行转义处理。例如“foo\*bar”,在Makefile中它表示了文件“foo*bar”。Makefile中对一些特殊字符的转移和B-SHELL以及C语言中的基本上相同。
另外需要注意:在Linux(unix)中,以波浪线“~”开始的文件名有特殊含义。单独使用它或者其后跟一个斜线(~/),代表了当前用户的宿主目录(在shell下可以通过命令“echo ~(~\)”来查看)。例如“~/bin”代表“/home/username/bin/”(当前用户宿主目录下的bin目录)。波浪线之后跟一个单词(~word),代表由这个“word”所指定的用户的宿主目录。例如“~john/bin”就是代表用户john 的宿主目录下的bin目录。在一些系统中(像MS-DOS和MS-Windows),用户没有各自的宿主目录,此情况下可通过设置环境变量“HOME”来模拟。




clean: 
rm *.o temp 
规则中“rm”不是创建文件“clean”的命令,而是删除当前目录下的所有.o文件和temp文件。当工作目录下不存在“clean”这个文件时,我们输入“make clean”,“rm *.o temp”总会被执行。这是我们的初衷。但是如果在当前工作目录下存在文件“clean”,情况就不一样了,同样我们输入“make clean”,由于这个规则没有任何依赖文件,所以目标被认为是最新的而不去执行规则所定义的命令,因此命令“rm”将不会被执行。这并不是我们的初衷。为了解决这个问题,我们需要将目标“clean”声明为伪目标。将一个目标声明为伪目标的方法是将它作为特殊目标.PHONY”的依赖。如下:
.PHONY : clean 
这样目标“clean”就被声明为一个伪目标,无论在当前目录下是否存在“clean”这个文件。我们输入“make clean”之后。“rm”命令都会被执行。而且,当一个目标被声明为伪目标后,make在执行此规则时不会去试图去查找隐含规则来创建它。这样也提高了make的执行效率,同时也不用担心由于目标和文件名重名而使我们的期望失败。
在书写伪目标规则时,首先需要声明目标是一个伪目标,之后才是伪目标的规则定义。
目标“clean”的完整书写格式应该如下:
.PHONY: clean 
clean: 
rm *.o temp 




在Makefile中,一个伪目标可以有自己的依赖(可以是一个或者多个文件、一个或者多个伪目标)。在一个目录下如果需要创建多个可执行程序,我们可以将所有程序的重建规则在一个Makefile中描述。因为Makefile中第一个目标是“终极目标”,约定的做法是使用一个称为“all”的伪目标来作为终极目标,它的依赖文件就是那些需要创建的程序。下边就是一个例子:
#sample 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 
执行make时,目标“all”被作为终极目标。为了完成对它的更新,make会创建(不存在)或者重建(已存在)目标“all”的所有依赖文件(prog1、prog2和prog3)。当需要单独更新某一个程序时,我们可以通过make的命令行选项来明确指定需要重建的程序。(例如:“make prog1”)。当一个伪目标作为另外一个伪目标依赖时,make将其作为另外一个伪目标的子例程来处理(可以这样理解:其作为另外一个伪目标的必须执行的部分,就行C语言中的函数调用一样)。下边的例子就是这种用法:
.PHONY: cleanall cleanobj cleandiff 
cleanall : cleanobj cleandiff 
rm program 
cleanobj : 
rm *.o 
cleandiff : 
rm *.diff 
“cleanobj”和“cleandiff”这两个伪目标有点像“子程序”的意思(执行目标“clearall时会触发它们所定义的命令被执行”)。我们可以输入“make cleanall”和“make cleanobj”和“make cleandiff”命令来达到清除不同种类文件的目的。例子首先通过特殊目标“.PHONY”声明了多个伪目标,它们之间使用空各分割,之后才是各个伪目标的规则定义。
说明:
通常在清除文件的伪目标所定义的命令中“rm”使用选项“–f”(--force)来防止在缺少删除文件时出错并退出,使“make clean”过程失败。也可以在“rm”之前加上“-”来防止“rm”错误退出,这种方式时make会提示错误信息但不会退出。为了不看到这些讨厌的信息,需要使用上述的第一种方式。

另外make存在一个内嵌隐含变量“RM”,它被定义为:“RM = rm –f”。因此在书写“clean”规则的命令行时可以使用变量“$(RM)”来代替“rm”,这样可以免出现一些不必要的麻烦!这是我们推荐的用法。



:在一个规则的命令中,命令行“cd”
改变目录不会对其后的命令的执行产生影响。就是说其后的命令执行的工作目录不会是之前使用“cd”进入的那个目录。如果要实现这个目的,就不能把“cd”和其后的命令放在两行来书写。而应该把这两条命令写在一行上,用分号分隔。这样它们才是一个完整的shell命令行。如:
foo : bar/lose 
cd bar; gobble lose > ../foo 


个不带任何参数的指示符“export”指示符:
export 
含义是将此Makefile中定义的所有变量传递给子make过程。如果不需要传递其中的某一个变量,可以单独使用指示符“unexport”来声明这个变量。使用“export”将所有定义的变量传递给子Makefile时,那些名字中包含其它字符(除字母、数字和下划线以外的字符)的变量可能不会被传递给子make,对这类特殊命名的变量传递需要明确的使用“export”指示符对它进行声明。虽然不带任何参数的“export”指示符具有特殊的含义,但是一个不带任何参数的“unexport”指示符却是没有任何意义的,它不会对make的执行过程(变量的传递)产生任何影响。


变量的替换引用
对于一个已经定义的变量,可以使用“替换引用”将其值中的后缀字符(串)使用指定的字符(字符串)替换。格式为“$(VAR:A=B)”(或者“${VAR:A=B}”),意思是,替换变量“VAR”中所有“A”字符结尾的字为“B”结尾的字。“结尾”的含义是空格之前(变量值多个字之间使用空格分开)。而对于变量其它部分的“A”字符不进行替换。例如:
foo := a.o b.o c.o 
bar := $(foo:.o=.c) 
在这个定义中,变量“bar”的值就为“a.c b.c c.c”。使用变量的替换引用将变量“foo”以空格分开的值中的所有的字的尾字符“o”替换为“c”,其他部分不变。如果在变量“foo”中如果存在“o.o”时,那么变量“bar”的值为“a.c b.c c.c o.c”而不是“a.c b.c c.c c.c”。
变量的替换引用其实是函数“patsubst”(参考 8.2 文本处理函数 一节)的一个简化实现。在GNU make中同时提供了这两种方式来实现同样的目的,以兼容其它版本make。
另外一种引用替换的技术使用功能更强大的“patsubst”函数。它的格式和上面“$(VAR:A=B)”的格式相类似,不过需要在“A”和“B”中需要包含模式字符“%”。这时它和“$(patsubst A,B $(VAR))”(可参考 8.2 make的文本处理函数 一小节)所实现功能相同。例如:
foo := a.o b.o c.o 
bar := $(foo:%.o=%.c) 
这个例子同样使变量“bar”的值为“a.c b.c c.c”。这种格式的替换引用方式比第一种方式更通用。




1.  “ifeq”表示条件语句的开始,并指定了一个比较条件(相等)。之后是用圆括号括包围的、使用逗号“,”分割的两个参数,和关键字“ifeq”用空格分开。参数中的变量引用在进行变量值比较时被展开。“ifeq”之后就是当条件满足make需要执行的,条件不满足时忽略。
2.  “else”之后就是当条件不满足时的执行部分。不是所有的条件语句都需要此部分。
3.  “endif”表示一个条件语句的结束,任何一个条件表达式都必须以“endif”结束。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章