从简单实例开始,学会写Makefile(二)

五、.d文件,解决文件间的相互引用

1、自动生成依赖关系

        在前文的项目基础上,考虑一下这种情况:如果我们在w1.h文件里包含了头文件w2.h以及w3.h并且用到其中定义的函数。

        第一次编译没有遇到问题,但是如果后续的开发过程中修改了w2.h或者w3.h文件中的内容,再执行gmake命令的时候,就遇到问题了——w1.cpp文件不会被重新编译了!

        

        显然,我们需要将生成目标文件w1.o的规则的依赖项加上w2.h和w3.h。可是如果手动的去检查每一个文件的引用关系,然后修改Makefile文件,这样做的效率就太低了。

 

        万幸的是,编译器可以帮助我们自动生成依赖关系,只需要在编译命令中加上“-M”选项,就可以让编译器自动寻找源文件中包含的头文件,并生成一个依赖关系,例如,你可以在shell界面下敲下如下的命令:

        

        g++-MM w1.cpp

        

        可以看到,其输出为w1.o:w1.cppw2.h w3.h。这里需要特别注意的是,我们使用“-MM”而不是“-M”,因为我们使用的是GUN的C/C++编译器,使用“-M”参数会将标准库的头文件也一并包含进来,但这并不是我们想要的,而使用“-MM”则不会。

 

        现在的问题是,如何利用这个命令去写好我们的Makefile呢?

        GUN组织建议把每一个源文件自动生成的依赖关系放到一个.d文件中,让每一个.cpp文件都对应一个.d文件,例如之前的w1.cpp,我们可以生成一个w1.d文件,内容为自动生成的依赖关系 w1.o:w1.cpp w2.h w3.h,然后在Makefile中包含所有的.d文件,我们只需要写出.cpp文件和.d文件的依赖关系,让make自动更新或生成.d文件即可。

 

2、生成.d文件

        dep/%.d:%.cpp

                @if test ! -d "dep"; then\

                        mkdir -p dep;\

                fi; \

                set -e; rm -f $@;

                g++ -MM $< > $@.
; \

                sed 's/$*\.o[ :]*/obj\/$*\.o dep\/$*\.d: /g' < $@.
> $@; \

                rm -f $@.
        

        在Makefile中加上如上的代码,就可以生成我们所需要的.d文件了。

        又是一堆莫名其妙的符号,我们还是来逐句进行分析。

 

(1)dep/%.d: %.cpp

        使所有的.d文件依赖于对应的.cpp文件,也就是说只要.cpp更新了,我们就重新生成对应的.d文件。这里和.o文件类似的,我们也创建一个dep目录用来存放所有的.d文件,既能保持项目文件的整洁和统一,也方便管理。

 

(2)@if test ! -d "dep"; then\

                   mkdir -p dep;\

            fi; \

        检查当前目录下是否存在dep目录,如果不存在,就使用mkdir命令创建dep目录。

 

(3)set -e; rm -f $@;

        set–e 的作用是如果命令执行出错就直接退出。$@的含义之前已经说过,这里rm –f $@的意思就是删除所有的目标文件。

 

(4)g++ -MM $< > $@.
; \

        $< 的含义是第一个依赖项的名称,> 是重定向符号,将输出结果重定向到指定文件中。$@.
就是这个文件的文件名,其中“
”表示一个随机的编号,例如如果有目标文件是w1.d,那么“$@.
”一个可能的结果就是w1.d.12345。那么,这句话的含义就是将g++ -MM w1.cpp的输出结果重定向到w1.d.12345这个文件中。

 

(5)sed 's/$*\.o[ :]*/obj\/$*\.o dep\/$*\.d : /g' < $@.
> $@;\

        这里使用了sed这个工具对文本进行替换处理,单引号中的规则是’s/old/new/g’,s表示替换,末尾的g代表全局的意思,对文本中所有符合要求的字符串进行替换,sed会将符合old模式的字符串替换为new,具体的使用方法可以查阅一下sed这个工具的帮助文档。

        <$@
,将这个文件的内容作为sed工具的输入。

        > $@,将sed处理后的内容重定向输出到这个文件中。

        经过这一步的处理后,就把自动生成的依赖关系:

 

        w1.o:w1.cpp w2.hw3.h

 

        转成:

 

        w1.o w1.d:w1.cppw2.h w3.h

 

        这样,我们的.d文件也会自动更新啦。

 

(6)rm -f $@.

        删除掉这个临时文件。

 

3、使用include包含其他文件

        在Makefile中我们也可以像在C++文件中那样包含其他文件。

        现在在我们的Makefile中加上这样一句:

        

        includew1.d

        

        使用这个语句就可以将之前我们生成的.d文件中的内容包含到当前的Makefile中。

        当然,也可以用这个命令来包含其他的Makefile文件。具体的用法后面再进行介绍。

 

        我们希望把所有的.d文件都包含在当前的Makefile中。

        先定义一个变量,存放所有的.d文件名:

 

        DEPS = $(addsuffix .d,$(addprefix dep/,$(BASE)))

        

        然后使用include$(DEPS) 包含所有的.d文件。

 

六、-I,引用其他目录下的.h文件

        考虑这种情况:现在有两个目录,一个inc目录用来存放.h文件,一个src目录,用来存放.cpp文件。

        怎么让编译器找到引用的.h文件在哪个目录下呢?

        

        我们可以使用“-I”选项。  格式为“-I目录名”,这样在编译的时候,编译器就会依次到我们指定的目录中寻找.h文件。

 

        同样,先定义一个变量,存放所有头文件的目录名:

        INCLUDEDIR = -I../inc

 

        然后将

        g++ -c -o $@$<

        这样的编译命令中写成

        g++ -c -o $@$(INCLUDEDIR) $<

 

        OK,再来尝试用gmake命令编译一下吧,已经可以成功编译了。

        如果需要包含多个目录下的.h文件,可以重复使用-I选项,中间需要用空格隔开。

 

七、使用静态库

1、修改生成静态库的Makefile

        有的时候我们不需要生成一个可执行的程序,而是生成一个静态库文件,之后在其他的地方引用这个静态库文件。

        

        假设我们的项目目录结构是这样的,src是项目根目录,src下面有common和app以及lib两个目录,common和app下面都有inc和src两个目录。common存放公共库的源文件,app存放程序源文件,lib存放生成的静态库。

 

        修改我们在common目录下的Makefile文件:

 

        top_srcdir         = ../..

        #生成静态库后所存放的位置

        libdir =$(top_srcdir)/lib

        #静态库文件名

        LIBNAME          = libfa_common.a

        #路径+静态库文件名

        TARGET             = $(libdir)/$(LIBNAME)

        $(TARGET): $(OBJS)

                -rm -f $@

                ar cr $(TARGET) $(OBJS)

        (1)  top_srcdir是项目根目录的路径,使用相对路径,方便我们在后面引用其他目录。

        (2)  libdir是生成的静态库所存放的路径。

        (3)  LIBNAME是静态库名称,注意,静态库的命名必须以“lib”开头,以“.a”结尾。

        (4)  TARGET是目标文件名称,包含路径。

        (5)  在生成静态库文件的规则中,使用ar这个命令。

 

2、修改引用静态库的Makefile

        在app/src目录下的源文件中,编译的时候需要引用libfa_common.a这个静态库,这就需要我们再修改app目录下的Makefile文件。

        

        这里使用了两个新的参数,“-l”和“-L”。

 

        “-l”参数指定要引用的库的名称。例如我们要引用libfa_common.a这个静态库,那么需要在编译命令里加上“-lfa_common”,可以看出,-l后面的库名称需要去除前面的“lib”和后面的“.a”。

 

        “-L”参数指定了要引用的库的目录,用法和之前的“-I”一样。这里需要注意的是,我们需要修改一下VPATH这个变量,指明要引用的静态库的目录。类似这样:

        VPATH:= -L $(top_srcdir)/lib

 

八、完整的Makefile

        其实在每一个目录下的Makefile中有很多部分是重复的,我们可以考虑将重复的部分提取出来,单独放在一个公共的Makefile中,然后在其他Makefile中用include包含这个公共的Makefile即可。

        

        我写了三套Makefile,分别是Makefile(app)、Makefile(lib)、Make.rules。

 

        其中,Make.rules是公共部分,Makefile(app)是用来生成可执行程序的,Makefile(lib)是用来生成静态库的,为了以后迁移方便,考虑到Linux和Unix平台的差异,以及各个编译器之间的差异,可以将各种命令也定义成变量,之后使用宏定义进行条件编译。

        

        贴一下完整的Makefile代码。

 

1、Make.rules
        

        #公用Make规则配置

 

        #设置编译器类型

        CXX := g++

        CC := gcc

 

        #设置编译.d文件相关内容

        DEPFLAGS := -MM

        DEPFILE = $@.
 

        #设置所有静态库文件所在位置,会根据每个Makefile文件的top_srcdir设置相对位置

        LIBDIR := $(top_srcdir)/lib

 

        #设置编译程序时需要在哪些目录查找静态库文件

        LDFLAGS := -L.\

                                -L$(top_srcdir)/lib

 

        #设置VPATH,在检查依赖关系时,如果查找-lxxxx时,在哪些目录查找静态库文件

        VPATH := $(LIBDIR)

 

        #设置编译程序时查找头文件的目录位置

        INCLUDEDIR := -I.\

                                      -I../inc\

 

        #声明要生成的目标文件,具体规则在具体的Makefile中定义

        $(TARGET):

 

        #生成.o文件所依赖的.cpp和.c文件

        obj/%.o:%.cpp

        @if test ! -d "obj"; then\

                mkdir-p obj;\

        fi;

        $(CXX)-c -o $@ $(INCLUDEDIR) $<

        

        obj/%.o:%.c

                @iftest ! -d "obj"; then\

                        mkdir-p obj;\

                fi;

                $(CC)-c -o $@ $(INCLUDEDIR) $<

        

        #生成.d文件,存放.cpp文件的所有依赖规则

        dep/%.d: %.cpp

                @iftest ! -d "dep"; then\

                        mkdir-p dep;\

                fi;\

                set-e; rm -f $@;

                $(CXX)$(DEPFLAGS) $(INCLUDEDIR) $< >$(DEPFILE); \

                sed's/$*\.o[ :]*/obj\/$*\.o dep\/$*\.d : /g' < $@.
> $@;\

                rm-f $@.
 

        #生成.d文件,存放.c文件的所有依赖规则

        dep/%.d: %.c

                @iftest ! -d "dep"; then\

                        mkdir-p dep;\

                fi;\

                set-e; rm -f $@;

                $(CC)$(DEPFLAGS) $(INCLUDEDIR) $< > $(DEPFILE); \

                sed's/$*\.o[ :]*/obj\/$*\.o dep\/$*\.d : /g' < $@.
> $@; \

                rm-f $@.
 

        include $(DEPS)

 

        #检测是否有文件被修改,只要有就全部编译

        all: $(SRCS) $(TARGETS)

 

        #清除编译文件

        .PHONY:clean

        clean:

                -rm-f $(TARGET)

                -rm-f obj/*.o

                -rm-f dep/*.d

                -rm-f core

 

2、Makefile(lib)

        #需要生成静态库的Makefile

 

        #程序根目录

        top_srcdir         =../../..

 

        #生成静态库后所存放的位置

        libdir = $(top_srcdir)/lib

        #静态库文件名

        LIBNAME          =libfa_common.a

        #路径+静态库文件名

        TARGET             =$(libdir)/$(LIBNAME)

 

        CPP_FILES = $(shell ls *.cpp)

        C_FILES = $(-shell ls *.c)

        SRCS = $(CPP_FILES) $(C_FILES)

        BASE = $(basename $(SRCS))

        OBJS = $(addsuffix .o, $(addprefixobj/,$(BASE)))

        DEPS = $(addsuffix .d, $(addprefixdep/,$(BASE)))

 

        #包含公共Make规则

        include$(top_srcdir)/makeinclude/Make.rules

 

        #设置头文件及库文件的位置

        INCLUDEDIR := $(INCLUDEDIR)

 

        $(TARGET): $(OBJS)

                -rm-f $@

                ar cr $(TARGET) $(OBJS)

3、Makefile(app)

        #需要生成可执行程序的Makefile

 

        #程序根目录

        top_srcdir         =../../..

 

        #目标程序名

        TARGET = test

 

        CPP_FILES = $(shell ls *.cpp)

        C_FILES = $(-shell ls *.c)

        SRCS = $(CPP_FILES) $(C_FILES)

        BASE = $(basename $(SRCS))

        OBJS = $(addsuffix .o, $(addprefixobj/,$(BASE)))

        DEPS = $(addsuffix .d, $(addprefixdep/,$(BASE)))

 

        #包含公共Make规则

        include $(top_srcdir)/makeinclude/Make.rules

 

        #额外需要包含的头文件的目录位置

        INCLUDEDIR := $(INCLUDEDIR)\

                                        -I$(top_srcdir)/src/common/inc\

 

        #所有要包含的静态库的名称

        LIBS := -lfa_common

 

        #设置目标程序依赖的.o文件

        $(TARGET):$(OBJS) $(LIBS)

                -rm-f $@
                $(CXX)-o $(TARGET) $(INCLUDEDIR) $(LDFLAGS) $(OBJS) $(LIBS)
 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值