緣由
被一個問題困擾很久了
每當建立一個專案程式都要寫一個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)*
|
留言列表