在多文件的项目中,一个工程中的源文件比较多,其按类型、功能、模块分别放在若干个目录中,为了项目更加规整,我们常常要将源文件头文件执行文件等分开,所以在编译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)1234567891011121314151617181920212223242526272829303132333435
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 :CLEANDIR1234567891011121314151617181920212223
这里存在的问题就是这个ECHO这个作用,为什么会这样执行,可能就要更深入探究Makefile的隐含规则
3.执行底层各目录的Makefile
#./src/main/Makefile OBJ = main.o OBJ += hello.o #添加需要的文件就行,类似内核编译那种
$(OBJ):$(OBJ:.o=.c) $(CC) -c $^
clean: -rm $(OBJ)123456789
这里最后执行的结果文件树如下
. ├── 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来执行,更方便清理123456789101112131415161718192021222324252627282930313233
2.执行第二层Makefile
和第一种方法中的基本一样,去掉清理
SUBDIR = main\ api
all:$(SUBDIR)
$(SUBDIR):ECHO make -C $@
ECHO: @echo $@123456789101112
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)1234567891011
从这里的依赖规则来看,第一种方法它不需要自定义规则,因为输出的路径就在当前路径,所以可以使用自动规则来生成目标 但是这里如果要指定路径,那就不能使用自动的规则来做,添加这个%.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文件没有被修改则在总的编译会被再次编译,如果文件很多且比较大,这样就会影响编译时间 --------------------- 转自:https://blog.csdn.net/leumber/article/details/79842778