ILD

make学习笔记:基础知识
作者:Yuan Jianpeng 邮箱:yuanjp89@163.com
发布时间:2019-5-18 站点:Inside Linux Development

2 An Introduction to Makefiles

2.1 What a Rule looks like

target ... : prerequisites ...

    recipe

    ...

    ...


A recipe is an action that make carries out. 


target过期的时候,recipe会执行,下列任何一种情况被认为是过期的:

1 target对应的文件(或文件夹)不存在。

2 target是PHONY类型。

3 target存在依赖,存在一个依赖是过期的。

4 target存在依赖,存在一个依赖的时间戳比target的时间戳新。


除了rule,makefile也可以包含其他语法,比如变量赋值等。


目标

Goals are the targets that make strives ultimately to update。default goal是makefile中的第一个target。


3 Writing Makefiles

3.1 What Makefiles Contain

makefile包含5种东西:

explicit rules, implicit rules, variable definitions, directives, and comments.


directive包括:

reading another makefile

deciding whether to use or ignore a part of the makefile

define a variable from a verbatim string containing multiple lines


#开头的行,是注释,recipe中的注释,将传递给shell。


3.1.1 splitting long lines

makefile使用line-based语法,newline字符是特殊的,标志着statement的结束。GNU make不限制statement line的长度,直到用尽电脑的内存。


但是长行不利于阅读,可以使用 backslash (\) / newline 来打断长行。


makefile处理backslash/newline 对于recipe和non-recipe不同。


对于non-recipe,backslash /newline被转换成一个单个的space character。一旦这个完成,backslash/newline周围的所有空白都被浓缩成一个空白字符。

1
2
3
4
5
6
7
8
9
10
$ cat Makefile 
word = hello \
        world
 
all:
        echo "$(word)"
 
$ make
echo "hello world"
hello world


3.2 What name to give your makefile

默认按下列文件去搜索:GNUmakefile, makefile, Makefile

也可以使用-f name或--file=name,来指定makefile文件。


3.3 Including Other Makefiles

include filenames ...

filenames可以包含shell file name patterns,如果filenames为空,则什么也不包含,也不会报错。

filenames可以包含变量和函数引用,它们会被扩展。


如果被包含的文件不存在,那么make会给出一个警告信息,但是不会立即导致一个fatal error。然后make继续解析,执行remake(执行叫filename的target),remake之后,如果文件存在,则会include。如果make找到了remake,但是执行失败,那么make会产生fatal error。如果没有remake的rule,make会立即产生fatal error。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ cat Makefile 
include a
 
all:
        echo "c=$(c)"
 
a:
        echo c=a > a
 
$ make
Makefile:1: a: No such file or directory
echo c=a > a
echo "c=a"
c=a
$ make
echo "c=a"
c=a

第一次执行,a文件不存在,所以给出警告,然后remake,执行a目标,生成a,然后在执行make的默认目标。

第二次执行,a已经存在了,不会触发remake,目标a就不会执行了。


如果include的makefile不存在,但是存在remake的rule,如果这个rule执行后,没有产生makefile,那么也不会报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ cat Makefile
include a
 
all:
        echo all
 
a:
        echo a
herbert@herbert-pc:/work/learn/make$ make
Makefile:1: a: No such file or directory
echo a
a
echo all
all

这里只是给出了警告,没有报错。

-include或者sinclude,可以忽略文件不存在警告和错误,但是remake还是会执行,remake rule的失败也被忽略。


3.4 the variable MAKEFILES

如果定义了环境变量MAKEFILES,那么make会在读取其它之前包含这些文件。但是default goal不会在这些文件中,如果这些文件不存在,也不会报错。


3.5 How makefile are remade

makefile可以包含其它makefile,如果其它makefile可能被更新,那么make可以得到最新版本的makefile来读取。makefile也可以写规则来更新自己,这也会导致remake。


makefile在读取所有makefiles后,make会尝试将每个makefile作为一个goal target,尝试更新它。如果makefile有一个rule来表明如何更新它,或者有implicit rule,那么执行这些rule。如果make发现某些makefile更新了,那么会再次包含它们。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ cat Makefile 
include a
 
all:
        echo "c=$(c)"
 
.PHONY: a
a:
        echo c=a > a
 
$ ls
a  Makefile
$ make
echo c=a > a
echo "c=a"
c=a

如上,include a,尽管a已经存在了,但是还是会执行,因为a总是过时的,也就是说a总是依赖于规则被remake的。


如果使用double-colon,且有一个没有依赖的recipe,对于这种被包含的makefile,make有意地不执行remake。因为如果执行的话,那将是一个死循环。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ ls
Makefile
$ cat Makefile 
include a
 
all:
        echo "c=$(c)"
 
a::
        echo c=a > a
 
$ make
Makefile:1: a: No such file or directory
echo "c="
c=

如果a::是双冒号的,所以a不会被remake,没有规则remake a,此时include失败,也不会导致fatal error。


3.6 Overriding part of another makefile

有时候,你想一个Makefile几乎像另外一个Makefile,你可以使用include,添加更多的targets或者变量定义,但是两个makefile有同一个target,不同的recipe是非法的,但是有其它的方法:

1
2
3
4
5
6
7
foo:
    frobnicate > foo
     
%: force
    @$(MAKE) -f Makefile $@
 
force: ;

force是为了保证target那个文件存在,也执行recipe。force添加一个空的recipe是为了防止隐含规则,否则它将匹配%,导致一个循环依赖。


上述还有一个问题,%会匹配这个makefile本身,同时这个%又总是过时的,这会导致remake。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ cat Makefile
 
sd:
        echo sd
 
%: force
        make -f inc.mk $@
 
force: ;
 
$ make all
make -f inc.mk Makefile
make[1]: Entering directory '/work/learn/make'
make[1]: Nothing to be done for 'Makefile'.
make[1]: Leaving directory '/work/learn/make'
make -f inc.mk all
make[1]: Entering directory '/work/learn/make'
echo inc.mk
inc.mk

可以看到,这里触发了Makefile的remake。这可能不是我们想要的。可以加一个空的 Makefile: ;


3.7 How make reads a Makefile

GNU make通过两个distinct phases来完成它的工作。


first phase读取所有的Makefiles,internalizes所有的变量和它们的值,implicit / explicit rules,构造targets的依赖路径和它们的依赖。


second phase根据内部结构决定那些targets需要rebuilt,执行它们的rule。


了解这两个阶段是很重要的,这影响变量和函数扩展是如何发生的。


如果扩展在第一阶段发生,则我们称它是immediate的。这种情况下,make会在parse的时候扩展变量或函数。


如果扩展没有立即执行,我们称它是deferred的。deferred construct的扩展不会立即执行,直到这个construct出现在immediate context中,或者直到second phase。


variable assignment

如下,左边的变量名都是立即扩展的,但是变量的值根据情况是immediate或者deferred的。


immediate = deferred

immediate ?= deferred

immediate := immediate

immediate ::= immediate

immediate += deferred or immediate

immediate != immediate


define immediate

deferred

endef


define immediate =

deferred

endef


define immediate ?=

deferred

endef


define immediate :=

immediate

endef


define immediate ::=

immediate

endef


define immediate +=

deferred or immediate

endef


define immediate !=

immediate

endef


+= 评估的类型,跟里面变量的类型一致。


注意一个延后的变量被立即评估,附给另外一个变量,但是这个被延后的变量,仍然是延后的。看下面的例子

1
2
3
4
5
6
a := 2
b = $(a)
c := $(b)
a := 3
$(error $c))
$(error $b))

两个error处,c的值是2,但是b的是3。


conditional directives

条件语法是立即评估的。因此automatic variables不能用在条件语法中。自动变量直到rule的recipe被调用时才会设置。如果需要根据自动变量条件执行,可以通过shell的条件语法实现。


Rule definition

rule总是用相同的方式扩展,如下:

immediate : immediate ; deferred

    deferred


target和prerequisite总是立即扩展,recipe总是延后扩展。


3.8 Second Expansion

前面,我们学习到make工作在两个phase,read-in phase和target-update phase。make还有一个能力:只对一些targets的prerequisites执行second expansion。


二次扩展是在两个phase之间执行的。


二次扩展通过添加一个特殊的目标.SECONDEXPANSION,这个目标之后的所有目标的依赖执行二次扩展。


例子:

1
2
3
4
5
.SECONDEXPANSION:
ONEVAR := onefile
TWOVAR := twofile
all: $(ONEVAR) $$(TWOVAR)
        echo $^

第一次评估的时候:onefile $(TWOVAR)

第二次评估的时候:onefile twofile


由于一次扩展是immediate的,而二次扩展是在first phase之后,所以对于延后的变量一次扩展和二次扩展可能有不同的结果。


二次扩展真正展现力量的是用在自动变量上面,因为在依赖上面,自动变量,只有在二次扩展时,才可用:

1
2
3
4
.SECONDEXPANSION:
main_OBJS := main.o try.o test.o
lib_OBJS := lib.o api.o
main lib: $$($$@_OBJS)

第一次扩展的目的是变成 $($@_OBJS),这样第二次扩展就能根据目标来选择依赖。


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