只说一下思路。
先说说make吧。make这个工具只用来编译软件太可惜了,它最强大的地方其实是在模式推导,我觉得用它来编译软件只有一个理由,就是你希望做增量编译,而实际上仔细一想,它的增量编译只能做到比较文件修改时间上,这太粗糙了,想一下你有一个头文件,被100个.cpp包含,你只改其实一个类的实现,而只有一个实现的.cpp文件用到了它,那其它99个也会被增量编译出来,它没办法做到‘发现’更小的修改单元。而对于‘增量编译’的需求,往往是程序员自己写程序的过程中,实际软件发布构建的时候都是rebuild,所以make真是一把巨大的牛刀杀了只小鸡。反过来想,如果make不能做到增量编译,那干嘛要用make,bash脚本能做的事更多。
make执行的过程大致为,先执行一遍make中的脚本,把所有变量的值求出来,它的求值很诡异,它是反复求的,你不能写出这样的var = $(var)cc,类似这种。然后依次执行make 给出的参数中的目标,如果不给目标,就执行第一个目标,然后停止。执行目标的时候就可能引发推导:
目标1 目标2 。。。: 依赖1 依赖2 。。。
模式动作
如果当前在make目标1,那它通过这条规则需要依赖后面那些目标,如果后面那些是文件,就会比较和目标的最后修改时间,如果依赖更新,就执行动作,如果目标1不存在,或者是伪目标,也会执行动作,如果依赖本身不存在,或者是伪目标,就会递归的make它,直到需要的目标全部make出来。它的执行可能会失败,因为没有规则可供make出依赖的目标。
软件构建的过程,通常一个软件源码会放在某个目录下,你可以分很多目录,这里放模块1,那里放模块2等等。首先需要告诉make,我们有哪些source要编译,很简单比如:
source = a.c b.c main.c
有了source,后面的过程就很机械化,编译到.o,连接到目标程序,目标库等
通过make的脚本函数,求出object = a.o b.o main.o,写出规则:
$(product) : $(object)
连接
%.o : %.c
编译
后面这个是模式规则,它说形如这样的目标,可以被形如那样的目标这样make,%是通配符。
大概过程是这样,还有一些,比如用什么编译,编译参数是什么,用什么连接,连接器参数是什么,我们可以用另外一个配置文件来控制,另外那个文件也是一个makefile,只不过是一部分,用来配置的部分,像这样:
CC = gcc
CFLAGS = xxx
product = hello
等等
这部分可以做成成套的配置,比如用一个脚本变量表示你需要在哪种环境下构建,env=linux env=macos等,用type表示你要构建执行程序 静态库还是动态库等,它们会影响到后面的规则中的动作,这样只要在make命令中加参数 #make env=linux type=exe中控制就好了。
更复杂的事情:自动依赖,和自动搜索源
.d文件是GNUC推荐的依赖描述文件,它表示对应的.o文件依赖于哪些文件,通常会是一个源文件和一些头文件,我们不需要自己去写,使用gcc -M参数就能求出来,于是一个通用的规则就出来了:
%.d : %.c
gcc -M $< > $@
这些.d文件这样make出来后,怎样包含进来呢?include $(depend)。include会被执行2次,第一次失败,因为确实没有$(depend),报错,但是make不会马上失败,它会尝试make $(depend),然后再include,如果成功继续执行,第一次include会报错,所以改成-include,安静的包含。
自动搜索的意思就是,我不需要指定source,我只指定一个目录或多个目录,我就是要编译这些目录下的源文件,这个功能很淫荡,你不用每次向项目中添加一个.cpp文件就要改工程配置,我们的makefile会察觉出你的新增文件,在下次构建时自动编译进来。
先写一个bash脚本,名叫findsource,递归的把当前目录下的源文件,以及目录的目录下的所有源文件找出来,这不难办到,这个脚本直接输出到标准输出流,格式是:
source = ...
在makefile里先写:
-include __auto_source__
再写一条规则:
__auto_source__ :
findsource > $@
make里还能写clean install,这些就不多说了。