另一种范式
我一直觉得,Makefile确实是C/C++程序员的良配,因为Makefile所使用的两种范式都是C/C++程序员不熟悉的,一种是函数式的思想,一种是依赖构成的目标链的模式。
Makefile从最基本上来说,可以抽象成下面这样的:
target ... : prerequisites ...
command
...
...
如大家所熟悉的,这段的意义是:当prerequisites有更新的时候,执行command命令。如果target是一个真实的目标,也就是对应一个真实的文件,那么就生成这个文件。如果是伪目标,可以被用来做为一个入口,比如clean,也可以成为一个真实目标的依赖。
可以明显地分为两个部分:一个是target依赖链的范式,这与过程式语言的C语言非常不同。用蒋军的话讲,跟Prolog有点像。有着它自己的一套逻辑系统。
后面的command,我们前面讲了不少了,我个人是希望大家以函数式的思想来写。
我们落地到一个实际的例子中:
$(BUILT_SYSTEMIMAGE): $(FULL_SYSTEMIMAGE_DEPS) $(INSTALLED_FILES_FILE) $(call build-systemimage-target,$@)
这个$(BUILT_SYSTEMIMAGE),是个真实的目标,对应了要生成的文件system.img,如下:
BUILT_SYSTEMIMAGE := $(systemimage_intermediates)/system.img
下面来看看,system.img所依赖目标,先看第一个,结果这一个实际上又是两个:
FULL_SYSTEMIMAGE_DEPS := $(INTERNAL_SYSTEMIMAGE_FILES) $(INTERNAL_USERIMAGES_DEPS)
然后,我们发现这个依赖在一层层地扩张:
INTERNAL_SYSTEMIMAGE_FILES := $(filter $(TARGET_OUT)/%, \ $(ALL_PREBUILT) \ $(ALL_COPIED_HEADERS) \ $(ALL_GENERATED_SOURCES) \ $(ALL_DEFAULT_INSTALLED_MODULES) \ $(PDK_FUSION_SYSIMG_FILES) \ $(RECOVERY_RESOURCE_ZIP))
扩张还在继续,比如,对于ALL_PREBUILT,各个模块不断地把自己的东西增加进去:
ALL_PREBUILT += $(TARGET_OUT)/bin/monkey
其他的目标原理相通,这里就不多浪费篇幅了。
总而言之,这一大套目标中,只要有任何一个有变化,system.img就要重新生成了。如何生成?在下一行中写着呢:调用build-systemimage-target啊。
Makefile写作指南
目标式和函数式两种范式都学好了,下面是我们如何组织材料来完成我们的工程的时候了。
- 先定义总目标
Makefile的总的目的是输出一个或多个结果文件,先把总的目标定义好。
然后假设子目标都已经构建好了,下面写一个将这些中间产品变成最终目标的脚本。
比如编译一个简单的C程序,总的目标是一个可执行文件,最终加工时的材料是已经编译好的.o文件和输入的第三方的库文件等,我们先不管它们是如何编译的,假设它们已经做好了,我们只要写一个链接的脚本就好了。
像我们上面的system.img的例子,反正依赖多,就分门别类的列吧,最终我们只需要把它们打个包就好了。
- 层层分解,逐步完成
然后去寻找,构成这个大目标的第一层的构件是什么,像上面我们所看过的一样,逐层扩张。
对于C文件,这时候才考虑每一个.o是如何从源文件编译的。
- 模块化、函数化
上面两步都是目标模式的,这一步开始搞函数模式了。将各目标中可重用的函数抽象出来,该分文件就分文件,该整理代码就整理代码等
- 测试调优
当一个工程大到一定程度的时候,Makefile的可读性会严重下降。
这时候我们还是按目标式和函数式两条主线来降低复杂度。目标是层次式的,我们可以一层一层地调试,比如先调从.c到.o的编译过程,再调将.o链接起来的总装部分.
哪一个子模块出问题,就专门调那一部分的。
对于功能部分,我们一直强调函数式思想就是希望,对于某一个确实性的输入,能有一个确定性的输出,没有副作用,这样能够将调试的难度降低,我们可以一个函数一个函数地调试。
Makefile的调试以打日志为主,还可以通过make -p来输出完整的变量和目标列表。
make -p,看看make都做了些什么
下面是我在cygwin下的make -p的输出结果的节选
Make工具的信息
首先是Make工具汇报下自己的基础情况:
The files is:main.cpp
变量
下面是变量的列表,包含我们自己定义的,也包含make自动为我们生成的。
目录信息
隐含规则信息
文件目标和假目标