Make 程序最初设计
是
为了维护C
程序文件防止不必要的重新编译。在使用命令行编译器的时候,修改了一个工程中的头文件,如何确保包含这个头文件的所有文件都得到编译?现在10
机的版本生成是使用批处理程序,编译那些文件依赖于程序的维护者,在模块之间相互引用头文件的情况下,要将所有需要重新编译的文件找出来是一件痛苦的事
情;在找到这些文件之后,修改批处理进行编译。实际上这些工作可以让make 程序来自动完成,make 工具
对于维护一些具有相互依赖关系的文件特别有用,它对文件和命令的联系(在文件改变时调用来更新其它文件的程序)提供一套编码方法。Make 工具的基本概念类似于Proglog语言,你告诉make 需要做什么,提供一些规则,make 来完成剩下的工作。
1 简介
make 工作自动确定工程的哪部分需要重新编译,执行命令去编译它们。虽然make多用于C
程序,然而只要提供命令行的编译器,你可以将其用于任何语言。实际上,make
工具的应用范围不仅于编程,你可以描述任和一些文件改变需要自动更新另一些文件的任务来使用它。
1.1 准备工作
如果要使用make,你必须写一个叫做“makefile”的文件,这个文件描述工程中文件之间的关系,提供更新每个文件的命令。典型的工程是这样的:可执行文件靠目标文件来更新,目标文件靠编译源文件来更新。
Makefile 写好之后,每次更改了源文件后,只要执行make 就足够了,所有必要的重新编译将执行。Make 程序利用makefile
中的数据库和文件的最后修改时间来确定那个文件需要更新;对于需要更新的文件,make 执行数据库中记录的命令。可以提供命令行参数给make
来控制那个文件需要重新编译。
1.2 Makefile 介绍
Makefile 文件告诉make 做什么,多数情况是怎样编译和链接一个程序。这里有一个简单的makefile,描述如何编译链接由8 个C 文件和3 个头文件组成的一个编辑器:
-----------------------------------------------------------------------------
edit : main.o kbd.o command.o display.o insert.o serach.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
kdb.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 创建可执行文件“edit”时运行make 就可以了;如果要将可执行文件和目标文件删除,执行make clean
make 重新编译这个编辑器时,每个更改的C 文件必须重新编译;如果头文件更改了,每个包含头文件的C 文件必须重新编译;每次编译产生一个对应于原文件的目标文件。最终,目标文件链接在一起产生新的可执行文件。
1.3 规则简介
makefile 中的规则是这样的:
TARGET... : DEPENDENCIE
S ...
COMMAND
...
目标(TARGET)程序产生的文件,如可执行文件和目标文件;目标也可以是要执行的动作,如“clean”。
依赖(DEPENDENCIES)是用来产生目标的输入文件,一个目标通常依赖于多个文件。
命令(COMMAND)是make 执行的动作,一个可以有多个命令,每个占一行。
注意:每个命令行的起始字符必须为TAB 字符!
有依赖关系规则中的命令通常在依赖文件变化时负责产生target 文件,make 执行这些命令更新或产生target。规则可以没有依赖关系,如包含target “clean”的规则。
规则解释如何和何时重做该规则中的文件,make 根据依赖关系执行产生或更新目标;规则也说明如何和何时执行动作。有的规则看起来很复杂,但都符合上述模式。
1.4 make 工作原理
缺省make 从第一个target 开始(第一个非 "." 开始的target),这称作缺省目标。在上述的makefile
中,缺省目标是更新执行程序"edit",将这个目标置于最前面。当执行make 的时候,make 程序从当前目录读入makefile
开始处理第一个规则;在例子中,这个规则是重新链接"edit";在make
处理这个规则之前,必须处理"edit"所依赖的那些文件的规则,例子中是目标文件。这些文件按照他们自己的规则处理:通过编译源文件来更新每个".o"
文件;当依赖关系中的源文件或头文件比目标文件新,或目标文件不存在时,必须重新编译。
其它的规则被处理是因为他们的target是目标的依赖,和目标没有依赖关系的规则不会被处理,除非指定make 处理(如make
clean)。在重新编译目标文件之前,make 会试图更新它的依赖:源文件和头文件。例子中的makefile
对源文件和头文件未指定任何操作:".c"和".h"文件不是任何规则的目标。确认所有的目标文件都是最新的之后,make
决定是否重新链接"edit":如果"edit"不存在,或者任何一个目标文件都比它新,则链接工作将进行。
这样,如果我们改变insert.c 运行make,make
会编译这个文件来更新"insert.o",然后链接"edit";如果修改了"command.h"运行make, "kbd.o",
"command.o", "files.o"会重新生成,链接"edit"。
1.5 使用变量
在例子中,在规则"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
这样的重复容易出错:假设工程中加入了一个新的目标文件,可能只将其加入了一个列表中;通过使用变量可以消除这种风险:变量允许一个预定义的字符串在多个地方被替换。
在makefile 中,可以写这样一行来定义"object"变量:
objects = main.o kbd.o command.o display.o insert.o search.o files.o utils.o
于是在需要目标文件名列表的地方,使用$(object) 来代替变量的值。以下是使用了变量以后的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 : 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)
--------------------------------------------------------------------------------
1.6 简化命令
为每个文件写出编译命令不是必要的,因为make 可以自己来做;以".c"文件更新".o"文件有一个隐含的规则,使用"cc
-c"命令。Make 将利用"cc -c main.c -o main.o"来将main.c
编译为main.o,因此在生成目标文件的规则中,可以省略命令。
当".c"文件以这样的方式使用时,将自动加入到依赖关系中;由是在省略命令的前提下,可以将".c"文件从依赖关系中省略。以下是简化过的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)
---------------------------------------------------------------------------------
1.7 另一种风格
如果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
----------------------------------------------------------------------------
这里"defs.h"作为所有目标文件的依赖。这种风格是好是坏取决于个人喜好,它非常紧凑,但是将每个目标的依赖信息放在一起看起来更清楚一些。
1.8 清理
编写规则不至于编译程序。Makefile 通常描述如何做其它事情:比如删除目录中的目标文件和可执行文件来清理目录。例子中是这样写的:
clean:
rm edit $(objects)
实际情况是,我们需要处理一些意外事件:存在一个叫做"clean"的文件;如果rm 出错,并不希望make 过程停止下来,修改过的版本如下:
.PHONY : clean
clean :
rm edit $(objects)
这样的规则当然不能放在makefile 的开始,因为这并不是我们缺省要做的工作。由于"clean"并不是"edit"的依赖,在运行make 时没有参数时,这条规则不会执行;要执行这个规则,必须运行"make clean"。
Makefile
Makefile 中包含五种内容:显式规则,隐式规则,变量定义,指令(directive)和注释。
显式规则描述如何生成规则的目标,它列出了目标依赖的文件,指定了产生或更新目标的命令
隐式规则描述如何生成基于文件名的一类文件,说明目标可能依赖于和其文件名类似的文件,指定了相应的命令。
指令类似与编译器的伪指令,包含:指示make 读入另一个makefile;决定是否忽略makefile
中的一部分;定义一个变量;一行中‘#"开始是注释,直到行末,除非遇到续行符号。在"define"和命令中不能有注释,其它情况下注释可出现在任何地
方。
2.1 makefile 名字
缺省情况下,make 以下列名字查找makefile:"GNUmakefile","makefile" 和
"Makefile"(注意大小写)。通常你的makefile
应叫做"makefile"或"Makefile"。"GNUmakefile"不推荐,除非你的makefile 是为GNU 的make
定制的,其它的make 不认为该名字是一个makefile 的名字。如果你使用非标准命名的makefile,必须用命令开关"-f" 或
"-file"。参数 "-f NAME" 或 "--file NAME"告诉make 读入NAME
作为makefile。如果使用多个该开关,所有的文件将按顺序连接起来。如果使用该选项,标准的makefile 名字不会自动检测。
2.2 包含
"include"指令告诉make 暂停处理余下的内容,读入其它makefile。语法如下:
include FILENAMES
这一行起始可以有空格,但TAB 字符不允许。如果文件名包含变量或函数,这些将被扩展。
2.3‘MAKEFILE"变量
如果环境变量"MAKEFILE"已定义,make 认为它的值是一系列空格隔开的文件名,这些文件在处理其它makefile 前被make
程序读入。这类似于include 指令;这些文件中的目标不会影响缺省目标,而且如果文件未找到的话,make
并不认为是错误。这个变量的主要用途是递归引用make 程序时通讯。
2.4 如何重新生成makefile
有时候makefile 是从其它文件生成的,比如RCS 或SCCS 文件。如果makefile 是由其它文件生成的,需要make读入最新版本的makefile。
在读入所有makefile 之后,make 认为每个makefile 是一个目标,试图去更新它;如果makefile
中有一条如何更新它的规则,或者有适用的隐式规则,需要的更新会进行。所有的makefile 检查完之后,如果有的改变了,make
重新开始再读入(make 会试图再做更新,但通常不会再改变了,因为已经是最新的了)。
如果一个文件使用双冒号规则,提供了命令但没有依赖关系,文件始终会被更新。在makefile 的情况下,如果makefile
双冒号规则,提供了命令但没有依赖关系,这样makefile 始终会重新生成,这会导致循环:make
只是在不断更新makefile,却不干活。为避免这种情况,make 不会重新生成那些只有命令没有依赖关系的双冒号规则的makefile。
如果没有使用"-f"或"--file"选项,make 会尝试缺省的makefile
文件名。和指明"-f"或"--file"选项不同,make 不能确定这些文件是否应当存在。然而,如果缺省makefile
不存在但可以通过运行make 规则生成,你可能希望这些规则被运行使得makefile 可以使用。
因此,如果没有缺省makefile,make 试图按照makefile 名查找的顺序生成它,直到成功或名字用完。注意如果make 不能找到或生成makefile,这并不是错误;makefile 不总是必需的。
当使用"-t"或"--touch"选项时,不希望使用过时的makefile 来决定那个目标来touch。所以"-t"选项对makefile
更新不起作用;类似"-q"(or ‘-question")和"-n"(or "-just-print")不阻止makefile
的更新,因为过时的makefile 会产生错误的输出。这样"make -f mfile -n
foo"会更新"mfile",读入它,打印出更新"foo"需要执行的命令但不运行这些命令。与"foo"有关的命令是更新过的"mfile"中的内
容。
但是有时不希望更新makefile,可以将makefile 作为命令行的目标,当makefile被显式指定为目标时,"-t"选项也适用于它们。
这样"make -f mfile -n mfile foo"会读入"mfile",打印出更新执行的命令,"foo"的命令是当前的"mfile"中的内容。
2.5 重载makefile
可以使用"include"指令来包含其它makefile,增加目标的变量定义。然而,make 不允许同一个目标有不同的命令,有其它的途径可以达到目的。
假设有"makefile"
和"mfile","makfile"要包含"mfile",但都有对于目标"foo"的规则。这是可以在"makefile"中写一条匹配任意模式的规
则,指明当make 在"makefile"中未找到目标时,搜索"mfile":
foo:
frobnicate > foo
%: force
@$(MAKE) -f mfile $@
force: ;
当执行"make foo"时,make 找到"makefile",执行命令" frobnicate > foo";执行"make
bar"时,在"makefile"中未找到相应的规则,这时模式规则适用,执行命令"make -f mfile
bar","makefile"中未提及的其它目标也是类似的。
这种方法之所有工作是因为模式规则的模式是"%",可以匹配任何的目标;这条规则的依赖是"force",保证即使目标存在命令也会执行;"force"规则的命令为空防止"make"为其搜索隐式规则-这样会导致依赖循环。
3 规则
makefile
中的规则描述如何生成特定的文件,即规则的目标。规则列出了目标的依赖文件,指定生成或更新目标的命令。规则的次序是不重要的,除非是确定缺省目标:缺省
目标是第一个makefile
中的第一个规则;如果第一个规则有多个目标,第一个目标是缺省的。有两个例外:以"."开头的目标不是缺省目标;模式规则对缺省目标没有影响。
通常我们所写的第一个规则是编译整个或makefile 中指定的所有程序。
3.1 例子
foo.o : foo.c defs.h # module for twiddling the frobs
cc -c -g foo.c
它的目标是"foo.o",依赖于"foo.c"和"defs.h",有一个命令"cc -c -g foo.c"。命令行以TAB 字符开始标识它是一个命令。
这条规则说明两件事:如何决定"foo.o"是旧的:如果它不存在,或者"foo.c"或者"defs.h"比它新。如何更新"foo.o"文件:通过运
行"cc"程序。命令未提及"defs.h",但可以猜想"foo.c"包含了它,这是"defs.h"被置于依赖关系中的理由。
3.2 规则的语法
语法如下:
TARGETS : DEPENDENCIES
COMMAND
...
或者
TARGETS : DEPENDENCIES ; COMMAND
COMMAND
...
TARGETS 是以空格隔开的文件名,统配符可以使用。通常一个规则只有一个目标,偶尔也有多个。命令行以TAB 键开始。第一条命令可在依赖关系的下一行;或者在同一行,在分号后面;两种方式效果相同。
因为"$"符号被用做变量引用,如果要在规则中使用"$"符号,必须写两个:"$$"。可以用"\"符号来分割一个长行,这不是必须的,因为make 对行的长度没有限制。
3.3 通配符
规则中的文件名可以包含统配符,如"*","?"。 文件名前的字符"~"有特殊的含义。单独使用,或跟随一个"/",代表用户的home
目录,比如"~/bin"扩展为/home/you/bin";如果"~"跟随一个单词,表示单词指示的那个用户的home
目录,如"~john/bin"扩展为"/home/john/bin"。通配符在目标,依赖关系,命令中自动扩展,其它情况下,统配符的扩展除非显式使
用"wildcard"函数。通配符的特殊意义可以使用"\"符号关闭。
例子:
clean:
rm -f *.o
和
print: *.c
lpr -p $?
touch print
通配符在定义变量时并不扩展,例如:objects = *.o 则objects 的值是字符串"*.o";但是如果你将objects
用于目标,依赖或命令中,扩展会进行。要将objects 设置成扩展过的内容,使用:objects := $(wildcard *.o)
3.3.1 通配符的缺陷
这是一个使用通配符的例子,但结果不是你所期望的。假设可执行文件"foo" 是从当前目录中的所有".o"文件生成的:
objects = *.o
foo : $(objects)
cc -o foo $(CFLAGS) $(objects)
objects 变量的值是字符串"*.o"。通配符扩展在规则"foo"中进行,于是所有存在的".o"文件成为"foo"的依赖而且在需要时重新编译。
但如果删除了所有的".o"文件呢?当通配符不匹配任何文件时,一切都保持原样:则"foo"依赖于一个叫做"*.o"的文件;由于这个文件不大可能存
在,"make"程序会报告一个无法生成"*.o"文件的错误,这不是期待的结果。实际上可以用通配符获得期望结果,但是需要复杂的技术,包
括"wildcard"函数和字符串替换函数。
3.3.2wildcard 函数
通配符自动在规则中进行。但是在变量赋值的和函数的参数中通配符不会扩展,如果在这些情况下需要通配符扩展,必须使用"wildcard"函数。语法如下:
$(wildcard PATTERN...)
这个在makefile
任何地方出现的字符串,会被匹配任何一个文件名格式的以空格隔开的现有文件列表替换。如果没有任何文件匹配一个模式,这个模式从"wildcard"的输
出中忽略,注意,这和上述的通配符的处理是不一样的。"wildcard"函数的一个功能是找出目录中所有的".c"文件:$(wildcard
*.c),可以通过替换后缀".c"为".o"从C 文件列表得到目标文件的列表:
$(patsubst %.c,%.o,$(wildcard *.c))
这样,上节中的makefile 改写为:
objects := $(patsubst %.c,%.o,$(wildcard *.c))
foo : $(objects)
cc -o foo $(objects)
这个makefile 利用了编译C 程序的隐含规则,所以不需要对编译写出显式的规则。(":="是"="的一个变体)
注意:"PATTERN"是大小写敏感的。
3.4 目录搜索
对于大的系统,通常将源文件和目标文件放在不同的目录中。目录搜索功能可以让make 自动在多个目录中搜寻依赖文件,当你将文件重新分布是,不需要改变规则,更改搜索路径即可。
3.4.1‘VPATH"
make 变量"VPATH"列出make
应当搜索的目录列表。很多情况下,当前目录不包含依赖文件,"VPATH"描述一个对所有文件的搜索列表,包含那些是规则的目标的文件。如果一个目标或者
依赖文件在当前目录没找到的话,"make"在"VPATH"中列出的目录中查找同名的文件。如果找到的话,那个文件成为依赖文件;规则可以象这些文件在
当前目录中一样来使用他们。
"VPATH"变量中,目录名以冒号或空格隔开;目录列出的顺序决定make查找的顺序。(注:在pSOSystem 2.5 移植到Win32 的GNU make 目录名必须使用分号隔开,以下均简称Win32 GNU make)。举例说明:
VPATH = src:../headers 则规则
foo.o : foo.c
被解释为
foo.o : src/foo.c
假设"foo.c"在当前目录不存在,在"src"目录中可以找到。
3.4.2 选择性搜索
与"VPATH"变量相似但更具选择性的是"vpath"指令(注意是小写),可以指定对于符合特定模式文件的查找路径。这样可以为不同类型的文件指定不同的搜索路径。
"vpath"指令共有三中形式:
"vpath PATTERN Director
IES" 为匹配PATTERN 的文件名指定搜索路径DIRECTORIES,目录的分隔和"VPATH"的相同
"vpath PATTERN" 清除为匹配PATTERN 的文件名指定的搜索路径
"vpath" 清除所有以前用"vpath"指定的搜索路径
"vpath"的模式是包含"%"的字符串:这个字符串必须匹配需要搜索的依赖文件名,"%"字符匹配0 个或多个任意字符。例如:"%.h"匹配任何以".h"结尾的文件(如果没有%,则PATTERN 必须和依赖文件完全一致,这种用法不太多)。
当前目录中不存在依赖文件时,如果"vpath"中的PATTERN 匹配依赖文件名,则指令中DIRECTORIES 列出的目录和"VPATH"中同样处理。举例:
vpath %.h ../headers
告诉make 在当前目录中未找到的".h"文件在../headers 目录中查找。如果多个"vapth"的模式匹配依赖文件名,make
将逐一处理,在所有指定的目录中搜索。Make 按照"vapth"在makefile
中的次序;来处理它们,多个相同模式的"vapth"是相互独立的。
vpath %.c foo
vpath % blish
vpath %.c bar
将按照"foo",‘blish","bar"的次序查找".c"文件。而
vpath %.c foo:bar
vpath % blish
按照"foo","bar","blish"的顺序搜索。
3.4.3 使用自动变量
目录搜索的结果并不改变规则中的命令:命令按原样被执行。因此,必须写出与目录搜索功相适应的命令。这可以通过使用"$^"这样的自动变量来完成。
"$^"表示规则中的所有依赖文件,包含它们所在的目录名(参见目录搜索);"$@"表示目标。例如:
foo.o : foo.c
cc -c $(CFLAGS) $^ -o $@
通常情况下,依赖文件也包含头文件,但命令中并不提及这些文件:
变量"$<"表示第一个依赖文件:
VPATH = src:../headers
foo.o : foo.c defs.h hack.h
cc -c $(CFLAGS) $< -o $@
3.4.4 目录搜索和隐含规则
使用"VPATH"和"vpath"指定目录搜索也会影响隐含规则。例如:文件"foo.o"没有显式规则,make
会考虑隐式规则:如果"foo.c"存在则编译它;如果这个文件不存在,则在相应的目录中查找;如果"foo.c"在任一的目录中存在,则C编译的隐式规
则被应用。
隐式规则的命令使用自动变量通常是必要的,这样无需其它努力即可以使用目录搜索得到的文件名。
3.5 PHONY 目标
PHONY 目标并非实际的文件名:只是在显式请求时执行命令的名字。有两种理由需要使用PHONY 目标:避免和同名文件冲突,改善性能。
如果编写一个规则,并不产生目标文件,则其命令在每次make 该目标时都执行。
例如:
clean:
rm *.o temp
因为"rm"命令并不产生"clean"文件,则每次执行"make
clean"的时候,该命令都会执行。如果目录中出现了"clean"文件,则规则失效了:没有依赖文件,文件"clean"始终是最新的,命令永远不会
执行;为避免这个问题,可使用".PHONY"指明该目标。如:
.PHONY : clean
这样执行"make clean"会无视"clean"文件存在与否。
已知phony 目标并非是由其它文件生成的实际文件,make 会跳过隐含规则搜索。这就是声明phony 目标会改善性能的原因,即使你并不担心实际文件存在与否。
完整的例子如下:
.PHONY : clean
clean :
rm *.o temp
phony 目标不应是真正目标文件的依赖。如果这样,每次make 在更新此文件时,命令都会执行。只要phony 目标不是真正目标的依赖,规则的命令只有在指定此目标时才执行。
phony 目标可以有依赖关系。当一个目录中有多个程序,将其放在一个makefile 中会更方便。因为缺省目标是makefile 中的第一个目标,通常将这个phony 目标叫做"all",其依赖文件为各个程序:
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"将可以将三个程序都生成了。d
当一个phony 目标是另一个的依赖,其作用相当于子程序,例如:
.PHONY: cleanall cleanobj cleandiff
cleanall : cleanobj cleandiff
rm program
cleanobj :
rm *.o
cleandiff :
rm *.diff
3.6 FORCE 目标
当规则没有依赖关系也没有命令,而且其目标不是存在的文件名,make 认为此规则运行时这个目标总是被更新。这意味着如果规则依赖于此目标,其命令总是被执行。
clean: FORCE
rm $(objects)
FORCE:
例中目标"FORCE"满足这种特殊条件,这样依赖于它的目标"clean"被强制执行其命令。名字"FORCE"没有特殊含义,只不过通常这样用而已。
这种方式使用"FORCE"和".PHONY :
clean"效果相同。使用".PHONY"更加明确高效,但不是所有的"make"都支持;这样许多makefile 中使用了"FORCE"。
3.7 空目标
空目标(empty target)是phony 目标的变种:用来执行显式请求的一个动作。和phony
目标不同的是:这个目标文件可以真实存在,但文件的内容无关紧要,通常是空的。空目标文件的目的是利用其最后修改时间来记录命令最近一次执行的时间,这是
通过使用"touch"命令更新目标文件来达到的。
print: foo.c bar.c
lpr -p $?
touch print
利用这条规则,执行"make print"时如果自上次"make print"之后任一文件改变了,"lpr"命令会执行。自动变量"$?"是为了只打印出那些变化了的文件。
3.8 内建的特殊目标
某些名字作为目标存在时有特殊含义。
★.PHONY 该目标的依赖被认为是phony 目标,处理这些目标时,命令无条件被执行,不管文件名是否存在及其最后修改时间
★.SUFFIXES 该目标的依赖被认为是一个后缀列表,在检查后缀规则时使用
★.DEFAULT 该目标的规则被使用在没有规则(显式的或隐含的)的目标上。如果"DEFAULT"命令定义了,则对所有不是规则目标的依赖文件都会执行该组命令
★.PRECIOUS 该目标的依赖文件会受到特别对待:如果make 被kill
或命令的执行被中止,这些目标并不删除;而且如果该目标是中间文件,在不需要时不会被删除。可以将隐含规则的目标模式(如%.o)做
为".PRECIOUS"的依赖文件,这样可以保存这些规则产生的中间文件。
★.INTERMEDIATE 该目标的依赖文件被当作中间文件;如果该目标没有依赖文件,则makefile 中所有的目标文件均被认为是中间文件。
★.IGNORE 在执行该目标的依赖规则的命令时,make
会忽略错误,此规则本身的命令没有意义。如果该规则没有依赖关系,表示忽略所有命令执行的错误,这种用法只是为了向后兼容;由于会影响到所有的命令,所以
不是特别有用,推荐使用其它更有选择性忽略错误的方法。
★.SILENT 在执行该目标的依赖规则的命令时,make 并不打印命令本身。该规则的命令没有意义。在".SILIENT"没有依赖关系时,表示执行makefile 中的所有命令都不会打印,该规则只是为了向后兼容提供的。
★.EXPORT_ALL_VARIABLES 只是作为一个目标存在,指示make 将所有变量输出到子进程中。
定义的隐含规则的后缀作为目标时,也认为它是特殊目标;两个后缀的连接也是一样,比如".c.o"。这些目标是后缀规则,一种定义隐式规则的过时方法(但仍然广泛使用)。后缀通常以"."开始,所以特殊目标也以"."开始。
3.9 一个规则多个目标
一条有多个目标的规则和写多条规则,每条一个目标作用是等同的。同样的命令应用于所有目标,但其效用会因将实际目标以"$@"代替而不同。规则中所有目标的依赖关系是一样的。这在两种情况下有用:
★只有依赖关系,不需要命令。例如:
kbd.o command.o files.o: command.h
★所有的目标同样的命令。命令不需要完全相同,因为在命令中可以使用"$@":
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
等同。这里假设程序"generate"产生两种输出:一种使用"-big"选项,一种使用"-little"选项。如果想象使用"$@"变化命令那样来变化依赖关系,不能通过多目标的普通规则实现,但是可以通过模式规则来实现。
3.10 一个目标多条规则
一个文件可以是多条规则的目标,所有规则的依赖关系被合并。如果目标比任一个依赖文件旧,命令被执行。一个文件只能有一组命令执行。如果多个规则对于同一
个文件都给出了命令,make 使用最后一组并打印错误信息(特殊情况:如果文件名以"."开始,并不打印错误信息,这一点是为了和其它make
兼容)。没有任何理由需要将makefile写成这样,这是make 给出错误信息的理由。
一条只有依赖关系的附加规则可以一次给出许多文件的附加依赖文件。例如"objects"变量表示系统中编译器的所有输出,说明当"config.h"更改时所有文件必须重做的简单方法如下:
objects = foo.o bar.o
foo.o : defs.h
bar.o : defs.h test.h
$(objects) : config.h
不用改变实际目标文件生成的规则,这条规则可以在需要增删附加的依赖关系时插入或提出。另一个诀窍是附加的依赖关系可以用变量表示,在make 执行时,以给变量赋值:
extradeps=
$(objects) : $(extradeps)
当命令`make extradeps=foo.h'执行时会认为"foo.h"是每个目标文件的依赖文件,但简单的"make"命令不是这样。
3.11 静态模式规则
静态模式规则(static pattern rules)可以指定多个目标,并且使用目标名字来建议依赖文件的名字;比普通多目标规则更通用因为不需要依赖关系是相同的:依赖关系必须类似但不需要相同。
3.11.1 语法
TARGETS ...: TARGET-PATTERN: DEP-PATTERNS ...
COMMANDS
...
TARGETS 列表指出规则应用的目标,可以包含通配符,于普通规则的目标相同。
TARGET-PATTERN 和DEP-PATTERNS 来表明目标的依赖关系如何计算:匹配TARGET-PATTERN 的目标从名字中抽出一部分,叫做词干(stem),词干被替换到DEP-PATTERNS 来形成依赖文件名。
每个模式通常包含一个"%"字符。当TARGET-PATTERN
匹配一个目标时,"%"字符可以匹配目标名中的任何部分;这部分即是词干,模式的其余部分必须完全匹配。例如"foo.o"匹配"%.o","foo"是
词干;目标"foo.c"和"foo.out"并不匹配这个模式。
目标的依赖文件名通过将DEP-PATTERNS 中的"%"替换为词干形成:如果依赖模式为"%.c",在替换词干"foo"可以得到"foo.c"。依赖模式中不包含"%"也是合法的,此依赖文件对所有的目标均有效。
如果需要在模式规则中使用"%"字符,必须在其前面加"\"字符,如果"%"前的"\"字符是有实际意义的,必须在其前面加"\",其它的"\"不必如此
处理。如"the\%weird\\%pattern\\"在有效的"%"前是"the%weird\",其后是"pattern\\"。最后的"\\"
保持原样是因为其并不影响"%"字符。
以下例子从相应的".c"文件编译"foo.o"和"bar.o":
objects = foo.o bar.o
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
每个目标必须匹配目标模式,对于不匹配的目标会给出警告。如果列表中只有部分文件匹配模式,可以使用filter 函数移去不匹配的文件名:
files = foo.elc bar.o lose.o
$(filter %.o,$(files)): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
$(filter %.elc,$(files)): %.elc: %.el
eMac
s -f batch-byte-compile $<
例子中"$(filter %.o,$(files))" 结果是"bar.o lose.o"; "$(filter %.elc,$(files))" 的结果是"foo.elc"。
以下例子说明"$*"的使用:
bigoutput littleoutput : %output : text.g
generate text.g -$* > $@
命令 "generate"执行时,"$*"扩展为词干"big"或"little"。
3.11.2 静态模式规则和隐式规则
静态模式规则和隐式规则在作为模式规则是具有很多共同点,都有目标模式和构造依赖文件名的模式,不同之处在于make
决定何时应用规则的方法。隐式规则可应用于匹配其模式的任何目标,但只限于没有指定命令的目标,如果有多条可应用的隐式规则,只有一条被使用,取决于规则
的顺序。反之,静态模式规则适用于规则中明确目标列表,不适用于其它目标且总是适用于指定的每个目标。如果有两条冲突的规则,且都有命令,这是一个错误。
静态模式规则比隐式规则优越之处如下:
★可为一些不能按句法分类,但可以显式列出的文件重载隐式规则
★不能判定目录中的精确内容,一些无关的文件可能导致make 适用错误的隐式规则;最终结果可能依赖于隐式规则的次序。适用静态模式规则时,这种不确定性是不存在的:规则适用于明确指定的目标。
3.12 双冒号规则
双冒号规则(Double-colon
rules)的目标后是"::"而不是":",当一个目标出现在多条规则中时,其处理和普通规则的处理不同。当一个目标出现在多条规则中时,所有规则必须
是相同类型的:都是普通的或者都是双冒号的。如果是双冒号,规则之间相互独立;如果目标需要更新,则规则的命令被执行;结果可能是没有执行,或者执行了其
中一些,或者所有的规则都执行了。
同一目标的双冒号规则事实是完全孤立的,每条规则被被单独处理,就象不同目标的规则一样;规则按照在makefile 中出现的次序被处理,此类规则真正有意义的是那些于命令执行次序无关的。
这种规则有时比较晦涩不是特别有用;它提供了一种机制:通过不同依赖文件的更新来对目标进行不同的处理,这种情形很罕见。每个这种规则应当提供命令,如果没有,适用的隐式规则将使用。
3.13 自动生成依赖关系
在makefile 中,许多规则都是一些目标文件依赖于一些头文件。例如:"main.c"通过"#include"使用"defs.h",这样规则:
main.o: defs.h
告诉make 在"defs.h"变化时更新"main.o"。在程序比较大时,需要写许多这样的规则;而且当每次增删"#include"时,必须小心的更新makefile。许多现代的编译器可以帮你写这些规则,通常这是通过编译器的"-M"选项,例如命令:
cc -M main.c
输出以下内容:
main.o : main.c defs.h
这样就不必写这些规则,有编译器代劳了。
注意这样的依赖关系中提及"main.o",不会被隐式规则认为是中间文件,这意味这make
在使用过它之后不会将其删除。使用老的"make"程序时,习惯做法是使用"make
depend"命令利用编译器的功能产生依赖关系,该命令会产生一个"depend"文件包含所有自动产生的依赖关系,然后在makefile
中使用"include"将其读入。
使用GNU 的make 时,重新生成makefile 的功能使得这种做法变得过时:从不需要显式请求更新依赖关系,因为它总是重新生成任何过时的makefile。
自动依赖关系生成推荐的做法是对每个源文件做一个makefile。对每个源文件"NAME.c",有一个makefile
"NAME.d",其中列出了目标文件"NAME.o"依赖的所有文件,这样在源文件更新时,需要扫描来产生新的依赖关系。例子是一个从"NAME.c"
产生依赖关系文件"NAME.d"的模式规则:
%.d: %.c
$(SHELL) -ec '$(CC) -M $(CPPFLAGS) $< | sed '\''s/\($*\)\.o[ :]*/\1 $@/g'\'' > $@'
-e 选项是当$(CC)命令失败时(exit 状态非0),shell 立刻退出。通常shell 的返回值是管道中最后一条命令(sed)的返回值,这样make 不会注意到编译器出错。
使用GNU 的C 编译器时(gcc),可以用"-MM"选项来代替"-M"选项,这样省略系统头文件的依赖关系。"sed"命令的目的是将main.o : main.c defs.h转换为main.o main.d : main.c defs.h
这样使得每个".d"文件依赖于".o"文件相应源文件和头文件,make 则可以在原文间或头文件变化时更新依赖关系文件。
如果定义了生成".d"文件的规则,可以使用"include"指令来读入所有的文件:
sources = foo.c bar.c
include $(sources:.c=.d)
例中使用替换变量来将源文件列表" foo.c bar.c"转换为依赖关系文件的列表。因为".d"文件和其它文件一样,不需要更多工作,make 会在需要时重新生成它们。
4 编写命令
规则的命令是由一一执行的shell 命令组成。除了以分号隔开写在依赖关系后的命令,每个命令行必须以tab
字符开始空行和注释行可以出现在命令行中,处理时被忽略(注意:以tab
字符开始的空行不是"空"行,是一条空命令)。可以在命令中使用任何程序,但这些程序是由$(SHELL)来执行的。
4.1 回显
通常make 打印出要执行的命令,称之为回显,这和亲自敲命令的现象是一样的。当行之前有"@"字符时,命令不再回显,字符"@"在传递给shell 前丢弃。
典型的用法是只对打印命令有效,比如"echo"命令:
@echo About to make distribution files
当make 使用"-n"或"-just-print"选项时,显示要发生的一切,但不执行命令。只有在这种情况下,即使命令以"@"开始,命令行仍然显示出来。这个选项对查看make 实际要执行的动作很有用。
‘-s"或"-silent"选项阻止make 所有回显,就象所有命令以"@"开始一样;一条没有依赖关系的".SILENT"规则有相同的作用,但是"@"更加灵活。
4.2 执行
在需要执行命令更新目标时,make 为每一行创建一个子shell 来执行。这意味着诸如为进程设置局部变量的shell 命令"cd"(改变进程的当前目录)不会影响以后的命令。如果需要"cd"影响下一个命令,将它们放在一行上用分号隔开,
这样make 认为是一条命令传递给shell 程序(注意:这需要shell 支持):
foo : bar/lose
cd bar; gobble lose > ../foo
另一个形式使用续行符:
foo : bar/lose
cd bar; \
gobble lose > ../foo
shell 程序的名字是通过"SHELL"变量来取得的。
"SHELL"变量不是通过环境来设置的(即需要在makefile 中设置),因为"SHELL"环境是个人选择的,如果不同人的选择会影响makefile 的功能的话,这样很糟糕。
4.3 并行执行
GNU make
可以一次执行几条命令。通常make一次执行一条命令,等待其返回,再执行下一条。使用"-j"或"-jobs"可以同时执行多条命令。如果"-j"后跟
一个正数,表示一次可以执行的命令条数;如果"-j"之后没有参数,则不限制可执行的命令数。缺省的数量是一。
一个讨厌的问题是如果同时执行多条命令,它们的输出会混在一起;另一个问题是两个进程不能从同一个设备获得输入。
4.4 错误
每条shell 命令返回时,make 会检查其返回状态。如果命令执行成功,则下一条命令被执行,最后一条命令执行完后,规则执行结束。
如果有错误(返回非0 状态),make 放弃当前规则,也可能是所有规则。
有时候命令执行错误并不是问题,比如使用"mkdir"命令确保目录存在:如果目录一存在,则"mkdir"会报告错误,但仍希望make 继续。要忽略命令的错误,在命令之前使用"-"字符,"-"字符在传递给shell 之前被丢弃:
clean:
-rm -f *.o
如果使用"-i"或"-ignore-errors"选项,make
会忽略所有命令产生的错误;一条没有依赖关系的".IGNORE"规则有相同的作用,但"-"更灵活。在忽略错误时,make
将错误也认为是成功,只是通知你命令的退出状态和错误被忽略。如果make
并未告知忽略错误,在错误发生时,表明该目标不能成功。更新,直接或间接依赖于此的目标当然也不能成功;这些目标的命令不会被执行,因为其先决条件不满
足。
通常make 会立即以非0 状态退出。然而,如果给定"-k"或"-keep-going"选项,make
在退出前会处理其它的依赖关系,进行必要的更新。例如,在编译一个目标文件遇到错误,"make
-k"会继续编译其它的目标文件。通常认为你的目的是更新指定的目标,当make
知道这是不可能时,会立即报告失败;"-k"选项指示真正目的是测试更新程序的更多可能性:在编译之前找出更多不相关的问题。
如果命令失败了,假设它更新的目标文件,这个文件是不完整的不能使用-至少不是完全更新的。但文件的最后修改时间表明已经是最新的,下一次make
运行时,不会再更新这个文件。这种情况和命令被kill
相同;则通常情况下在命令失败时将目标删除是正确的;当".DELETE_ON_ERROR"是目标时make 帮你做这件事。虽然你总是希望make
这么做,但这不是过去的习惯;所以必须显式要求make 这样做(其它的make 自动这样做)。
4.5 中断make
如果make 执行命令时遇到错误,可能会删除命令更新的目标文件: make 检查文件的修改时间是否变化。删除目标的目的是确保make 下次执行时重新生成它。
为什么这样做?假设在编译器运行时按了"Ctrl-c",此时编译器写生成目标文件"foo.o"。"Ctrl-c" kill
了编译器,留下一个不完整的文件,但它的修改时间比源文件"foo.c"新;此时make 也受到"Ctrl-c"信号删除这个不完整的文件,
如果make 不这样做,下次make 运行时认为"foo.o"不需要更新,会在链接时出现奇怪的错误。
可以使用".PRECIOUS"规则来防止目标文件被删除。在make
更新目标时,会检测其是否为".PRECIOUS"的依赖,决定在命令出错或中断时是否删除该目标。如果你希望目标的更新是原子操作,或是用来记录修改时
间,或必须一直存在防止其它类型的错误,这些理由使得你必须这样做。
4.6 递归使用
递归使用make 就是在makefile 中使用make
命令。这种技术在你将一个大系统分解为几个子系统,为每个自系统提供一个makefile
时有用处。比如有一个子目录"subdir"中有自己的makefile,希望make 在自己目录中运行,可以这样做:
subsystem:
cd subdir; $(MAKE)
或者
subsystem:
$(MAKE) -C subdir
可以照抄这个例子来递归使用make
4.6.1‘MAKE"变量
递归的make 必须使用"MAKE"变量,不是显式的make 命令:
subsystem:
cd subdir; $(MAKE)
该变量的值是被调用的make的名字。在命令中使用"MAKE"有特殊的功能:它改变了"-t" ("--touch"), "-n"
("--just-print")和"-q" ("--question")选项的含义。使用上例来考虑"make
-t"命令("-t"选项将目标标记为最新但不运行命令),"-t"选项的功能,该命令将创建一个"subsystem"文件,实际希望的操作是运
行"cd subdir; make -t";但这回执行命令,与"-t"的原意不符。
这个特殊功能做了期望的工作。当命令行包含变量"MAKE"时,选项"-t","-n"和"-q"并不适用。不管这些导致不会执行命令的标志,包含"MAKE"变量的命令始终会执行。正常的"MAKEFLAGS"机制将这些标志传递到子make,
这样打印命令的请求被传播到子系统中。
4.6.2 传递变量到子make
上级(top-level)make
中的变量可以显式通过环境传递到子make中。在子make中,这些变量被缺省定义,但不会重载子makefile
中的定义除非使用"-e"选项向下传递,或输出变量,make 在运行命令时将其加入到环境变量中;子make 可以使用环境变量来初始化变量表。
除非显式要求,make 只输出初始环境中或命令行设置的变量而且变量名只由字母,数字和下划线组成。一些shell
不能处理有其它字符的环境变量。特殊变量"SHELL","MAKEFLAGS"总是输出,如果"MAKEFILE"变量有值,也会输出。Make
自动通过"MAKEFLAGS"来输出命令行定义的变量。
如果想要输出特定变量,使用"export"指令:
export VARIABLE ...
如果要阻止输出一个变量,使用"unexport"指令:
unexport VARIABLE ...
为方便起见,可以在定义变量时输出它:
export VARIABLE = value
和
VARIABLE = value
export VARIABLE
作用相同。
如果要输出所有的变量,使用"export"指令本身就可以了。
变量"MAKELEVEL"在一级一级传递时会改变,这个变量的值是表示嵌套层数的字符串,顶级"make"是,变量的值为"0";子make 的值为"1";子子make 的值为"2",依此类推。
"MAKELEVEL"的用途是在条件指令中测试它,这样写出在递归运行时和直接运行时表现不同的makefile。
发表评论
-
C语言宏定义技巧(常用宏定义)
2012-09-21 17:03 762C语言宏定义技巧(常用宏定义) 写好C语言,漂亮的宏 ... -
C中的预编译宏定义
2012-09-21 16:56 2081C中的预编译宏定义 在将一个C源程序转换为可执行程序的过 ... -
c 函数指针
2012-09-17 14:55 927函数指针是什么? ... -
linux 常用C函数
2012-09-12 17:19 1493字符测试篇 isalnum(测试字符是否为英文或数字) 相关 ... -
字符串和数字之间的转换
2012-06-11 15:46 988#include<stdlib.h> 字符串转数 ... -
字符串拼接,分割
2012-06-08 17:53 924#include<stdio.h> #inclu ... -
c字符串拷贝操作
2012-06-06 11:28 1655#include <string.h> char ... -
C/C++ 中如何获取数组长度
2012-05-28 15:14 2393C、C++中没有提供直接获 ... -
C/C++ 字符串处理函数【转】
2012-05-28 15:07 1627C: 1. 字符串长度 extern int strl ... -
linux下C,C++学习资料
2012-04-25 11:32 979挂载学习资料附件 1.C,C++深层探索,pdg格式 http ...
相关推荐
### GNU Make简介 #### 一、GNU Make概述 GNU Make是一种强大的自动化构建工具,它广泛应用于Unix/Linux系统下的软件开发过程中。通过解析一个名为`Makefile`或`makefile`的文件,GNU Make能够自动执行一系列编译...
1. **GNU make简介**: GNU make 是GNU项目的一部分,由Richard Stallman领导的自由软件基金会(FSF)开发。它是用于自动化编译过程的工具,遵循Makefile文件中的规则来决定哪些文件需要被重新编译,以确保程序始终...
在Windows操作系统上安装GNU Make是开发过程中的常见需求,尤其是对于那些经常进行C或C++编程,或是使用基于Makefile的构建系统的人来说。GNU Make是一个自动化构建工具,它能够读取名为“Makefile”的文件,根据...
GNU make手册 GNU make是GNU计划的一部分,由Richard Stallman和Roland McGrath开发的自动化构建工具。它能够自动地生成目标文件,减少编译和链接的时间,並且可以根据需要自动地重新编译和链接。 第一章:概述 --...
GNU Make是一款在Unix和其他操作系统上广泛使用的软件构建工具。自从1970年诞生以来,Make一直都是程序开发项目的必备工具,甚至在编译Linux内核时也扮演着核心角色。这本书《GNU Make项目管理(第三版)》由OREILLY...
**GNU Make 4.2.1 知识详解** GNU Make是开源软件开发中的一个关键工具,用于自动化构建过程,特别是在C/C++等编译型语言的项目中。它的核心功能是读取名为Makefile的文件,根据其中的规则来执行一系列命令,以构建...
《GNU make中文手册》是一本全面介绍GNU make工具的指南,涵盖了从基础到高级的各个方面。GNU make是一个自动化构建工具,常用于管理软件项目的编译和构建过程,尤其是在Linux环境中。通过编写Makefile,程序员可以...
GNU Make for Windows是一个专为在Windows操作系统环境下使用的版本管理工具,它是GNU项目的一部分,由GNU组织开发和维护。Make工具是构建自动化系统的核心,它能够根据预定义的规则自动编译和链接源代码,极大地...
2. **命令兼容性**: 由于Windows和Unix的命令语法有所不同,某些Unix命令(如`rm`、`cp`)可能需要通过`gnumake`或其他工具的Windows版本来实现。 3. **批处理文件**: Windows用户可能会更熟悉批处理脚本(.bat),...
《Gnu Make 使用手册》是理解与掌握自动化构建工具Gnu Make的重要参考资料。Gnu Make是开源软件项目中广泛使用的工具,它允许开发者通过规则定义如何编译、链接和其他方式处理源代码,从而实现构建软件的目标。在这...
GNU make 指南GNU make 指南GNU make 指南GNU make 指南GNU make 指南GNU make 指南GNU make 指南GNU make 指南GNU make 指南GNU make 指南GNU make 指南GNU make 指南
The GNU Make Book demystifies GNU make and shows you how to use its best features. You'll find a fast, thorough rundown of the basics of variables, rules, targets, and makefiles. Learn how to fix ...
#### 二、GNU Make简介 - **定义**:GNU Make是一种广泛应用于软件项目构建和管理的自动化工具。它通过读取一个名为“Makefile”的文本文件来执行一系列预定义的任务。 - **起源与发展**:Make最初是在Unix系统中...
本文比较完整的讲述GNU make工具,涵盖GNU make的用法、语法。同时重点讨论如何为一个工程编写Makefile。作为一个Linux程序员,make工具的使用以及编写Makefile是必需的。系统、详细讲述make的中文资料比较少,出于...
GNU make是一款在Linux环境下广泛使用的自动构建工具,它通过读取Makefile文件中定义的依赖关系来自动化编译、链接和安装程序。Makefile是一种脚本文件,其中包含了如何编译和链接程序的规则和指令。学习makefile...