一个适用于层级目录结构的makefile模版

今天写了个层次化的Makefile模版,用来自动化编译项目,这个模版应当包含如下功能:shell

  • 适用于层次化结构,Makefile主要内容都放在顶层目录下的Makefile.env中,子层Makefile包含这个Makefile.env,只要增长一些变量就能够编译,特别方便添加新的功能模块
  • 自动解析头文件依赖

个人程序的目录结构是这样的:spa

1. 源文件目录src,模块xxx放在src/xxx下,主程序在src/main下面code

2.公共头文件放在include目录下,模块xxx的头文件放在include/xxx目录下blog

3.模块输出的连接库放在lib目录下进程

4.可执行文件放在bin目录下it

先来看一下Makefile.env,这个相似于c的头文件,包含了全部Makefile的公共部分,自动化

###########  MakeFile.env  ##########
# Top level pattern, include by Makefile of child directory
# in which variable like TOPDIR, TARGET or LIB may be needed

CC=gcc
MAKE=make

AR=ar cr
RM = -rm -rf

CFLAGS+=-Wall

dirs:=$(shell find . -maxdepth 1 -type d)
dirs:=$(basename $(patsubst ./%,%,$(dirs)))
dirs:=$(filter-out $(exclude_dirs),$(dirs))
SUBDIRS := $(dirs)

SRCS=$(wildcard *.c)
OBJS=$(SRCS:%.c=%.o)
DEPENDS=$(SRCS:%.c=%.d)


all:$(TARGET)  $(LIB) subdirs

$(LIB):$(OBJS) 
    $(AR)  $@  $^
    cp $@ $(LIBPATH) 

subdirs:$(SUBDIRS)
    for dir in $(SUBDIRS);\
    do $(MAKE) -C $$dir all||exit 1;\
    done

$(TARGET):$(OBJS)
    $(CC) -o $@ $^ $(LDFLAGS)
    cp $@ $(EXEPATH)


$(OBJS):%.o:%.c
    $(CC) -c $< -o $@ $(CFLAGS)


-include $(DEPENDS)

$(DEPENDS):%.d:%.c
    set -e; rm -f $@; \
    $(CC) -MM $(CFLAGS) $< > $@.$$$$; \
    sed 's,\($*\)\.o[:]*,\1.o $@:,g' < $@.$$$$ > $@; \
    rm $@.$$$$

clean:
    for dir in $(SUBDIRS);\
    do $(MAKE) -C $$dir clean||exit 1;\
    done
    $(RM) $(TARGET) $(LIB)  $(OBJS) $(DEPENDS)

当前目录下的子目录是经过shell命令自动获得的,subdirs:$(SUBDIRS) 这块会进入每一个子目录执行make,固然有些子目录并不须要编译,能够经过exclude_dirs指定,好比顶层目录的exclude_dirs=bin lib include。编译

$(DEPENDS):%.d:%.c 这块做用是自动生成头文件依赖,这部分包括5条命令,看起来很复杂,其实原理很简单,假设main.c,包含头文件depend.h, 解析过程以下:class

1. @set –e 命令设置当前Shell进程状态为:若是执行的任何一条命令的退出状态非零则马上终止当前进程。变量

2. rm -f $@ 删除原来的main.d文件

3. gcc的-MM参数可以生成文件的依赖关系main.o:main.c depend.h,写入文件main.d. $$$$,$$是进程号

4. sed命令做用是将main.o:main.c depend.h替换成main.o main.d:main.c depend.h, 并写入main.d文件

5. rm -f $@.$$$$删除临时文件

Include $(SRCS:.c=.d)将main.d包含进来后,Makefile增长了如下依赖

main.o main.d:main.c depend.h

不论是main.c仍是depend.h的变化都会更新main.o 以及main.d,main.d的更新又反过来更新上面这条依赖关系。

这条依赖下面并无对应的命令,为何会更新目标文件呢?这跟Makefile的运行步骤有关系,引用下陈浩先生的《跟我一块儿写Makefile》

GNU的 make 工做时的执行步骤以下:

一、读入全部的 Makefile。

二、读入被 include 的其它 Makefile。

三、初始化文件中的变量。

四、推导隐晦规则,并分析全部规则。

五、为全部的目标文件建立依赖关系链。

六、根据依赖关系,决定哪些目标要从新生成。

七、执行生成命令。

因此1-5 步为第一个阶段,造成了全部的依赖关系链,6-7 为第二个阶段,决定了全部须要生成的目标文件后,执行对应的命令。上面的依赖关系虽然没有命令,可是肯定了main.o要从新生成,就会找到如下编译模块生成目标文件

$(OBJS):%.o:%.c
    $(CC) -c $< -o $@ $(CFLAGS)

假设有一个模块first,源文件都放在src/first下,Makefile以下

TOPDIR=./../..

LIB=libfirst.a

INCPATH=$(TOPDIR)/include/first
LIBPATH=$(TOPDIR)/lib
CFLAGS= -I$(INCPATH)

include $(TOPDIR)/Makefile.env

TOPDIR是相对于顶层目录的相对路径,LIB是要生成的连接库,这样只要几行命令就能够完成当前模块的编译了,并且first下面还能够添加子模块。

假设main.c在src/main目录下,调用了first模块,Makefile以下

TOPDIR=./../..

TARGET=main

LIBPATH=$(TOPDIR)/lib
EXEPATH=$(TOPDIR)/bin

CFLAGS= -I$(TOPDIR)/include/first 
LDFLAGS= -lfirst

include $(TOPDIR)/Makefile.env

TARGET是生成的可执行文件名,在LIBPATH目录下寻找连接库,生成的可执行文件会被mv到EXEPATH目录下

src下没有源文件,只有目录,因此Makefile很是简单

TOPDIR=./..

include $(TOPDIR)/Makefile.env

顶层目录下的Makefile也很简单,相对增长了exclude_dirs,排除不须要编译的目录

TOPDIR=.

exclude_dirs= include  bin  lib

include $(TOPDIR)/Makefile.env

如今只须要在顶层目录下make一下,src下全部目录都会编译,生成的连接库放在lib下,可执行文件在bin目录中。若是要增长新的功能模块,只要在src/目录下新建目录,增长一个相似first下的Makefile便可,是否是很方便?

相关文章
相关标签/搜索