伪目标不是一个真实的文件名。当用 make 显式指定请求时,伪目标仅仅是将要执行的命令的名字。有两个理由去使用伪目标:一是避免和相同的文件名产生冲突;二是提升性能。
如果你写了一条规则(rule),而该规则的命令不会实际创建目标文件,那么 make 每次到达该目标时,其命令总会被执行。例如:
clean:
rm *.o temp
因为 rm 命令不会创建名为 clean 的文件,也许永远也不存在这样的文件。因此,每次执行 make clean 的时候,rm 命令都会被执行。
上面的例子中,如果 Makefile 同目录下存在名为 clean 的文件,clean 目标的执行将会出现问题。因为没有依赖文件,clean 总是被认为是最新的,所以 clean 的命令永远不会被执行。为了避免这种问题,可以显式的将其声明为伪目标,方法是让它成为特殊目标 .PHONY 的依赖目标,如下所示:
.PHONY: clean
clean:
rm *.o temp
一旦将 clean 指定为伪目标,无论名为 clean 的文件是否存在,命令总会被执行。
伪目标在串联 make 的递归调用时也非常有用(请参阅 5.7 Recursive Use of make)。在这种情形下,makefile 总会包含一个变量,该变量列出了多个将要被构建的子目录。一种简单的处理方法是定义一个规则,其命令通过循环遍历子目录,如下:
SUBDIRS = foo bar baz
subdirs:
for dir in $(SUBDIRS); do \
$(MAKE) -C $$dir; \
done
然而,这种方法存在很多问题。首先,子目录的构建中, make 检测到的任何错误都会被忽略,也就是说一条命令执行失败时,将会继续构建其余的目录。当然,通过添加 shell 命令可以解决这个问题,但不幸的是,这会使得 make 的 -k 参数失效。此外,更重要的一点是,你将无法利用 make 并行构建(请参阅 5.4 Parallel Execution)的能力,因为只有一条规则。
你可以通过将子目录声明为伪目标(你应当这么做,因为很显然子目录总会存在,否则将不会构建)解决这些问题。
SUBDIRS = foo bar baz
.PHONY: subdirs $(SUBDIRS)
subdirs: $(SUBDIRS)
$(SUBDIRS):
$(MAKE) -C $@
foo: baz
这里我们做了这样的声明,baz 子目录完成构建前,foo 子目录不会构建。在并行构建中,这种关系声明非常重要。
对于 .PHONY 目标,隐式规则(请参阅 [10. 隐式规则](#10. 隐式规则))的搜索过程将会被跳过。这就是 .PHONY 能提升性能的原因,甚至你根本就不用关心真实文件是否存在。
伪目标不应当是一个真实目标文件的依赖,如果是,那么每次 make 更新这个文件的时候,伪目标的命令都会被执行。只要伪目标不是一个真实目标的依赖,那么只有伪目标被指定为最终目标时,其命令才会被执行(请参阅 [9.2 通过参数指定最终目标](#9.2 通过参数指定最终目标))。
伪目标也可以有依赖。当一个目录下包含多个程序时,通过一个 ./Makefile 描述所有程序将非常便利。因为 makefile 里面的第一个目标时默认执行的目标,所以通常将命名为 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
prog1: prog1.o sort.o utils.o
cc -o prog3 prog3.o sort.o utils.o
你可以通过 make 执行者三个程序,或者将他们中的部分指定为 make 参数(如:make prog1 prog3)。伪目标不能继承,也就是说其依赖本身不是伪目标,除非显式指定。
当一个伪目标是另一个伪目标的依赖时,其将成为另一个伪目标的子路径。例如,下面的 make cleanall 将会删除目标文件,差异文件和程序文件。
.PHONY: cleanall cleanobj cleandiff
cleanall: cleanobj cleandiff
rm program
cleanobj:
rm *.o
cleandiff:
rm *.diff