Makefile嵌套编译多文件项目

在多文件的项目中,一个工程中的源文件比较多,其按类型、功能、模块分别放在若干个目录中,为了项目更加规整,我们常常要将源文件头文件执行文件等分开,所以在编译Makefile时就要做好整个项目的编译准备工作,Makefile定义了一系列的规则来指定,哪些文件需要编译,需要生成什么目标,需要生成库等等。
下面就用实例来列出这些规则,这也是一个基本框架,后边如果需要添加更多的源文件和目录就可以如法炮制,这就可以作为一个较为通用的规则适配到更多的工程中,我也是在这里做一下记录,已被今后需要。

有几种思路:
1.在每层都去编译生成需要的.o文件,然后从顶层Makefile文件去链接编译目标文件
2.编译所有.o文件输出到obj下,在obj下创建Makefile编译obj下的.o生成目标文件
3.在每层加载当前目录的依赖文件,在顶层根据依赖文件生成目标文件
下边是这个测试工程的文件树
.
├── bin
├── Makefile
├── obj
└── src
├── api
│?? ├── api_a.c
│?? ├── api.c
│?? └── Makefile
├── main
│?? ├── hello.c
│?? ├── main.c
│?? └── Makefile
└── Makefile

一、用第一种方法

在每层都去编译生成需要的.o文件,然后从顶层Makefile文件去链接编译目标文件

1.首先编写顶层Makefile

#TOP ./Makefile
CC = gcc        #编译工具
FLAGS =         #编译规则
TAG = test      #目标文件

TOPDIR = $(PWD)    #顶层目录

OBJDIR = $(TOPDIR)/obj #输出文件路径
BINDIR = $(TOPDIR)/bin #输出目标文件路径
SRCDIR = $(TOPDIR)/src #源文件路径

INC = -I./inc           #指定头文件检索的路径

export CC TAG TOPDIR SUBDIR OBJDIR BINDIR INC   #导出全局变量

all:CHECK $(SRCDIR) $(TAG)    #make 目标

CHECK:      #检测并创建需要的目录
    mkdir -p $(OBJDIR) $(BINDIR)

$(SRCDIR):ECHO #去执行对应目录中的Makefile文件
    make -C $@

$(TAG):        #生成目标文件,查找源文件目录下的所有.o文件编译生成目标到指定路径下
    $(CC) -o $(addprefix $(BINDIR)/,$(TAG)) $$(find ./${SRCDIR} -name '*.o')

ECHO:  
    @echo $@   #打印一下,这里还有个作用后边说

CLEANDIR:ECHO
    make -C $(SRCDIR) clean

.PHONY : clean
clean :CLEANDIR
    -rm $(BINDIR)/$(TAG)

2.然后执行第二层Makefile

#./src/Makefile
SUBDIR = main api   #这里就添加源文件的目录结构

all:$(SUBDIR)

$(SUBDIR):ECHO
    make -C $@     #进入底层目录的Makefile

#这里这个echo比较神奇,因为SUBDIR变量定义就是两个目录名,也不是绝对路径,如果被展开执行make -C main api 这是一个未知的路径
#但是加入echo后,就会变成 make -C main 和 make -C api 就能遍历执行 make -C 
#这个可以使用CLEANDIR:的执行规则遍历执行各目录

ECHO:  
    @echo $@


CLEANDIR:
    @for dir in $$(echo $(SUBDIR)); \
	do make -C $$dir clean ;\
    done

.PHONY : clean
clean :CLEANDIR

这里存在的问题就是这个ECHO这个作用,为什么会这样执行,可能就要更深入探究Makefile的隐含规则

3.执行底层各目录的Makefile

#./src/main/Makefile
OBJ = main.o
OBJ += hello.o  #添加需要的文件就行,类似内核编译那种

$(OBJ):$(OBJ:.o=.c)
    $(CC) -c $^

clean:
    -rm $(OBJ)

这里最后执行的结果文件树如下

.
├── bin
│?? └── test
├── Makefile
├── obj
└── src
├── api
│?? ├── api_a.c
│?? ├── api_a.o
│?? ├── api.c
│?? ├── api.o
│?? └── Makefile
├── main
│?? ├── hello.c
│?? ├── hello.o
│?? ├── main.c
│?? ├── main.o
│?? └── Makefile
└── Makefile

这种情况的好处就是.o文件和.c文件在同一路径下,在编译时如果对应的.c文件没有被修改则在总的编译不会被再次编译
内核中往往在这里生成一个lib从而供顶层Makefile来调用,更加简洁

二、第二种思路

编译所有.o文件输出到obj下,在obj下创建Makefile编译obj下的.o生成目标文件
这种相对于第一种方法变化不大,只是把.o文件做了统一路径输出,其实也没什么用,只是方便整理

1.首先编写顶层Makefile

和上边的基本类似

CC=gcc
TAG = test 

TOPDIR = $(PWD)

OBJDIR = $(TOPDIR)/obj
BINDIR = $(TOPDIR)/bin
SRCDIR = $(TOPDIR)/src

INC = -I./inc

export CC TAG TOPDIR SUBDIR OBJDIR BINDIR INC

all:CHECK $(SRCDIR) $(TAG)

CHECK:
    mkdir -p $(OBJDIR) $(BINDIR)

$(SRCDIR):ECHO
    make -C $@

$(TAG):ECHO        #这里只需要把obj路径下的.o文件编译加载就行
    $(CC) -o $(BINDIR)/$(TAG) $(wildcard $(OBJDIR)/*.o)
#   $(CC) -o $(addprefix $(BINDIR)/,$(TAG)) $(wildcard $(OBJDIR)/*.o)

ECHO:
    @echo $@


.PHONY : clean
clean :
    -rm $(BINDIR)/$(TAG)
    -rm $(OBJDIR)/*.o  #这里的clean就不需要去调用各层的Makefile来执行,更方便清理

2.执行第二层Makefile

和第一种方法中的基本一样,去掉清理

SUBDIR = main\
            api

all:$(SUBDIR)


$(SUBDIR):ECHO
    make -C $@


ECHO:  
    @echo $@

3.执行底层各目录的Makefile
在输出目标时添加obj路径

OBJ = main.o
OBJ += hello.o

all:$(OBJ)
$(OBJ):%.o:%.c #添加生成依赖规则,这个很重要
    $(CC) -c $^ -o $(OBJDIR)/$@    #添加obj路径

ECHO:
    @echo $(OBJS)
clean:
    -rm $(OBJ)

从这里的依赖规则来看,第一种方法它不需要自定义规则,因为输出的路径就在当前路径,所以可以使用自动规则来生成目标
但是这里如果要指定路径,那就不能使用自动的规则来做,添加这个%.o:%.c
“%”表示长度任意的非空字符串
“%.o” 表明要所有以”.o” 结尾的目标
依赖模式”%.c”则取模式”%.o”中的”%”,替代为所有以”.c”结尾的目标

.
├── bin
│?? └── test
├── Makefile
├── obj
│?? ├── api_a.o
│?? ├── api.o
│?? ├── hello.o
│?? └── main.o
└── src
├── api
│?? ├── api_a.c
│?? ├── api.c
│?? └── Makefile
├── main
│?? ├── hello.c
│?? ├── main.c
│?? └── Makefile
└── Makefile

这种情况下生成的.o文件和.c文件不在同一路径下,在编译时如果对应的.c文件没有被修改则在总的编译会被再次编译,如果文件很多且比较大,这样就会影响编译时间

  • 5
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值