Skip to content

LV030-目标

一、特殊目标

Makefile 中有很多特殊的目标,很可能都不会接触到,但是还是在这里写下笔记吧,万一后边用到了呢。

名称功能
.PHONY这个目标的所有依赖被作为伪目标。伪目标是这样一个目标:当使用 make 命令行指定此目标时,这个目标所在的规则定义的命令、无论目标文件是否存在都会被无条件执行。
.SUFFIXES这个目标的所有依赖指出了一系列在后缀规则中需要检查的后缀名
.DEFAULTMakefile 中,这个特殊目标所在规则定义的命令,被用在重建那些没有具体规则的目标,就是说一个文件作为某个规则的依赖,却不是另外一个规则的目标时,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进程中,命令行可以并行执行。此目标不应该有依赖文件,所有出现的依赖文件将会被忽略。
## 二、伪目标

还记得前边提到的 .PHONY:clean 把,当时说这是一个伪目标,但是并未说明啥叫伪目标,伪目标有啥用,接下来,就来探索一下吧。

1. 一个问题?

还是以 clean 这个清除操作为例,我们在编译过后,会生成大量的中间文件,当我们定义了 clean 命令后,却并不需要依赖于任何文件,而且也不需要生成任何文件,我们只需要执行这个规则下边的命令即可.

正常情况下,我们执行 make clean 命令就可以执行 clean 目标下的文件,但是如果说 Makefile 文件所在目录下刚好有一个文件叫 clean ,由于这个文件不依赖于任何文件,也不会被修改,所以,它永远都是最新的。于是,我们除了第一次执行 make clean 命令有效外,再执行的时候,就会发现 make 一直会提醒:

shell
make: “clean”已是最新。

make 只会处理修改过的文件,为我们带来便利的同时,也为我们带来了隐藏的麻烦,也就是说我们的目标名不能与某一个文件名一致。

2. 怎么办呢?

为了解决上边的问题,我们可以使用一种特殊的目标 .PHONY , make 不会去检查是否存在有文件名和依赖体中的一个名字相匹配的文件,而是直接执行与之相关的命令,于是这也就被称为伪目标。 make 后不会生成与伪目标同名的文件,伪目标只是一个标签

总的来说,伪目标的作用其实就是为了避免目标名与文件名冲突。那么怎么声明伪目标呢?语法格式如下:

makefile
.PHONY: object
object:
<Tab>[option]command

其实一开始的 clean 就是一个很好的例子:

makefile
.PHONY: clean
clean:
    rm -rf *.o main

伪目标一般没有依赖的文件。但是,我们也可以为伪目标指定所依赖的文件。伪目标说到底也算是一个目标,同样可以作为最终目标,只要将其放在第一个即可。

其实,只要目标名称不要与某一个文件名称一致,不用声明成伪目标也可以,但是建议还是注意一下这个问题。

3. 成为依赖

伪目标可以成为依赖,这是什么意思呢?接下来我们来看一个例子:

makefile
.PHONY: clean clean-o
clean: clean-o
    rm -rf main
clean-o: 
    rm -rf *.o

这样的话,我们执行 make clean 命令会清除所有的 .o 文件和 main 可执行文件,执行 main clean-o 便会只清除所有的 .o 文件。这样我们就可以通过执行不同的命令来删除一部分文件,而保留不想删除的文件。

三、多目标

啥又是多目标嘞?简单来说就是一个 Makefile 文件,直接生成多个可执行文件, Makefile 文件每次只能有一个最终目标,也就是说正常情况下,只会有一个可执行文件,加上一堆的中间文件。

1. 单目标测试

其实我们可以试一下在一个 Makefile 中写上两个目标,然后执行 make 来看看最后输出多少个目标文件。

makefile
demo1:
	@echo "=== 创建演示文件1 ==="
	@mkdir -p demo_dir
	@echo "源文件1" > demo_dir/$@.c

demo2:
	@echo "=== 创建演示文件2 ==="
	@mkdir -p demo_dir
	@echo "源文件2" > demo_dir/$@.c

.PHONY: clean
clean:
	rm -rf demo_dir

然后在终端执行 make 命令,会发现生成了只会执行demo1这个目标下的命令,最终也只会创建一个文件。执行make demo2就可以运行第二个目标的规则生成另一个文件。

一般情况下,一个 Makefile 文件只通过 make 命令一般只会生成一个最终目标,但是可以生成很多中间文件啊,这样就可以让我们生成多个自己想要的文件啦。

2. 多个目标文件

其实很容易想到,一个目标,它所依赖的规则都会生效,那么我们现在要生成两个目标文件,完全可以定义一个总的目标,这个目标依赖于另外两个目标不就行了,于是我们可以修改 Makefile 文件如下:

makefile
all: demo1 demo2
	@echo "Start!"
	
demo1:
	@echo "=== 创建演示文件1 ==="
	@mkdir -p demo_dir
	@echo "源文件1" > demo_dir/$@.c

demo2:
	@echo "=== 创建演示文件2 ==="
	@mkdir -p demo_dir
	@echo "源文件2" > demo_dir/$@.c

.PHONY: clean
clean:
	rm -rf demo_dir

我们执行make命令,会看到如下输出信息:

shell
sumu@virtual-machine:~/hk/alpha$ make
=== 创建演示文件1 ===
=== 创建演示文件2 ===
Start!

如果不能保证工程文件和生成文件没有与all目标同名的文件的话,可以加上 .PHONY: all,让其变成一个伪目标。不写的话可以理解为标签。

3. 多目标规则

上边的其实总的来说还是一个规则只有一个目标,是多个单目标的规则

而 Makefile 还支持一个规则中有多个目标,这个多目标规则所定义的命令对所有目标都生效,一个具有多目标的规则相当于定义了多个单目标规则,但是它们执行的命令类似。多目标规则意味着所有的目标具有相同的依赖文件

多目标通常用于以下情况:

(1)仅需要一个描述依赖关系的规则,不需要在规则中定义命令。例如,

shell
# 这个规则实现了同时给三个目标文件指定一个依赖文件
main.o test1.o test2.o: test.h

(2)对于多个具有类似重建命令的目标。重建这些目标的命令并不需要是完全相同,我们可以在命令行中使用自动化变量 $@ 来引用具体的目标,完成对它们的重建。

Tips:这里还有一个实例,来自于 书写规则 — 跟我一起写Makefile 1.0 文档

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

上边的 generate 根据命令行参数来决定输出文件的类型。使用了 make 的字符串处理函数 subst 来根据目标产生对应的命令行选项。

在多目标的规则中,虽然可以根据不同的目标使用不同的命令(在命令行中使用自动化变量 $@ )。但是,多目标的规则并不能做到根据目标文件自动改变依赖文件(像上边例子中使用自动化变量 $@ 改变规则的命令一样)。需要实现这个目的的话,需要要用到 make 的静态模式。

4. 多规则目标

4.1 :: 规则

看完上边的,我会想,那要是我一个目标需要多个规则来完成怎么办?这个时候我们可以使用 :: 双冒号,这种规则也可以被称之为双冒号规则。双冒号规则允许在多个规则中为同一个目标指定不同的重建目标的命令。

在 Makefile 中,一个目标可以出现在多个规则中,但是这些规则必须是同一类型的,要么都是普通规则,要么都是双冒号规则,坚决不允许一个目标出现在两种规则中

  • 双冒号规则与普通规则的不同

(1)双冒号规则中,当依赖文件比目标更新时,规则将会被执行。对于一个没有依赖而只有命令行的双冒号规则,当引用此目标时,规则的命令将会被无条件执行。而普通规则是,当规则的目标文件存在时,此规则的命令永远不会被执行(目标文件永远是最新的)。

(2)当同一个文件作为多个双冒号规则的目标时,这些不同的规则会被独立的处理,而不是像普通规则那样合并所有的依赖到一个目标文件。这就意味着对这些规则的处理就像多个不同的普通规则一样。也就是说多个双冒号规则中的每一个的依赖文件被改变之后, make 只执行此规则定义的命令,而其它的以这个文件作为目标的双冒号规则将不会被执行。

【注意】同一个目标出现在多个双冒号规则中时,规则的执行顺序和普通规则的执行顺序一样,按照其在 Makefile 文件中的书写顺序执行。

4.2 :::对比

特性单冒号 : (普通规则)双冒号 :: (双冒号规则)
多条规则定义同一目标合并处理:依赖项合并,命令追加。独立处理:每条规则独立检查和执行。
触发条件只要 任意一个 依赖比目标新,所有 命令都会执行。只有当 该条规则特定的依赖 比目标新时,才执行 该条规则 的命令。
混用限制不能与 :: 混用于同一目标。不能与 : 混用于同一目标。
使用频率极高 (99% 的情况)。极低 (罕见,通常不推荐)。

4.3 使用实例

其实一般没怎么见过这种用法,而且搜索的一些资料中似乎也没有很建议用这个,等后面有需要再补充。