分类:
2011-06-13 00:32:13
原文地址:makefile入门实践 作者:fireaxe
一、
学习makfile Example000基础makefile:这是最基本的makefile工程框架,其中包括一个main.c文件、六个source文件与六个header文件,还有一个Makefile文件与一个.批处理文件。每个source文件包含一个header文件,所有的header文件都是空的,而main文件则包含所有的header文件。
/* c001.c */ #include "../include/c001.h" void c001(void) { }
main.c文件是工程的入口函数。
/* mian.c */ #include "../include/c001.h" #include "../include/c002.h" #include "../include/c003.h" #include "../include/c004.h" #include "../include/c005.h" #include "../include/c006.h" int main(void) { return 0; }
run.bat的作用是设置环境变量,当然也可以用bat自动运行makefile文件。注意,等号与前后命令之间不能有空格存在,否则dos无法识别
echo run.bat @echo off set path=C:\Tornado2.2ppc\host\x86-win32\bin;%path% echo %path%
工程没有makefile文件也可以编译,而批处理文件完全是为了是为了实现自动化而编写的,利用批处理文件也可以一次完成工程的编译,但是批处理文件的功能太过简单,自动化程度很低,难以实现工程的维护。而且批处理文件一般不能识别文件是否需要编译,因此它每次都是编译所有文件,对于大型项目会浪费大量的时间。
Makefile的基本原理是建立文件之间的依赖关系,利用时间戳来实现文件的自动更新,而不是把所有的文件都重新编译一次。
下面是example000中的makefile的结构:
# example000 OBJS = main.o c001.o c002.o c003.o c004.o c005.o c006.o CC = ccppc LD = ldppc myedit : $(OBJS) $(LD) -e main -o $@ main.o $(OBJS) main.o : main.c c001.h c002.h c003.h c004.h c005.h c006.h $(CC) –c $^ c001.c : c001.c c001.h $(CC) –c $^ c001.c : c001.c c001.h $(CC) –c $^ c001.c : c001.c c001.h $(CC) –c $^ c001.c : c001.c c001.h $(CC) –c $^ c001.c : c001.c c001.h $(CC) –c $^ c001.c : c001.c c001.h $(CC) –c $^
.PHONY: clean clean: -rm myedit $(OBJS)
|
Example001 工程层次化(F
Example000中把所有的源文件都放到了当前目录下,这样不利于工程文件的组织,因此在Example001中建立了source与include两个文件夹,暂时还无法把便以后的文件放到指定文件夹中的功能。如果是直接利用cc命令编译文件名,那么就需要明确指明被编译文件的位置,如include/c001.h,而定义为makefile的变量后只需利用VPATH与vpath两个命令指定搜索路径就可以直接找到所需文件。
另外我们看到$(CC) –c $^重复出现了多次,它的规则都是相同的,因此即使去掉该命令行makefile也是可以自动进行的,这也就是makefile中的默认规则,这样我们的makefile得到了极大简化。
# example001
CC = ccppc # 新加入 LD = ldppc # 新加入
vpath %.c source vpath %.h include
OBJS = main.o c001.o c002.o c003.o c004.o c005.o c006.o
myedit : $(OBJS) $(LD) -e main -o $@ $(OBJS) main.o : main.c c001.h c002.h c003.h c004.h c005.h c006.h c001.o : c001.c c001.h c002.o : c002.c c002.h c003.o : c003.c c003.h c004.o : c004.c c004.h c005.o : c005.c c005.h c006.o : c006.c c006.h
.PHONY: clean clean : -rm myedit $(wildcard *.o) # 有改动 |
在学习makefile是,我曾经使用过一种极端简化的格式,但是实践证明该方法只能保证第一次编译时工程能够被正确编译,但他们的依赖关系并没有建立起来,也就是说之是由于工程比较简单,makefile利用默认规则就完成了编译,当修改了某一个头文件时,makefile并不能重新编译依赖它的文件。
# wrong example
CC = ccppc LD = ldppc
vpath %.c source vpath %.h include OBJS = c001.o c002.o c003.o c004.o c005.o c006.o c007.o c008.o c009.o c010.o
myedit : main.o $(OBJS) $(LDFLAGS) -e main -o $@ main.o $(OBJS) main.o : $(@: .o=.c) $(OBJS: .o=.h) $(OBJS) : $(@: .o=.c) $(@: .o=.h)
.PHONY: clean clean : -rm myedit $(wildcard *.o) |
通过对该错误的总结,得到了一下结论:
² makefile 中除了定义变量、建立环境和包含其它make文件外,其结构是非常单一的。依赖关系与依赖关系下的各种命令组成了一个个单元,这一个个的单元组成了makefile文件。单元间没有太多联系
² 单元内部是一脸关系与命令组成的双层结构。当依赖关系建立后,下面的命名可以使用$@与$<等自动变量,而依赖关系中是不能使用他们的。
模块化(From001):
为了实现模块化结构,要求把实现不同内容的makfile放到不同的文件中,在需要的时候才包含如总makefile文件中,下面是总makefile文件的内容。
# example002 Makefile
CC = ccppc LD = ldppc
include m001.mak
.PHONY: clean001 clean001 : -rm myedit $(wildcard *.o) |
下面是子模块的makefile文件,一般以mak或mk为后缀。在上层makefile中定义的变量是可以直接在子makefile文件中使用的。
# example002 m001.mak
vpath %.c source vpath %.h include
OBJS = c001.o c002.o c003.o c004.o c005.o c006.o有改动
myedit : main.o $(OBJS) $(LD) -e main -o $@ main.o $(OBJS) main.o : main.c $(OBJS:.o=.h) # 有改动 c001.o : c001.c c001.h c002.o : c002.c c002.h c003.o : c003.c c003.h c004.o : c004.c c004.h c005.o : c005.c c005.h c006.o : c006.c c006.h |
Example003 自动生成依赖关系(From002):
Gnu的编译器提供了自动推导依赖关系的功能,其语句是 $(CC) –M c001.c。在例子中这句命令的输出是c001.o : c001.c c001.h。利用该功能得到了如下的改进后的makefile。
# example003 m001.mak OBJS = main.o c001.o c002.o c003.o c004.o c005.o c006.o
.PHONY: m001 m001: myedit myedit : $(OBJS) $(LD) -e main -o $@ $(OBJS)
%.d: %.c $(CC) -M $< | \ sed "s/$*.o[ :]/$*.o $*.d :/g" \ > $@
include $(OBJS:.o=.d) |
这个文件与Example002相比区别就是下面这些代码。
%.d: %.c 的作用是所有以“.d”结尾的文件都依赖于与它同名的以“.c”结尾的文件。
后三句其实是一句命令,只是分割成三段。$(CC) -M $< 作用是利用编译器推导依赖关系。后面的“|”符号是管道连接符,用于把前一行命令产生的结果传给后一个命令。sed "s/$*.o[ :]/$*.o $*.d :/g" 用来修改编译器产生的结果,实际上就是把“.d”结尾的文件也加入了目标集。例如c001.o : c001.c c001.h就变为了c001.o c001.d : c001.c c001.h。 “>”用于把结果输出道文件。“$@”表示目标文件,也就是“%.d”。
$*:makefile中的自动化变量,用于模式规则中的%号。
通过上面这一段代码,就生成了依赖关系并送入相应的“%.d”文件,然后只需把这些规则加入makefile即可。当然其中只有依赖关系没有编译命令。也就是结果Example002中的一样。因此通过makefile的默认规则就可以自动编译。
到此为止,我们的makefile已经基本成型了,当加入新的自文件时,只需修改极少的地方能够就可以完成工程架构。
Example004 make的递归执行(From003):
下面试Example003中的总makefile的内容,该文件实质的内容就是一个submake的伪目标。调用该为目标后,就会自动执行cd subprj && $(MAKE) -f submake.mak,从而调用subprj目录下的submake.mak文件。
# example004 Makefile
include variable.mak
.PHONY: submake submake: cd subprj && $(MAKE) -f submake.mak
.PHONY: clean clean : $(RM) myedit $(wildcard *.o *.d) |
执行cd命令后,make的当前目录变量CURDIR会改变为subprj(注意此变量不要手动赋值,否则它就是去了自动更新的功能)。这样所有的工作目录就转到了subprj中,而且除非用export声明,父makefile中的变量都不能在子makefile中使用。因此本文中把新的变量声明放到了variable.mak文件中,当进入子目录后,再次包含该文件就完成了变量的设置。
# example004 variable.mak
MAKE = make CC = ccppc LD = ldppc RM = rm -f SOURCEPATH = source INCLUDEPATH = include OUTPUTPATH = output
vpath %.c source vpath %.h include |
可以利用如下方法验证该规则:
可以看到,引入variable.mak后,$(CC)的内容变成了我们定义的ccppc,而未引入时的内容则是make默认的cc。
为了不改变原有工程的结构,对与不涉及工程目录编译的实践(如:变量)将在subprj目录中进行。
Example005 变量(From004):GNU make中有两种定义变量的方法:
名称 |
递归展开式变量 |
直接展开式变量 |
方式 |
VAR = var |
VAR := var(多一个冒号) |
特点 |
在引用的地方是严格的文本替换,相当于c语言中的宏定义 |
更类似于c语言中变量的定义方式 |
展开时机 |
只有在被引用的时候才回被展开 |
在定义时被展开 |
递归调用的特点 |
根据起展开时机,当发生递归时,会根据当前变量的值来赋值 |
由于是定义时展开,变量的值只与定义时变量的值有关 |
例子 |
foo = hello bar = $(foo) foo = world .PHONY: sub sub : @echo $(bar) |
foo := hello bar := $(foo) foo := world .PHONY: sub sub @echo $(bar) |
结果 |
world |
hello |
分析 |
变量 bar被echo调用时会被展开成$(foo),而此时foo的值为world,因此输出world |
变量定义时会自动展开$(foo),此时foo的值为hello,因此bar的的值也变为了hello,当echo调用变量bar时,就会自动替换为hello |
变量有如下三种类型:
² 全局变量:一般形式的比那辆
² 目标指定变量:target : VAR = var
² 模式指定变量:%.d : VAR = var
后两种变量相当于局部变量,只有条件满足时才会起作用