忽然发觉应该对android 编译脚本Makefile进行下系统的学习,每天都在使用它对android源码进行编译,但是不知道这个编译的过程和配置是很痛苦的。好了,不多说了;
我跟着How to Write makefile这一本书进行学习;
makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 makefile就像一个shell脚本一样,其中也可以执行操作系统的命令。
一、什么是编译?
把代码源文件编译成中间代码文件,这个动作就叫编译,如unix下的.o文件(object file)就是中间代码文件;然后把大量的object文件合成执行文件,这个动作是叫链接;
大多数时候源文件太多,编译生成的中间代码文件太多,而在链接时需明显指出中间目标文件名,很不方便,所以给中间目标文件打个包,windows下叫.lib文件,unix下是.a文件;
二、关于makefile文件
make命令在执行时,需要一个makefile文件告诉make命令怎样去编译和链接程序。
举个例子说明:
[html]
- edit : main.o kbd.o command.o display.o \
- insert.o search.o files.o utils.o
- cc -o edit main.o kbd.o command.o display.o \
- insert.o search.o files.o utils.o
- main.o : main.c defs.h
- cc -c main.c
- kbd.o : kbd.c defs.h command.h
- cc -c kbd.c
- command.o : command.c defs.h command.h
- cc -c command.c
- display.o : display.c defs.h buffer.h
- cc -c display.c
- insert.o : insert.c defs.h buffer.h
- cc -c insert.c
- search.o : search.c defs.h buffer.h
- cc -c search.c
- files.o : files.c defs.h buffer.h command.h
- cc -c files.c
- utils.o : utils.c defs.h
- cc -c utils.c
- clean :
- rm edit main.o kbd.o command.o display.o \
- insert.o search.o files.o utils.o
makefile文件的格式是:
[html]
- target ... : prerequisites ...
- command
target代表目标文件
prerequisites代表生成目标文件所需要的文件或者目标文件,就是依赖文件
command代表执行的命令
当我们执行make命令时,make会找到当前目录下的makefile或者Makefile文件,然后它会找到第一个target,也就是上面例子的edit,把它作为最终文件。(另外clean后面没有依赖文件,所以make不会自动执行,需要make clean去执行),如果edit的依赖文件修改时间要比edit新,那么它就会执行后面的命令,一层一层下去;文件修改了才会从新编译,否则不会,比如我们改变了“command.h”,那么,kdb.o、command.o和files.o都会被重编译,并且,edit会被重链接。
三、makefile中变量的使用
上面的makefile可以看出来.o文件都是多次被使用,这个时候如果加新的.o文件,就需要在好几处加,容易出错。所以这时候就需要变量来。这个时候可以定义一个变量:
[html]
- OBJS = main.o kbd.o command.o display.o \
- insert.o search.o files.o utils.o
使用的时候就可以直接用$(OBJS)来使用变量就可以了;
在makefile中预定义的变量有:
[html]
- $* :不包含扩展名的目标文件名称。
- $+ :所有的依赖文件,以空格分开,并以出现的先后为序,可能包含重复的依赖文件。
- $< :第一个依赖文件的名称。
- $? :所有的依赖文件,以空格分开,这些依赖文件的修改日期比目标的创建日期晚。
- $@ :目标的完整名称。
- $^ :所有的依赖文件,以空格分开,不包含重复的依赖文件。
- $% :如果目标是归档成员,则该变量表示目标的归档成员名称。例如,如果目标名称为(image.o),则 $@ 为 ,而 $% 为 image.o。
- AR :归档维护程序的名称,默认值为 ar。
- ARFLAGS :归档维护程序的选项。
- AS :汇编程序的名称,默认值为 as。
- ASFLAGS :汇编程序的选项。
- CC :C编译器的名称,默认值为 cc。
- CFLAGS :C编译器的选项。
- CPP :C 预编译器的名称,默认值为 $(CC) -E。
- CPPFLAGS :C编译器的选项。
- CXX :C++编译器的名称,默认值为 g++。
- CXXFLAGS :C++编译器的选项。
- FC :FORTRAN编译器的名称,默认值为 f77。
- FFLAGS :FORTRAN编译器的选项。
四、make的自动推导
GNU的make可以自动推导文件以及文件依赖关系后面的命令;如:make看一一个what.o文件,就会自动将what.c加入到依赖文件中来,并且cc -c what.c也会推导出来;
结合变量和自动推导的使用,就可以更新一个全新的makefile文件了;
[html]
- OBJS = main.o kbd.o command.o display.o \
- insert.o search.o files.o utils.o
- edit : $(OBJS)
- cc -o edit $(OBJS)
- main.o : defs.h
- kbd.o : defs.h command.h
- command.o : defs.h command.h
- display.o : defs.h buffer.h
- insert.o : defs.h buffer.h
- search.o : defs.h buffer.h
- files.o : defs.h buffer.h command.h
- utils.o : defs.h
- .PHONE : clean
- clean :rm edit $(OBJS)
这种方法也成为隐晦规则,上面的makefile中,.PHONE代表clean是个伪目标文件,后面会详细解释;
经过进一步的推导,再清新下makefile文件:
[html]
- OBJS = main.o kbd.o command.o display.o \
- insert.o search.o files.o utils.o
- edit : $(OBJS)
- cc -o edit $(OBJS)
- $(OBJS) : defs.h
- kbd.o command.o files.o : command.h
- display.o insert.o search.o files.o : buffer.h
- .PHONE : clean
- clean :
- rm edit $(OBJS)
但是有个缺点就是依赖关系变得很凌乱,具体要看你的喜好来决定是否这样写了;
五、清空目标文件的规则
每个makefile文件都应该有一个清空目标文件(.o,执行文件)的规则,
[html]
- .PHONY : clean
- clean :
- -rm edit $(objects)
.PHONY意思表示clean是一个“伪目标”,而在rm命令前面加了一个小减号的意思就是,也许某些文件出现问题,但不要管,继续做后面的事。
六、Makefile总述
1、makefile文件主要包含五个东西:显式规则,隐晦规则,变量定义,文件指示,注释;
显式规则说明如何生成一个或多个目标文件,是明显指出的要生产的文件,依赖文件,生成的命令;
隐晦规则说明因为自动推导的原因,它可以让makefile文件写的简洁,粗糙,这是由make支持的;
变量不多说;
文件指示包含三个部分,一个是在一个makefile文件中引用另外一个makefile;另一个是根据某些情况制定makefile中的有效部分;还有一种就是定义一个多行的命令(待续);
注释是以#开始,如果要使用#,\#这样转义;
最后还有一个重要的说明,makefile中的命令,必须以Tab键开始;
七、引用其他的makefile文件
makefile中使用include把别的makefile包含进来,make命令开始时,就会寻找include的mk文件,有“-I”或“--include-dir”参数,表示在指定的目录下寻找;
八、环境变量
定义环境变量MAKEFILES;
九、make的工作方式
GNU的make执行不步骤如下:
1、读入所有的mk文件
2、读入所有include的mk文件
3、初始化文件中的变量
4、推导隐晦规则,并分析所有规则
5、为所有目标文件创建依赖关系链
6、根据依赖关系,觉得哪些文件需要生成
7、执行生成命令
1-5步为第一个阶段,6-7为第二个阶段。第一个阶段中,如果定义的变量被使用了,那么,make会把其展开在使用的位置。但make并不会完全马上展开,make使用的是拖延战术,如果变量出现在依赖关系的规则中,那么仅当这条依赖被决定要使用了,变量才会在其内部展开。
一般来说,make会以UNIX的标准Shell,也就是/bin/sh来执行命令。
十、makefile书写规则/文件搜索
通配符:make支持三个通配符:“*”,“?”和“[...]”;×.o并不代表多个.o文件,$(wildcard *.o)可以表示多个.o文件;
VPATH = src:../headers,也可以在make命令的关键字vpath
十一:伪目标
前面说道clean是一个伪目标。“伪目标”并不是一个文件,只是一个标签,由于“伪目标”不是文件,所以make无法生成它的依赖关系和决定它是否要执行。我们只有通过显示地指明这个“目标”才能让其生效。当然,“伪目标”的取名不能和文件名重名,不然其就失去了“伪目标”的意义了。当然,为了避免和文件重名的这种情况,我们可以使用一个特殊的标记“.PHONY”来显示地指明一个目标是“伪目标”,向make说明,不管是否有这个文件,这个目标就是“伪目标”。
伪目标一般没有依赖的文件。但是,我们也可以为伪目标指定所依赖的文件:
[html]
- all : prog1 prog2 prog3
- .PHONY : all
- prog1 : prog1.o utils.o
- cc -o prog1 prog1.o utils.o
- prog2 : prog2.o
- cc -o prog2 prog2.o
- prog3 : prog3.o sort.o utils.o
- cc -o prog3 prog3.o sort.o utils.o
顺便说下:伪目标同样也是可以被作为依赖文件的;
十二:多目标
Makefile的规则中的目标可以不止一个,其支持多目标,有可能我们的多个目标同时依赖于一个文件,并且其生成的命令大体类似。于是我们就能把其合并起来。当然,多个目标的生成规则的执行命令是同一个,这可能会可我们带来麻烦,不过好在我们的可以使用一个自动化变量“$@”(关于自动化变量,将在后面讲述),这个变量表示着目前规则中所有的目标的集合,这样说可能很抽象,还是看一个例子吧。
[html]
- bigoutput littleoutput : text.g
- generate text.g -$(subst output,,$@) > $@
上述规则等价于:
[html]
- bigoutput : text.g
- generate text.g -big > bigoutput
- littleoutput : text.g
- generate text.g -little > littleoutput
其中,-$(subst output,,$@)中的“$”表示执行一个Makefile的函数,函数名为subst,后面的为参数。关于函数,将在后面讲述。这里的这个函数是截取字符串的意思,“$@”表示目标的集合,就像一个数组,“$@”依次取出目标,并执于命令。
十三、静态目标
静态模式可以更加容易地定义多目标的规则,可以让我们的规则变得更加的有弹性和灵活
。我们还是先来看一下语法:
[html]- <targets ...>: <target-pattern>: <prereq-patterns ...>
- <commands>
- ....
targets定义了一系列的目标文件,可以有通配符。是目标的一个集合。
target-parrtern是指明了targets的模式,也就是的目标集模式。
prereq-parrterns是目标的依赖模式,它对target-parrtern形成的模式再进行一次依赖目标的定义。
举个例子说明下:
[html]- objects = foo.o bar.o
- all: $(objects)
- $(objects): %.o: %.c
- $(CC) -c $(CFLAGS) $< -o $@
target-pattern用%.o表示target都是.o结尾的;<prereq-parrterns>定义成“%.c”,意思是对<target-parrtern>所形成的目标集进行二次定义,其计算方法是,取<target-parrtern>模式中的“%”(也就是去掉了[.o]这个结尾),并为其加上[.c]这个结尾,形成的新集合。
而命令中的“$<”和“$@”则是代表自动化变量,“$<”表示所有的依赖目标集(也就是“foo.c bar.c”),“$@”表示目标集(也就是“foo.o bar.o”)。
再看一个例子:
[html]- files = foo.elc bar.o lose.o
- $(filter %.o,$(files)): %.o: %.c
- $(CC) -c $(CFLAGS) $< -o $@
- $(filter %.elc,$(files)): %.elc: %.el
- emacs -f batch-byte-compile $<
$(filter %.o, $(files))表示调用makefile的filter函数;这个例子展示了makefile中更大的弹性。
八、自动生成依赖性
在makefile中,我们的依赖关系可以需要一系列的头文件,但是一个大的工程必须知道c文件包含了哪些头文件,并且加入或删除头文件的时候也要小心翼翼的修改makefile文件,很难维护;大多数的c/c++编译器都支持一个-M/-MM命令选项,可以自动寻找头文件,并生成一个依赖关系。如cc -M main.c,会输出main.o : main.c defs.h。
如何把它应用到makefile文件呢,就是为每个.c文件都生成一个.d的makefile文件用于存放依赖关系,这样我们就可以在makefile文件中更新使用这种依赖关系了;
这里给出一个规则来产生.d文件:
[html]
- %.d: %.c
- @set -e; rm -f $@; \
- $(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
- sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
- rm -f $@.$$$$
这个规则的意思是,所有的[.d]文件依赖于[.c]文件,“rm -f $@”的意思是删除所有的目标,也就是[.d]文件,第二行的意思是,为每个依赖文件“$<”,也就是[.c]文件生成依赖文件,“$@”表示模式“%.d”文件,如果有一个C文件是name.c,那么“%”就是“name”,“$$$$”意为一个随机编号,第二行生成的文件有可能是“name.d.12345”,第三行使用sed命令做了一个替换,关于sed命令的用法请参看相关的使用文档。第四行就是删除临时文件。
总而言之,这个模式要做的事就是在编译器生成的依赖关系中加入[.d]文件的依赖,即把依赖关系:
main.o : main.c defs.h
转成:
main.o main.d : main.c defs.h
于是,我们的[.d]文件也会自动更新了,并会自动生成了,当然,你还可以在这个[.d]文件中加入的不只是依赖关系,包括生成的命令也可一并加入,让每个[.d]文件都包含一个完赖的规则。一旦我们完成这个工作,接下来,我们就要把这些自动生成的规则放进我们的主Makefile中。我们可以使用Makefile的“include”命令,来引入别的Makefile文件(前面讲过),例如:
[html]
- sources = foo.c bar.c
- include $(sources:.c=.d)
上述语句中的“$(sources:.c=.d)”中的“.c=.d”的意思是做一个替换,把变量$(sources)所有[.c]的字串都替换成[.d],关于这个“替换”的内容,在后面我会有更为详细的讲述。当然,你得注意次序,因为include是按次来载入文件,最先载入的[.d]文件中的目标会成为默认目标。