Makefile之大型工程项目子目录Makefile的一种通用写法
原创作品,转载时请务必以超链接形式标明文章原始出处:http://blog.csdn.net/gqb666/article/details/8902133,作者:gqb666
管理Linux环境下的C/C++大型项目,如果有一个智能的Build System会起到事半功倍的效果,本文描述Linux环境下大型工程项目子目录Makefile的一种通用写法,使用该方法,当该子目录内的文件有增删时无需对Makefile进行改动,可以说相当的智能。下面先贴代码(为减小篇幅,一些非关键的代码被去掉,本方法的局限是用于一个C文件生成一个可执行文件的场合):
- ROOTDIR = .
- EXE_DIR = ./bin
- CFLAGS = -I$(INCLUDE_DIR) -I$(LIB_INC) -Wall
- LFLAGS = -L$(LIB_DIR)
- objects := $(patsubst %.c,%.o,$(wildcard *.c))
- executables := $(patsubst %.c,%,$(wildcard *.c))
- all : $(objects)
- $(objects) :%.o : %.c
- @mkdir -p ./bin$
- $(CROSS_COMPILE)gcc -c $(CFLAGS) $< -o $@
- $(CROSS_COMPILE)gcc $(CFLAGS) $< -o $(subst .o, ,$(EXE_DIR)/$@) $(LFLAGS) $(LIBS)
- clean:
- @rm -f *.o rm -f $(executables)
- @rm -rf ./bin
- 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的公共部分,
![](/assets/blank.gif)
########### 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)
![](/assets/blank.gif)
当前目录下的子目录是通过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如下
![](/assets/blank.gif)
TOPDIR=./../..LIB=libfirst.aINCPATH=$(TOPDIR)/include/first LIBPATH=$(TOPDIR)/lib CFLAGS= -I$(INCPATH)include $(TOPDIR)/Makefile.env
![](/assets/blank.gif)
TOPDIR是相对于顶层目录的相对路径,LIB是要生成的链接库,这样只要几行命令就可以完成当前模块的编译了,而且first下面还可以添加子模块。
假设main.c在src/main目录下,调用了first模块,Makefile如下
![](/assets/blank.gif)
TOPDIR=./../..TARGET=mainLIBPATH=$(TOPDIR)/lib EXEPATH=$(TOPDIR)/binCFLAGS= -I$(TOPDIR)/include/first LDFLAGS= -lfirstinclude $(TOPDIR)/Makefile.env
![](/assets/blank.gif)
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的一种通用写法相关推荐
- Makefile:跟我一起学makefile
跟我一起写Makefile 陈皓 (博客地址:http://blog.csdn.net/haoel/article/details/2886) 整理的PDF文件:http://download.csd ...
- 浅显易懂 Makefile 入门 (01)— 什么是Makefile、为什么要用Makefile、Makefile规则、Makefile流程如何实现增量编译
1. 什么是 Makefile Makefile 文件描述了 Linux 系统下 C/C++ 工程的编译规则,它用来自动化编译 C/C++ 项目.一旦写编写好 Makefile 文件,只需要一个 ma ...
- makefile:中文版最权威的makefile文档( 转载 )
跟我一起写 Makefile 陈皓 (CSDN) 概述 -- 什么是makefile?或许很多Winodws的程序员都不知道这个东西,因为那些Windows的IDE都为你做了这个工作,但我觉得要作一个 ...
- makefile 学习笔记 二:makefile变量
一.变量定义语法 变量的名称 = 值列表 变量的名称可以由大小写字母.阿拉伯数字和下划线构成. 等号左右的空白符没有明确的要求,因为在执行 make 的时候多余的空白符会被自动的删除. 至于值列表,既 ...
- Makefile 实际用例分析(一) ------- 比较通用的一种架构
这里不再说Makefile的基本知识,如果需要学习,那么请参考: 下载:makefile 中文手册 或者 点击打开链接 或者 跟我一起写Makefile( 陈皓 ) 这里说的是一般的实际的一个工程应该 ...
- linux下makefile中cp,make与makefile 的理解
当我们写程序过程中存在多个文件之间有复杂的包含关系时,若修改了其中一个源文件,就重新编译所有文件,一般是不必要的,并且当文件很多时,就显得非常笨拙.所有包含该文件的文件需要重新编译,而其它无关系的文件 ...
- 内核编程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 ...
- 大型ERP等数据库系统常见几种设计
大型ERP等数据库系统常见几种设计 1. 自增长 primary key 采用自增长 primary key主要是性能.早期的数据库系统,经常采用某种编号,比如身份证号码,公司编号等等作为数据库表的 ...
- 大型ERP等数据库系统常见几种设计------(转)
1. 自增长 primary key 采用自增长 primary key主要是性能.早期的数据库系统,经常采用某种编号,比如身份证号码,公司编号等等作为数据库表的 primary key.然而,很快, ...
最新文章
- 艾伟:ASP.NET实用技巧(一)
- 河北科技大学——数据结构课后习题
- jquery如何实现ajax技术,使用JavaScript和jQuery简单实现Ajax技术(示例代码)
- 用java求直角三角形的面积_JAVA 已知三角形的三个边判断 是否为直角三角形,如果是求面积!...
- 字符串经典题之正则匹配字符串
- 人生赢家!带着宝宝去面试~清华90后女学霸范楚楚加入麻省理工MIT任助理教授!...
- Go 上下文取消操作
- A. Raising Bacteria
- AngularJs angular.element
- Android 使用字符串动态获取资源ID
- Linux源码安装包快速升级方法
- python爬虫ip限制_爬虫访问中如何解决网站限制IP的问题?
- Java通过坐标点进行拟合函数
- STM32F407主控板PCB
- discuz制作自己的门户列表模板
- gmx_MMPBSA--计算蛋白-配体自由能及能量分解
- R数通杀思路分享-反部分混淆解析canvas和fonts指纹
- Photoshop技术学习有感
- 6个在线正则表达式工具
- OpenGL ES:绘制函数glDrawArrays 和 glDrawElements 的区别