如何写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”。

再顺便提一下自动变量,$@ 扩展成当前规则的目的文件名, $< 扩展成依靠列表中的第一个依赖文件,而 $^ 扩展成整个依赖的列表(除掉了里面所有重 复的文件名)。读者根据输出内容自行查看情况。

五、总结

本文打算到这里就告一段落,由于时间关系,的确没有什么时间可以去认真而又详细的去描述一个知识点,这里只是一个框架,模板,在实际工程中也是相当复杂的。总之,不断努力多摸索才能得到一些更深的体会,欢迎各位朋友讨论。日后若有时间再写其他一些更多的内容。最后费时较多,转载注明出处。

posted @ 2012-11-22 19:38  Loylin  阅读(6588)  评论(3编辑  收藏  举报