1. 程式人生 > >安卓打包 有關Android.mk 引入.cpp .c檔案路徑問題

安卓打包 有關Android.mk 引入.cpp .c檔案路徑問題

問題的引入
在使用NDK編譯C/C++專案的過程中,免不了要編寫Android.mk檔案,其中最重要的就是LOCAL_SRC_FILES原始檔列表.
考慮有如下原始檔分佈的情況:

cpp檔案全部位於android專案下的jni資料夾下,結構如下

jni    
 |---1.cpp
 |---2.cpp
 |---Android.mk
 |---Application.mk
 |---ndk_test.cpp
 |---src    
 |    |---core
 |    |    |---core1.cpp
 |    |    |---core2.cpp
 |    |---src1.cpp
 |    |---src2.cpp

按照通常的寫法,在android.mk中,應該寫入

LOCAL_SRC_FILES := ndk_test.cpp
1.cpp
2.cpp
src/src1.cpp
src/src2.cpp
src/core/core1.cpp
src/core/core2.cpp
繁瑣不堪!

初步解法:一句話引入單個目錄(不包括子目錄)下的所有cpp原始檔

繼續上面的情況為例,我可以這樣寫

MY_CPP_LIST := $(wildcard $(LOCAL_PATH)/.cpp)
MY_CPP_LIST += $(wildcard $(LOCAL_PATH)/src/
.cpp)
MY_CPP_LIST += $(wildcard $(LOCAL_PATH)/src/core/*.cpp)

LOCAL_SRC_FILES := (MYCPPLIST:(MY_CPP_LIST:(LOCAL_PATH)/%=%)
問題解決.
簡單解釋一下上面的幾句話

MY_CPP_LIST := $(wildcard (LOCALPATH)/.cpp),使wildcard(LOCAL_PATH)/*.cpp),這句話的意思是使用wildcard函式獲取(LOCAL_PATH)目錄也就是jni目錄下的所有後綴名為cpp的檔案,並把結果放到變數MY_CPP_LIST裡.我們知道$(LOCAL_PATH)指的是當前Android.mk檔案所在目錄,所以通過這句話,MY_CPP_LIST中的值應該是jni/1.cpp jni/2.cpp jni/ndk_test.cpp.
MY_CPP_LIST += $(wildcard $(LOCAL_PATH)/src/.cpp), 獲取jni/src目錄下的原始檔,並追加到變數MY_CPP_LIST裡
MY_CPP_LIST += $(wildcard $(LOCAL_PATH)/src/core/

.cpp),同上,獲取jni/src/core目錄下的原始檔
通過以上幾步,得到MY_CPP_LIST中內容是jni/1.cpp jni/2.cpp jni/ndk_test.cpp jni/src/src1.cpp jni/src/src2.cpp jni/src/core/core1.cpp jni/src/core/core2.cpp
LOCAL_SRC_FILES := (MYCPPLIST:(MY_CPP_LIST:(LOCAL_PATH)/%=%),前面我們獲取的檔案都是以jni開頭的,而真正編譯所需要的檔案都應該是直接從jni目錄開始的,所以我們使用模式替換把所有檔名前面的jni/去掉.
這裡我解釋一下(MYCPPLIST:(MY_CPP_LIST:(LOCAL_PATH)/%=%)的語法含義,它的意思是對MY_CPP_LIST中每一項,應用冒號後面的規則,規則是什麼呢?規則是(LOCALPATH)/(LOCAL_PATH)/%=%,意思是,查詢所有(LOCAL_PATH)/開頭的項,並擷取後面部分

最後一句話也可以使用subst函式寫成:

#替換每一項中的 “$(LOCAL_PATH)/” 為 “”(空)
LOCAL_SRC_FILES := $(subst $(LOCAL_PATH)/, , $(MY_CPP_LIST))
或使用patsubst函式寫成

#同模式替換,這裡使用patsubst函式
LOCAL_SRC_FILES := $(patsubst $(LOCAL_PATH)/%, %, $(MY_CPP_LIST))
具體語法請參考:Functions for String Substitution and Analysis

實際使用中,可以把程式碼放在jni目錄以外的目錄裡,這時只要修改wildcard函式裡的相對路徑就可以了,甚至也可以使用絕對路徑,只要你願意.

以上程式碼已經足以應付大多數情況了,不過人的懶惰是無極限的,像上面的情況我的所有原始檔都在jni目錄下,為什麼還要把每個子目錄都寫一行呢,不太優雅呀,最好能寫一句話把jni目錄下的所有原始檔都引入.

進階:引入單個目錄(包括子目錄)下的所有cpp原始檔
為了達到引入目錄下的所有原始檔,包括子目錄這個目標,我在android.mk中這樣寫

#宣告一個變數MY_CPP_PATH表示原始碼目錄
MY_CPP_PATH := $(LOCAL_PATH)/

#獲取目錄下的所有檔案
My_All_Files := $(shell find $(MY_CPP_PATH)/.)
My_All_Files := (MyAllFiles:(My_All_Files:(MY_CPP_PATH)/./%=$(MY_CPP_PATH)%)

#從My_All_Files中再次提取所有的cpp檔案,這裡也可以使用filter函式
MY_CPP_LIST := (foreachcfile,(foreach c_file,(My_All_Files), $(wildcard $(c_file)/*.cpp) )
MY_CPP_LIST := (MYCPPLIST:(MY_CPP_LIST:(LOCAL_PATH)/%=%)

LOCAL_SRC_FILES := (MYCPPLIST),jnicpp,.使,jni,MYCPPPATH,:MYCPPPATH使(MY_CPP_LIST) 通過以上幾行,成功得到了jni目錄包含它的子目錄下的所有cpp原始檔,並正確編譯.實際使用中,程式碼不一定存放在jni目錄下,修改MY_CPP_PATH就可以了,注意:MY_CPP_PATH最好使用以(LOCAL_PATH)開頭的相對目錄

這種寫法極大的方便了專案的開發,以前在原始碼目錄下新建cpp原始檔,新建目錄都不需要再來修改android.mk檔案了.

還有一個問題,上面程式碼裡只是引入cpp檔案,如果原始碼資料夾下還有c檔案呢,怎麼辦?再多寫幾行?

進階2.0:引入單個目錄(包括子目錄)下的所有*.cpp和*.c原始檔
這裡,我直接給出程式碼

MY_CPP_PATH := $(LOCAL_PATH)/
My_All_Files := $(shell find $(MY_CPP_PATH)/.)
My_All_Files := (MyAllFiles:(My_All_Files:(MY_CPP_PATH)/./%=$(MY_CPP_PATH)%)
MY_CPP_LIST := (filter(filter %.cpp %.c,(My_All_Files))
MY_CPP_LIST := (MYCPPLIST:(MY_CPP_LIST:(LOCAL_PATH)/%=%)

LOCAL_SRC_FILES := $(MY_CPP_LIST)
程式碼中用到了filter函式.

還不滿足?如果專案的原始碼有多個目錄放在不同的地方,而且有多個字尾,怎麼辦?

終極進階:引入多個目錄(包括子目錄)下的多個字尾名的原始檔
上程式碼(2013年10月9日修正):

掃描目錄下的所有原始檔

MY_FILES_PATH := $(LOCAL_PATH)
$(LOCAL_PATH)/…/…/Classes

MY_FILES_SUFFIX := %.cpp %.c %.cc

My_All_Files := (foreachsrcpath,(foreach src_path,(MY_FILES_PATH), $(shell find $(src_path) -type f) )
My_All_Files := (MyAllFiles:(My_All_Files:(MY_CPP_PATH)/./%=$(MY_CPP_PATH)%)
MY_SRC_LIST := $(filter (MYFILESSUFFIX),(MY_FILES_SUFFIX),(My_All_Files))
MY_SRC_LIST := (MYSRCLIST:(MY_SRC_LIST:(LOCAL_PATH)/%=%)
LOCAL_SRC_FILES := $(MY_SRC_LIST)
以上程式碼中,變數MY_FILES_PATH儲存原始檔所在目錄,MY_FILES_SUFFIX儲存原始檔的字尾名

原創文章,轉載請註明,謝謝!

PS:如何debug 一個android.mk檔案
有一個辦法,那就是在編譯過程輸出android.mk檔案中變數的值,就可以觀察分析問題所在了,使用程式碼

$(warning $(LOCAL_SRC_FILES))
就可以在編譯過程中從終端視窗中觀察到變數LOCAL_SRC_FILES的值