原创作品,转载时请务必以超链接形式标明文章原始出处:http://blog.csdn.net/gqb666/article/details/8902133,作者:gqb666

管理Linux环境下的C/C++大型项目,如果有一个智能的Build System会起到事半功倍的效果,本文描述Linux环境下大型工程项目子目录Makefile的一种通用写法,使用该方法,当该子目录内的文件有增删时无需对Makefile进行改动,可以说相当的智能。下面先贴代码(为减小篇幅,一些非关键的代码被去掉,本方法的局限是用于一个C文件生成一个可执行文件的场合):

[plain] view plaincopy
  1. ROOTDIR = .
  2. EXE_DIR = ./bin
  3. CFLAGS = -I$(INCLUDE_DIR) -I$(LIB_INC) -Wall
  4. LFLAGS = -L$(LIB_DIR)
  5. objects := $(patsubst %.c,%.o,$(wildcard *.c))
  6. executables := $(patsubst %.c,%,$(wildcard *.c))
  7. all : $(objects)
  8. $(objects) :%.o : %.c
  9. @mkdir -p ./bin$
  10. $(CROSS_COMPILE)gcc -c $(CFLAGS) $< -o $@
  11. $(CROSS_COMPILE)gcc $(CFLAGS) $< -o $(subst .o, ,$(EXE_DIR)/$@) $(LFLAGS) $(LIBS)
  12. clean:
  13. @rm -f *.o rm -f $(executables)
  14. @rm -rf ./bin
  15. distclean: clean

假如当前目录里面有a.c b.c 两个文件

Makefile 里的函数跟它的变量很相似——使用的时候,你用一个$符号跟左圆括号,函数名,空格后跟一列由逗号分隔的参数,最后用右圆括号结束。例如,在 GNU Make里有一个叫'wildcard' 的函数,它有一个参数,功能是展开成一列所有符合由其参数描述的文件名,文件间以空格间隔。像这个命令:

objects= $(wildcard *.c)

会产生一个所有以'.c' 结尾的文件列表(本例结果为a.c b.c),然后存入变量 objects里。

另一个有用的函数是 patsubst ( patten substitude,匹配替换的缩写)函数。它需要3个参数——第一个是一个需要匹配的式样,第二个表示用什么来替换它,第三个是一个需要处理由空格分隔的序列。我们将两个函数合起来用:

objects := $(patsubst %.c,%.o,$(wildcard *.c))

会被处理为:

objects := a.o b.o

同理:

executables := $(patsubst %.c,%,$(wildcard *.c))

会被处理为:

executables := a b

%o:所有以“.o”结尾的目标,也就是a.o b.o

依赖模式“%.c”:取模式“%.o”的%,也就是foo bar,并为其加上.c后缀,即a.c,b.c

$<:表示所有依赖目标集,也就是a.c b.c

$@:表示目标集,也就是a.o b.o

命令前加@,表示在终端中不打印,如@mkdir -p ./bin

$(objects) : %.o: %.c 
       $(CROSS_COMPILE)gcc -c $(CFLAGS) $< -o $@

即可翻译为:

a.o b.o : a.c b.c    $(CROSS_COMPILE)gcc -c $(CFLAGS) (a.c b.c) -o (a.o b.o)

明白了这些,这种Makefile的写法就可以完全掌握了。

注:当前目录直接make的两种写法见博文Makefile之写demo时的通用Makefile写法

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

今天写了个层次化的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 neededCC=gcc
MAKE=makeAR=ar cr
RM = -rm -rfCFLAGS+=-Walldirs:=$(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:%.cset -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, 解析过程如下:

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.aINCPATH=$(TOPDIR)/include/first
LIBPATH=$(TOPDIR)/lib
CFLAGS= -I$(INCPATH)include $(TOPDIR)/Makefile.env

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

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

TOPDIR=./../..TARGET=mainLIBPATH=$(TOPDIR)/lib
EXEPATH=$(TOPDIR)/binCFLAGS= -I$(TOPDIR)/include/first
LDFLAGS= -lfirstinclude $(TOPDIR)/Makefile.env

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

src下没有源文件,只有目录,所以Makefile非常简单

TOPDIR=./..include $(TOPDIR)/Makefile.env

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

TOPDIR=.exclude_dirs= include  bin  libinclude $(TOPDIR)/Makefile.env

现在只需要在顶层目录下make一下,src下所有目录都会编译,生成的链接库放在lib下,可执行文件在bin目录中。如果要增加新的功能模块,只要在src/目录下新建目录,增加一个类似first下的Makefile即可,是不是很方便?

Makefile之大型工程项目子目录Makefile的一种通用写法相关推荐

  1. Makefile:跟我一起学makefile

    跟我一起写Makefile 陈皓 (博客地址:http://blog.csdn.net/haoel/article/details/2886) 整理的PDF文件:http://download.csd ...

  2. 浅显易懂 Makefile 入门 (01)— 什么是Makefile、为什么要用Makefile、Makefile规则、Makefile流程如何实现增量编译

    1. 什么是 Makefile Makefile 文件描述了 Linux 系统下 C/C++ 工程的编译规则,它用来自动化编译 C/C++ 项目.一旦写编写好 Makefile 文件,只需要一个 ma ...

  3. makefile:中文版最权威的makefile文档( 转载 )

    跟我一起写 Makefile 陈皓 (CSDN) 概述 -- 什么是makefile?或许很多Winodws的程序员都不知道这个东西,因为那些Windows的IDE都为你做了这个工作,但我觉得要作一个 ...

  4. makefile 学习笔记 二:makefile变量

    一.变量定义语法 变量的名称 = 值列表 变量的名称可以由大小写字母.阿拉伯数字和下划线构成. 等号左右的空白符没有明确的要求,因为在执行 make 的时候多余的空白符会被自动的删除. 至于值列表,既 ...

  5. Makefile 实际用例分析(一) ------- 比较通用的一种架构

    这里不再说Makefile的基本知识,如果需要学习,那么请参考: 下载:makefile 中文手册 或者 点击打开链接 或者 跟我一起写Makefile( 陈皓 ) 这里说的是一般的实际的一个工程应该 ...

  6. linux下makefile中cp,make与makefile 的理解

    当我们写程序过程中存在多个文件之间有复杂的包含关系时,若修改了其中一个源文件,就重新编译所有文件,一般是不必要的,并且当文件很多时,就显得非常笨拙.所有包含该文件的文件需要重新编译,而其它无关系的文件 ...

  7. 内核编程Makefile名开头要大写,scripts/Makefile.build:44: /home/linux/Makefile: 没有那个文件或目录

    内核模块编程-makefile make -C /home/linux/linux-5.10.61/ M=/home/linux modules make[1]: 进入目录"/home/li ...

  8. 大型ERP等数据库系统常见几种设计

    大型ERP等数据库系统常见几种设计 1. 自增长 primary key 采用自增长 primary key主要是性能.早期的数据库系统,经常采用某种编号,比如身份证号码,公司编号等等作为数据库表的 ...

  9. 大型ERP等数据库系统常见几种设计------(转)

    1. 自增长 primary key 采用自增长 primary key主要是性能.早期的数据库系统,经常采用某种编号,比如身份证号码,公司编号等等作为数据库表的 primary key.然而,很快, ...

最新文章

  1. 艾伟:ASP.NET实用技巧(一)
  2. 河北科技大学——数据结构课后习题
  3. jquery如何实现ajax技术,使用JavaScript和jQuery简单实现Ajax技术(示例代码)
  4. 用java求直角三角形的面积_JAVA 已知三角形的三个边判断 是否为直角三角形,如果是求面积!...
  5. 字符串经典题之正则匹配字符串
  6. 人生赢家!带着宝宝去面试~清华90后女学霸范楚楚加入麻省理工MIT任助理教授!...
  7. Go 上下文取消操作
  8. A. Raising Bacteria
  9. AngularJs angular.element
  10. Android 使用字符串动态获取资源ID
  11. Linux源码安装包快速升级方法
  12. python爬虫ip限制_爬虫访问中如何解决网站限制IP的问题?
  13. Java通过坐标点进行拟合函数
  14. STM32F407主控板PCB
  15. discuz制作自己的门户列表模板
  16. gmx_MMPBSA--计算蛋白-配体自由能及能量分解
  17. R数通杀思路分享-反部分混淆解析canvas和fonts指纹
  18. Photoshop技术学习有感
  19. 6个在线正则表达式工具
  20. OpenGL ES:绘制函数glDrawArrays 和 glDrawElements 的区别

热门文章

  1. 【AD】破解WindowsServer2008R2 AD域控目录还原模式密码及域管理员账号密码
  2. SAX解析XML文件
  3. SVN 之 去掉SVN管理标记
  4. 套接字I/O模型之WSAEventSelect
  5. 0046算法笔记——【随机化算法】舍伍德随机化思想解决跳跃表问题
  6. python爬虫人门(10)Scrapy框架之Downloader Middlewares
  7. CentOS7编译安装LNMP
  8. Javascript-稳妥构造函数模式
  9. 编译器的普遍翻译步骤
  10. 直接运行PowerShell脚本