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

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

我的程序的目录结构是这样的:

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

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

3.模块输出的链接库放在lib目录下

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

先来看一下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 -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 ;\
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 ;\
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, 解析过程如下:

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 工作时的执行步骤如下:

1、读入所有的 Makefile。

2、读入被 include 的其它 Makefile。

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

4、推导隐晦规则,并分析所有规则。

5、为所有的目标文件创建依赖关系链。

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

7、执行生成命令。

所以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即可,是不是很方便?

一个适用于层级目录结构的makefile模版的更多相关文章

  1. 层级目录结构的Makefile递归编译方法

    层级目录结构的Makefile编写方法. 层级目录结构的Makefile编写方法. 0.前言 1.如何编译整个工程 2.过滤每层不需要编译的目录 3将所有输出文件定向输出. 0.前言 假如现在有这样一 ...

  2. Maven 使用了一个标准的目录结构和一个默认的构建生命周期。

    Maven 使用了一个标准的目录结构和一个默认的构建生命周期. 约定优于配置 当创建 Maven 工程时,Maven 会创建默认的工程结构.开发者只需要合理的放置文件,而在 pom.xml 中不再需要 ...

  3. 设计一个树型目录结构的文件系统,其根目录为 root,各分支可以是目录,也可以是文件,最后的叶子都是文件。

    设计一个树型目录结构的文件系统,其根目录为 root,各分支可以是目录,也可以是文件,最后的叶子都是文件. 我实现的功能是提供父目录(兄弟目录),输入文件名,创建树型目录结构,文本文件不可以再有子目录 ...

  4. .net core 第一个mvc项目目录结构简析

    创建项目的命令     首先来认识一下创建项目可使用的各种命令,.NETCore 的命令都以 dotnet 打头,这很好理解,输入 dotnet xxx,就是执行环境变量指向的 C:\Program ...

  5. Eclipse中一个Maven工程的目录结构

    在之前的javaSE开发中,没有很关注Eclipse工程目录下的环境,总是看见一个src就点进去新建一个包再写一个class.以后的日子中也没有机会注意到一个工程到底是怎么组织的这种问题,跟不要说自己 ...

  6. Eclipse中一个Maven工程的目录结构 (MacOS)

    1. 为什么写这篇文章 在之前的javaSE开发中,没有很关注Eclipse工程目录下的环境,总是看见一个src就点进去新建一个包再写一个class.以后的日子中也没有机会注意到一个工程到底是怎么组织 ...

  7. iOS项目的目录结构和开发流程

    转自无网不剩的博客 网上相关的资源不多,开源的且质量还不错的iOS项目也是少之又少,最近正好跟同事合作了一个iOS项目,来说说自己的一些想法.   目录结构 AppDelegate Models Ma ...

  8. VC 2005 解决方案的目录结构设置和管理

    VC 2005 解决方案的目录结构设置和管理   Roger (roger2yi@gmail.com)   一个中等规模的解决方案通常都会包含多个项目,其中一些项目产出静态库,一些产出动态库,一些用于 ...

  9. VS解决方案的目录结构设置和管理

    一个中等规模的解决方案通常都会包含多个项目,其中一些项目产出静态库,一些产出动态库,一些用于单元测试,还有的产出最终的应用程序执行档.除此以外,根据项目的需求,还会使用一些第三方的库.   所以为解决 ...

随机推荐

  1. XCODE4.6从零开始添加视图

    转自:http://www.cnblogs.com/luoxs/archive/2012/09/23/2698995.html 对于很多初学者来说,肯定希望自己尝试不用傻瓜的“Single View ...

  2. 果然还是SB了

    编译原理的龙书和虎书,各看了两章之后,¥……&……*……@%¥ 好吧,既然是码农,就要从基层做起,我尝试handwritten一下C或者C的极小子集的one-pass编译器先,等有了深切的体会 ...

  3. C++面试题:list和vector有什么区别?

    原文:http://genwoxuevc.blog.51cto.com/1852984/503337 C++面试题:list和vector有什么区别?考点:理解list和vector的区别出现频率:★ ...

  4. XMPP协议、IM、客户端互联详解

    导读 对于推送,IM服务器,目前可以使用一些厂家提供的SDK来实现,但是我们的老板又总是担心使用别人的SDK,假如别人的服务出现问题,或者别人偷看咱们的信息,那岂不是出现很大问题了 聊一聊xmpp的服 ...

  5. Docker 底层实现

    基本架构 Docker 采用了 C/S架构,包括客户端和服务端. Docker daemon 作为服务端接受来自客户的请求,并处理这些请求(创建.运行.分发容器). 客户端和服务端既可以运行在一个机器 ...

  6. Youth(青春)

    Youth is not a time of life; it is a state of mind; it is not a matter of rosy cheeks, red lips and ...

  7. 1.7.4.3 Parsers

    Parsers 除了主查询解析器外,还有一些其他的查询解析器可以使用或者和主查询解析器连合使用.这部分描述了其他查询解析器的细节,并且给出了一些例子: 大多数的解析器都可以使用局部查询参数的方式来表达 ...

  8. iOS ----Pods-resources.sh Permission denied

    1:执行指令 chmod a+x "路径(Pods-resources.h文件所在的路径)" 2:cd 进如pods文件夹下面 3:执行指令 第一步: sudo chmod 777 ...

  9. Android消息推送完美方案

    转自:http://bbs.hiapk.com/thread-4652657-1-1.html 推送功能在手机应用开发中越来越重要,已经成为手机开发的必须.在Android应用开发中,由于众所周知的原 ...

  10. TCP/IP协议原理与应用笔记22:静态和动态路由选择

    1. 静态路由选择: Static routing 管理员手工设置 管理员手动更新 不能保证路由的一致性和及时性 管理性强 没有路由开销 小型.变化缓慢网络 2. 动态路由选择 Dynamic rou ...