上一篇笔记对makefile变量的用法做了一些介绍:makefile学习笔记(二),本文将介绍makefile的其他规则。
1. 多目标规则与多规则目标
多目标规则可以简单地理解为是一种将多条具有相同依赖和相同生成命令的规则合并成一条规则的语法。其基本格式为:
targets...: prerequisites...
commands
...
假设有如下所示的makefile:
all: target1 target2
echo "This is a rule for $@"
target1: dep
echo "This is a rule for $@"
target2: dep
echo "This is a rule for $@"
dep:
利用多目标规则,可以将上述makefile改写成如下所示的makefile文件,文件名为multi_targets.mk:
all: target1 target2
echo "This is a rule for $@"
# 利用多目标规则合并 target1 和target2的规则
target1 target2: dep
echo "This is a rule for $@"
dep:
在终端中make一下这个文件,结果如下:
makefile中,一个目标可以同时出现在多条规则中,这样的目标就称为多规则目标。这种情况下,此目标文件的所有依赖文件将会被合并成此目标一个依赖文件列表,其中任何一个依赖文件比目标更新(比较目标文件和依赖文件的时间戳)时,make将会执行特定的命令来重建这个目标。要注意以下两点:
- 对于一个多规则的目标,重建此目标的命令只能出现在一个规则中(可以是多条命令)。
- 如果多个规则同时给出重建此目标的命令,make将使用最后一个规则的命令,同时提示错误信息。
2. 静态模式规则
以上一节project_demo2项目的makefile文件作为例子,引出这个新话题:
# 定义可执行文件变量
executbale := hello
# 定义源文件列表变量
sources := main.c hello.c
# 使用变量的引用替换,定义objects文件列表
objects := $(sources:.c=.o)
# 定义编译命令变量
CC := gcc
# 终极目标规则: 生成hello可执行文件
$(executbale): $(objects)
# 使用自动化变量改造编译命令
# 等价于:gcc main.o hello.o -o hello
$(CC) $^ -o $@
# 子规则1:main.o的生成规则
main.o: main.c
# 等价于:gcc -c main.c -o main.o
$(CC) -c $< -o $@
# 子规则2:complicated.o的生成规则
hello.o: hello.c
# 等价于:gcc -c hello.c -o hello.o
$(CC) -c $< -o $@
截取其中的两条子规则如下所示:
# 子规则1:main.o的生成规则
main.o: main.c
# 等价于:gcc -c main.c -o main.o
$(CC) -c $< -o $@
# 子规则2:complicated.o的生成规则
hello.o: hello.c
# 等价于:gcc -c hello.c -o hello.o
$(CC) -c $< -o $@
观察一下这两条子规则,我们不难发现:
1.它们的命令是一样的;
2.它们的目标依赖关系有点相似(目标都是以 “.o” 结尾的文件,依赖都是以 “.c” 结尾的文件)。
对于这种长得很像的规则,makefile提供了一种称为静态模式规则的规则来帮助我们简化规则的编写。我们可以把它理解为一种特殊的多目标规则,它仅要求多条规则具有相同的命令,而依赖可以不完全一样。其基本语法如下:
TARGETS ...: TARGET-PATTERN: PREREQ-PATTERNS ...
COMMANDS
...
大致意思就是:用TARGET-PATTERN: PREREQ-PATTERNS … 描述的模式,从TARGETS … 取值来形成一条条规则,所有规则的命令都用COMMANDS。对这些词句的解释如下:
- TARGETS … 代表具有相同模式的规则的目标列表,在我们的项目中就是main.o和hello.o,我们可以直接引用我们先前定义的objects变量 。
- TARGET-PATTERN 称为目标模式,其作用就是从目标列表中的目标匹配过滤出需要的值。目标模式中的字符%表示在匹配过滤的过程中不做过滤的部分,其他字符表示要与目标列表中的目标精确匹配,例如,目标模式 "%.o"表示从目标列表的目标中匹配所有以.o结尾的目标,然后过滤掉匹配目标的.o部分, 因此目标main.o经过目标模式%.o匹配过滤后,得到的输出就是main。
- PREREQ-PATTERNS 称为依赖模式,其作用是表示要如何生成依赖文件。具体的生成过程,就是使用目标模式过滤出来的值,替换依赖模式字符%所表示的位置。因此,如果依赖模式为%.c, 则使用上述例子过滤出来的main来替换字符%, 最终得到依赖文件main.c目标模式和依赖模式中,一般需要包含模式字符 %。
因此就可以用静态模式规则来简化项目的makefile文件,将子规则1和子规则2替换为如下的代码(其中objects变量先前已经定义过,表示main.o 和hello.o):
$(objects): %.o: %.c
$(CC) -o $@ -c $<
- “%.o"是目标模式,它将目标文件 main.o 和 hello.o 的”.o"部分
过滤掉,只剩下"main"和"hello";- “%.c"是依赖模式,过滤后的"main"和"hello"将各自替换”%",形成两个依赖文件名"main.c"和"hello.c"。
这样就可以只通过一句话就搭建起 main.o 依赖于 main.c 、hello.o 依赖于 hello.c 这两组依赖关系,并让这两组依赖关系都套用下面的规则命令。
3. 伪目标
在项目编译完成后,会产生可执行文件及中间目标文件,有时出于某些需求,需要将编译生成的文件都删除,让整个项目回到最初的状态。对此,可以在项目makefile定义添加一条目标为clean的规则。以project_demo2项目为例,我们编写其makefile如下:
executbale := hello
sources := main.c hello.c
objects := $(sources:.c=.o)
CC := gcc
$(executbale) : $(objects)
$(CC) $^ -o $@
$(objects): %.o : %.c
#(CC) -c $^ -o $@
#为项目新添加了如下的clean规则
clean:
rm -rf hello main.o hello.o
先make一下:
然后查看项目的文件列表,发现多了hello, hello.o 和 main.o 三个文件:
make一下clean规则,然后再次查看项目文件列表,发现 hello, hello.o 和 main.o 三个文件被删除,说明clean起了作用。
上述的clean规则,貌似能正常工作,但当项目目录下刚好存在一个叫做clean的文件时,会存在bug,clean无法正常使用:
出现以上问题的原因是:当编译目录下存在clean文件时,由于clean规则没有依赖,所以clean文件的时间戳永远显得都是最新的,导致在执行make clean时,系统将make的注意力集中在了clean文件上,而clean规则无法被执行。要解决这个问题,我们需要定义伪目标。
当我们将一个目标定义成伪目标时,就意味着它再也不会代表一个真正的文件名,这样的话,在执行make时就可以指定这个目标来执行其所在规则定义的命令,而不会去理踩目录中的同名文件。
定义一个伪目标的基本语法如下:
.PHONY: <伪目标>
定义项目的clean目标为伪目标,代码如下:
.PHONY: clean
clean:
rm -rf complicated complicated.o main.o
这样目标clean就是一个伪目标,无论当前目录下是否存在clean这个文件,系统都不会将clean看作一个真正的文件名,输入make clean之后,rm命令都会被执行。验证一下:
可以看到,就算目录中有clean文件,makefile中的clean规则也可以正常执行。