您的位置:首页 > 编程语言 > C语言/C++

【ZZ】一个C++项目的Makefile编写-Tony与Alex的对话系列

2010-05-26 16:22 399 查看
 

一个C++项目的Makefile编写-Tony与Alex的对话系列 - [技术前沿]

Tag:技术前沿
版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://bigwhite.blogbus.com/logs/1205156.html
Tony : Hey Alex, How
are you doing? 

Alex : 不怎么样。(显得很消沉的样子)

Tony : Oh , Really ? What is the matter? 

Alex : 事情是这样的。最近有一个Unix下的C++项目要求我独自完成,以前都是跟着别人做,现在让自己独立完成,还真是不知道该怎么办,就连一个最简单的项目的Makefile都搞不定。昨晚看了一晚上资料也没有什么头绪。唉!!

Tony : 别急,我曾经有一段时间研究过一些关于Makefile的东西,也许能帮得上忙,来,我们一起来设计这个项目的Makefile。

Alex : So it is a deal。(一言为定) 

Tony : 我们现在就开始吧,给我拿把椅子过来。
(Tony坐在Alex电脑的旁边)

Tony : 把你的项目情况大概给我讲讲吧。

Alex : No Problem ! 这是一个“半成品”项目,也就是说我将提供一个开发框架供应用开发人员使用,一个类似MFC的东西。

Tony : 继续。

Alex : 我现在头脑中的项目目录结构是这样的:
APL (Alex's
Programming Library)

 -Make.properties

 -Makefile(1)

 -include  //存放头文件

  -Module1_1.h

  -Module1_2.h

  -Module2_1.h

  -Module2_2.h

 -src   //存放源文件

  -Makefile(2)

  -module1

   -Module1_1.cpp

   -Module1_2.cpp

   -Makefile(3)

  -module2

   -Module2_1.cpp

   -Module2_2.cpp

   -Makefile(3)

  -...

  

 -lib  //存放该Project依赖的库文件,型如libxxx.a

 -dist  //存放该Project编译连接后的库文件libapl.a

 -examples  //存放使用该“半成品”搭建的例子应用的源程序

  Makefile(4)

  -appdemo1

   -Makefile(5)

   -src  //存放应用源代码

   -include

   -bin  //存放应用可执行程序

  -appdemo2

   -Makefile(5)

   -src  //存放应用源代码

   -include

   -bin  //存放应用可执行程序

  -...
Tony : I got it!

Alex : 下面我们该如何做呢?

Tony : 我们来分析一下各个Makefile的作用。你来分析一下各个级别目录下的Makefile的作用是什么呢?

Alex : (思考了一会儿)我想应该是这样的吧。

 Makefile(3)负责将其module下的.cpp源文件编译为同名.o文件,同时其phony target "clean"负责删除该目录下的所有.o文件;

 Makefile(2)负责调用src目录下所有module的Makefile文件。

 Makefile(1)负责先调用src中的Makefile生成静态库文件,然后调用examples中的Makefile构建基于该框架的应用。

 至于Make.properties,定义通用的目录信息变量、编译器参数变量和通用的依赖关系。
Tony : 说得很好。我们一点一点来,先从src中每个module下的Makefile着手,就如你所说在每个module下的Makefile负责将该module下的.cpp文件编译为同名的.o文件。

Alex : 好的,我来写吧,这个我还是能搞定的。看下面:

module1下的Makefile如下:
#

# Makefile for module1

#

all : Module1_1.o Module1_2.o
Module1_1.o :
Module1_1.cpp

 g++ -c $^ -I ../../include

Module1_2.o : Module1_2.cpp

 g++ -c $^ -I ../../include
clean :

 rm -f *.o
module2下的Makefile如下:
#

# Makefile for module2

#

all : Module2_1.o Module2_2.o
Module2_1.o :
Module2_1.cpp

 g++ -c $^ -I ../../include

Module2_2.o : Module2_2.cpp

 g++ -c $^ -I ../../include
clean :

 rm -f *.o
make一下,顺利产生相应的.o文件。
/*=============================================================

Note: 关于$^、$<和$@的用法说明:

$@ -- “$@”表示目标的集合,就像一个数组,“$@”依次取出目标,并执于命令。

$^ -- 所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。

$< -- 依赖目标中的第一个目标名字
举例: Module1_1.o
Module1_2.o : Module1_1.cpp Module1_2.cpp
则$@ -- Module1_1.o
Module1_2.o

$^ -- Module1_1.cpp Module1_2.cpp

$< -- Module1_1.cpp
==============================================================*/
Tony : Well done! 不过发现什么问题了么?

Alex : 什么问题?

Tony : 存在重复的东西。在重构中我们知道如果两个子类中都定义相同的接口函数,我们会将其pull up到基类中。同样我们可以重构我们的Makefile,把一些重复的东西拿到外层去。

Alex : (似乎略微明白了一些)我想有三处重复:a)查找头文件的路径是重复的; b)g++这个字符串可以用一个变量定义代替 c)编译器的编译参数可以也定义到一个变量中。我知道Make工具支持include一个文件,我们就建立一个公用的文件来存放一些通用的东西吧。

Tony : 没错,Just do it.

Alex : 就按我原先的想法,把这些公共的部分放到Make.properties中吧。


# Properties for demo's Makefile 



MAKEFILE  = Makefile
BASEDIR = $(HOME)/proj/demo
####################

# Directory layout #

####################

SRCDIR = $(BASEDIR)/src

INCLUDEDIR = $(BASEDIR)/include

LIBDIR = $(BASEDIRE)/lib

DISTDIR = $(BASEDIR)/dist
####################

# Compiler options #

#    F_ -- FLAG    #

####################

CC = g++
# Compiler search
options

F_INCLUDE = -I$(INCLUDEDIR)

F_LIB = -L $(LIBDIR)
CFLAGS = 

CPPFLAGS = $(CFLAGS) $(F_INCLUDE)
然后修改一下,各个module中的Makefile文件,以module1为例,修改后如下:

#

# Makefile for module1

#

include ../../Make.properties
all : Module1_1.o Module1_2.o
Module1_1.o :
Module1_1.cpp

 $(CC) -c $^ $(CPPFLAGS)

Module1_2.o : Module1_2.cpp

 $(CC) -c $^ $(CPPFLAGS)
clean :

 rm -f *.o
Tony : 其实这两个Makefile中还有一个隐含的重复的地方

Alex : 你是指依赖规则么?

Tony : 嗯,这个依赖规则在src中的各个module中都会用得到的。

Alex : 没错,我也是这么想的,我现在就把这个规则抽取出来,然后你来评审一下。我想利用make工具的传统的“后缀规则”来定义通用依赖规则,我在Make.properties加入下面的变量定义:
####################

# Common depends   #

####################

DEPS = .cpp.o
然后还是以module1为例,修改module1的Makefile后如下:

#

# Makefile for module1

#

include ../../Make.properties
all : Module1_1.o Module1_2.o
$(DEPS):

 $(CC) -c $^ $(CPPFLAGS)
clean :

 rm -f *.o
Tony : 基本满足需求。我们可以进行上一个层次的Makefile的设计了。我们来设计Makefile(2)。Alex,你来回顾一下Makefile(2)的作用。

/*=============================================================

Note: 关于后缀规则的说明
后缀规则中所定义的后缀应该是make
所认识的,如果一个后缀是make
所认识的,那么这个规则就是单后缀规则,而如果两个
连在一起的后缀都被make
所认识,那就是双后缀规则。例如:".c"和".o"都是make 所知道。因而,如果你定义了一个规则是

".c.o"那么其就是双后缀规则,意义就是".c"是源文件的后缀,".o"是目标文件的后缀, ".c.o"意为利用.c文件构造同名.o文件。

==============================================================*/
Alex : No Problem! 正如前面说过的Makefile(2)负责调用src目录下所有module子目录下的Makefile文件,并负责将各个module下的.o文件打包为libdemo.a文件放到dist目录中。所以存在简单的依赖关系就是libdemo.a依赖各个module子目录下的.o文件,而前面的Makefile(3)已经帮我们解决了.o文件的生成问题了,即我们只需要逐个在各module子目录下make即可。我的Makefile(2)文件设计如下:

#

# Makefile for src directory

#
include ../Make.properties
TARGET = libdemo.a
####################

#  Subdirs define  #

####################

MODULE1_PATH = module1

MODULE2_PATH = module2

SUBDIRS = $(MODULE1_PATH) $(MODULE2_PATH) 
####################

#  Objects define  #

####################

MODULE1_OBJS = $(MODULE1_PATH)/Module1_1.o $(MODULE1_PATH)/Module1_2.o

MODULE2_OBJS = $(MODULE2_PATH)/Module2_1.o $(MODULE2_PATH)/Module2_2.o

DEMO_OBJS = $(MODULE1_OBJS) $(MODULE2_OBJS)
all : subdirs
$(TARGET)

 cp $(TARGET) $(DISTDIR)
subdirs:

 @for i in $(SUBDIRS); do /

  echo    "===>$$i"; /

  (cd $$i &&$(MAKE) -f $(MAKEFILE)) || exit 1; /

  echo    "<===$$i"; /

 done
$(TARGET) :
$(DEMO_OBJS)

 ar -r $@ $^
clean:

 @for i in $(SUBDIRS); do /

  echo    "===>$$i"; /

  (cd $$i &&$(MAKE) clean -f $(MAKEFILE)) || exit 1; /

  echo    "<===$$i"; /

 done

 rm -f $(DISTDIR)/$(TARGET)
Tony : Alex你的进步真的是很大,分析问题的能力提高的很快,方法也不错。这个设计的缺点在于一旦新增了一个module子目录,这个Makefile文件就需要改动,不过改起来倒不是很难。有机会可以再想想,使这个Makefile更加通用。
Alex : 我记住了。我们继续么?

Tony : 歇一回吧^_^。
/*=============================================================

Alex and Tony are having a short break.

==============================================================*/
Tony : 你的咖啡味道真不错。

Alex : 这可是朋友从巴西带回来的极品咖啡豆,经过我精心研磨而成的。

Tony : 想不到你在这方面还有研究。

Alex : 呵呵。

Tony : Let's go on 。有了Makefile(2),后面的工作就轻松多了。

Alex : 现在我的信心也很足,我来设计Makefile(1),它负责先调用src中的Makefile生成静态库文件,然后调用examples中的Makefile构建基于该框架的应用。我还是按照Makefile(2)的思路走,看我的Makefile(1):
#

# Makefile for whole project

#
include Make.properties
SRC_PATH = src

EXAMPLES_PATH = examples
SUBDIRS = $(SRC_PATH) $(EXAMPLES_PATH) 
all : subdirs

 

subdirs:

 @for i in $(SUBDIRS); do /

  echo    "===>$$i"; /

  (cd $$i && $(MAKE) -f $(MAKEFILE)) || exit 1; /

  echo    "<===$$i"; /

 done
clean:

 @for i in $(SUBDIRS); do /

  echo    "===>$$i"; /

  (cd $$i && $(MAKE) clean -f $(MAKEFILE)) || exit 1; /

  echo    "<===$$i"; /

 done
运行一下,由于examples目录下的Makefile还是空的,所以没有成功。
Tony : 有了前面的经验,相信完成examples目录下的两个Makefile对你来说不成问题。

Alex : I could not agree with you any more(Alex脸上满是笑容),我来完成它。
每个appdemoX下的Makefile(5)我设计成这样:

#

# Makefile for appdemoX

#

include ../../Make.properties
TARGET = appdemoX

SRC = ./src/appdemoX.cpp
all : 

 $(CC) -o $(TARGET) $(SRC) $(CPPFLAGS) -L $(DISTDIR) -ldemo

 mv $(TARGET).exe ./bin
clean :

 rm -f ./src/*.o ./bin/$(TARGET).exe
而examples目录下的Makefile(4)的样子如下:

#

# Makefile for examples directory

#
include ../Make.properties
EXAMPLE1_PATH =
appdemo1

EXAMPLE2_PATH = appdemo2
SUBDIRS = $(EXAMPLE1_PATH)
$(EXAMPLE2_PATH) 
all : subdirs

 

subdirs:

 @for i in $(SUBDIRS); do /

  echo    "===>$$i"; /

  (cd $$i &&$(MAKE) -f $(MAKEFILE)) || exit 1; /

  echo    "<===$$i"; /

 done
clean:

 @for i in $(SUBDIRS); do /

  echo    "===>$$i"; /

  (cd $$i &&$(MAKE) clean -f $(MAKEFILE)) || exit 1; /

  echo    "<===$$i"; /

 done
Tony : 可以,不知不觉间,我们的工作已经接近尾声,剩下的工作就是细节了,包括编译器参数的细化等。

Alex : 在Makefile(1)中加上install,tar等目标,使用户得到有更多的功能。十分感谢你的指导。

Tony : 那晚上去原味斋吧,想烤鸭了^_^。
/*=============================================================

Note : Makefile常识

a) "=" vs ":="
例子:

C_OPTIONS = $(C_EXTRA_OPTION) -O2

C_EXTRA_OPTION = -g

cfoo: foo.c exam.c

    gcc $(C_OPTIONS) -o $@ $^

=>gcc -g -O2 -o cfoo foo.c exam.c

C_OPTIONS := $(C_EXTRA_OPTION) -O2

C_EXTRA_OPTION = -g

cfoo: foo.c exam.c

    gcc $(C_OPTIONS) -o $@ $^

=>gcc -O2 -o cfoo foo.c exam.c
大家发现不同了,Why? 使用“=”赋值的变量在使用时才被展开,并且每使用一次就会展开一次,其值每次展开的时候有可能是不同的,就如第一个C_OPTION由于在使用时展开,所以C_EXTAR_OPTION定义的位置不影响C_OPTION的值。而使用“:=”进行赋值的变量,则在赋值的时候就被展开,并且仅仅展开一次,从此以后其值将不会发生任何变化,就第二个C_OPTION由于定义时展开所以由于定义时看不到C_EXTAR_OPTION所以值为-02,而不是-g -02。
b) wildcard 函数
在 GNU Make 里有一个叫 'wildcard' 的函数,它有一个参数,功能是展开成一列所有符合由其参数描述的文件名,文件间以空格间隔。你可以像下面所示使用这个命令:SOURCES = $(wildcard *.cpp)  
<=> SOURCES = xx.cpp yy.cpp ... zz.cpp

==============================================================*/
 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息