ILD

Makefile学习:rules & recipes
作者:Yuan Jianpeng 邮箱:yuanjp89@163.com
发布时间:2019-5-19 站点:Inside Linux Development

4 Writing rules

rule的顺序是无关紧要的,除了决定default goal之外。如果第一个rule有多个targets,只有第一个target被当成default goal。


.开头的target不会被选中为default goal,除非它包含斜杠/。定义pattern rule的target也不影响default goal。


4.1 Rule syntax

长这样:

1
2
3
targets : prerequisites
    recipe
    ...

或者这样:

1
2
3
targets : prerequisites ; recipe
    recipe
    ...


targets是space分开的文件名,可以使用wildcard characters。

1
2
3
4
5
6
7
8
9
$ ls 
a  inc.mk  Makefile
$ cat Makefile 
 
*:
        echo all
 
$ make
make: 'a' is up to date.

如上,可以使用*作为targets。


recipe以TAB键开始,可以使用.RECIPEPREFIX变量修改默认行为。由于$被做为变量引用符,如果想使用$字面量,可以使用两个$,即:$$


4.2 Types of prerequisites

有两种prerequisites:normal prerequisites,order-only prerequisites,语法:

targets : normal-prerequisites | order-only-prerequisites


order-only prerequisites这种依赖,会被执行,但是target是否更新,与这种依赖无关。


4.3 Using Wildcard Characters in File Names

Makefile里的wildcard charcters有* ? [...]


targets和prerequisites中的wildcard expansion是由make执行的。recipes里面的则是由shell执行的。


定义变量的时候,wildcard expansion不生效:

objects = *.o

是真实的字符串*.o,但是如果objects变量用在target或者prerequisite,那么wildcard expansion将在那里发生。


4.4 searching directories for prerequisites

使用VPATH变量,来指定make搜索的目录。如果prerequisite不在当前目录,make就会尝试在VPATH指定的目录去搜索。


VPATH中,变量用冒号:或者空白分开。



4.4.2 vpath directive

可以按文件的类型指定不同的搜索目录,有3种vpath directive


vpath pattern directories

    指定pattern类型文件的搜索目录

    vpath %.o obj/


vpath pattern

    清除pattern关联的搜索路径


vpath

    清除之前vpath指定的所有搜索路径




4.4.3 How Directory Searches are Performed

对于VPATH,如果target要被rebuilt,那么target是本地的,被搜索的路径被thrown away。而依赖则是搜索的路径。

1
2
3
4
5
6
7
8
9
10
11
12
$ cat Makefile 
 
VPATH = obj/
 
a: b
        @echo target: $@
        @echo prerequisite: $<
 
$ touch obj/b
$ make
target: a
prerequisite: obj/b

可以发现依赖为obj/b


4.4.5 Directory Search and Implicit Rules

VPATH或者vpath也会应用到implicit rules。


4.5 Phony targets

有些target本身不是一个文件,你想它们总是会被执行。那么就把它声明为PHONY目标,一个例子足以说明一切:

1
2
3
.PHONY: clean
clean:
    rm *.o temp


.PHONY目标的隐含规则搜索被跳过。这可以提升一些性能。


一个真实文件的目标不应该依赖于一个phony 目标。


4.6 Rules without recipes or prerequisites

如果一个rule没有依赖或者recipe。且rule的target文件不存在,则make认为这个rule的target总是过时,意味着依赖这个rule的targets总是会执行它们的recipe。

1
2
3
clean: FORCE
    rm $(objects)
FORCE:


4.9 Multiple targets in a rule

主要是为了书写方便。


4.10 Multiple rules for one target

一个target可以在多个rule中存在,每个rule的依赖都会合并成一个依赖列表,但是只能有一个rule有recipe,如果有多个rule有recipe,make选择最后一个,同时给出警告信息。

1
2
3
4
5
6
7
8
9
10
11
$ cat Makefile 
 
a: c
a: b
        @echo target: $@
        @echo prerequisite: $?
 
$ touch b c
$ make
target: a
prerequisite: b c


如果没有显示规则有recipe,make搜隐式规则来找到一个。


4.11 static pattern rules

静态模式规则是这样一种规则,它有多个静态目标,它们的依赖可以根据目标的名字pattern来匹配,不同目标的依赖必须是相似的,不一定要相同。


语法:

1
2
3
targets ...: target-pattern: prereq-patterns ...
    recipe
    ...


target可以包含通配符。

target-pattern和prereq pattern用来确定每个target如何计算依赖。


每个pattern通常包含一个%。%可以匹配target name的任何部分,这部分被叫做stem。pattern的其它部分必须严格匹配。


prereq pattern中的%被替换为target pattern中匹配的部分。


一个例子足以说明问题:

1
2
3
4
5
6
objects = foo.o bar.o
 
all: $(objects)
 
$(objects): %.o: %.c
    $(CC) -c $(CFLAGS) $< -o $@


每个target必须匹配target-pattern,如果不匹配会给出警告。


4.12 Double-Colon rules

双冒号规则,在target name后面有两个冒号。


当一个目标出现在多个rule中时,它们的类型必须相同,要么都是普通的,要么都是双冒号的。


如果是双冒号的,每个rule是独立的,它们依赖各自的依赖,它们执行各自的recipe。就好像它们的target名字不同一样。


如果双冒号 rule没有任何依赖,那么这个rule的recipe总是会执行,即使target文件已经存在,这和单冒号的不同。


每个双冒号规则,应该指定一个recipe,如果没有隐式的规则将被应用,如果存在的话。


5 Writing recipes in rules


5.1 recipe syntax

recipe以tab字符开始,任何以tab开始的行且出现在一个rule context都被当成recipe的一部分。

rule context是rule开始后,直到另外一个rule或者变量定义。


recipe lines中的空行和注释被忽略。


以tab开始的空行不是空的,它是一个empty recipe。


5.1.1 splitting recipe lines

recipe中的backslash/newline对不会被make移除,如何处理是shell的事情。

如果backslash/newline后面的字符是一个recipe prefix character,那这个字符会被移除。

空白字符绝不会添加到recipe中。


5.1.2 using variables in recipes

简单,没啥特别的,要产生shell中的$,可以使用两个$$


5.2 Recipe echoing

recipe line前面加一个@,可以防止回显。-s或者--silent选项可以让make阻止所有的回显。


5.3 Recipe execution

每一行,在一个子shell中执行。


5.4 Parallel execution

-j, --jobs 选项控制并行执行的个数。

在特别的makefile中,可以通过.NOTPARALLEL伪目标来阻止并行执行。


job slots

如果-j后面没接参数,则并行没有限制。


5.5 errors in recipes

recipe line前面加一个破折号-,忽略这个行的错误。


有时候失败了,但是目标文件的时间戳已经更新了,下次可能不会在重新编译目标文件,这可能是一个错误。

.DELETE_ON_ERROR伪目标,可以在失败的时候删除目标文件。


5.7 recursive use of make

使用$(MAKE)变量,而不要直接使用make,它可以保证使用同一个make。


另外,在recipe line中使用MAKE变量,会和在recipe line的前面+有一样的效果。


-t (--touch) -n(--just-print) -q (--question)

这些选项不会执行recipe,但是使用了+的recipe line会被执行。比如你使用了-t选项来touch过期的目标,对于+开始后的行,它们会被执行,比如+make -C subdir,这通常是你想要的结果,因为你也想更新子目录的过期目标。看例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ cat Makefile 
 
 
.PHONY: all
all:
        echo all
        +make -f inc.mk
         
$ cat inc.mk 
 
suball:
        echo suball
 
$ ls
inc.mk  Makefile
 
$ make -t
make -f inc.mk
make[1]: Entering directory '/work/learn/make'
touch suball
make[1]: Leaving directory '/work/learn/make'


可以看到make -f inc.mk执行了,但是echo all没有执行。


5.7.2 Communicating Variables to a Sub-make

export变量到sub-make,使用export语法

export variable ...


想要阻止变量被exported,使用unexport语法

unexport variable


如果想默认所有变量都导出,执行

export


5.7.3 Communicating Options to a Sub-make

选项自动通过MAKEFLAGS传递给sub-make。如果5.7中的例子,MAKEFLAGS的值为tw。


同样的,命令行中的变量定义也通过MAKEFLAGS传递给sub-make。make对待MAKEFLAGS中的变量就好像定义在命令行上一样。


-j选项比较特殊,主make和子make会沟通,保证最多N个job同时执行。


如果你不想传递任何标记下去,可以改变MAKEFLAGS的值:

subsystem:

    cd subdir && $(MAKE) MAKEFLAGS=


5.7.4 The ‘--print-directory’ Option

-w 或 --print-directory,进入目录时打印,make -C时会自动打开这个选项。

--no-print-directory关闭这个选项。


5.8 defining canned recipes

将recipe lines定义在一个define变量中,然后将其扩展到recipe中:

1
2
3
4
5
6
7
define run-yacc =
yacc $(firstword $^)
mv y.tab.c $@
endef
 
foo.c : foo.y
    $(run-yacc)


define在定义的额时候,不会扩展变量引用和和函数调用。另外define是recursively expanded 的。


define里面的每一行,都作为一个recipe行,可以为某些行加上@,那取消回显,只正对该行有效。


整个灌装的也可以加@,如

1
2
foo.c : foo.y
    @$(run-yacc)

那么所有行都取消回显。


5.9 Using Empty Recipes

定义个什么也不做的recipes有时很有用:

target: ;


当然,你也可以使用一行,只有recipe prefix字符,但是在看起来很迷惑,不好识别是空行,还是空recipe


Copyright © linuxdev.cc 2017-2024. Some Rights Reserved.