編寫Android.mk中的LOCAL_SRC_FILES的終極技巧
已廢棄, 請參考Update: Android.mk 中的 LOCAL_SRC_FILES, LOCAL_C_INCLUDES
問題的引入
在使用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源文件
#使用wildcard函數獲取$(LOCAL_PATH)目錄下所有後綴名是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) #模式替換,把所有文件名前面的jni/去掉 LOCAL_SRC_FILES := $(MY_CPP_LIST:$(LOCAL_PATH)/%=%) #替換沒意向中"$(LOCAL_PATH)/"爲"" LOCAL_SRC_FILES := $(subst $(LOCAL_PATH)/,,$(MY_CPP_LIST)) #同模式替換,使用patsubst函數 LOCAL_SRC_FILES := $(patsubst $(LOCAL_PATH)/%, %, $(MY_CPP_LIST))
問題解決.
簡單解釋一下上面的幾句話
MY_CPP_LIST := $(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 := $(MY_CPP_LIST:$(LOCAL_PATH)/%=%)
,前面我們獲取的文件都是以jni
開頭的,而真正編譯所需要的文件都應該是直接從jni
目錄開始的,所以我們使用模式替換
把所有文件名前面的jni/
去掉.
這裏我解釋一下$(MY_CPP_LIST:$(LOCAL_PATH)/%=%)
的語法含義,它的意思是對MY_CPP_LIST
中每一項,應用冒號後面的規則,規則是什麼呢?規則是$(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 := $(My_All_Files:$(MY_CPP_PATH)/./%=$(MY_CPP_PATH)%) #從My_All_Files中再次提取所有的cpp文件,這裏也可以使用filter函數 MY_CPP_LIST := $(foreach c_file,$(My_All_Files), $(wildcard $(c_file)/*.cpp) ) MY_CPP_LIST := $(MY_CPP_LIST:$(LOCAL_PATH)/%=%) LOCAL_SRC_FILES := $(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 := $(My_All_Files:$(MY_CPP_PATH)/./%=$(MY_CPP_PATH)%)MY_CPP_LIST := $(filter %.cpp %.c,$(My_All_Files)) MY_CPP_LIST := $(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 := $(foreach src_path,$(MY_FILES_PATH), $(shell find $(src_path) -type f) ) My_All_Files := $(My_All_Files:$(MY_CPP_PATH)/./%=$(MY_CPP_PATH)%)MY_SRC_LIST := $(filter $(MY_FILES_SUFFIX),$(My_All_Files)) MY_SRC_LIST := $(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
的值