Makefile中的规则是为了说明何时以及如何生成target,规则列出了targetprerequisitesrecipe

除了第一条规则作为默认规则,其他规则的顺序并不重要。如果第一条规则有多个target,那仅有第一个target作为默认target。以“.”开头的target一定不会是默认target

因此当Makefile编译多个程序时,会使第一条规则的默认targetallprerequsities为依赖的多个程序。

规则示例

下面是一个规则示例

foo.o : foo.c defs.h       # module for twiddling the frobs
        cc -c -g foo.c

targetfoo.oprerequisitesfoo.c def.crecipe中有一条命令cc -c -g foo.crecipe是以tab开头的。

这条规则说明两件事,一是如何判断foo.o是否过期;二是如果更新foo.o

规则语法

targets : prerequisites
        recipe

targets : prerequisites ; recipe
        recipe

targets是以空格分隔的文件名,也可以是通配符表示的文件名。正常情况下,targes唯一。

recipe默认以tab开头,但是也可以使用.RECIPEPREFIX变量指定。第一行recipe可以以;跟在prerequisites后,也可以以tab开头另起一行。

$被用于变量引用,需要使用$本身,语法$$

规格告诉make两件事情:targets何时过期;如果更新targets

Prerequisites的类型

prerequisites有两种类型,一是normal prerequisites,另一种时order-only prerequisites

normal prerequisites有两个功能,分别是:

  1. 规定了prerequisitesrecipe必须在target之前执行。
  2. prerequisites发生变化时,target必须更新。

order-only prerequisitesnormal prerequisites的不同点在于,不执行第2条,也就是说order-only prerequisites发生变化时,target不会更新。

使用order-only prerequisites的语法如下:

targets: normal-prerequisites | order-only-prerequisites

示例:

objdir := objdir
# 规则1
all: test.c | $(objdir)
    @echo "excute recipe"

# 规则2
$(objdir):
    @echo "mkdir objdir"
    mkdir $(objdir)

在这个例子中,第一次make时会先执行规则2->规则1,但是修改objdir文件夹中的内容后再次make时仅会执行规则1,而不会执行规则2。因为order-only prerequisites发生变化时,targets是不会更新的。

文件名中使用通配符

使用通配符的单个文件名可以指定多个文件。make中的通配符有*?[...]*.c指代一系列以.c结尾的文件名。

~开头的文件名具有特殊含义,如果后面直接跟/则表示当前用户目录。如果后面跟单词则代表该单词所表示的用户目录,例如~john/bin表示/home/john/bin。和环境变量HOME含义一致。

targetsprerequisites中的通配符会被make扩展,recipes中的通配符由shell来扩展。其他情况下只有使用通配符函数才会被扩展。

要使用通配符自身,需要使用\转移。

下面是在recipes中使用通配符,由shell扩展。

clean:
    rm -rf *.o

下面是在prerequisites中使用通配符,由make进行扩展。

print: *.c
    lpr -p $?
    touch print

该条规则会打印更新的所有.c文件。

在变量中直接使用通配符不会被扩展,例如objects=*.o。但是如果该变量用到prerequisitesrecipes时或使用通配符函数会被扩展,例如objects:=$(wildcard *.c)

wildcard函数

规则中的通配符会自动扩展,但是定义变量或函数参数中的通配符不会自动替换。如果需要在这些扩展通配符,需要wildcard函数。函数原型如下:

$(wildcard pattern...)

这个函数可以用在makefile的任何地方,会被模式规则匹配的一系列文件进行替代。

# 当前目录下所有.c文件
$(wildcard *.c)
# 所有.c文件对应的.o文件
$(patsubst %.c, %.o, $(wildcard *.c))

# 编译所有源文件并链接如下所示(采用隐含规则)
objects := $(patsubst %.c, %.o, $(wildcard *.c))
foo: $(objects)
    cc -o foo $(objects)

在目录中查找prerequisites

在大型过程中,通常会将源码和二进制文件放在不同目录。make可以自动搜索多个目录来查找prerequisites来支持这一点。

VPATH变量为所有prerequisites指定搜索目录。make会在VPATH指定的目录中查找当前目录中不存在prerequisitestargets

VPATH变量中,目录名称由冒号或空格分隔。列出的目录顺序是make的搜索顺序。

# 列出了两个目录,src和../headers, make查找的顺序一致
VPATH = src:../headers

vpath指令

  • 类似于VPATH变量,但是更具选择性,因为它可以为模式匹配的文件指定搜索路径。
  • 使用语法如下:

vpath pattern directoriespattern匹配的文件查找指定的directories

vpath pattern清除与pattern关联的目录。

vpath清除之前使用的所有目录。

# 在../headers目录中查找头文件
vpath %.h ../headers

vpath %.c foo
vpath % blish
vpath %.c bar

目录搜索执行规则

  1. 如果在makefile指定的路径中不存在target文件时,则执行目录搜索。
  2. 如果目录搜索成功,则保留该路径,并将此文件暂时作为目标。
  3. targetprerequisites搜索方法同上。
  4. 如果target不用更新,则使用目录搜索的路径;如果target需要更新,那么target会在当前目录生成而不是在目录搜索产生的路径中。

使用目录搜索时写recipe

当通过目录搜索在另一个目录中找到prerequisites时,写recipe务必细心,要保证make能够找到prerequisites

自动变量:$^表示列出的所有prerequisites(包括从目录搜索中找到文件);$@表示target$<表示第一个prerequisites

# CFLAGS变量能够指定C编译参数。
foo.o : foo.c
    cc -c $(CFLAGS) $^ -o $@

# $<表示第一个prerequisites,不会包含头文件
VPATH = src:../headers
foo.o : foo.c defs.h hack.h
        cc -c $(CFLAGS) $< -o $@

伪target

target不是一个文件名,而是要执行的recipe的名称。使用伪target有两个原因:1是避免和同名文件冲突;2是提高性能。

# 该规则不会创建clean文件,所有每次执行make clean时rm命令都会被执行。并且如果目录中存在clean文件时,这条规则不会正常执行。
clean:
    rm *.o temp

# 下面这条规则,无论当前目录中是否存在clean,都会被执行
.PHONY: clean
clean:
    rm *.o temp

将伪targetmake递归调用结合使用是很有用的。

# 在这个例子中,SUBDIRS变量列出所有要构建的目录。
# 并且采用循环遍历子目录的方式构建子目录
SUBDIRS = foo bar baz

subdirs:
    for dir in $(SUBDIRS); do \
        $(MAKE) -C $$dir; \
    done

# 这种方式存在两个问题:
# 一是如果其中一个子目录编译出现问题,其他子目录会继续编译。
# 二是不能使用make的并行构建方式。
# 为了解决上述问题,可以采用下面的构建方式
SUBDIRS = foo bar baz

.PHONY: subdirs $(SUBDIRS)

subdirs: $(SUBDIRS)

$(SUBDIRS):
    $(MAKE) -C $@

# 定义foo必须在baz构建之后
foo: baz

**伪target会自动跳过隐式规则搜索。**这也是伪target性能高的原因。

target不应该是targetprerequisites,否则的话每次运行make都会更新target

target也可以拥有prerequisites

# 如果Makefile同时生成多个程序时,通常采用如下形式。
# 将第一个target定义为伪target,依赖其他所有需要生成的程序。
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

如果一个规则没有prerequisitesrecipe,并且target是一个不存在的文件,那么这条规则无论何时运行都会更新target,因此所有依赖这个target的规则recipe总是会被执行。

# FORCE没有prerequisites和recipe,依赖于FORCE的clean的recipe总是会被执行
# 这种方式和.PHONY异曲同工。
clean: FORCE
        rm $(objects)
FORCE:

# ECHO没有prerequisites,依赖于ECHO的$(SUBDIRS)的recipe每次都会被执行。
$(SUBDIRS):ECHO
    make -C $@

ECHO:
    @echo $(SUBDIRS)

target规则

当规则中拥有多个target时,target会分为两类:独立target和分组target。具体是哪种target,由target后面的分隔符决定。

具有独立target的规则

使用标准target分隔符(“:”)的规则为独立target的规则,“:”用来定义独立targets。这等价于为每个target编写具有相同prerequisitesrecipe的规则。recipe能够使用自动变量$@来指定正在构建的target

具有独立target的规则有两个使用场景:

# 1、所有target仅有prerequisites,没有recipe
kbd.o command.o files.o: command.h

# 上面这条规则等价于
kbd.o: command.h
command.o: command.h
files.o: command.h

# 2、所有target具有相似的recipes,通过$@变量来指定将要构建的target
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

上面的示例展示如果对不同的target使用相同的prerequisitesrecipes,如果想要对不同的target使用不同的prerequisites,可以采用静态模式规则。

具有分组target的规则

多个targetprerequisites使用&:分隔则是具有分组target的目标。

分组target的规则的使用场景是组内的每个target都是由规则中的recipe生成。

make构建任何一个分组target时,他知道组内的其他target也是由recipe构建的,因此当其他目标不存在或过时,会自动进行更新。

具有多条规则的target

单个文件可以是多个规则的target。make生成该target,所有这些规则的prerequisites会进行合并。如果任何一个prerequisitestarget新,则会重新生成target

单个文件尽可以使用一个recipe生成,如果多条规则具有多个recipe,则会执行最后一个并打印错误。

静态模式规则

静态模式规则是指定多个target并根据target名称为每个target构造prerequisites的规则。这比具有多个target的规则更通用,因为prerequisites不要求相同,相似即可。

静态模式规则语法

targets...: target-pattern: prereq-patterns...
    recipe
    ...

targets列表指定规则适用的target。可以和普通规则的target一致包含通配符。

target-patternprereq-pattern说明了如何生成每个targetprerequisites。每个target都与target-pattern进行匹配用来提取target匹配的部分作为词干,这个词干替换到prereq-pattern用以生成每个targetprerequisites

每个模式通常仅会包含一个%。当target-pattern匹配target时,%会匹配target名称的一部分,这部分成为词干。剩余的部分也必须完全匹配。例如foo.o匹配%.ofoo.c不匹配%.o

每个targetprerequisites都是将名称中%替换为词干。例如如果词干是foo,而prereq-pattern%.c,则prerequisitesfoo.c

prerequisites中可以不包含%,那么这个prerequisites对每个target都是一致的。

# 从对应的.c文件生成foo.o和bar.o
objects = foo.o bar.o

all: $(objects)

$(objects): %.o: %.c
        $(CC) -c $(CFLAGS) $< -o $@
# $<是自动变量表示prerequisites,$@表示target。

**每个target必须匹配target-pattern,否则会提示警告。**如果由多个文件,其中由部分文件不匹配target-pattern,可以使用filter函数进行过滤。

files = foo.elc bar.o lose.o

$(filter %.o,$(files)): %.o: %.c
    $(CC) -c $(CFLAGS) $< -o $@
$(filter %.elc,$(files)): %.elc: %.el
    emacs -f batch-byte-compile $<

静态模式规则和隐式规则