make命令执行时,需要一个Makefile文件,以告诉make命令需要怎么去编译和链接程序。

Makefile中规则的定义

一个Makefile通常由一条或若干条规则组成,每条规则格式通常如下:

1
2
3
target … : prerequisites …
recipe

  1. target: 通常是文件名或要执行的动作名
    • 单个文件
    • 多个文件,使用空格分隔
    • 使用通配符,表示多个文件名
    • 压缩包中的文件: archive(member)
    • 要执行的动作名,参见:伪目录
  2. prerequisites: 通常是目录文件所依赖的文件
    • 可以为空,表示目录不依赖任何文件
    • 多个文件使用空格分隔,当一行太长时可以使用 \ 分割成多行写
    • 也可以使用通配符,表示多个文件
    • 也可是压缩包中的文件
  3. recipe: 通常是产生目标文件或执行动作名,所执行的具体命令

Make如何获取变量值

make命令可以通过以下几种方式来获取变量的值:

  1. 在执行make命令时指定变量名及变量值(e.g: mingw32-make CXXFLAGS='-g -Wall')
  2. 在Makefile中定义并设置变量值
  3. 在环境变量中定义并设置变量值
  4. 在Makefile中的自变量中取值
  5. 在make命令的预定义变量中取值

Makefile中的变量

在Makefile中定义一个变量用 VariableName = value 定义变量,引用变量用:${VariableName}$(VariableName)。如果需要在Makefile中使用引用字符 $ ,则必须输入 $$ 才行

下面示例就定义并引用了 “objects” 变量

1
2
3
4
5
objects = program.o foo.o utils.o
program : $(objects)
cc -o program $(objects)

$(objects) : defs.h

变量赋值

在Makefile中可以通过以下几种赋值操作给变量赋值:

赋值操作符 功能描述
= 直接赋值,若值中有引用会递归展开
:= 简单赋值,或值中有变量引用只展开在此之前已定义的变量值
::= :=等效
?= 如果变量未被定义过,才赋值
!= 先将右边的值在shell中执行,再将执行的结果赋给变量(换行符被替换成空格)
+= 追加赋值
= 跟 := , ::= 的区别
1
2
3
4
5
6
7
8
9
10
11
12
13
14
a = gcc
b = $(a) $(d)
c := $(a) $(d)
d = '-c' -g
e ::= $(a) $(d) $(f)
f = '-o'

all:
@echo "b = $(a) $(d) -> " ${b} # b是使用'='赋值其后的值会递归展开,其输出是“ b = gcc '-c' -g -> gcc -c -g ”
@echo 'c := $(a) $(d) -> ' $(c) # c使用':='赋值其后的值只会简单展开,在定义c之前只定义了a,d还未定义。其输出是“ c := gcc -c -g -> gcc ”
@echo 'e ::= $$(a) $$(d) $$(f) -> ' $(e) # '::='跟':='效果一样,其输出为“ e ::= $(a) $(d) $(f) -> gcc -c -g ”

# 上面的示例也展示了Makefile中‘’和“”的输出差别

?= 赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
a = gcc
c = -c
a ?= g++
b ?= -g -Wall
c = -o

all:
@echo $(a) $(b) $(c)

# 上面示例输为:gcc -g -Wall -o
# a开始已定为gcc,后的a ?= g++将不起作用
# b开如没定义,所以 b ?= -g -Wall有效
# c开始定义为-c,后面又被覆盖为-o了
!= 跟 += 赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
hash = test
hash != printf '\043'
file_list != ls -F .
hash += '!=赋值示例'

all:
@echo '$(hash)'
@echo $(file_list)

# 上面示例输出为:
# # !=赋值示例
# HelloWorld_Mingw/ Makefile
# 使用!=赋值时会将右边的值在shell中执行,再将执行的结果赋给变量(换行符被替换成空格)

注:可以在变量名前添加override关键字,来强行覆盖原值。此时不管有哪种赋值操作符

Makefile编译规则中的自变量

在Makefile编译规则中可以使用如下自变量

变量名 功能描述
$@ 表示target名,如果目录是压缩文件,则指压缩文件名;在有多个目标的模式规则中$@指引起该规则的目标名
$% 目标成员名称,当目标是存档成员时。(e.g: 如果目标是 foo.a(bar.o),那么“$%”就是 bar.o,“$@”就是 foo.a。 当目标不是存档成员时,“$%”为空)
$< 第1个依赖的先决条件名(e.g: 如果目标是 prerequisite : a.o b.o 那么“$<”就是 a.o)
$? 比目标新的所有先决条件的名称,它们之间有空格。 如果目标不存在,则将包括所有先决条件。 对于存档成员的先决条件,仅使用命名成员
$^ 所有先决条件的名称,它们之间有空格。 对于存档成员的先决条件,仅使用命名成员(请参阅存档)。 目标对其依赖的每个其他文件只有一个先决条件,无论每个文件被列为先决条件多少次。 因此,如果您为某个目标多次列出先决条件,则 $^ 的值仅包含该名称的一个副本
$+ 这就像’$^’,但不止一次列出的先决条件按照它们在makefile中列出的顺序重复。 这主要用于链接命令,其中以特定顺序重复库文件名是有意义的。
$| 所有先决条件的文件列表名(暂没理解到)
$* 这个变量表示目标模式中“%”及其之前的部分。如果目标是“dir/a.foo.b”,并且目标的模式是“a.%.b”,那么,“$*”的值就是“dir/a.foo”。这个变量对于构造有关联的文件名是比较有较。如果目标中没有模式的定义,那么“$*”也就不能被推导出,但是,如果目标文件的后缀是make所识别的,那么“$*”就是除了后缀的那一部分。例如:如果目标是“foo.c”,因为“.c”是make所能识别的后缀名,所以,“$*”的值就是“foo”。这个特性是GNU make的,很有可能不兼容于其它版本的make,所以,你应该尽量避免使用“$*”,除非是在隐含规则或是静态模式中。如果目标中的后缀是make所不能识别的,那么“$*”就是空值。
$(@D) $@中对就去的目录名(去掉最后一个反斜线),如$@中含目录刚$(@D)为‘ . ’
$(@F) 目标文件名的目录内文件部分(e.g: 如果‘$@’ 是 dir/foo.o ,则 ‘$(@F)’ 是 foo.o. ‘$(@F)’ 等同于 ‘$(notdir $@)’. )
$(*D) 跟上面类似
$(*F) 跟上面类似
$(%D) 跟上面类似
$(%F) 跟上面类似
$(<D) 跟上面类似
$(<F) 跟上面类似
$(^D) 跟上面类似
$(^F) 跟上面类似
$(+D) 跟上面类似
$(+F) 跟上面类似
$(?D) 跟上面类似
$(?F) 跟上面类似
示例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

all : a.o b.o c.o foo.a(bar.a)
@ls *.[o,c]

%.o :
@echo '$$@ is: ' $@
@touch $@

clean:
-rm --verbose *.o
-rm --verbose prerequisite

foo.a(bar.a) :
@echo '$$@ is: ' $@ '$$% is: ' $%

prerequisite : a.c b.c a.c c.c b.c
@echo "$$< is: " $<
@echo '$$? is: ' $?
@echo '$$^ is: ' $^
@echo '$$+ is: ' $+
@echo '$$| is: ' $|
touch $@

dir/a/foo.a :
@echo '$$(@D) is: ' $(@D)
@echo '$$(@F) is: ' $(@F)
@echo $(notdir $@)


# TGL@TGL MSYS ~
# $ make;make clean;make prerequisite ; touch b.c ; make prerequisite; make dir/a/foo.a;
# $@ is: a.o
# $@ is: b.o
# $@ is: c.o
# $@ is: foo.a $% is: bar.a
# a.c a.o b.c b.o c.c c.o
# rm --verbose *.o
# removed 'a.o'
# removed 'b.o'
# removed 'c.o'
# rm --verbose prerequisite
# removed 'prerequisite'
# $< is: a.c
# $? is: a.c b.c c.c
# $^ is: a.c b.c c.c
# $+ is: a.c b.c a.c c.c b.c
# $| is:
# touch prerequisite
# $< is: a.c
# $? is: b.c
# $^ is: a.c b.c c.c
# $+ is: a.c b.c a.c c.c b.c
# $| is:
# touch prerequisite
# $(@D) is: dir/a
# $(@F) is: foo.a
# foo.a

将多条语句定义成一个变量

1
2
3
4
define two-lines
echo foo
echo $(bar)
endef

等效于

1
two-lines = echo foo; echo $(bar)

示例

1
2
3
4
5
6
7
define two-lines
echo foo
echo $(bar)
endef

all :
@$(two-lines)

删除定义的变量

1
2
3
4
5
6
7
8
9
foo := foo
bar := bar

undefine foo
undefine bar

all :
$(info $(origin foo))
$(info $(flavor bar))

递归变量

当定义一变量,这个变量的值又是另一个变量的引用。将递归展开变量引用最后得到该变量的值:

1
2
3
4
5
6
foo = $(bar)
bar = $(ugh)
ugh = Huh?

all:
echo $(foo)

上面例子中:foo变量值是bar变量的引用,bar变量值又是ugh变量的引用。所以foo变量值为ugh变量的值“ Huh? ”

递归变量的优点

1
2
CFLAGS = $(include_dirs) -O
include_dirs = -Ifoo -Ibar

正如我们所期望的哪样,CFLAGSrecipe中将展开为 “-Ifoo -Ibar -O”

递归变量的缺点

递归变量有可能产生循环引用的情况如: CFLAGS = $(CFLAGS) -O
另一个缺点是每次扩展变量时,都会执行定义中引用的任何函数(请参阅转换文本的函数)。这使得 make 运行速度变慢;
更糟糕的是,它会导致通配符和 shell 函数给出不可预测的结果,因为您无法轻松控制它们何时被调用,甚至调用多少次。


































伪目标

伪目录不是表示一个真实的文件名,它仅表示Makefile的一个动作名,这个动作只有在你明却要求时才会被执行。有以下两个原因需要使用伪目标:

  1. 避免文件名与目标名冲突

如果你写了一个Makefile运行,但在该动作中又不会创建对文件的目标文件。哪么该动作在make都会被执行。例如下这个规则:

1
2
clean:
rm *.o temp

因为rm命令不会创建名为clean的文件,如果原目录中本身也没有名为clean的文件。哪么rm命令每次都会被执行,当你执行mingw32-make clean时。
但是如果目录是存在名为clean的文件是,上面例子中的clean将不能为正确的执行。当你输入mingw32-make clean时。为了解次这个问题,你需要定伪目录代码如下:

1
2
3
4
.PHONY: clean

clean:
rm *.o temp

当定义了伪目标时,当你执行mingw32-make clean时,无论名为“clean”的文件是否存在都会执行clean动作。

  1. 提高性能

多个命令写在一行和写在多行的区别

我们以下面这个Makefile文件为例来讲解一些特殊情况下的单行命令和和多行命令的区别

Makefile
1
2
3
4
5
6
7
8
9
10
.PHONY: test1 test2

test1:
@echo "-------------两条命令写在两只一行-----------------"
cd subdir; pwd

test2:
@echo "-------------两条命令分别写在两行-----------------"
cd subdir
pwd

在该Makefile中创建了两个虚拟动作”test1”和”test2”两个动作都是执行相同的两条命令。唯一的区别就是:一个是把两条命令写在同一行,而另一个是把两条命令写在了两行。

mingw32-make test1 执行结果
1
2
3
4
5
TGL233@TGL-ThinkPad MINGW64 ~/Desktop/Hello_wxWidgets
$ mingw32-make test1
-------------两条命令写在两只一行-----------------
cd subdir; pwd
/c/Users/TGL233/Desktop/Hello_wxWidgets/subdir
mingw32-make test2 执行结果
1
2
3
4
5
6
TGL233@TGL-ThinkPad MINGW64 ~/Desktop/Hello_wxWidgets
$ mingw32-make test2
-------------两条命令分别写在两行-----------------
cd subdir
pwd
/c/Users/TGL233/Desktop/Hello_wxWidgets

从上面示例的执行结果可以看出:
写在同一行时执行完cd subdir命令后make并没有返回当前目录,而是留友subdir目录下继续执行了pwd命令所以输出的结果是在subdir下。
而分别写在两行时,在执行完cd subdir命令后make返回到当前目录了,所以再执行pwd命令时就输出了当前目录。