Makefile 快速上手 (again)
说来惭愧,之前笔者还认为 Makefile 这种工具已经过时,只需要学 CMake 就行。
但最近在写 boot loader 时遇到了一些问题:我既不是在编译可执行文件,也不是在编译库,这样 CMake 就显得比较无力了,因为总是用 add_custom_*
也不是办法,非常臃肿——毕竟不是在管理一个 C/C++ 应用的项目嘛。所以决定再整理一下 Makefile 的写法。
本文充当一个 Makefile cheat sheet 的作用,自己有点遗忘的时候回来查一查。
Define a Target
在 Makefile 中定义一个可以构建的 target:
1 | <target>: dependency1 dependency2 ... dependency3 |
这样可以使用 make <target>
来执行它。
注意哦,make 会认为 <target>
是一个需要构建的目标文件名。最终按照 commands 生成的文件会被命名为 target
;
Use Variables
Makefile 中定义变量也很方便:
1 | variable_name = file1 file2 ... fileN |
然后和 shell 类似直接用 $()
包裹使用:
1 | myTarget : $(var1) |
Makefile 中还可以使用环境变量,和普通变量用起来一样。你还可以通过执行 make 时的 -e
参数来 override 对应的环境变量:
1 | make -e name=value myTarget |
Use Shell Results
Makefile 还可以直接使用 shell 的输出结果,和变量一样用 $()
包裹:
1 | CC=gcc |
Phony Commands
Makefile 不仅仅可以用于管理项目的编译流程,还能定制一些自动化的指令。例如一个没有任何 dependencies 的 target 就可以被 make 直接执行其中的指令:
1 | clean: |
但由于 make 按照当前 target 文件是否存在、依赖的 dependencies 的时间戳来判断增量执行,假设你创建了一个名为 clean
的文件,很可能 make 就不会再执行上面的指令了:因为 make 认为构建的目标文件已经构建完成了,并没有把它作为一组指令看待。
为了区分指令组,以及真正的 target 目标文件,make 允许在 Makefile 中使用伪指令 .PHONY
,以此来标识这个 target 仅仅是一组指令,每次调用时执行它就行。例如:
1 | .PHONY clean rebuild # 可以指定多个 |
Automatic variables
有的时候在写 commands 的时候需要用到 target
或者 dependencies
中的名字,但是不想重复一遍,因为存在耦合,下次想改名的时候就要一个一个改,不利于维护。于是 Makefile 预定义了一组变量符号:
$@
:当前规则中target
的名称;$^
:当前规则中所有dependencies
的名称;$<
:当前规则中第一个dependencies
的名称;$?
:当前规则中时间戳比target
更新的所有dependencies
的名称组成的变量;
Implicit Rules
如果你使用 Makefile 不是用来管理编译项目的话,本节就不用看啦。
由于 make 一开始是为管理编译 C 语言而设计的,所以它对 C 语言有些 “偏爱”,包含了很多 “隐晦规则”,这让很多人在阅读 Makefile 的时候可能感到困惑。
这就像一些约定俗成的 magic,下面向你展示一个:
对于所有以 .o
扩展名结尾的 target:
- 默认依赖于同名的
.c
文件(如果找不到,则 fallback 到.cc / .cpp
); - 如果依赖于
.c
(C 程序),则默认使用 command:$(CC) -c $(CPPFLAGS) $(CFLAGS) $^ -o $@
; - 如果依赖于
.cc/.cpp
(C++ 程序),则默认使用 command:$(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $^ -o $@
;
然后你就会看见很简洁的 Makefile:
1 | CC = gcc # Flag for implicit rules |
Static Pattern Rules
有的时候,我想利用隐晦规则少写一点东西,但是我又不是在编译 C/C++ 程序(例如做汇编 / 管理其他语言的项目),怎么办?
你可以用这个语法:
1 | targets...: target-pattern: prereq-patterns ... |
这相当于自己定义了一套规则,让所有匹配 prereq-patterns
的 dependencies 在执行 commands 后输出为 target-pattern。
官方的说法是:
The essence is that the given
target
is matched by thetarget-pattern
(via a%
wildcard). Whatever was matched is called the stem. The stem is then substituted into theprereq-pattern
, to generate the target’s prereqs.其本质是,给定的
target
与target-pattern
(通过%
通配符)相匹配。匹配到的内容称为 stem。然后,将 stem 替换为prereq-pattern
,生成target
的 dependencies。
例如,如果我不想编译 C/C++ 程序,只是想汇编一批文件,那么可以这么做:
1 | AS=${CC} -c |
More… ?
好了,上面的用法已经能涵盖 90% 的 Makefile 的用途了
如果你还希望更详细、更“刁钻” 的用法,就应该去查官方文档啦。