以下内容源于C语言中文网的学习与整理,非原创,如有侵权请告知删除。
规则的目标具有很多形式,可以是一个或多个的文件,也可以是一个伪目标,或者其他的类型,下面是对这些类型的详细说明。
一、强制目标
如果一个目标中没有命令或者依赖,并且它的目标不是一个存在的文件名,则在执行此规则时,目标总会被认为是最新的。也就是说,这个规则一旦被执行,make 就认为这个规则的目标已经被更新过。这样的目标在作为另外一个规则的依赖时,因为它总被认为更新过,所以它所在的规则中定义的命令总会被执行。
比如下面的例子,目标 "FORCE" 符合上述条件。它作为目标 "clean" 的依赖,在执行 make 的时候,总被认为更新过,因此 "clean" 所在的规则的命令总会被执行。这样的一个目标通常我们将其命名为 "FORCE"。
clean:FORCE
rm $(OBJECTS)
FORCE:
上述例子中使用 "FORCE" 目标的效果和将 "clean" 声明为伪目标的效果相同,即make FORCE时,FORCE已经更新,因此以clean为目标的规则的命令得以执行。
二、空目标文件
空目标文件是伪目标的一个变种,执行此目标的目的和伪目标相同,都是make时通过命令行指定将其作为终极目标,来执行此规则所定义的命令。和伪目标不同的是,这个目标可以是一个存在的文件,但文件的具体内容我们并不关心,通常此文件是一个空文件。
空目标文件只是用来记录上一次执行此规则的命令的时间。在这样的规则中,命令部分都会使用 "touch" 在完成所有的命令之后来更新目标文件的时间戳,记录此规则命令的最后执行时间。make 时通过命令行将此目标作为终极目标,如果当前目标下不存在此文件,"touch" 会在第一次执行时创建一个的文件。
通常,一个空目标文件应该存在一个或者多个依赖文件。将这个目标作为终极目标,在它所依赖的文件比它更(第四声)新时,此目标所在的规则的命令将被执行,也就是说,如果空目标文件的依赖文件被改变之后,空目标文件所在的规则中定义的命令会被执行。
看一个例子:
print:foot.c bar.c
lpr -p $?
touch print
执行 "make print",当目标文件 "print" 的依赖文件被修改之后,命令 "lpr -p $?" 都会被执行,打印这个被修改的文件。
三、特殊的目标
名称 | 功能 |
---|---|
.PHONY: | 这个目标的所有依赖被作为伪目标。伪目标是这样一个目标:当使用 make 命令行指定此目标时,这个目标所在的规则定义的命令,无论目标文件是否存在都会被无条件执行。 |
.SUFFIXES: | 这个目标的所有依赖指出了一系列在后缀规则中需要检查的后缀名 |
.DEFAULT: | Makefile 中,这个特殊目标所在规则定义的命令,被用在重建那些没有具体规则的目标,就是说一个文件作为某个规则的依赖,却不是另外一个规则的目标时,make 程序无法找到重建此文件的规则,这种情况就执行 ".DEFAULT" 所指定的命令。 |
.PRECIOUS: | 这个特殊目标所在的依赖文件在 make 的过程中会被特殊处理:当命令执行的过程中断时,make 不会删除它们。而且如果目标的依赖文件是中间过程文件,同样这些文件不会被删除。 |
.INTERMEDIATE: | 这个特殊目标的依赖文件在 make 执行时被作为中间文件对待。没有任何依赖文件的这个目标没有意义。 |
.SECONDARY: | 这个特殊目标的依赖文件被作为中过程的文件对待。但是这些文件不会被删除。这个目标没有任何依赖文件的含义是:将所有的文件视为中间文件。 |
.IGNORE | 这个目标的依赖文件忽略创建这个文件所执行命令的错误,给此目标指定命令是没有意义的。当此目标没有依赖文件时,将忽略所有命令执行的错误。 |
.DELETE_ON_ERROR: | 如果在 Makefile 中存在特殊的目标 ".DELETE_ON_ERROR" ,make 在执行过程中,荣国规则的命令执行错误,将删除已经被修改的目标文件。 |
.LOW_RESOLUTION_TIME: | 这个目标的依赖文件被 make 认为是低分辨率时间戳文件,给这个目标指定命令是没有意义的。通常的目标都是高分辨率时间戳。 |
.SILENT: | 出现在此目标 ".SILENT" 的依赖文件列表中的文件,make 在创建这些文件时,不打印出此文件所执行的命令。同样,给目标 "SILENT" 指定命令行是没有意义的。 |
.EXPORT_ALL_VARIABLES: | 此目标应该作为一个简单的没有依赖的目标,它的功能是将之后的所有变量传递给子 make 进程。 |
.NOTPARALLEL: | Makefile 中如果出现这个特殊目标,则所有的命令按照串行的方式执行,即使是存在 make 的命令行参数 "-j" 。但在递归调用的子make进程中,命令行可以并行执行。此目标不应该有依赖文件,所有出现的依赖文件将会被忽略。 |
四、多规则目标
Makefile 中,一个文件可以作为多个规则的目标。这种情况时,以这个文件为目标的规则的所有依赖文件将会被合并成此目标的一个依赖文件列表,当其中的任何一个依赖文件比目标更新时,make 将会执行特定的命令来重建这个目标。
对于一个多规则的目标,重建这个目标的命令只能出现在一个规则中。如果多个规则同时给出重建此目标的命令,make 将使用最后一个规则中所定义的命令,同时提示错误信息。某些情况,需要对相同的目标使用不同的规则中所定义的命令,我们需要使用另一种方式——双冒号规则来实现。
一个仅仅描述依赖关系的描述规则可以用来给出一个或者是多个目标文件的依赖文件。例如,Makefile 中通常存在一个变量,就像我们以前提到的 "objects" ,它定义为所有的需要编译的生成 .o 文件的列表。这些 .o 文件在其源文件中包含的头文件 "config.h" 发生变化之后能够自动的被重建,我们可以使用多目标的方式来书写 Makefile:
objects=foo.o bar.o
foo.o:defs.h
bar.o:defs.h test.h
$(objects):config.h
这样做的好处是,当源文件增加或者删除了包含的头文件,不用修改已存在的 Makefile 的规则,只需要增加或者删除某一个 .o 文件依赖的头文件。这种方式很简单也很方便。
我们也可以通过一个变量来增加目标的依赖文件,使用 make 的命令行来指定某一个目标的依赖头文件,例如对于下面的代码,我们如果执行 "make exteradeps=foo.h",则 "foo.h" 将作为所有的 .o 文件的依赖文件;如果只执行 "make" ,就没有指定任何文件作为 .o 文件的依赖文件。
extradeps=
$(objects):$(exteradeps)
五、伪目标(精讲)
1、伪目标的定义
在上面第三节提到,“.PHONY”这个目标的所有依赖被用来作为伪目标(注意这句话的描述,伪目标不是指.PHONY,而是指.PHONY这个目标的依赖)。
伪目标是这样一个目标:当使用 make 命令行指定此目标时,这个目标所在的规则定义的命令,无论目标文件是否存在都会被无条件执行。
伪目标,它的目的并不是创建目标文件(所以称作“伪”),而是想去执行这个目标下面的命令。它有点像汇编语言里的标签。
2、伪目标的意义
使用伪目标的原因,在于避免Makefile中定义的(只为了执行命令的)目标与实际文件的名字出现冲突,同时提高执行 make 时的效率。
我们书写这样一个规则,规则所定义的命令不是去创建文件,而是通过 make 命令明确指定它来执行一些特定的命令,比如下面的代码,规则中 rm 命令不是为了创建 clean 这个文件,而是执行删除某些文件的任务。
clean:
rm -rf *.o test
当工作目录中不存在一个文件名为“clean”的文件时,在 shell 中输入 make clean 命令,命令 rm -rf *.o test 总会被执行 ,这也是我们期望的结果。
但是如果当前目录下存在文件名为“clean” 的文件时,我们在 shell 中执行命令 make clean时,由于这个规则没有依赖文件,所以目标被认为是最新的,因此不会去执行规则所定义的命令,因此命令 rm 将不会被执行。
为了解决这个问题,可以删除 clean 文件或者是在 Makefile 中将目标 clean 声明为伪目标。
3、将一个目标声明称伪目标的方法
将一个目标声明称伪目标的方法,是把它作为特殊的目标.PHONY的依赖,如下所示:
.PHONY:clean #将clean声明为伪目标
clean: #伪目标的规则定义
rm -rf *.o test
这样 clean 就被声明成一个伪目标,无论当前目录下是否存在 clean 这个文件,当我们执行 make clean 后 rm 都会被执行。而且当一个目标被声明为伪目标之后,意味着它不是一个文件(而是一个标签),因此make 在执行此规则时不会去试图查找隐含的关系去创建它。这样同样提高了 make 的执行效率,同时也不用担心目标和文件名重名而使编译失败。
4、伪目标的使用场合
伪目标的另一种使用的场合是在 make 的并行和递归执行的过程中。此情况下一般会存在一个变量,定义为所有需要 make 的子目录。对多个目录进行 make 的实现,可以在一个规则的命令行中使用 shell 循环来完成。如下:
SUBDIRS=foo bar baz
subdirs:
for dir in $(SUBDIRS);do $(MAKE) -C $$dir;done
代码表达的意思是当前目录下存在三个子文件目录,每个子目录文件都有相对应的 Makefile 文件,代码中实现的部分是用当前目录下的 Makefile 控制其它子模块中的 Makefile 的运行,但是这种实现方法存在以下几个问题:
(1)当子目录执行 make 出现错误时,make 不会退出。就是说,在对某个目录执行 make 失败以后,会继续对其他的目录进行 make。在最终执行失败的情况下,我们很难根据错误提示定位出具体在哪个目录下执行 make 发生的错误。这样给问题定位造成很大的困难。为了解决问题可以在命令部分加入错误检测,在命令执行的错误后主动退出。不幸的是如果在执行 make 时使用了 "-k" 选项,此方式将失效。
(2)另外一个问题就是使用这种 shell 循环方式时,没有用到 make 对目录的并行处理功能,由于规则的命令是一条完整的 shell 命令,不能被并行处理。
有了伪目标之后,我们可以用它来克服以上方式所存在的两个问题,代码展示如下:
SUBDIRS=foo bar baz
.PHONY:subdirs $(SUBDIRS)
subdirs:$(SUBDIRS)
$(SUBDIRS):
$(MAKE) -C $@
foo:baz
上面的实例中有一个没有命令行的规则“foo:baz”,这个规则是用来规定三个子目录的编译顺序。因为在规则中 "baz" 的子目录被当作成了 "foo" 的依赖文件,所以 "baz" 要比 "foo" 子目录更先执行,最后执行 "bar" 子目录的编译。
一般情况下,一个伪目标不作为另外一个目标的依赖。这是因为当一个目标文件的依赖包含伪目标时,每一次在执行这个规则伪目标所定义的命令都会被执行(因为它作为规则的依赖,重建规则目标时需要首先重建规则的所有依赖文件)。当一个伪目标没有任何目标(此目标是一个可被创建或者是已存在的文件)的依赖时,我们只能通过 make 的命令来明确的指定它的终极目标,执行它所在规则所定义的命令。例如 make clean。
5、借助伪目标实现同时生成多个可执行文件
如果在一个文件里想要同时生成多个可执行文件,我们可以借助伪目标来实现。使用方式如下:
.PHONY:all
all:test1 test2 test3
test1:test1.o
gcc -o $@ $^
test2:test2.o
gcc -o $@ $^
test3:test3.o
gcc -o $@ $^
在当前目录下创建了三个源文件,目的是把这三个源文件编译成为三个可执行文件。将重建的规则放到 Makefile 中,约定使用 "all" 的伪目标来作为最终目标,它的依赖文件就是要生成的可执行文件。这样的话只需要一个 make 命令,就会同时生成三个可执行文件。
之所以这样写,是因为伪目标的特性,它总会被执行,所以它依赖的三个文件的目标就不如 "all" 这个目标新,所以,其他的三个目标的规则总是被执行,这也就达到了我们一口气生成多个目标的目的。我们也可以实现单独编译这三个中的任意一个源文件,比如我们想去重建 test1,可以执行命令make test1
来实现 。