Makefile step by step

 

0 前言

前几日为了将json_spirit移植到linux下去,所以学习了一下makefile,现整理一个简单的makefile学习过程。本文的描述均以编译json_spirit为例,可能会忽略掉一些makefile基础知识,有过编程经验的同学应该能够轻松看懂。由于作者也是初学makefile,兼linux菜鸟,所以难免存在表述不准确甚至错误的地方,欢迎各位朋友指正。json_spirit是一个c++的json解析器,具体信息请参考以下网址:http://www.codeproject.com/KB/recipes/JSON_Spirit.aspx

 

1 起步

json_spirit这个库不大,一共就7个文件,其实如果没有makefile的话,要编译它也就一行命令。需要说明的是json_spirit引用了boost库,所以在编译它之前,还得先下载好boost。

  1. json_spirit.h
  2. json_spirit_reader.h
  3. json_spirit_reader.cpp
  4. json_spirit_writer.h
  5. json_spirit_writer.cpp
  6. json_spirit_value.h
  7. json_spirit_value.cpp

让我们先来看一个最简单的makefile文件 

 在makefile文件中,形如

XXX:YYY

    ZZZ

这种形式的语句(如4-5行)表明了一种依赖关系,即XXX的存在是依赖于YYY的,如果XXX不存在或者XXX的更新时间比YYY要旧的话,则执行语句ZZZ。如果在检查时,发现YYY不存在,那么则先寻找YYY的依赖关系。明白这个原理之后,Makefile就很好理解了。

我们先来看第4行,表明JsonSpirit的存在是依赖于$(OBJS)的。$(OBJS)是什么呢?OBJ是一个变量,它等于json_spirit_reader.o json_spirit_writer.o json_spirit_value.o,而$(OBJS)就是对OBJS求值,结果就是前面这一串字符。了解这一情况后,makefile第1行也就可以理解了。回到我们的依赖关系上来,$(OBJS)存在吗?即是说json_spirit_reader.o json_spirit_writer.o json_spirit_value.o存在吗?那它们存在吗?当然不存在。继续向下寻找它们各自的依赖关系。三者各自的依赖关系分别如下:

    • json_spirit_reader.o: json_spirit_reader.cpp json_spirit_reader.h json_spirit.h     
    • json_spirit_writer.o:  json_spirit_writer.cpp json_spirit_writer.h json_spirit.h   
  1. json_spirit_value.o:   json_spirit_value.cpp json_spirit_value.h json_spirit.h   

 那么这些.cpp和.h存在么?答案是肯定的,这些不正是我们要编译的源文件吗?所以根据前面的规则,现在可以开始执行语句ZZZ了(请注意:ZZZ语句前面的空白不是空格,而是一个Tab键,即一个制表符,这一点一定要搞清楚,不然make说什么也无法顺利执行)。以json_spirit_reader为例,它的执行语句是 

$(CC) -c json_spirit_reader.cpp $(INC),

展开后就是

g++ -c json_spirit_reader.cpp -I/home/tony/workspace/boost_1_37_0  

通过执行这条指令,就可以将源文件json_spirit_reader.cpp编译成json_spirit_reader.o。$(INC)即是-I/home/tony/workspace/boost_1_37_0,参见makefile第2行。它表示编译时需要包含的头文件的路径,即,以-I开头,后面立刻跟上路径,中间没有空格。若要包含多个头文件文件夹,则INC= -IXXXX -IXXXX -IXXXX,每个文件夹前都需要使用-I,中间以空格隔开。 

在$(OBJS)都被成功生成后,那么就可以满足第4行的依赖关系了,所以开始执行第5行的命令。

$(CC) -shared -o libJsonSpirit.so $(OBJS)  

展开后即为

g++ -shared -o libJsonSpirit.so json_spirit_reader.o json_spirit_writer.o json_spirit_value.o

-shared表示生成的目标为动态链接库,linux下为.so文件,即windows下的dll文件。-o 表示生成目标的文件名为libJsonSpirit.so。makefile可以理解为gcc编译过程的一个批处理,所有的makefile动作最终将转化为gcc的编译命令,如果对于编译选项还有所疑问,具体请参见gcc帮助文件。

17-19表行makefile的clean命令,在shell下输入make clean,则会执行19行的语句,即删除当前文件夹下的libJsonSpirit.so和$(OBJS)所指代的内容。

 

2 自动推导

在第一节的代码中,我们可以看到每一个.o文件都依赖于其同名的.cpp和相应的.h文件,通过对这些文件编译产生。其实在makefile里面有一些隐晦规则,根据这些规则可以实现自动推导。代码如下:

 可以发现类似于
 $(CC) -c json_spirit_reader.cpp $(INC) 
这样的代码消失了。这就是makefile的自动推导。make可以根据json_spirit_reader.o的名称自动推导出它需要json_spirit_reader.cpp文件,并且对json_spirit_reader.cpp和相关的.h文件进行编译。

不过自动推导时,make怎么知道我们需要引用哪些相关的头文件和库呢?因为我们不能再像刚才一样显示的指明编译参数 $(INC)。不过没关系,我们有变量CPPFLAGS(见第2行)。这是一个预设的变量(也许我这样解释不是很准确),或者说make本身就知道CPPFLAGS的存在,我们只需把需要的参数赋给CPPFLAGS就可以了。为了避免我们的赋值覆盖掉CPPFLAGS原有的值,所以在赋值时使用“+=”。本节的makefile与上一节的区别主要集中在9-11行,仔细分析便可理解。

 

3 头文件的困扰

虽然上一节的makefile已经轻松了很多,但是还有很多麻烦之处。比如一个.cpp文件引用了10几个头文件,那么如下面这行一样:
json_spirit_reader.o: json_spirit_reader.h json_spirit.h

我们是不是要在冒号后面写上10个头文件呢?

json_spirit_reader.o能够自动找到json_spirit_reader.cpp,那么能不能自动找到json_spirit_reader.cpp里面所引用的.h文件呢?答案是肯定的,使用gcc的-MM编译参数可以将.cpp文件里面所引用的头文件给输出出来,具体的实现请看下面的代码:

不要问我为什么,因为我也不知道。准确的说应该是我无法正确或者准确的解释这一段代码的原理和功能,所以记得这样用就行了,具体请参看参考文献。9-16行代码就可以实现我们想要的功能,它能够从xxx.o这个名称推导出对应的.cpp及其引用的头文件,并编译生成.o文件。请不要随便改动和移动9-16行代码。

 

4 让我们更懒点吧

注意上面makefile代码里面第一行:
OBJS = json_spirit_reader.o json_spirit_value.o json_spirit_writer.o

这句话的意思就是表明我们要编译哪些文件。如果我们的工程很大,有好几十个文件,那岂不是要把几十个文件的名字都写在这里。让我们更懒点吧,让makefile来自动搜索当前目录下的所有文件!

 如上面代码所示,主要的变化集中在1-2行,我就不详细讲解了,相关命令的具体含义请自己搜索。

 

5 整理目录

现在我们还需要做一点清理工作。目前我们是把makefile和源文件放在一起,在源文件目录下调用make进行编译。这样的话,编译完后,源文件目录下会留下大量的.o和.d文件;如果一个工程文件夹里面还要放入一些document之类的东西,这会让目录显得很乱,所以我们对makefile稍微做一下调整,重新整理一个目录结构。假设我们的工程叫做AosStack,那么在最顶层,我们会建立一个名为AosStack的文件夹,然后在其下面建立src,obj,bin,inc,lib这几个文件夹。我们把makefile文件放在AosStack下面,把源代码放在src下面,把临时生成的.o文件放在obj下面,把生成的目标(二进制程序)放在bin下面;剩下两个文件夹inc和lib里面可以放入引用的头文件和库文件。对应的makefile代码如下:

随着目录的改变,原有的自动推导已经无法正常工作,我增加了15-16这段代码,重新定义了隐晦规则,使得自动推导能够将.o文件顺利地生成到obj目录下。其他地方的代码也稍微做了相应的调整,请仔细查看。

这一节给出的makefile可以作为一个通用的makefile,在使用时只需对1-6行稍作修改即可。SRC_DIR,OBJ_DIR,BIN_DIR这三个变量是对应的文件夹目录,可以根据自己工程文件夹的安排进行调整。如果不想使用子文件夹,这三个变量全部赋值“.”即可。TARGET表示生成目标文件的名称。CPPFLAGS和TARGET_FLAGS是编译参数。CPPFLAGS可以用来指明引用的头文件路径和库文件,TARGET_FLAGS用来指明我们需要生成so文件。这些都可以根据需要自行进行调整。需要注意的是这里给出的makefile无法自动生成目录结构,也就是说如果在makefile里面指定了SRC_DIR这几个变量的目录,而目录并不存在的话,那么make将出错。所以在make前一定要手动建立对应的目录结构。下面是一个引用头文件和库文件的例子:

CPPFLAGS += -I../inc/boost_1_37_0 -L../lib -lgtk

-I在第一节已经介绍过了,-L表示引用的库文件的路径,-l表示搜索含有gtk名字的库,一般为 libgtk.so、libgtk.a或者libgtk.o,如果还有其他头文件或者库文件,按照这个格式继续跟在后面即可。 

 

6 参考文献

http://blog.csdn.net/ugg/archive/2007/05/23/1622555.aspx

http://blog.csdn.net/tanlijun37/archive/2009/02/20/3915295.aspx

http://bigwhite.blogbus.com/logs/1205156.html 

 

7 下载

文中提到的所有代码可以前往以下地址下载:

http://www.namipan.com/d/b01b3bd9995f9ae5aa398e90c3eb62d28ad83f8e90210000

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值