章節
  1. 緣由
  2. 目錄結構
  3. 動態產生之規則
  4. 程式碼解析
  5. 小結
  6. 程式原始碼

 

緣由

被一個問題困擾很久了

每當建立一個專案程式都要寫一個Makefile

縱然可以複製後再作修改,但簡單的Makefile已經沒辦法應付多層的資料夾

目前遇到的狀況是一個大專案下有多個個別的小專案

每個小專案江個別建置分開的模組 (library) 檔案,並且建置程序非常類似

希望能建立一個Makefile能一次將所有小專案全部建置完畢

但也希望在增加新專案或新檔案的時候

可以不要再對Makefile作過多的修改,甚至是可以不修改

也許能依靠 .SUFFIX 來調整source與target的對應

例如 %o: %.cpp 或者 .cpp.o

但是一個C系列的建置,是需要header file (.h) 檔的

今天若希望各自的.h檔,能成為各自模組的需求 (requisite)

那麼 .cpp.o這種萬用規則,就顯得太簡單

所以一個解決的辦法,即限制目錄結構以使make動態產生Makefile規則的方式來進行

automake不是我的選擇。一來,各個模組內結構其實都非常簡單

二來,我現在不會用automake,而且產生的結果看來是不太好懂

三來,我習慣自己動手作,做出來比較有成就感,而且能知道比較多的錯誤狀況

寫程式要像玩洛克人,可以玩到不用特殊武器破關

 

專案說明

此專案是一個C++專案

目的在建置個人的C++ library,包含將已有或未整理之機制,進行包裝或再包裝

使用別人的東西,要變是掌握在別人手上

以自己習慣的方式再包一層可以減少上層呼叫之程式之大幅度變動

並且若改換執行平台 (例如變換作業系統)

上層呼叫之程式也能以較小幅度的變動轉移

最後相同的目的,為了要建構完整的運作,必須熟悉該機制的運作模式

 

目錄結構

因為這個Makefile的運作必須在有規範的目錄結構下才能正常運行

首先必須先講講規劃的結構

  • ROOT: 主專案根目錄,目前亦為Makefile所放置之目錄
  • SOURCE: 原始碼目錄,底下包含各小專案之目錄
  • OBJECT: 建置過程中產生之.o檔皆放置於此。此為使原始碼資料夾保持乾淨狀態 
  • PUBLIC: 預備開放提供外部使用之程式檔,以模組名稱分類,包含
    • INCLUDE: 提供外部include之 .h 檔,模組建置時使用此處之 .h 檔
    • LIB: 產出之library檔放置位置包含 .so 與 .a 檔
  • EXPORT: 將模組檔案自PUBLIC目錄中重新依模組作目錄配置

 

動態產生之規則

本Makefile目標為動態產生下列規則

外部使用規則

  • [模組名稱]:外部建置模組時,以此target進行個別模組建置
  • list[模組名稱]src:此類規則將列出一個模組之原始程式檔,包括 .cpp與.h檔
  • export[模組名稱]:此類規則將一個模組之欲公開之產出整理至EXPORT目錄
  • clean[模組名稱]:此類規則將清除一個模組之建置產出,包含 .o檔與函式庫檔

內部使用規則

使用者應避免直接以此規則進行建置

  • .[模組名稱]:此類規則為模組之實際建置規則,
                    實際之建置動作定義於此,產出函式庫檔案後
                    將產生「.[模組名稱]」之檔案代表該模組已進行建置
  • %.o:各模組原始檔之object建置規則

 

程式碼解析

Line 23:

DEBUG flag。若設定為 true,將條件展開debug用訊息指令 

Line 26:

TARGET變數儲存所有目標模組名稱

SOURCE目錄與PUBLIC/INCLUDE目錄之模組目錄須符合TARGET變數名稱

Line 29:

儲存根目錄之絕對路徑,以Makefile之所在目錄找出根目錄之路徑

MAKEFILE_LIST代表目前使用之Makefile,自己將被放置於最後項

因目前並未有include其它Makefile,故MAKEFILE_LIST即自己

但為以防萬一,仍使用lastword function將最後一個項目取出

並以realpath與dir產生絕對路徑之字串

專案中所有目錄皆必須以ROOT根目錄為基礎進行定位

並為避免使用者在其他目錄

Makefile -f [檔名] 之進行建置,而使整個建置程序搞亂

Line 33-38

以下目錄變數依序代表,各專案原始碼目錄、OBJECT目錄、INCLUDE目錄、

Library連結目錄 (目前未有用處)、產出目錄 (PUBLIC/INCLUDE)、EXPORT目錄

Line 41

列出各模組之所有原始檔 (.cpp) ,列出結果為絕對路徑

此處以shell指令,利用find指令達成

Line 44

將.cpp檔更換為對應主檔名之 .o檔

並將原有路徑替換為 OBJECT 路徑

Line 47

列出各模組原始檔會使用之 .h 檔絕對路徑

包含各模組之私用檔,與PUBLIC/INCLUDE中之公開檔

Line 49-59

建置 toolchain 之共同設定參數

Line 61

指定 .PHONY 內容為假目標

當 make執行該 rule時,將永遠執行該rule之指令

Line 63-155 為動態展開之模板 (template)

Line 71-73

產生下之字串 

$(addprefix [OBJECT目錄路徑], $(basename $(notdir [原始檔路徑]))) 

此 define 輸出僅為暫時結果,之後會將輸出結果置入其它字串中

最後將以make之 eval function進行動態規則的展開

若僅以$符號,在define被call function展開時

就會使make function執行,而無法達到預期結果

故需以 $$ 使其可以產生 $ 符號

Line 75-78

list[模組名稱]src 規則之模板,參數 1 為模組名稱

Line 82-84

為簡化後續程式之文字產生之變數

為了在能應付各模組的展開,必須以「=」進行指派

當這些變數被參考時,就會展開為不同的結果

Line 86-92

[模組名稱]之規則模板,這些規則將以 .[模組名稱] 檔案作相依

這類規則必須永遠被執行,故以 .PHONY 標示之

否則執行目錄下不慎產生同名檔案或目錄,此規則將可能不被執行

而使實際的建置無法動作

Line 94-102

list[模組名稱]src 規則之模板,參數1 為模組名稱

此類規則必須永遠被執行,故以 .PHONY 標示之

Line 104-116

export[模組名稱] 規則之模板,參數1 為模組名稱

此類規則必須永遠被執行,故以 .PHONY 標示之

Line 118-127

各 object code之規則模板,參數1為模組名稱;參數2 為該模組之 .h檔

事實上參數2,可以參數1 產生,但須搭配 make的 eval function使用

如 $(eval HEADER = 展開式),展開式見221行

因 Linux 之共享函式庫,需要 position independent code

(PIC,日後再撰文 Linking 詳述之)

故此處需產生兩類 object code

Line 129-141

.[模組名稱] 規則之模板,參數 1為單一原始碼名稱

此規則將以 .o檔組裝為靜態函式庫,以 -pic.o 組裝為共享函式庫

並在建置成功後產生同名之檔案

Line 143-155

clean[模組名稱] 規則之模板,參數1 為模組名稱

此類模組必須永遠被執行,故以 .PHONY 標示之

Line 160-161

make預設建置目標,all之意義為進行所有建置。此規則將被永遠執行

Line 166-167

建置所有 object code。此規則將被永遠執行

Line 169-170

執行所有模組之 export。此規則將被永遠執行

Line 175-177, 185-187, 195-197, 202-204, 212-214, 227-229

打印動態展開之訊息,僅在DEBUG 變數被設定為 true時執行

Line 179-182, 189-191, 199, 206-209, 231-234

動態展開之執行程式

foreach function之作用為將一個字串 (參數2) 以空白分割

並將切割後之子字串 (參數1) 依序進行字串展開 (參數3)

任何字串展開之後事實上都可被當作 make之指令

但foreach function執行後為單一結果

因此動態展開後之結果中,會包含多個規則

若將此一次執行,對make來說的運作是錯誤的

故必須分別以 eval function作單次執行

eval之輸出為空字串,故foreach執行結束後是不會產生錯誤的

Line 216-224

object file規則之展開執行程式

為使各模組 cpp檔以各自之 .h檔為需求而不混合

故以兩層之 foreach function作執行

第一層依次列出各模組之cpp檔

第二層將列出之結果,搭上模組之.h檔進行展開

Line 236-237

移除所有建置產生之檔案。此規則必須永遠被執行

 

小結

此Makefile可以在小幅更動之下

動態產生新增模組與新增檔案規則,並使所有原始檔都得以被建置

但目前仍有不足之處,此模組現在有一大限制

模組必須完全獨立,僅能相依共通模組,而不能個別指定

因共享函式庫在建置時,亦必須指定其會跟哪些其它共享函式庫連結

不同的模組會需要不同的函式庫,並且若相依函式庫不存在亦必須進行建置

這不能以最小公倍數對所有函式庫進行

因使用該模組之程式在初始運行時

會相需要連結之共享函式庫進行載入而產生無謂的記憶體消耗

但事實上隨著模組慢慢增多,個別相依之問題將無可避免

目前對解決方法並沒有一個最佳做法,但目前有一草案

include其它簡單的 Makefile進行特定的參數設定

但是若讓各模組之開發人員進行這些 Makefile的撰寫

必須嚴格防範開發人員誤輸入其它規則

且開發人員需要記憶需要之變數,並重複輸入特定格式之文字

這對開發人員可能也是一個小負擔 (不能以年輕人的思考評斷其他程式開發人員)

因此可再搭配小程式,協助開發人員產生這類簡單的Makefile

 

程式原始碼

  1 # Makefile to make all module listed in $(TARGET).
  2 #
  3 # Each module represent as a folder with the same name in the $(ROOT).
  4 #
  5 # This Makefile automatically generates all rules needed by the modules
  6 # including static libraries, shared libraries, and object files.
  7 # When you want to add a new module, you just create the module folder in $(ROOT)/source,
  8 # include folder with module name in $(ROOT)/public/include, if needed,
  9 # and add the module name to $(TARGET) macro
 10 #
 11 # All object files will be put into "object" folder in $(ROOT)
 12 # All libraries files will be put into public/lib in $(ROOT)
 13 #
 14 # the exported related Makefile target of a module contains:
 15 #     [module_name]: make the module
 16 #     list[module_name]src: list all the source files (.cpp and .h) of the module
 17 #     export[module_name]: put the public include files and library files into "export" folder in $(ROOT)
 18 #     clean[module_name]: clean all made files of a module
 19 #
 20 # Ex. modules "sockets" will generate "sockets", "exportsockets", "listsocketssrc" and "cleansockets"
 21 #
 22
 23 DEBUG = false
 24
 25 # Modules to make
 26 TARGET = sockets
 27
 28 # Get the absolute path of where this Makefile located
 29 ROOT := $(dir $(realpath $(lastword $(MAKEFILE_LIST))))
 30 SROOT := $(ROOT)source/
 31 IROOT := $(ROOT)public/include/
 32
 33 SOURCE_PATH := $(addprefix $(SROOT), $(TARGET))
 34 INCLUDE_PATH := $(IROOT)
 35 OBJECT_PATH = $(ROOT)object/
 36 LINK_PATH := $(ROOT)public/lib/
 37 PRODUCT_PATH := $(ROOT)public/lib/
 38 EXPORT_PATH := $(ROOT)export/
 39
 40 #All absolute path of cpp files of all modules
 41 SRC := $(shell find $(SOURCE_PATH) -type f -name '*.cpp' ! -name 'test*')
 42
 43 #Corresponding object file absolute path of cpp source files
 44 OBJ := $(addprefix $(OBJECT_PATH), $(notdir $(SRC:%.cpp=%.o)))
 45
 46 #All absolute path of header files of all modules
 47 HDR := $(shell find $(SOURCE_PATH) $(INCLUDE_PATH) -type f -name '*.h')
 48
 49 CC = gcc
 50
 51 INCLUDE_SEARCH := $(addprefix -I, $(INCLUDE_PATH))
 52 BIT_MODE :=
 53 INCLUDE :=
 54 DEFINE :=
 55 OPTIMIZE :=
 56 WARNING :=
 57 CFLAGS := $(BIT_MODE) $(OPTIMIZE) $(WARNING) $(INCLUDE_SEARCH) $(INCLUDE) $(DEFINE)
 58
 59 LDFLAGS := -shared -L$(LINK_PATH) -lstdc++
 60
 61 .PHONY: all objects export clean nothing
 62
 63 ##########################################################################
 64
 65 # Generate string of Makefile command which will be the following text form
 66 # $(addprefix [object_path], $(basename $(notdir [passed text])))
 67 # We must evaluate it after the text is generated
 68 # use info function to see the calling result
 69 #
 70 # In this define, we cannot put any newline character into the text
 71 define tmplGenObjectName
 72 $$(addprefix $(OBJECT_PATH), $$(basename $$(notdir $(strip $1))))
 73 endef
 74
 75 define listSrc
 76 $(filter $(SROOT)$(strip $1)/%, $(SRC)) \
 77 $(filter $(SROOT)$(strip $1)/% $(IROOT)$(strip $1)/%, $(HDR))
 78 endef
 79
 80 # MACRO used for generate rule, must be expanded when used
 81 # $(strip $1) is the name of target
 82 STATIC_LIB_XFIX = $(addprefix $(PRODUCT_PATH)lib, $(addsuffix ++.a, $(strip $1)))
 83 SHARED_LIB_XFIX = $(addprefix  $(PRODUCT_PATH)lib, $(addsuffix ++.so, $(strip $1)))
 84 TARGET_FLAG = $(OBJECT_PATH).$(strip $1)
 85
 86 define maketarget
 87
 88 .PHONY: $(strip $1)
 89 $(strip $1) : $(TARGET_FLAG)
 90     @echo "$(strip $1) is made"
 91
 92 endef
 93
 94 define listtargetsrc
 95
 96 .PHONY: list$(strip $1)src
 97 list$(strip $1)src:
 98     @echo -e "\033[1;33m"
 99     @ls $(call listSrc, $(strip $1))
100     @echo -e "\033[m"
101
102 endef
103
104 define exporttarget
105
106 .PHONY: export$(strip $1)
107 export$(strip $1): $(strip $1)
108     @rm -rf $(EXPORT_PATH)$(strip $1)
109     mkdir $(EXPORT_PATH)$(strip $1)
110     mkdir $(EXPORT_PATH)$(strip $1)/include
111     mkdir $(EXPORT_PATH)$(strip $1)/lib
112     cp -r $(IROOT)$(strip $1)/* $(EXPORT_PATH)$(strip $1)/include
113     cp $(STATIC_LIB_XFIX) $(EXPORT_PATH)$(strip $1)/lib
114     cp $(SHARED_LIB_XFIX) $(EXPORT_PATH)$(strip $1)/lib
115
116 endef
117
118 define makeobject
119
120 # OBJ_BASE_ACT = $$(addprefix [object_path], $$(strip $1))
121 $(eval OBJ_BASE_ACT = $(call tmplGenObjectName, $$(strip $$1)))
122
123 $(addsuffix .o, $(OBJ_BASE_ACT)): $(1) $(2)
124     $(CC) -o $$@ -c $$< $(CFLAGS)
125     $(CC) -o $(addsuffix -pic.o, $(OBJ_BASE_ACT)) -c $$< $(CFLAGS) -fPIC
126
127 endef
128
129 define makelib
130
131 # OBJ_BASE_ACT = $$(addprefix  [object path], $$(basename $$(notdir $$(filter [root path]$$(strip $1)%, $$(SRC)))))
132 $(eval OBJ_BASE_ACT = $(call tmplGenObjectName, $$(filter $(SROOT)$$(strip $$1)%, $$(SRC))))
133
134 $(TARGET_FLAG): $(addsuffix .o, $(OBJ_BASE_ACT)) $(addsuffix -pic.o, $(OBJ_BASE_ACT))
135     $(CC) -o $(SHARED_LIB_XFIX) $(LDFLAGS) \
136         $(addsuffix -pic.o, $(OBJ_BASE_ACT))
137     ar csrv $(STATIC_LIB_XFIX) \
138         $(addsuffix .o, $(OBJ_BASE_ACT))
139     touch $(TARGET_FLAG)
140
141 endef
142
143 define cleantarget
144
145 # OBJ_BASE_ACT = $$(addprefix  [object path], $$(basename $$(notdir $$(filter [root path]$$(strip $1)%, $$(SRC)))))
146 $(eval OBJ_BASE_ACT = $(call tmplGenObjectName, $$(filter $(SROOT)$$(strip $$1)%, $$(SRC))))
147
148 .PHONY: clean$(strip $1)
149 clean$(strip $1):
150     rm -rf $(addsuffix .o, $(OBJ_BASE_ACT)) \
151         $(STATIC_LIB_XFIX) $(SHARED_LIB_XFIX) \
152         $(TARGET_FLAG) \
153         $(EXPORT_PATH)$(strip $1)/*
154
155 endef
156
157
158 #########################################################################################
159
160 all: $(TARGET)
161     @echo "All module are made"
162
163 nothing:
164     @echo ""
165
166 objects: $(OBJ)
167     @echo "object making completes"
168
169 export: $(addprefix export, $(TARGET))
170     @echo "All module are exported"
171
172 # Dynamically Generate Rules
173
174 # Genterate list[module_name]src rules
175 ifeq ($(DEBUG), true)
176 $(foreach v, $(TARGET), $(info $(call listtargetsrc, $v)))
177 endif
178
179 $(foreach \
180     v, $(TARGET), \
181     $(eval $(call listtargetsrc, $v)) \
182 )
183
184 # Generate [module_name] rules
185 ifeq ($(DEBUG), true)
186 $(foreach v, $(TARGET), $(info $(call maketarget, $v)))
187 endif
188
189 $(foreach \
190     v, $(TARGET), \
191     $(eval $(call maketarget, $v)) \
192 )
193
194 # Generate export[module_name] rules
195 ifeq ($(DEBUG), true)
196 $(foreach v, $(TARGET), $(info $(call exporttarget, $v)))
197 endif
198
199 $(foreach v, $(TARGET), $(eval $(call exporttarget, $v)))
200
201 # Generate module making rules
202 ifeq ($(DEBUG), true)
203 $(foreach v, $(TARGET), $(info $(call makelib, $v)))
204 endif
205
206 $(foreach \
207     v, $(TARGET), \
208     $(eval $(call makelib, $v)) \
209 )
210
211 # Generate object code making rules
212 ifeq ($(DEBUG), true)
213 $(foreach t, $(TARGET), $(eval $(foreach v, $(filter %.cpp, $(call listSrc, $t)), $(info $(call makeobject, $v, $(filter %.h, $(call listSrc, $t)))))))
214 endif
215
216 $(foreach \
217     t, $(TARGET), \
218     $(eval $(foreach \
219         v, $(filter %.cpp, $(call listSrc, $t)), \
220         $(eval\
221             $(call makeobject, $v, $(filter %.h, $(call listSrc, $t))) \
222         ) \
223     )) \
224 )
225
226 # Generate clean[module_name] rules
227 ifeq ($(DEBUG), true)
228 $(foreach v, $(TARGET), $(info $(call cleantarget, $v)))
229 endif
230
231 $(foreach \
232     v, $(TARGET), \
233     $(eval $(call cleantarget, $v)) \
234 )
235
236 clean:
237     rm -rf $(OBJECT_PATH)*.o $(PRODUCT_PATH)*.a $(PRODUCT_PATH)*.so  $($OBJECT_PATH).[^.] $(OBJECT_PATH).??* $(EXPORT_PATH)*
arrow
arrow
    文章標籤
    make dynamic rule
    全站熱搜

    wylokgo101 發表在 痞客邦 留言(0) 人氣()