如何写Makefile
在该文开始之前,在chianunix推荐一篇有关Makefile的论坛文章“跟我一起写Makefile”:http://www.chinaunix.net/old_jh/23/408225.html
在csdn见陈浩专栏:http://blog.csdn.net/haoel/article/details/2886#comments
而本文主要关注如何有效的编写一个实用的Makefile,达到在短时间内可以利用Makefile进行编程的目的,所以会略去很多详细的细节,对此有疑问的可以参照上文链接进行阅读。本文分两块来阐述,一是makefile和源文件在同一目录下,二是makefile和源文件不在同一目录下,下面将会针对这两种情况来阐述。
一、同目录下的makefile
1.简单运用
首先,假定在同一目录下有这样几个文件:a.c b.c c.c h.h mian.c。
其中a.c的内容为:
#ifdef __cplusplus extern "C"{ #endif // _cplusplus #include <stdio.h> int fun_a() { printf("called fun_a!\n"); return 0; } #ifdef __cplusplus } #endif // _cplusplus
h.h的内容为:
#ifndef __HEADER__ #define __HEADER__
#ifdef __cplusplus extern "C"{ #endif // _cplusplus int fun_a(); int fun_b(); int fun_c(); #ifdef __cplusplus } #endif // _cplusplus
#endif // __H_H_H__
main.c的内容为:
#ifdef __cplusplus extern "C"{ #endif // _cplusplus #include "h.h" int main() { fun_a(); fun_b(); fun_c(); return 0; } #ifdef __cplusplus } #endif // _cplusplus
限于本文篇幅不再展示b.c的内容和c.c的内容,与a.c基本相同,唯独不同的是二者函数名称分别为fun_b 和 fun_c,并在函数体内输出调用该函数的语句,在此不在列举。
然后在该目录下新建一个名为“Makefile”的文件,注意没有引号并且没有后缀,直接命名为Makefile,然后在其中输入以下内容:
1 test : a.o b.o c.o main.o 2 cc -o test a.o b.o c.o main.o 3 4 a.o : a.c 5 cc -c a.c 6 7 b.o : b.c 8 cc -c b.c 9 10 c.o : c.c 11 cc -c c.c 12 13 main.o : main.c h.h 14 cc -c main.c 15 16 clean : 17 rm test.exe a.o b.o c.o main.o
保存后,进入该目录直接输入make命令。然后输入./test.exe即可看到程序输出结果。先对该Makefile解释如下。
保存后,进入该目录直接输入make命令。然后输入./test.exe即可看到程序输出结果。这时若查看目录,则会发现多了这样几个文件:a.o b.o c.o mian.o test.exe ,接着输入make clean,再次查看目录则发现以上多出的文件已经不存在了。现对该Makefile解释如下。
首先第一行test : a.o b.o c.o main.o 其前段test:为该工程最后的目标名,即我们要生成一个名为test.exe的可执行文件。然而为了生成这样一个文件,它依赖于a.o b.o c.o main.o这些目标文件。然后执行第二句命令用所依赖的文件生成最终目标—test.exe,并且每一行命令必须用tab开头。
接着就会发现a.o 又是依赖于a.c ,为了得到a.o 就会执行第五行生成a.o,同理生成b.o 和 c.o 以及main.o。至此,当我们输入make命令时,make命令所执行的就是以上步骤,注意它并没有执行第16和第17两行。当我们输入make clean时才开始执行这两行语句,用来删除中间文件和最后的.exe文件,就是上文提到的那几个多出的文件。
注意:写Makefile时尽量注意复制粘贴,粘贴别人的makefile有时可能会出问题,因为在粘贴过程中破坏了命令行前的tab。所以这时最后自己再检查一遍。
现在,你可以仿照该Makefile在同一目录下写自己任何想写的程序了。下面再来看看其他一些技巧。
2.使用变量
再次对该Makefile中的内容改成以下内容。
1 Objs = a.o b.o c.o main.o 2 3 test : $(Objs) 4 cc -o test $(Objs) 5 6 a.o : a.c 7 cc -c a.c 8 9 b.o : b.c 10 cc -c b.c 11 12 c.o : c.c 13 cc -c c.c 14 15 main.o : main.c h.h 16 cc -c main.c 17 18 clean : 19 rm test.exe $(Objs)
然后同样输入make 和 ./test.exe查看输出内容,然后make clean,就会发现与前文所说一样。这不过在这次的Makefile中我们使用了变量,即makefile第一行Objs = a.o b.o c.o main.o,这行表示将a.o b.o c.o main.o赋给Objs,即可以通俗的认为现在Objs里就是a.o b.o c.o main.o,而为了a.o b.o c.o main.o,可以使用美元符$,即$(Objs)现在就是a.o b.o c.o main.o,当然以下就不用多说了,用$(Objs)生成最终目标,最后删除$(Objs)。
注意:也可以用${Objs}来引用a.o b.o c.o main.o,读者可自行测试,但一般的“潜规则”直接使用小括号。
3.自动推导
继续将该Makefile改成以下内容,然后执行同上文一样的操作。
1 Objs = a.o b.o c.o main.o 2 3 test : $(Objs) 4 cc -o test $(Objs) 5 a.o : a.c 6 b.o : b.c 7 c.o : c.c 8 main.o : main.c h.h 9 10 clean : 11 rm test.exe $(Objs)
这次我们没有显示的执行生成中间目标文件的命令,如cc -c a.c。但Makefile却工作完好,这时因为make命令具有自动推导的功能,即只要make看到一个[.o]文件,它就会自动的把[.c]文件加在依赖关系中,并且 cc -c whatever.c 也会被推导出来;即如果make找到一个whatever.o,那么whatever.c,就会是whatever.o的依赖文件。所以这次的Makefile变得更加清爽。
既然make可以自动推导,那我们像下面这样会不会有问题了?
Objs = a.o b.o c.o main.o test : $(Objs) cc -o test $(Objs) clean : rm test.exe $(Objs)
当然没有问题,这次让make自己去全部利用.o去推导相应的.c。
注意:以上文件因为都是自身并没有包含其他头文件,即a.c 就是简单的实现一个函数,没有其他头文件引入,所以可以全部略去a.o的生成命令。若其还包含其他头文件,则要显示引入该头文件,如a.o:other.h,然后a.c 文件make自己推导。
4.伪目标
再次对该Makefile改成以下内容。
1 Objs = a.o b.o c.o main.o 2 3 test : $(Objs) 4 cc -o test $(Objs) 5 6 .PHONY : clean 7 clean : 8 -rm test $(Objs)
这次多了第6行——.PHONY : clean;还记得前面我们删除中间文件时都输入“make clean”,这时才会执行clean:行下的删除文件命令。其实,这个clean就是一个标签,make命令本身并不认识,所以make的时候它不会执行,但如果显示的调用时——make clean ,这时make就会知道该执行clean:这句下面的命令了,所以起到了删除文件的效果。当然,你可以将它改成你喜欢的任何名字,如I_Like:,然后make I_Like同样执行位于I_Like下的命令。但这时就会产生问题,若你有个文件名为clean,直接你再执行删除命令make clean时却发现起不到任何效果。
这时使用. PHONY : clean ,即使你现在有一个名为clean的文件,也不会对make clean的作用起到任何影响。申明这句后,输入make XXX时,make只执行Makefile中的XXX。
还有在rm test $(Objs)的前面多了一个“-”,这个只是用来忽略执行该语句的错误,即执行该语句产生的任何错误都将会被忽略掉,以使位于该语句下面的语句得以执行,否则,该语句错误,则make会直接返回而不执行以下的语句。读者可以自行测试这种情况,如加入几行rm命令(本文全是一行),依次删除一个文件,看看效果。
二、不同目录下的Makefile
1.环境变量
这次在同一目录下建立这样几个文件夹:DirA, DirB, DirC, Include, Main,分别将上面的a.c ,b.c, c.c, h.h, main.c放到这几个目录下,然后在和这些文件夹同一目录下建立一个Makefile。输入以下内容。
1 Objs = DirA/a.o DirB/b.o DirC/c.o Main/main.o 2 CFLAGS += -I./Include 3 4 test : $(Objs) 5 cc -o test $(Objs) 6 7 .PHONY : clean 8 clean : 9 -rm *.exe $(Objs)
其他大家现在都应该已经明白,唯独第二行是看起来比较诡异的。CLFAGS是环境变量,由于时间关系,很多东西都介绍不了,细节大家自己去查找资料参照。这里只是告诉make去Include文件夹下去找需要的文件。并且也可以将$(CFLAGS)添加到第五行末尾,或者不加(如本文)。
2.其他
再次更改该Makefile内容如下。
1 Objs = DirA/a.o DirB/b.o DirC/c.o Main/main.o 2 CFLAGS += -I./Include 3 Target = test 4 5 all : $(Target) 6 $(Target) : $(Objs) 7 $(CC) -o $(Target) $(Objs) 8 9 .PHONY : clean 10 clean : 11 -$(RM) $(Target) $(Objs)
继续执行输入make或者make all,将会看到效果是一致的,唯独$(CC)和$(RM)读者已经猜到就是cc和rm,这样我们的makefile更加通用。
3.使用函数
同样,将Makefile内容改成以下内容。
1 Sources = $(wildcard DirA/*.c) $(wildcard DirB/*.c) $(wildcard DirC/*.c) 2 3 Sources += $(wildcard Main/*.c) 4 Objs = $(Sources:%.c=%.o) 5 CFLAGS += -I./Include 6 Target = test 7 8 all : $(Target) 9 $(Target) : $(Objs) 10 $(CC) -o $(Target) $(Objs) 11 12 .PHONY : clean 13 clean : 14 -$(RM) $(Target) $(Objs)
这次不同的是使用了函数wildcard,第一行表示分别从DirA ,DirB,DirC中取出所有的以.c结尾的文件并存在Sources中,第二句则为为Sources追加值,即将从main文件夹里取出的.c文件也存到Sources中去。第四句则是将Sources中所有以.c结尾的文件全部替换成.o结尾的文件,其他同于前文。
三、其他应用
再次更改Makefile文件内容如下:
1 ProjectDir = ./ 2 ConfigMakefile = $(ProjectDir)/Makefile.config 3 -include $(ConfigMakefile) 4 5 Sources = $(wildcard DirA/*.c) $(wildcard DirB/*.c) $(wildcard DirC/*.c) 6 Sources += $(wildcard Main/*.c) 7 8 CFLAGS += -I./Include 9 Target = test 10 11 GlobalMakefile = $(ProjectDir)/Makefile.global 12 -include $(GlobalMakefile)
同时另外新建两个文件,名为:Makefile.config和Makefile.global。分别在其中输入以下内容:
#This is Makefile.config
CC = gcc
RM = rm
#This is Makefile.global
Objs = $(Sources:%.c=%.o)
all : $(Target)
$(Target) : $(Objs)
$(CC) -o $(Target) $(Objs)
.PHONY : clean
clean :
-$(RM) $(Target).exe $(Objs)
然后输入make,make clean等验证结果。
现简要说明Makefile,前三行分别是指定当前工程目录为当前目录,指定ConfigMakefile 为当前目录下的Makefile.config,接着加入该文件,4——9行同前文。11——12行同Makefile.config文件。
四、举例
经过这些,在短时间内写一些Makefile应该问题不大,或者手头要用的时候可以套用,完了再回头来看其他细节问题。现最后举一例来复习和看看新的东西。
假设我们的目录是在"E:\makefile\test2",将我们本文中用到的a.c b.c c.c main.c 和h.h都放在该目录下,在同目录下写一Makefile,其内容如下:
1 CROSS_COMPILE := 2 3 TARGET = $(notdir $(CURDIR)) 4 5 C_FLAGS += -Wall -g 6 7 LD_FLAGS += -lpthread 8 9 SOURCES = $(wildcard *.c) 10 HEADERS = $(wildcard *.h) 11 12 OBJFILES = $(SOURCES:%.c=%.o) 13 14 DATE := $(shell date '+%Y%m%d') 15 16 all: clean $(TARGET) 17 18 $(TARGET): $(OBJFILES) 19 @echo Linking $@ form $^... 20 $(CROSS_COMPILE)gcc $(LD_FLAGS) -o $@ $^ 21 cp $(TARGET) $(TARGET)-$(DATE) 22 $(OBJFILES): %.o: %.c $(HEADERS) 23 @echo Compiling $@ from $<... 24 $(CROSS_COMPILE)gcc $(C_FLAGS) -c -o $@ $< 25 clean: 26 @echo Removing files 27 @rm -rf $(OBJFILES) $(TARGET) *~ *.d .dep
然后执行make,会看到以下信息:
1 $ make 2 Removing files 3 Compiling b.o from b.c... 4 gcc -Wall -g -c -o b.o b.c 5 Compiling main.o from main.c... 6 gcc -Wall -g -c -o main.o main.c 7 Compiling a.o from a.c... 8 gcc -Wall -g -c -o a.o a.c 9 Compiling c.o from c.c... 10 gcc -Wall -g -c -o c.o c.c 11 Linking normal form b.o main.o a.o c.o... 12 gcc -lpthread -o normal b.o main.o a.o c.o 13 cp normal normal-20121122
简单说明下:第一行输出为Makefile第26行,@echo 的作用是显示其后的内容,所以在Makefile中适当加入信息可以查看Makefile的执行情况,若去掉@,直接用echo,则终端会连echo一起输出,读者自行验证。Makefile第27行前也加了@,对命令加@后,则在终端不会显示该命令直接执行。
Makefile第1行为交叉编译,它的赋值是采用“:=”形式的,这里随便提一下,Makefile中的赋值可以是先赋值后声明,如:a = $(b) b=2。但这样问题是如果这样赋值:a=$(b) b = $(a)就会出现问题,所以采用“:=”这种形式就是只能赋给已经做出声明的变量。
Makefile第3行 中的 notdir 为一函数,该函数作用是提取取文件函数,即提取目录地址中“\”后的内容,而$(CURDIR)表示当前目录,所以TARGET最后应该等于我们的目录后的最后一位“test2”。
再顺便提一下自动变量,$@ 扩展成当前规则的目的文件名, $< 扩展成依靠列表中的第一个依赖文件,而 $^ 扩展成整个依赖的列表(除掉了里面所有重 复的文件名)。读者根据输出内容自行查看情况。
五、总结
本文打算到这里就告一段落,由于时间关系,的确没有什么时间可以去认真而又详细的去描述一个知识点,这里只是一个框架,模板,在实际工程中也是相当复杂的。总之,不断努力多摸索才能得到一些更深的体会,欢迎各位朋友讨论。日后若有时间再写其他一些更多的内容。最后费时较多,转载注明出处。