在上一篇文章“小项目实用makefile”中,已经说明了单个makefile管理层次目录的局限性。本文,主要总结一下项目中的一种实用makefile树写法,为10来个人协作的中小型项目makefile编写,提供参考。

 

1. 需求

        从实用角度,makefile树应该达到以下需求:

        1)自动加入编译系统。新增目录、文件后,能够自动添加(理想),或只需少许修改,就能添加到整个项目编译中。
        2)可读性好,易于添加。新增目录、文件,linux新人能自己看懂添加(理想),或只需口头10s描述就能很好完成。
        3)模块化。新增目录、文件后的makefile,和其他目录完全没有关系(理想),或只需与最邻近的前、后有关系。
 

2. 实现

2.1 自动加入编译系统

        使用makefile递归执行,能够解决此问题。以下脚本,能够执行SUBDIRS指定的子目录内的makefile。

makefile。
SUBDIRS	= modual-a modual-b	modual-c
   
.PHONY: subdirs $(SUBDIRS)
subdirs: $(SUBDIRS)
$(SUBDIRS):
	$(MAKE) -C $@

        说明:

        1)变量SUBDIRS,指定当前目录下,要递归编译的子目录。
        2).PHONY及subdirs目标结构,能保证递归到子目录中。

 2.2 可读性好,易于添加;模块化

        使用makefile的变量定义,其位置前后不敏感特性可以做到。示例脚本如下:

SUBDIRS	= modual-a modual-b	modual-c
OBJECTS	= x.o y.o z.o

all:subdirs ${OBJECTS}

clean:cleansubdirs
	rm ${OBJECTS}

        说明:

        1)变量OBJECTS,指定当前目录的目标文件。新增一个文件z.cpp,在变量OBJECTS中,增加z.o即可。
        2)新增一个目录dir-c,在变量SUBDIRS中,增加dir-c。然后参考本目录中的makfile,在dir-c中,建立一个类似makfile即可。

2.3 减少重复脚本

        实现中,将makefile中的共用变量,转移到文件中,使用include包含,能够达到函数效果,减少重复脚本,容易改变。

3. 实例

        示例项目目录如下所示(点击下载示例项目,这里没找到上传文件入口,只能链接别处了):

project-test
  +-- makeconfig
  |     +-- make.global
  +-- src
  |     +-- module-a
  |     |     +-- test.cpp
  |     |     +-- Makefile
  |     +-- module-b
  |     |     +-- test.cpp
  |     |     +-- Makefile
  |     +-- main.cpp
  |     +-- Makefile
  +-- Makefile

        说明:

        1)project-test/makeconfig/make.global,要包含的makefile共用变量。
        2)project-test/Makefile,顶层makefile,指定可执行目标,及源码目录。
        3)project-test/src/Makefile,子目录的makeflile。目录module-a、module-b的类似,每个目录一个。

        
3.1 project-test/makeconfig/make.global

     如下所示:

# compile macro
CC		= g++
CFLAGS		= -O2 -Wall
LDFLAGS	= -lm 
INCLUDES	= -I/usr/local/include


# recursive make
.PHONY: subdirs ${SUBDIRS} cleansubdirs
subdirs: ${SUBDIRS}
${SUBDIRS}:
	${MAKE} -C $@ all

	
# recursive make clean
cleansubdirs:
	@for dir in ${SUBDIRS}; do /
		${MAKE} -C $$dir clean; /
	done

	
# dependence
%.o: %.cpp
	${CC} ${CFLAGS} ${INCLUDES} -c $< -o $@
%.o: %.cc
	${CC} ${CFLAGS} ${INCLUDES} -c $< -o $@	

        说明:

        1)包含4个区域,共用变量,递归make,递归makeclean,依赖关系。
        2)递归makeclean,使用了不打印的shell语法。原因是,如果和递归一样写,会造成目标重载警告。

3.2 project-test/Makefile

        如下所示: 

# target, subdir, objects in current dir
TARGET		= test
SUBDIRS	= src
OBJECTS	= 


all:subdirs ${OBJECTS}
	${CC} -o ${TARGET} $$(find ./${SUBDIRS} -name '*.o') ${LDFLAGS} ${INCLUDES}


clean:cleansubdirs
	rm -f ${TARGET} ${OBJECTS}


# path of "make global scripts"
# NOTE, use absolute path. export once, use in all subdirs
export PROJECTPATH=${PWD}
export MAKEINCLUDE=${PROJECTPATH}/makeconfig/make.global

# include "make global scripts"
include ${MAKEINCLUDE}

        说明:

        1)使用shell语法,搜索出指定目录的所有.o,链接为执行目标文件。
        2)使用export,指定项目绝对路径,指定共用变量,包含所有目录共用的makefile变量。
        3)每个目标使用一个顶层的makefile,来执行make和makeclean的递归入口,共用makefile。

3.3 project-test/src/Makefile

        如下所示:

# subdir and objects in current dir
SUBDIRS	= module-a module-b
OBJECTS	= main.o


all:subdirs ${OBJECTS}

	
clean:cleansubdirs
	rm -f ${OBJECTS}
	
include ${MAKEINCLUDE}

        说明:

        1)增加目录,只用修改子目录变量SUBDIRS;增加文件,修改当前目标文件变量OBJECTS。
        2)不同目录下,源码相同名字,但类、函数不相同,可以正常编译。        

 

4. 专业makefile树

        以上,只是一个项目最最普通的makefile树。一个实现文件一个.o文件,不考虑库生成,不考虑功能配置项,不考虑平台兼容性。

        一些开源项目,考虑了各种平台兼容性,及功能特性,通常使用了autoconf和automake,自动生成特殊头文件和宏定义,来达到效果。使用以下3条命令,向用户提供配置项设置,编译,库、头文件、或目标文件安装路径。在复杂兼容项目中,非常实用。

./configure
make
make install

        还在继续学习中。

  
参考资料:

        1. GNU Make Manual:http://www.gnu.org/software/make/manual/
        2. 同事J的makefile树。

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=