Makefile学习
make命令执行时,需要一个Makefile文件,以告诉make命令需要怎么去编译和链接程序。
Makefile中规则的定义
一个Makefile通常由一条或若干条规则组成,每条规则格式通常如下:
1 | target … : prerequisites … |
target
: 通常是文件名或要执行的动作名- 单个文件
- 多个文件,使用空格分隔
- 使用通配符,表示多个文件名
- 压缩包中的文件:
archive(member)
- 要执行的动作名,参见:伪目录
prerequisites
: 通常是目录文件所依赖的文件- 可以为空,表示目录不依赖任何文件
- 多个文件使用空格分隔,当一行太长时可以使用
\
分割成多行写 - 也可以使用通配符,表示多个文件
- 也可是压缩包中的文件
recipe
: 通常是产生目标文件或执行动作名,所执行的具体命令- 每条命令必须: 以
Tab
开头,或以.RECIPEPREFIX
中设置的值开头 - 可以一条命令写一行,也可以多条命令写在同一行。当多条命令写在同一行时需用
;
分隔每条命令。有时命令写在同一行和写在多行特殊情况下的差异
- 每条命令必须: 以
Make如何获取变量值
make命令可以通过以下几种方式来获取变量的值:
- 在执行
make
命令时指定变量名及变量值(e.g:mingw32-make CXXFLAGS='-g -Wall'
) - 在Makefile中定义并设置变量值
- 在环境变量中定义并设置变量值
- 在Makefile中的自变量中取值
- 在make命令的预定义变量中取值
Makefile中的变量
在Makefile中定义一个变量用 VariableName = value
定义变量,引用变量用:${VariableName}
或$(VariableName)
。如果需要在Makefile中使用引用字符 $
,则必须输入 $$
才行。
下面示例就定义并引用了 “objects” 变量
1 | objects = program.o foo.o utils.o |
变量赋值
在Makefile中可以通过以下几种赋值操作给变量赋值:
赋值操作符 | 功能描述 |
---|---|
= |
直接赋值,若值中有引用会递归展开 |
:= |
简单赋值,或值中有变量引用只展开在此之前已定义的变量值 |
::= |
同:= 等效 |
?= |
如果变量未被定义过,才赋值 |
!= |
先将右边的值在shell中执行,再将执行的结果赋给变量(换行符被替换成空格) |
+= |
追加赋值 |
= 跟 := , ::= 的区别
1 | a = gcc |
?= 赋值
1 | a = gcc |
!= 跟 += 赋值
1 | hash = test |
注:可以在变量名前添加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 |
|
将多条语句定义成一个变量
1 | define two-lines |
等效于
1 | two-lines = echo foo; echo $(bar) |
示例
1 | define two-lines |
删除定义的变量
1 | foo := foo |
递归变量
当定义一变量,这个变量的值又是另一个变量的引用。将递归展开变量引用最后得到该变量的值:
1 | foo = $(bar) |
上面例子中:foo
变量值是bar
变量的引用,bar
变量值又是ugh
变量的引用。所以foo
变量值为ugh
变量的值“ Huh? ”
递归变量的优点
1 | CFLAGS = $(include_dirs) -O |
正如我们所期望的哪样,CFLAGS
在recipe
中将展开为 “-Ifoo -Ibar -O”
递归变量的缺点
递归变量有可能产生循环引用的情况如: CFLAGS = $(CFLAGS) -O
另一个缺点是每次扩展变量时,都会执行定义中引用的任何函数(请参阅转换文本的函数)。这使得 make 运行速度变慢;
更糟糕的是,它会导致通配符和 shell 函数给出不可预测的结果,因为您无法轻松控制它们何时被调用,甚至调用多少次。
伪目标
伪目录不是表示一个真实的文件名,它仅表示Makefile的一个动作名,这个动作只有在你明却要求时才会被执行。有以下两个原因需要使用伪目标:
- 避免文件名与目标名冲突
如果你写了一个Makefile运行,但在该动作中又不会创建对文件的目标文件。哪么该动作在make都会被执行。例如下这个规则:
1 | clean: |
因为rm
命令不会创建名为clean
的文件,如果原目录中本身也没有名为clean
的文件。哪么rm
命令每次都会被执行,当你执行mingw32-make clean
时。
但是如果目录是存在名为clean
的文件是,上面例子中的clean
将不能为正确的执行。当你输入mingw32-make clean
时。为了解次这个问题,你需要定伪目录代码如下:
1 |
|
当定义了伪目标时,当你执行mingw32-make clean
时,无论名为“clean”的文件是否存在都会执行clean
动作。
- 提高性能
多个命令写在一行和写在多行的区别
我们以下面这个Makefile文件为例来讲解一些特殊情况下的单行命令和和多行命令的区别
Makefile
1 |
|
在该Makefile中创建了两个虚拟动作”test1”和”test2”两个动作都是执行相同的两条命令。唯一的区别就是:一个是把两条命令写在同一行,而另一个是把两条命令写在了两行。
mingw32-make test1 执行结果
1 | TGL233@TGL-ThinkPad MINGW64 ~/Desktop/Hello_wxWidgets |
mingw32-make test2 执行结果
1 | TGL233@TGL-ThinkPad MINGW64 ~/Desktop/Hello_wxWidgets |
从上面示例的执行结果可以看出:
写在同一行时执行完cd subdir
命令后make并没有返回当前目录,而是留友subdir目录下继续执行了pwd
命令所以输出的结果是在subdir下。
而分别写在两行时,在执行完cd subdir
命令后make返回到当前目录了,所以再执行pwd
命令时就输出了当前目录。