1. 设计思路
经历的项目里,见过有用autoconf的,但是私有的server,部署环境是确定的,大多都是直接用Makefile。
为什么说是强迫症的呢?主要是最初有这么几个想法:
- 见到的其他所有Makefile,.o文件和.c/.cpp文件是放在一起的,觉得不美观,想仿VS,放在另外一个文件夹里,如.lib。和VS不同的是,笔者还想在这个目录下建立和源代码一样的目录体系,.o文件放在各自的目录下。这样有什么好处呢,不同的目录相当于不同的命名空间,如果没有目录结构,这部分信息就丢了。如我们的miniserver在不同的目录下,有两个相同名字的misc.cpp,内容是不一样的(一个有用,一个是废的),其Editor的版本是用VS编译的,因为VS是没有保留目录信息的,后编译的文件会覆盖前一个,就造成了一个诡异的结果,有时是ok的,有时链接符号未定义…这个bug让人好找
- Makefile支持include的语法,另一个想法是将它做成插件化,各层的Makefile只要include了之后,对几个变量进行赋值就ok了(Android的Makefile也是这么干的)。
- 增量编译
这个Makefile框架后来用于游戏项目,另外加了
- Debug版本的所有输出文件名加上”_d”,Release版本的所有输出文件加上”_r”,这样将Debug版和Release版的输出隔开,避免链接串文件。
对项目文件的管理,设想是一个顶层目录一个库(如前文的线程组一个目录,网络库一个目录,逻辑层的FSM一个目录…),先编译成静态库或者动态库(动态库问题比较多,我们最后都用静态库了);每个库带一个UnitTest目录,一边写一边测,最后这个UnitTest就被当成执行文件了。
2. 实现
先看一个目录的例子。
2.1. 一个应用的例子
1 | ######################################### |
- PROJBASE即工程根目录。.lib目录就在根目录下。
- PRE_CMD/POST_CMD 分别是编译前和编译后执行的命令。
- SUBDIR 是当前目录下的源文件目录,可包含多个,自动编译.c/.cpp
- EXCEPT_FILES 是不需要编译的文件列表
- INC_DIR 头文件查找路径
- DEBUG_STATIC_LIB/RELEASE_STATIC_LIB 分别是依赖的debug和release版本的静态库。
- SHARED_LIB_DIR 是动态库查找路径
- DEBUG_SHARED_LIB/RELEASE_SHARED_LIB 分别是依赖的debug和release版的动态库。
- makefile.compiler.gcc定义编译器,代码如下,我们warning当error处理,同时忽略了部分warning,-Wl,–eh-frame-hdr是tcmalloc的建议,其他没太多东西,备查。
1 | CC=g++ |
- makefile.compile.rules 即本文所要讲的工具了。
OK,设计思想就这样,定义变量,其他交给makefile.compile.rules,如下。
2.2. 定义输出
1 | ######################################### |
- MOD_NAME即最后一层目录
- DEBUG_SUFFIX/RELEASE_SUFFIX 是前面说的”_d”/“_r”后缀
- 然后定义了输出的静态库
- OUTDIR是本模块的输出目录,”.lib”下的目录
- 后面DEBUG_DIR/RELEASE_DIR是最后正式release的文件,不包含.o文件
- MOD_DEBUG_OUTDIR/MOD_RELEASE_OUTDIR是本模块的release文件。
2.2. 定义临时输出文件(.o文件)
1 | ######################################### |
- CFILES/CPPFILES/CCFILES 是所有源代码文件的相对路径
- TMP_OBJECTS 是所有.o文件的相对路径
- OUTSUBDIR 是需要新建的.lib目录文件
- TMP_PATH_OBJECTS 是所有.o文件的绝对路径
- DEBUG_OBJECTS/RELEASE_OBJECTS 是给.o文件加上”_d”/“_r”后缀
2.3. UnitTest的相关文件
1 | ######################################### |
和前面类似
2.4. 定义增量编译所需要的依赖文件
1 | ######################################### |
我们会在.d文件里临时存放.cpp所依赖的头文件
2.5. 顶层规则
1 | test: CFLAGS+=$(DEBUG_FLAGS) |
不同的target应用不同的变量,执行pre/post相关操作。debug版本是make test,release版本是make rtest。
2.6. 下层规则
1 | $(DEBUG_TEST_TARGET): $(MOD_OUTDIR) $(AUTOTARGET) $(DEBUG_TARGET) testdepend $(TEST_OUT_SUBDIR) $(DEBUG_TEST_OBJECTS) $(TEST_OUTDIR) $(TEST_OUT_FILE) $(SYMBOL_OUTDIR) deploy_common |
2.6.1. 新建各种目录
1 | $(MOD_OUTDIR): |
2.6.2. 增量编译.o
1 | -include $(DEPENDFILES) $(TESTDEPENDFILES) |
.o文件依赖.d.tmp和源文件,%d.tmp依赖与源文件,调用gcc生成源文件的依赖头文件,然后将这些文件include进来。这样,依赖的头文件或源文件有改动,都会再重新编译。
2.7. 抽出来的函数
1 | ######################################### |
2.8. 顶层的Makefile
1 | ######################################### |