C语言工程项目的Makefile模板,支持一级目录,二级目录,多级目录的Makefile模板。
linuxC_makefile_template
|—— image ******************* 放笔记用的图片
|—— material **************** 放笔记用的一些资料文档
|—— multilevel_dirs ********* 多级目录的Makefile模板示例
|—— onelevel_dirs *********** 一级目录的Makefile模板示例
|—— secondlevel_dirs ******** 二级目录的Makefile模板示例
-
支持自动推导头文件依赖
修改项目工程中的头文件,能够导致包含该文件的对象能够重新编译。在之前一些简单的Makefile模板中,当修改头文件时,重新执行
make
,项目不会进行对应模块的编译。 -
支持多级目录结构划分
多级目录的Makefile模板,使得在顶层执行make的时候,能够对多个目录下的所有源文件进行重新编译。这样子,我们就能够把代码按照模块功能的划分组织我们的代码,而编译的时候,仅仅通过一条
make
命令就可以帮助我们完成复杂的编译和链接,多级目录比较适合一些稍微复杂的项目工程。 -
支持一级目录和二级目录结构划分
Makefile模板支持多级目录的编译,也就意味着能够支持简单目录结构的编译,在这里给出相应的示例。
简单介绍make
实现多级目录编译的Makefile
编写思想以及如何在多级目录下添加新的子模块下的Makefile
的添加。
TOPDIR:=$(shell pwd)/
CC:=gcc
export TOPDIR CC
exclude_dirs:=inc lib
include $(TOPDIR)Makefile.env
-
传递顶层目录的绝对路径(
TOPDIR
)顶层目录的Makefile指定顶层目录的绝对路径,并且将这个变量传递到下一层的Makefile中,这样子所有的Makefile就知道顶层目录在哪里。
-
传递CC变量(
CC
)统一整个项目使用变量CC指定的编译器进行编译。
-
exclude_dirs
排除指定的目录不用进入到里面执行
make
编译。
注意:传递TOPDIR
目录时,理论上目录/xx/xx
和/xx/xx/
指的都是相同的目录,但在这里统一使用第二种表示,这样子是为了方便执行make clean
能够清除复制的文件的时候不会报错。
###src目录添加的Makefile
exclude_dirs:=
include $(TOPDIR)Makefile.env
直接扫描当前目录下的各个模块,进入到里面执行make
编译,如果不想要某一个模块也参与编译,可以通过变量exclude_dirs
指定。
对于一般模块,通过将其所有的源文件链接成一个动态库,然后放到顶层目录下的lib
目录下面,让其它模块调用。
LIB:=libmylib.so
LIBPATH:=$(TOPDIR)lib/
CPPFLAGS:=-I $(TOPDIR)inc
CFLAGS:=
LDFLAGS:=
LDFLAGS+= -lm
include $(TOPDIR)Makefile.env
- 指定生成的动态库名称(
LIB
) - 指定存放动态库的路径(
LIBPATH
) - 指定编译器参数,即生成的库文件所需要的信息
- CPPFLAGS:指定头文件的位置
- CFLAGS:编译器编译参数
- LDFLAGS:指定要链接的库位置和库名称等
注意:LIBPATH
指定的目录变量也是以/xx/xx/
的格式。
主模块里面包含主函数,在这里统一使用main文件夹来存放,若改成别的文件夹名,编译顺序会乱掉,从而报错。
vpath %.so $(TOPDIR)lib
TARGET:=make_test
EXEPATH:=$(TOPDIR)
TARGET_LIBS:=libmylib.so
CPPFLAGS:= -I $(TOPDIR)inc
CFLAGS:= -Wl,-rpath=$(TOPDIR)lib
LDFLAGS:= -L $(TOPDIR)lib
LDFLAGS+= -lmylib
include $(TOPDIR)Makefile.env
-
vpath关键字指定依赖的库文件.so到指定目录查找
主程序的编译依赖生成的库文件,库文件改变,主程序应当也需要重新编译。
-
指定生成的程序名称(
TARGET
) -
指定程序存放的路径(
EXEPATH
)可以放到顶层目录去,一执行
make
完毕,可以直接在当前目录下执行文件,方便。 -
指定主程序依赖的库文件列表(
TARGET_LIBS
)这是主程序依赖的库文件列表,如果不写,但又依赖某个生成的库文件,当这个库文件更新时候,主程序不会更新,这是不对的。所以,需要写好主函数依赖的库文件列表,使得主程序能够及时更新。
-
指定编译器的参数
-
CPPFLAGS:指定头文件的位置
-
CFLAGS:
编译器编译参数,指定程序运行时到指定的位置寻找动态库文件,不指定的话,程序执行的时候可能会出现找到库文件的,当然你也可以把库文件丢到类似
/lib
,/usr/lib
等目录下,执行的时候是找的到的,项目在测试过程中是不建议这么做的。 -
LDFLAGS:指定要链接的库位置和库名称等
-
以上的介绍是多级目录Makefile模板的使用方法,对于一级目录和二级目录的使用也是类似,直接生成可执行文件,不需要生成库文件,这些在自己写一些小项目的时候做为Makefile来管理工程是非常方便的,具体的写法直接看下示例就行了。
##实现思想:
在这里,先阅读一篇文章:一个适用于层级目录结构的makefile模版 ,这个项目的实现也基本都是参考这篇文章来写的,核心思想不变,在细节上做了一些改善。
能够实现多级目录编译的Makefile,完全依赖一个公共Makefile模板实现,在这个模板里面,能够实现三个基本的功能:
- 动态库的生成及复制到指定路径(可选);
- 可执行目标文件生成及复制到指定路径(可选);
- 递归执行每个目录下的Makefile(一定执行);
一个模块,是要生成库文件(普通模块)还是可执行文件(主模块),只需要给对应的变量(LIB
,TARGET
)赋值,比如在main
模块里面(包含main.c
文件)要生成可执行文件,也就是程序,直接在该模块下面的Makefile文件这么写上:TARGET:=main
,就代表这个模块要生成一个叫main的可执行文件,相应的:LIB:=libmylib.so
就是要生成一个动态库,目前也只支持动态库生成。
在这里,对公共的Makefile文件Makefile.env
每个模块作个简单介绍:
####目录搜寻
dirs:=$(shell find . -maxdepth 1 -type d)
dirs:=$(basename $(patsubst ./%,%,$(dirs)))
maindir:=$(findstring main,$(dirs))
dirs:=$(filter-out $(exclude_dirs) main,$(dirs))
SUBDIRS:=$(dirs) $(maindir)
这一部分的代码,用来查找需要编译的目录,并将主模块main目录放到最后才执行,因为主模块的编译依赖其它模块,必须在其它模块之后执行编译,做了个简单目录调整,确保main模块在最后才执行。在这里,如果不想对一些目录编译的话,通过指定变量exclude_dirs
来排除不需要编译的目录,如:
exclude_dirs:=module1 module2 ...
SRC:=$(wildcard *.c)
OBJS:=$(SRC:%.c=%.o)
DEPENDS:=$(SRC:%.c=%.d)
简单查找下当前目录下所有的.c文件,放到SRC变量中,简单的转换成.o(可重定位目标文件)和.d(头文件依赖关系文件)。
####all伪目标
all:$(TARGET) $(LIB) subdirs
在这里,是这个Makefile需要执行的规则,$(TARGET)
和$(LIB)
是可选的,不需要执行这两个目标时候执行留空,这样子目标all
就会变成:
all: subdirs
也就是只执行subdirs规则,这个规则在后面介绍。
subdirs:$(SUBDIRS)
@for dir in $(SUBDIRS); \
do $(MAKE) -C $$dir all || exit 1; \
done
就是进入到该目录下的每个子目录执行里面的Makefile
文件,实现递归编译,这样子,当我们添加新的模块时候,就能够自动进行编译了。
####$(TARHET)规则
$(TARGET):$(OBJS) $(TARGET_LIBS)
$(CC) $(CPPFLAGS) $^ -o $@ $(CFLAGS) $(LDFLAGS)
$(if $(EXEPATH),@cp $@ $(EXEPATH))
这个规则目前是用来生成可执行文件的,可执行文件依赖当前模块下的一堆.o
文件($(OBJS)
)以及库文件.so
($(TARGET_LIBS)
)。然后就执行gcc进行编译,根据一堆编译器参数,生成可执行文件,最后判断下存放程序的路径($(EXEPATH)
)是不是空,不是空就复制下,要是空的就不用复制。
TARGET_LIBS
:指定程序依赖的库文件列表EXEPATH
:指定程序存放的路径TARGET
:程序名
$(LIB):CFLAGS+= -fPIC
$(LIB):$(OBJS)
$(CC) -shared $(CPPFLAGS) $^ -o $@ $(CFLAGS) $(LDFLAGS)
$(if $(LIBPATH),@cp $@ $(LIBPATH))
在这里,默认生成动态库,所以给这个模式的变量CFLAGS
强制加上-fPIC
选项,然后编译器根据一堆编译参数,由外部指定,生成一个库文件,再拷贝到对应的位置($(LIBPATH)
)。
LIB
:库文件名LIBPATH
:存放库文件路径
$(OBJS):CFLAGS+= -c
$(OBJS):%.o:%.c
$(CC) $(CPPFLAGS) $< -o $@ $(CFLAGS)
把每个源文件.c
编译成可重定位的目标文件。
-include $(DEPENDS)
$(DEPENDS):%.d:%.c
@set -e; $(RM) $@; \
$(CC) -MM $(CPPFLAGS) $< > $@.$$$$; \
sed 's,\($*\)\.o[:]*,\1.o $@:,g' < $@.$$$$ > $@; \
$(RM) $@.$$$$
这个规则就是用来推导源文件.c
的头文件依赖关系,然后存到.d
文件去。
clean:
@for dir in $(SUBDIRS); \
do $(MAKE) -C $$dir clean || exit 1; \
done
$(RM) $(TARGET) $(LIB) $(OBJS) $(DEPENDS)
$(if $(notdir $(LIBPATH)$(LIB)),$(RM) $(LIBPATH)$(LIB))
$(if $(notdir $(EXEPATH)$(TARGET)),$(RM) $(EXEPATH)$(TARGET))
把生成的.o .d .so
等编译产生的文件都清除掉,递归扫描每个目录清除。
从Makefile.env
文件的实现中可以看出来,想要执行相应模块功能,直接给相应模块的变量进行赋值控制就行了,就像在使用章节中所展示的那样,定义变量,然后include
下Makefile.env
文件。
- coderkian. 一个适用于层级目录结构的makefile模版[DB/OL]. http://www.cnblogs.com/coderkian/p/3479564.html
-
从代码到生成可执行文件的四个阶段[^1]
-
Makefile中gcc编译程序各阶段的参数变量[^2][^3]
-
Makefile中的通配符[^4]
-
%
适用用于Makefile中的函数中的模式匹配以及静态模式和多模式规则中模式匹配。
-
*, ?, [xxx]
Shell中的通配符,适用于变量的定义以及规则中shell命令。
-
-
Makefile中的vpath关键字[^5]
让Makefile到指定的目录查找匹配模式的文件。
语法:
vpath <pattern> <path>
,如:vpath %.c ../src
-
Makefile中的字符串处理函数[^6]
-
gcc动态库以及静态库生成[^7][^8][^9]
-
Makefile中实现自动生成头文件的依赖[^10][^11]
-
shell相对路径转绝对路径[^12]
- Randal E.Bryant,David O'Hallaron. 深入理解计算机系统(第三版)[M]. 龚奕利,贺莲. 机械工业出版社,2016:3-3.
- 陈皓. 跟我一起写Makefile[DB/OL]. 2005:65-66.
- 宋劲杉. Linux C编程一站式学习[DB/OL]. http://docs.linuxtone.org/ebooks/C&CPP/c/index.html . B. 编译开发工具小结:gcc常用选项.
- 陈皓. 跟我一起写Makefile[DB/OL]. 2005:18-19.
- 陈皓. 跟我一起写Makefile[DB/OL]. 2005:19-20.
- 陈皓. 跟我一起写Makefile[DB/OL]. 2005:44-51.
- kaineshu. [轉貼]用gcc 自製 Library[DB/OL]. https://kaineshu.wordpress.com/2007/05/02/%E8%BD%89%E8%B2%BC%E7%94%A8gcc-%E8%87%AA%E8%A3%BD-library/
- vfhky. Linux gcc编译生成静态库和共享动态库的过程[DB/OL]. https://typecodes.com/cseries/gccgensharedlib.html
- René Nyffenegger's. Creating a shared and static library with the gnu compiler(gcc)[DB/OL]. http://www.adp-gmbh.ch/cpp/gcc/create_lib.html
- 陈皓. 跟我一起写Makefile[DB/OL]. 2005:15-16.
- 陈皓. 跟我一起写Makefile[DB/OL]. 2005:24-25.
- maimake. shell 相对路径转绝对路径[DB/OL]. http://blog.sina.com.cn/s/blog_7a691e910101gnra.html