项目管理级别的自动万能通用makefile模板:t-makefile (freetoo)

 

项目管理级别的自动万能通用makefile模板:t-makefile (freetoo)

 

t-makefile源码及示例项目下载链接(会不定期更新):

https://github.com/freetoo/t-makefile

 


一、t-makefile解决的痛点和难点

往往在一个项目工程中,目录名改变了、子目录变更位置了都需要去修改makefile,目录繁多的时候修改makefile也是一件耗时的工作。有没有一个自动的makefile呢?

对于makefile所在的当前目录及其子目录来说,自动makefile的功能实现是非常容易的事情,但难点是:

1、如何自动识别上层目录中(项目目录范围内)的公共目录

2、如何排除一些无关的目录,比如.git目录、test目录、tmp目录、doc目录等。

t-makefile正是解决了以上痛点和难点,从而达到了自动化的目的。t-makefile是一个高度自动化的项目管理级别的makefile源码,能够使您的linux c/c++项目的协同开发工作更加的便捷和高效。

 


二、t-makefile功能:

1、自动搜索源码、头文件、库文件目录并形成有效目录列表和有效文件列表

2、自动识别总makefile功能,可批量执行子目录的makefile

3、自动以目录名为TARGET文件名

4、可动态和静态混合链接成TARGET文件

5、可设置排除目录,避免搜索编译无关源码

6、目录框架灵活设定,框架内可自由移动子makefile仍具有自动功能

7、可避免链接无关符号(函数和变量),避免TARGET体积臃肿

8、支持test目录,可自动包含test工程引用到的模块源码,并能排除其它test目录

9、支持交叉编译,并自动搜索交叉编译环境下的项目库文件(.a/.so)

10、设置简单:仅需设置单个目录的名称即可(不需全路径名)

 


三、t-makefile作用域

t-makefile依赖build.mk文件(主makefle文件)放在项目根目录,若干个子Makefile分布在项目子目录下。

t-makefile的作用域是:makefile的当前目录及子其目录+上层公共目录及其子目录。

注:上层公共目录是指makefile所在的目录向上到项目根目录或build.mk文件所在的目录的每一级目录的一级子目录(由变量COMMON_DIR_NAMES指定名称)

公共目录示例:

 │─Project─│─Process─│─Module─│─Test─│

    ├── 01-lib
    ├── 02-com
    ├── tcp-client
    │     ├──────── 01-lib
    │     ├──────── 02-inc
    │     ├──────── Module1
    │     ├──────── Module2
    │     │            └────────── test
    │     │                          └──────── Makefile(test)
    │     └──────── Makefile(Process)
    ├── tcp-server
    ├── build.mk
    └── Makefile


说明:对于test目录的Makefile来说,它的公共目录就是: 

project/01-lib
project/02-inc
project/tcp-client/01-lib
project/tcp-client/02-com

 


四、规范约定

本章介绍的几种项目目录结构规范,仅作参考。目录结构之外的其它规范一定要遵守,如“main函数文件规范”和“库文件命名规范”。

1、 main函数文件规范

main函数所在文件名必须是main.c或main.cpp,且必须和makefile同在一个目录。

process-name
   ├──── inc
   ├──── src
   ├──── test
   ├──── main.c(cpp)
   └──── Makefile

2、 库文件命名规范

正常库文件命名规范:前缀(lib)+库名+后缀(.a/.so),如:

libcrc.a

交叉编译库文件命名规范:前缀(lib)+交叉编译关键字(arm-linux-gnueabihf-)+库名+后缀(.a/.so),如:

libarm-linux-gnueabihf-crc.a

交叉编译关键字需要设置makefile的变量CROSS_COMPILE_LIB_KEY才能自动识别,如:

CROSS_COMPILE_LIB_KEY ?= arm-linux-gnueabihf-

3、 模块目录规范(含库模块)

    模块目录规范1 (文件和实现文件分开目录):

module(lib)-name
   ├──── lib
   ├──── inc
   ├──── src
   └──── test

    模块目录规范2 (头文件和实现文件同在一个目录):

module(lib)-name
   ├──── lib
   ├──── test
   ├──── *.h(hpp)
   └──── *.c(cpp)

4、 test目录规范    

module(lib)-name
   ├──── *.h(hpp)
   ├──── *.c(cpp)
   └──── test
           ├──── main.c(cpp)
           └──── Makefile

5、 库目录规范

   有.a/.so库文件的库目录,库文件所在目录及其子目录下的源码文件不会重复编译,该库文件的头文件应该和库文件同在一个目录下或者库文件所在目录的inc子目录下。

   库目录规范1 (无源码的.a/.so文件):

lib-name
   ├──── *.h(hpp)
   ├──── *.a(so)
   └──── test

    库目录规范2  (有源码无.a/.so文件):

lib-name
   ├──── test
   ├──── *.h(hpp)
   └──── *.c(cpp)

   库目录规范3  (有源码有.a/.so文件,且*.c(cpp)文件不会重复编译):

lib-name
   ├──── test
   ├──── *.h(hpp)
   ├──── *.c(cpp)
   ├──── *.a(so)
   └──── Makefile

6、 进程目录规范

    进程目录规范1 (文件和实现文件分开目录):

process-name
   ├──── lib
   ├──── common
   ├──── inc
   ├──── src
   │      ├──── module-name1
   │      └──── module-name2
   ├──── main.c(cpp)
   └──── Makefile

    进程目录规范2 (头文件和实现文件同在一个目录):

process-name
   ├──── 01-lib
   ├──── 02-common
   ├──── module-name1
   ├──── module-name2
   ├──── main.c(cpp)
   └──── Makefile

 


五、项目部署

1、确定目录规范:

你可以根据第四章的目录规范样板统一目录结构,也可以自行定义规范。以下为个人推荐的目录结构规范个人观点):

● 全局共享的库(公共)目录应该放在项目根目录(lib/common)下,比如进程间公用的库和进程间的通讯协议等。

● 进程专用的库(公共)目录应该放在进程根目录(lib/common)下,比如进程下各个模块公用的库和模块间的接口协议等。

● 模块专用的库目录应该放在模块根目录(lib)下,此部分的库仅为该模块专用的。

● 头文件(*.h/*.hpp)和实现文件(*.c/*.cpp)尽可能的同在一个目录。

上述库(公共)目录规范能够保证各个进程和模块的独立性(尤其在多人协同开发时更显得重要),并且可以避免繁琐的目录跳转以提高工作效率也便于维护和阅读源码,特别是在使用gitlab的子模块功能的时候目录的独立性显得更为重要。

以下是一个简洁的项目目录规范示例:

project-name
    ├── 01-lib
    │     ├── crc
    │     └── md5
    ├── 02-common
    │     └── proto
    ├── process-name1
    ├── process-name2
    │      ├── 01-lib
    │      ├── 02-common
    │      ├── module-name1
    │      ├── module-name2
    │      │      ├── lib
    │      │      └── test
    │      │            └── makefile
    │      │
    │      └── makefile
    │
    ├── makefile
    └── build.mk 

2、下载t-makefile

下载t-makefile解压后将t-makefile目录下的build.mk和makefile文件复制到你的项目根目录下。

3、项目初期设置(设置build.mk文件,您也可也使用默认值这样就基本不需要修改设置)

    a、设置测试目录名(用于识别测试项目和排除其它测试目录的代码):

TEST_DIR_NAME ?= test

    b、设置临时目录名(用于存放编译时的*.o*.d等临时文件):            

TMP_DIR ?= tmp

    c、设置公共目录名(用于makefile向上层目录搜索公共库和公共接口代码等):

COMMON_DIR_NAMES ?= lib inc include com comment 

    d、设置要排除目录名(用于排除无关目录):             

EXCLUDE_DIR_NAMES ?= .git tmp temp doc docs bak

   e、设置相关编译选项

       根据实际需求设置各项编译选项。

4、派发makefile文件

将项目根目录下的makefile派发到各个进程目录或模块的test目录作为子makefile来用,此后可以根据实际需求简单修改子makefile即可。

注:其他功能设置请参阅第六章。

 


六、t-makefile功能设置:

1、总makefile功能

makefile所在目录无main.c、main.cpp文件和输出目标文件不是.a及.so文件时,自动默认为总makefile功能,makefile会执行子目录下的所有makefile文件(如果是“make”命令的则排除test目录下的makefile,是“make clean”的则执行所有的)。

或者执行make命令是加ALL=y参数即可强行执行子目录下的所有makefile。

2、自动以目录名为TARGET文件名

如果不手动设置变量TARGET,则默认以makefile的父目录名为输出文件名。如果是test目录,则以上层目录名加上“_test”。

3、动态和静态混合链接成TARGET文件

由变量DYMAMIC_LDFLAGS和STATIC_LDFLAGS的组合设置来决定是否是混合链接、还是纯动态链接和纯静态链接。

4、排除makefile所在目录下的功能模块目录

多人开发的时候,部分模块尚未完工的,可以设置该模块的目录名(目录要具有唯一性)将其排除在作用域范围之外,makefile还会自动创建该模块的宏定义(EXC_+大写模块名),你可以用此宏定义来做代码中的条件编译。

变量设置:EXCLUDE_DIR_NAMES += ModuleName

宏定义名:EXC_MODULENAME

5、模块独立调试时引用其它功能模块

某个模块在test目录独立调试时,需要引用其它已经完工模块的,仅需设置该模块的目录名称即可(目录要具有唯一性),makefile还会自动创建该模块的宏定义(INC_+大写模块名),你可以用此宏定义来做代码中的条件编译。

变量设置:INCLUDE_MODULE_NAMES += ModuleName

宏定义名:INC_MODULENAME

注:可参考示例工程multi-process1/client/Module2/test目录下的makefile。

6、交叉编译和识别交叉编译的库文件

用于交叉编译设置的变量(设置前请将交叉编译链可执行程序的路径设置到系统的环境变量PATH中,或者使用全路径名设置变量CROSS_COMPILE):

CROSS_COMPILE ?= arm-linux-gnueabihf-

# or

CROSS_COMPILE ?= /xxx/arm-linux-gnueabihf-

用于选择交叉编译使用的库文件的变量:

CROSS_COMPILE_LIB_KEY ?= arm-linux-gnueabihf-

注:makefile会根据CROSS_COMPILE_LIB_KEY的设置自动识别合适的库文件,关于库文件的命名规范请参阅3.8章节。

 


七、用法说明(make命令参数)

     1、make                                                    # 正常编译 
     2、make clean                                        # 清除临时文件及TARGET文件 
     3、make INFO=1                                     # 编译时打印详细信息 
     4、make INFO=2                                     # 静默编译无任何信息输出
     5、make CROSS_COMPILE=...           # 交叉编译设置,或者修改makefile
     6、make [clean] ALL=y                          # 执行本目录和子目录的makefile

 


八、示例项目说明

1、示例目录说明

示例项目中有三种目录结构的项目工程,它们的makefile文件高度一致,也就是说t-makefile的自动功能很完善。
A、multi-process1:简约目录结构的多进程项目工程(目录层次浅操作维护简单)
B、multi-progress2:标准目录结构的多进程项目工程(目录层次深操作维护繁琐)
C、single-progress:单一进程的项目工程

2、示例目录结构

示例目录结构是一个大(总)项目、子项目、多进程、多个模块的目录结构。子项目(multi-process1、multi-progress2、single-progress)可设置变量“PROJECT_ROOT_DIR_NAME”即可使用到总项目目录下的公共目录01-lib、02-com(即tmf-demo-project/01-lib、tmf-demo-project/02-com),参考multi-process1项目的build.mk文件设置:

PROJECT_ROOT_DIR_NAME ?= tmf-demo-project

示例目录结构如下:

| big-project | sub-project | process | module |

tmf-demo-project
├── 01-lib
├── 02-com
├── multi-process1
│   ├── 01-lib
│   ├── 02-com
│   ├── server
│   ├── client
│   │   ├── 01-lib
│   │   ├── 02-com
│   │   ├── Makefile
│   │   ├── Module1
│   │   │   ├── lib
│   │   │   └── test
│   │   └── Module2
│   │       ├── lib
│   │       └── test
│   ├── build.mk
│   └── Makefile
│
├── multi-process2
│   ├── inc
│   ├── lib
│   ├── src
│   │   ├── server
│   │   └── client
│   │       ├── inc
│   │       ├── lib
│   │       └── src
│   │           ├── Module1
│   │           │   ├── src
│   │           │   └── test
│   │           └── Module2
│   │               ├── src
│   │               └── test
│   ├── build.mk
│   └── Makefile
│
└── single-process
    ├── 01-lib
    ├── 02-com
    ├── 03-inc
    ├── Module1
    ├── Module2
    │
    └── Makefile

 


九、主makefile源码(最新版请从题头的链接下载)

############################################################
# Copyleft ©2018 freetoo(yigui-lu)
# name: t-makefile automatic makefile for ubuntu
# qq/wx: 48092788    e-mail: gcode@qq.com
# cn-help: https://blog.csdn.net/guestcode/article/details/81229127
# download: https://github.com/freetoo/t-makefile
# create: 2018-7-7
############################################################

# t-makefile功能说明:
#     1、自动搜索源码、头文件、库文件目录并形成有效目录列表和文件列表
#     2、自动识别总makefile功能,可批量执行子目录的makefile
#     3、自动以目录名为TTARGET文件名
#     4、可动态和静态混合链接成TARGET文件
#     5、可设置排除目录,避免搜索编译无关源码
#     6、目录框架灵活设定,框架内可自由移动子makefile仍具有自动功能
#     7、可避免链接无关符号(函数和变量),避免TARGET体积臃肿

# 使用方法(usage): 
#     1、make                             # 正常编译 
#     2、make clean                       # 清除临时文件及TARGET文件 
#     3、make INFO=1                      # 编译时打印详细信息 
#     4、make INFO=2                      # 静默编译 
#     5、make CROSS_COMPILE=...           # 交叉编译设置
#     6、make [clean] ALL=y               # 执行本目录和子目录的makefile

# 自动makefile作用域(示例):
# Automatic makefile scope(demo):
#
# │───Project───│───Process───│───Module───│───Test───│
#
#	├── 01-lib
#	├── 02-com
#	├── tcp-client
#	│     ├──────── 01-lib
#	│     ├──────── 02-inc
#	│     ├──────── Module1
#	│     ├──────── Module2
#	│     │            └────────── test
#	│     │                          └──────── Makefile(test)
#	│     └──────── Makefile(Process)
#	├── tcp-server
#	├── build.mk
#	└── Makefile
#
# Makefile Scope:current directory(subdirectory) + upper common directory(subdirectory)
# The setting of the upper common directory reference variable COMMON_DIR_NAMES
#
# makefile的作用域是:当前目录及子其目录+上层公共目录及其子目录,
# 公共目录的设置参考变量COMMON_DIR_NAMES的设置。

# 名词解释:
#   上层、向上:是指由makefile所在目录向系统根目录方向到build.mk文件
#             所在的目录位置的若干层目录。

############################################################
# 常用设置项
############################################################
# 输出目标文件名,不设置则默认使用makefile所在的目录名
# 注意:makefile要和main.c/main.cpp文件同级目录
#TARGET ?=
TARGET ?=

# 要包含的上层模块目录名列表(在makefile作用域内)
# 但要确保名称的唯一性,且为上层目录的一级目录名。
# 对于要包含的模块,makefile会为其增加宏定义用于条件编译:INC_MODULENAME
#INCLUDE_MODULE_NAMES += ModuleName
INCLUDE_MODULE_NAMES +=

# 要排除的模块目录名列表(在makefile作用域内)
# 对于要排除的模块,makefile会为其增加宏定义用于条件编译:EXC_MODULENAME
#EXCLUDE_DIR_NAMES += ModuleName
EXCLUDE_MODULE_NAMES +=

############################################################
# 编译设置部分(Compile setup part)
############################################################
# 设置调试编译选项(Setting the debug compilation options)
#DEBUG ?= y
DEBUG ?= y

# 宏定义列表(macro definition),用于代码条件编译,不需要前面加-D,makefile会自动补上-D
#DEFS ?= DEBUG WIN32 ...
DEFS +=

# C代码编译标志(C code compile flag)
#CC_FLAGS  ?= -Wall -Wfatal-errors -MMD
CC_FLAGS  ?= -Wall -Wfatal-errors -MMD

# C++代码编译标志(C++ code compile flag),注:最终CXX_FLAGS += $(CC_FLAGS)()
#CXX_FLAGS ?= -std=c++11
CXX_FLAGS ?= -std=c++11

# 编译静态库文件设置标志(Compiling a static library file setting flag)
#AR_FLAGS ?= -cr
AR_FLAGS ?= -cr

# 链接标志,默认纯动态链接模式(Link flag, default pure dynamic link mode)
# static  mode: DYMAMIC_LDFLAG ?=        STATIC_LDFLAGS ?=
#               DYMAMIC_LDFLAG ?= ...    STATIC_LDFLAGS ?=
# dynamic mode: DYMAMIC_LDFLAG ?=        STATIC_LDFLAGS ?= ... 
# bland   mode: DYMAMIC_LDFLAG ?= ...    STATIC_LDFLAGS ?= ... 
#
# 动态链接标志(dynamic link flag)
#DYMAMIC_LDFLAGS += -lrt -lpthread
DYMAMIC_LDFLAGS ?= -lrt -lpthread
# 静态链接标志(static link flag)
#STATIC_LDFLAGS += -lrt -ldl -Wl,--whole-archive -lpthread -Wl,--no-whole-archive
STATIC_LDFLAGS ?=

# 交叉编译设置,关联设置:CROSS_COMPILE_LIB_KEY
#CROSS_COMPILE ?= arm-linux-gnueabihf-
#CROSS_COMPILE ?= /usr/bin/arm-linux-gnueabihf-
CROSS_COMPILE ?=

# 交叉编译链库文件的关键字变量设置,用于识别交叉编译链的库文件
# 例如项目中有同样功能的库文件libcrc.a和libarm-linux-gnueabihf-crc.a,
# makefile会根据CROSS_COMPILE_LIB_KEY的设置来选择相应的库文件。
#CROSS_COMPILE_LIB_KEY ?= arm-linux-gnueabihf-
CROSS_COMPILE_LIB_KEY ?= arm-linux-gnueabihf-

############################################################
# 项目规划初期设置
############################################################
# 项目根目录名,不设置则自动以build.mk文件目录为准,如果也没有build.mk文件
# 则自动以makefile所在文件为准。
# PROJECT_ROOT_DIR_NAME ?= tmf-demo
PROJECT_ROOT_DIR_NAME ?=

# 测试目录的目录名称,makefile会排除在搜索范围之外(makefile所在目录例外)
#TEST_DIR_NAME ?= test
TEST_DIR_NAME ?= test

# 临时目录的目录名称,makefile会排除在搜索范围之外
# 编译时临时文件(.o/.d等文件)所在的目录,如果不设置则默认为tmp
#TMP_DIR ?= tmp
TMP_DIR ?= tmp

# 要包含的上层公共目录名列表,包含库目录、头文件目录等的目录名
#COMMON_DIR_NAMES += lib inc include com common \
#					01-lib 01-inc 01-include 01-com 01-common \
#					02-lib 02-inc 02-include 02-com 02-common \
#					03-lib 03-inc 03-include 03-com 03-common
COMMON_DIR_NAMES ?= lib inc include com common \
					01-lib 01-inc 01-include 01-com 01-common \
					02-lib 02-inc 02-include 02-com 02-common \
					03-lib 03-inc 03-include 03-com 03-common

# 头文件目录名列表,INC_DIR_NAMES是COMMON_DIR_NAMES的子集,
# 一旦设置了本变量,makefile只将其及其子目录加入编译参数-I中。
# 如果不设置,makefile会自动搜含有头文件的目录加入编译参数-I中。
#INC_DIR_NAMES ?= inc include 01-inc 01-include 02-inc 02-include 03-inc 03-include
INC_DIR_NAMES ?=

# 要排除的目录名列表,比如文档目录、备份目录等
#EXCLUDE_DIR_NAMES += .git tmp temp doc docs bak
EXCLUDE_DIR_NAMES ?= .git tmp temp doc docs bak

############################################################
# TARGET后置处理及杂项设置
############################################################
# makefile所在目录的全路径名称
CUR_DIR ?= $(shell pwd)
# makefile所在的目录名称
CUR_DIR_NAME := $(notdir $(CUR_DIR))

# 如果是test目录,SRC_DIR则向上跳一层
ifeq ($(CUR_DIR_NAME),$(TEST_DIR_NAME))
  SRC_DIR := $(shell dirname $(CUR_DIR))
else
  SRC_DIR := $(CUR_DIR)
endif

# 如果没有手动设置TARGET,则设置为makefile所在的目录名称
ifeq ($(TARGET),)
  TARGET := $(notdir $(SRC_DIR))
  ifeq ($(CUR_DIR_NAME),$(TEST_DIR_NAME))
    TARGET := $(TARGET)_$(TEST_DIR_NAME)
  endif
  ifneq ($(CROSS_COMPILE),)
    ifneq ($(CROSS_COMPILE_LIB_KEY),)
      TARGET := $(TARGET:lib%=lib$(CROSS_COMPILE_LIB_KEY)%)
    endif
  endif
endif

# 是编译exec文件还是库文件
IS_LIB_TARGET := $(suffix $(TARGET))
# 目标文件是库文件?
ifneq ($(IS_LIB_TARGET),)
  IS_LIB_TARGET := $(shell if [ $(IS_LIB_TARGET) = .a ] || [ $(IS_LIB_TARGET) = .so ]; then echo y; fi;)
endif

ifeq ($(IS_LIB_TARGET),y)
  # 补充lib前缀
  TARGET := $(if $(findstring lib,$(TARGET)),$(TARGET),lib$(TARGET))
  TMP_TARGET := $(basename $(TARGET))
else
  TMP_TARGET := $(TARGET)
endif

# 查找main文件
MAIN_FILE := $(shell find $(CUR_DIR) -maxdepth 1 -type f -iname 'main.c' -o -type f -iname 'main.cpp')
# 无main文件也不是编译库文件,则认为是总makefile功能
ifeq ($(MAIN_FILE)$(IS_LIB_TARGET),)
  TARGET :=
  MAKE_SUB := y
endif
# 入口参数ALL表示手动设置总makefile功能
ifeq ($(ALL),y)
  MAKE_SUB := y
endif

# 编译信息显示设置,1:为全部显示,2:仅显示步骤项,其它:静默无显示
ifeq ($(INFO),1)
  BUILD_INFO=
  STEP_INFO=@echo
else ifeq ($(INFO),2)
  BUILD_INFO=@
  STEP_INFO=@true
else
  BUILD_INFO=@
  STEP_INFO=@echo
endif

# 文件目录操作变量
RM      := rm -rf
MKDIR   := mkdir -p
MAKE    := make

############################################################
# 编译定义项及编译设置项的后置处理(非常用项,修改需谨慎)
############################################################

# c/c++编译器名称,默认为gcc,有cpp文件则被自动改为g++
CC  := $(CROSS_COMPILE)gcc
CXX := $(CROSS_COMPILE)g++
AR  := $(CROSS_COMPILE)ar
# 默认链接器是gcc,如果有cpp文件makefile会自动设置为g++
ifeq ($(suffix $(MAIN_FILE)),.cpp)
  LD := $(CXX)
else
  LD := $(CC)
endif

# 宏定义列表
# 转大写,+INC前缀
tmp := $(shell echo $(INCLUDE_MODULE_NAMES) | tr '[a-z]' '[A-Z]')
DEFS := $(DEFS) $(tmp:%=INC_%)
# 转大写,+EXC前缀
tmp := $(shell echo $(EXCLUDE_MODULE_NAMES) | tr '[a-z]' '[A-Z]')
DEFS := $(DEFS) $(tmp:%=EXC_%)
DEFS := $(DEFS:%=-D%)

# C代码编译设置标志
CC_FLAGS += $(DEFS)
ifeq ($(DEBUG), y)
  CC_FLAGS += -ggdb -rdynamic -g
else
  CC_FLAGS += -O2 -s
endif
# 不使用到的符号不链接到目标文件中
CC_FLAGS += -ffunction-sections -fdata-sections

# 链接标志和链接库设置(除TOP_MODULE_DIRS目录下的*.a和*.so文件之外的链接库设置)
# STATIC_LIB_FILES和DYNAMIC_LIB_FILES变量是makefile作用域里面的.a和.so文件列表,请一定保留
DYMAMIC_LDFLAGS := $(strip $(DYMAMIC_LDFLAGS))
STATIC_LDFLAGS := $(strip $(STATIC_LDFLAGS))
ifeq ($(DYMAMIC_LDFLAGS)$(STATIC_LDFLAGS),$(DYMAMIC_LDFLAGS))
  # 纯动态链接模式
  #LDFLAGS ?= -Wl,--as-needed -lrt -lpthread $(DYNAMIC_LIB_FILES) $(STATIC_LIB_FILES)
  LDFLAGS ?= $(DYNAMIC_LIB_FILES) $(STATIC_LIB_FILES) $(DYMAMIC_LDFLAGS)
else ifeq ($(DYMAMIC_LDFLAGS)$(STATIC_LDFLAGS),$(STATIC_LDFLAGS))
  # 纯静态链接模式
  #LDFLAGS ?= -static -lrt -Wl,--whole-archive -lpthread -Wl,--no-whole-archive $(STATIC_LIB_FILES)
  LDFLAGS ?= -static $(STATIC_LIB_FILES) $(STATIC_LDFLAGS)
else
  # 动态静态混合链接模式
  # 模板:LDFLAGS = -Wl,-Bstatic ... $(STATIC_LIB_FILES) -Wl,--as-needed -Wl,-Bdynamic ... $(DYNAMIC_LIB_FILES)
  #LDFLAGS ?= -Wl,-Bstatic -lpthread $(STATIC_LIB_FILES) -Wl,--as-needed -Wl,-Bdynamic -lrt $(DYNAMIC_LIB_FILES)
  LDFLAGS ?= -Wl,-Bstatic $(STATIC_LIB_FILES) $(STATIC_LDFLAGS) -Wl,-Bdynamic $(DYNAMIC_LIB_FILES) $(STATIC_LDFLAGS)
endif
LDFLAGS += -Wl,--gc-sections

# 编译动态库设置项
ifeq ($(suffix $(TARGET)),.so)
  CC_FLAGS += -fPIC
  LDFLAGS += -shared
endif

# 最终CXX_FLAGS包含CC_FLAGS
CXX_FLAGS += $(CC_FLAGS)

# 检查编译so文件时,是否是错误设置为静态链接标志
CHECK_LDFLAGS := $(if $(findstring static,$(LDFLAGS)),'Error: build file(*.so) not use static flag',)

############################################################
# 文件和路径搜索部分(非常用项,修改需谨慎)
############################################################
INCLUDE_MODULE_NAMES := $(strip $(INCLUDE_MODULE_NAMES))
EXCLUDE_MODULE_NAMES := $(strip $(EXCLUDE_MODULE_NAMES))
COMMON_DIR_NAMES := $(strip $(COMMON_DIR_NAMES))
EXCLUDE_DIR_NAMES := $(strip $(EXCLUDE_DIR_NAMES))
SPACE :=
SPACE:= $(SPACE) $(SPACE)

# 如果是总makefile
ifneq ($(MAKE_SUB),)
  # 不包含test目录名的排除目录名的列表
  tmp := $(subst $(SPACE),\\\|,$(strip $(EXCLUDE_DIR_NAMES)))
  # 执行make clean命令的makefile所在目录列表,包含test目录
  MF_CLEAN_DIRS := $(dir $(shell find . -type f -iname Makefile | grep -v $(tmp)))
  MF_CLEAN_DIRS := $(foreach dir,$(MF_CLEAN_DIRS),$(shell if [ ! $(dir) = ./ ]; then echo $(dir); fi;))
endif

# 要排除的目录名列表
EXCLUDE_DIR_NAMES += $(EXCLUDE_MODULE_NAMES) $(TEST_DIR_NAME)
EXCLUDE_DIR_NAMES := $(subst $(SPACE),\\\|,$(strip $(EXCLUDE_DIR_NAMES)))
# 如果是总makefile
ifneq ($(MAKE_SUB),)
  # 执行make命令的makefile所在目录列表,不包含test目录
  MF_MAKE_DIRS := $(dir $(shell find . -type f -iname Makefile | grep -v $(EXCLUDE_DIR_NAMES)))
  MF_MAKE_DIRS := $(foreach dir,$(MF_MAKE_DIRS),$(shell if [ ! $(dir) = ./ ]; then echo $(dir); fi;))
endif

# 如果没设置临时,默认等于tmp
ifeq ($(TMP_DIR),)
  TMP_DIR := tmp
endif

# build.mk文件所在目录,如果没有build.mk则等于当前目录
 BUILDMK_DIR ?= $(shell result=$(CUR_DIR); \
							for dir in $(strip $(subst /, ,$(CUR_DIR))); \
							do \
								dirs=$$dirs/$$dir; \
								if [ -f $$dirs/build.mk ]; then \
									result=$$dirs; \
								fi; \
							done; \
							echo $$result; \
					)

# 项目根目录全路径名称
ifneq ($(PROJECT_ROOT_DIR_NAME),)
  PROJECT_ROOT_DIR := $(shell result=$(BUILDMK_DIR); \
							for dir in $(strip $(subst /, ,$(CUR_DIR))); \
							do \
								dirs=$$dirs/$$dir; \
								if [ $$dir = $(PROJECT_ROOT_DIR_NAME) ]; then \
									result=$$dirs; \
									break; \
								fi; \
							done; \
							echo $$result; \
					)
else
  PROJECT_ROOT_DIR := $(BUILDMK_DIR)
endif

# 向上搜索COMMON_DIR_NAMES指定名称的公共目录,库文件编译除外
ifeq ($(IS_LIB_TARGET),)
ifneq ($(PROJECT_ROOT_DIR),)
  COMMON_DIR_NAMES += $(INCLUDE_MODULE_NAMES)
  tmp := $(strip $(subst /, ,$(subst $(PROJECT_ROOT_DIR),,$(SRC_DIR))))
  tmp := $(shell Dirs=$(PROJECT_ROOT_DIR); \
  			echo $$Dirs; \
			for dir in $(tmp); \
			do \
				Dirs=$$Dirs/$$dir; \
				echo $$Dirs; \
			done; \
		)
  COMMON_DIRS := $(shell \
					for dir in $(tmp); \
					do \
						for name in $(COMMON_DIR_NAMES); \
						do \
							if [ -d $$dir/$$name ];then \
								echo $$dir/$$name; \
							fi; \
						done; \
					done; \
				)
endif
endif

# 所有文件列表
SRC_DIRS += $(SRC_DIR)
tmp := $(COMMON_DIRS) $(filter-out $(COMMON_DIRS:%=%%),$(SRC_DIRS))
ALL_FILES := $(shell find $(tmp)  \
						-type f -name '*.h' -o -type f -name '*.hpp' \
						-o -type f -name '*.c' -o -type f -name '*.cpp' \
						-o -type f -name '*.a' -o -type f -name '*.so' \
						| grep -vw 'main.c\|main.cpp' \
						| grep -vw $(EXCLUDE_DIR_NAMES))
EXCLUDE_DIR_NAMES :=
ifeq ($(CUR_DIR_NAME),$(TEST_DIR_NAME))
  ALL_FILES += $(shell find $(CUR_DIR) -type f -name '*.h' -o -type f -name '*.hpp' \
						-o -type f -name '*.c' -o -type f -name '*.cpp' \
						-o -type f -name '*.a' -o -type f -name '*.so' \
						| grep -vw 'main.c\|main.cpp\|'$(TMP_DIR))
endif

# 所有库文件
LIB_FILES := $(filter %.a %.so,$(ALL_FILES))
ifneq ($(LIB_FILES),)
  # 库文件列表
  ifneq ($(IS_LIB_TARGET),)
  	LIB_FILES := $(filter $(CUR_DIR)%,$(LIB_FILES))
  	tmp := $(TMP_TARGET:lib%=%)
    LIB_FILES := $(filter-out $(CUR_DIR)/%$(tmp).a $(CUR_DIR)/%$(tmp).so,$(LIB_FILES))
  endif  

  # 所有库文件目录
  LIB_DIRS += $(sort $(dir $(LIB_FILES)))
  LIB_DIRS := $(strip $(LIB_DIRS))
  LIB_OUT_DIRS := $(LIB_DIRS:%=%%)
  LOAD_LIB_PATH := $(sort $(dir $(filter %.so,$(LIB_FILES))))
  
  # 库文件列表
  LIB_FILES := $(notdir $(LIB_FILES))
  # 如果是交叉编译,则使用交叉编译链的库文件并排除同名的非交叉编译链的库文件
  ifneq ($(CROSS_COMPILE),)
    ifneq ($(CROSS_COMPILE_LIB_KEY),)
      tmp := $(filter lib$(CROSS_COMPILE_LIB_KEY)%,$(LIB_FILES))
      tmp := $(subst $(CROSS_COMPILE_LIB_KEY),,$(tmp))
      LIB_FILES := $(filter-out $(tmp),$(LIB_FILES))
    endif
  else
    # 不是交叉编译链,排除交叉编译链的库文件
    LIB_FILES := $(filter-out lib$(CROSS_COMPILE_LIB_KEY)%,$(LIB_FILES))
  endif
  # 静态库文件列表
  STATIC_LIB_FILES := $(filter %.a,$(LIB_FILES))
  STATIC_LIB_FILES := $(STATIC_LIB_FILES:lib%.a=-l%)
  # 动态库文件列表
  DYNAMIC_LIB_FILES := $(filter %.so,$(LIB_FILES))
  LIB_FILES :=
  DYNAMIC_LIB_FILES := $(DYNAMIC_LIB_FILES:lib%.so=-l%)
endif # ifneq ($(LIB_FILES),)

# 源文件目录及obj文件列表
tmp := $(filter %.c %.cpp,$(ALL_FILES))
ifeq ($(CUR_DIR_NAME),$(TEST_DIR_NAME))
OBJ_FILES := $(filter $(CUR_DIR)%,$(tmp))
tmp := $(filter-out $(CUR_DIR)%,$(tmp))
endif
ifneq ($(LIB_DIRS),)
  # 过滤译库文件(.a/.so)的源码文件
  tmp := $(filter-out $(LIB_OUT_DIRS),$(tmp))
endif
# .o文件列表
TMP_DIR := $(TMP_DIR)/
OBJ_FILES += $(tmp)
OBJ_FILES := $(OBJ_FILES:%.c=$(TMP_DIR)%.o)
OBJ_FILES := $(OBJ_FILES:%.cpp=$(TMP_DIR)%.o)
MAIN_FILE := $(MAIN_FILE:%.c=$(TMP_DIR)%.o)
MAIN_FILE := $(MAIN_FILE:%.cpp=$(TMP_DIR)%.o)
ifeq ($(OBJ_FILES),)
  TMP_TARGET := $(TARGET)
endif


# 头文件所在目录
tmp := $(sort $(dir $(filter %.h %.hpp,$(ALL_FILES))))
ifeq ($(CUR_DIR_NAME),$(TEST_DIR_NAME))
INC_DIRS += $(filter $(CUR_DIR)%,$(tmp))
tmp := $(filter-out $(CUR_DIR)%,$(tmp))
endif
# 如果指定头文件目录名,则过滤掉其它的目录
ifneq ($(INC_DIR_NAMES),)
  tmp := $(foreach item,$(tmp),\
			$(shell \
				isfind=n; \
				for name1 in $(subst /, ,$(item)); \
				do \
					for name2 in $(INC_DIR_NAMES); \
					do \
						if [ $$name1 = $$name2 ]; then \
							isfind=y; \
							break; \
						fi; \
					done; \
					if [ $$isfind = y ]; then \
						break; \
					fi; \
				done; \
				if [ $$isfind = y ]; then \
					echo $(item); \
				fi; \
			) \
		)
else
# 头文件默认和库文件一起放或者放inc目录下
  ifneq ($(LIB_DIRS),)
    tmp2 := $(LIB_DIRS:%=%inc/)
    tmp2 := $(tmp2:%=%%) $(LIB_OUT_DIRS)
    tmp2 := $(filter $(tmp2),$(tmp))
# 过滤译库文件(.a/.so)的其它头文件的目录
    tmp2 += $(filter-out $(LIB_OUT_DIRS),$(tmp))
    tmp := $(sort $(tmp2))
    tmp2 :=
  endif
endif
ALL_FILES :=
INC_DIRS := $(strip $(INC_DIRS)) $(tmp)
tmp :=
INC_DIRS := $(INC_DIRS:%=-I%)

# 所有库目录
LIB_DIRS := $(LIB_DIRS:%=-L%)

# *.c/*/cpp文件搜索的目录,用于编译设置
#VPATH := $(SRC_DIRS)

# 临时库文件,将所有.o文件合并为一个临时.a文件,再和main.o文件链接,
# 避免链接进无用符号造成TARGET文件体积臃肿。
ifneq ($(OBJ_FILES),)
  TMP_LIB_TARGET := libtmp.a
  TMP_LIB_DIRS := -L$(TMP_DIR) $(TMP_LIB_TARGET:lib%.a=-l%)
  TMP_LIB_TARGET := $(TMP_DIR)$(TMP_LIB_TARGET)
endif

############################################################
# 链接成最终文件
############################################################
all: FIRST_EXEC $(TARGET)

FIRST_EXEC:
ifdef DEB
	@echo '**************************************'
	@echo 'PROJECT_ROOT_DIR:'$(PROJECT_ROOT_DIR)
	@echo '**************************************'
	@echo 'TARGET:'$(TARGET)
	@echo '**************************************'
	@echo 'CUR_DIR:'$(CUR_DIR)
	@echo '**************************************'
	@echo 'SRC_DIR:'$(SRC_DIR)
	@echo '**************************************'
	@echo 'COMMON_DIRS:'$(COMMON_DIRS)
	@echo '**************************************'
	@echo 'LIB_DIRS:'$(LIB_DIRS)
	@echo '**************************************'
	@echo 'STATIC_LIB_FILES:'$(STATIC_LIB_FILES)
	@echo '**************************************'
	@echo 'DYNAMIC_LIB_FILES:'$(DYNAMIC_LIB_FILES)
	@echo '**************************************'
	@echo 'INC_DIRS:'$(INC_DIRS)
	@echo '**************************************'
	@echo 'OBJ_FILES:'$(OBJ_FILES)
	@echo '**************************************'
endif
#*********************************************
# 总makefile模式,编译子目录下的所有makefile
ifneq ($(MF_MAKE_DIRS),)
	$(STEP_INFO) '[step] submakefile is making...'
	@for dir in $(MF_MAKE_DIRS); do $(MAKE) -C $$dir; done;
	$(STEP_INFO) '[step] submakefile make done'
endif

#*********************************************
# 生成exec程序
$(TMP_TARGET): $(TMP_LIB_TARGET) $(MAIN_FILE)
ifeq ($(INFO),1)
	@echo '**************************************'
endif
	$(STEP_INFO) '[step] Building exec file: '$@
	$(BUILD_INFO)$(LD) $(MAIN_FILE) $(LIB_DIRS) $(TMP_LIB_DIRS) $(LDFLAGS) -o $@
ifneq ($(LOAD_LIB_PATH),)
# 如果调用到.so文件,请执行以下命令设置库文件的搜索路径变量:LD_LIBRARY_PATH
	@echo '**********************************************************'
	@echo
	@echo 'Please execute the following command to load the LIB path, if you use it(.so):'
	@echo 'LD_LIBRARY_PATH=$(LOAD_LIB_PATH) && export LD_LIBRARY_PATH'
	@echo
endif

#*********************************************
# 生成临时静态库文件
$(TMP_LIB_TARGET): $(OBJ_FILES)
ifeq ($(INFO),1)
	@echo '**************************************'
endif
	$(STEP_INFO) '[step] Building temp static lib file: '$@
	$(BUILD_INFO)$(AR) $(AR_FLAGS) -o $@ $^

#*********************************************
# 生成静态库文件
$(TMP_TARGET).a: $(OBJ_FILES)
ifeq ($(INFO),1)
	@echo '**************************************'
endif
	$(STEP_INFO) '[step] Building static lib file: '$@
	$(BUILD_INFO)$(AR) $(AR_FLAGS) -o $@ $^

#*********************************************
# 生成动态库文件
$(TMP_TARGET).so: $(OBJ_FILES)
ifeq ($(INFO),1)
	@echo '**************************************'
endif
	$(STEP_INFO) '[step] Building dynamic lib file: '$@
ifneq ($(CHECK_LDFLAGS),)
	@echo $(CHECK_LDFLAGS)
endif
	$(BUILD_INFO)$(LD) -o $@ $^ $(LIB_DIRS) $(LDFLAGS) $(DYNC_FLAGS)

#*********************************************
# 编译c代码文件
$(TMP_DIR)%.o: %.c
ifeq ($(INFO),1)
	@echo '**************************************'
endif
	$(STEP_INFO) '[step] Compiling c file: '$<
	@$(MKDIR) $(dir $@)
	$(BUILD_INFO)$(CC) $(CC_FLAGS) -c $< -o $@ $(INC_DIRS)

#*********************************************
# 编译c++代码文件
$(TMP_DIR)%.o: %.cpp
ifeq ($(INFO),1)
	@echo '**************************************'
endif
	$(STEP_INFO) '[step] Compiling cpp file: '$<
	@$(MKDIR) $(dir $@)
	$(BUILD_INFO)$(CXX) $(CXX_FLAGS) -c $< -o $@ $(INC_DIRS)

#*********************************************
# 头文件关联
-include $(OBJ_FILES:.o=.d)

############################################################
# 清理临时文件
############################################################
clean:
ifneq ($(MF_CLEAN_DIRS),)
# 总makefile模式
	$(STEP_INFO) '[step] submakefile cleaning...'
	@for dir in $(MF_CLEAN_DIRS); do $(MAKE) -C $$dir clean; done;
	$(STEP_INFO) '[step] submakefile cleaned'
endif
#*********************************************
# 不删除库目标文件
ifeq ($(IS_LIB_TARGET),)
	@if [ -f $(TARGET) ]; then $(RM) -f $(TARGET); fi;
endif
# 子makefile模式,删除临时目录
	@if [ -d $(TMP_DIR) ]; then $(RM) -r $(TMP_DIR); fi;
	@echo '[step] cleaned'

.PHONY: all clean


 

关键字: make makefile shell find grep wildcard notdir patsubst findstring wordlist suffix foreach gcc g++ c++11 嵌入脚本 静态链接库 动态链接库 静态动态混合编译链接 语法 内嵌函数 函数嵌套执行 替换字符串 判断字符串相等 遍历数组  执行shell脚本 循环 逻辑与 逻辑或  查找文件  搜索文件目录 获取目录名 获取文件名

 

转载请注明出处!

 

  • 0
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值