1. 程式人生 > >專案管理級別的自動萬能通用makefile模板:t-makefile (freetoo)

專案管理級別的自動萬能通用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: [email protected]
# 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=
  [email protected]
else ifeq ($(INFO),2)
  [email protected]
  [email protected]
else
  [email protected]
  [email protected]
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: '[email protected]
	$(BUILD_INFO)$(LD) $(MAIN_FILE) $(LIB_DIRS) $(TMP_LIB_DIRS) $(LDFLAGS) -o [email protected]
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: '[email protected]
	$(BUILD_INFO)$(AR) $(AR_FLAGS) -o [email protected] $^

#*********************************************
# 生成靜態庫檔案
$(TMP_TARGET).a: $(OBJ_FILES)
ifeq ($(INFO),1)
	@echo '**************************************'
endif
	$(STEP_INFO) '[step] Building static lib file: '[email protected]
	$(BUILD_INFO)$(AR) $(AR_FLAGS) -o [email protected] $^

#*********************************************
# 生成動態庫檔案
$(TMP_TARGET).so: $(OBJ_FILES)
ifeq ($(INFO),1)
	@echo '**************************************'
endif
	$(STEP_INFO) '[step] Building dynamic lib file: '[email protected]
ifneq ($(CHECK_LDFLAGS),)
	@echo $(CHECK_LDFLAGS)
endif
	$(BUILD_INFO)$(LD) -o [email protected] $^ $(LIB_DIRS) $(LDFLAGS) $(DYNC_FLAGS)

#*********************************************
# 編譯c程式碼檔案
$(TMP_DIR)%.o: %.c
ifeq ($(INFO),1)
	@echo '**************************************'
endif
	$(STEP_INFO) '[step] Compiling c file: '$<
	@$(MKDIR) $(dir [email protected])
	$(BUILD_INFO)$(CC) $(CC_FLAGS) -c $< -o [email protected] $(INC_DIRS)

#*********************************************
# 編譯c++程式碼檔案
$(TMP_DIR)%.o: %.cpp
ifeq ($(INFO),1)
	@echo '**************************************'
endif
	$(STEP_INFO) '[step] Compiling cpp file: '$<
	@$(MKDIR) $(dir [email protected])
	$(BUILD_INFO)$(CXX) $(CXX_FLAGS) -c $< -o [email protected] $(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指令碼 迴圈 邏輯與 邏輯或  查詢檔案  搜尋檔案目錄 獲取目錄名 獲取檔名

 

轉載請註明出處!