標籤:
原文網址:http://blog.csdn.net/smfwuxiao/article/details/8530742
1、Android.mk檔案概述
Android.mk檔案用來告訴NDK編譯系統,應該如何編譯這些源碼。更確切地說,該檔案其實就是一個小型的Makefile。該檔案會被NDK的編譯工具解析多次,所以要注意不要過多使用環境變數,以免第一次解析時產生的變數影響後面的解析。Android.mk把源碼組織成不同的模組,每個模組可以是一個靜態庫也可以是一個動態庫。動態庫才會被拷貝到安裝包中,靜態庫只能用於編譯產生動態庫。
同一個Android.mk檔案可以定義多個模組,不同的模組可以共用同一個源檔案。
注意,NDK所使用的Android.mk檔案的文法與Android作業系統的開放源碼中的Android.mk的文法非常接近,但是兩個編譯系統對Android.mk的使用方法不同,這是為了方便應用程式開發人員複用以前的代碼。
2、一個簡單的例子
在詳細討論Android.mk檔案的文法之前,先看一個簡單的 “hello JNI“ 的例子,檔案位於 apps/hello-jni/project。其中的 src 子目錄存放Android工程的java源碼,jni子目錄存放C/C++源碼檔案,即 jni/hello-jni.c (實現了一個返回字串給虛擬機器的函數)。jni/Android.mk 檔案是這個模組的編譯指令碼,內容如下:
[plain] view plaincopy
- LOCAL_PATH := $(call my-dir)
-
- include $(CLEAR_VARS)
-
- LOCAL_MODULE := hello-jni
- LOCAL_SRC_FILES := hello-jni.c
-
- include $(BUILD_SHARED_LIBRARY)
以上內容解釋如下:
LOCAL_PATH := $(call my-dir)
每個Android.mk檔案都必須在開頭定義 LOCAL_PATH 變數。這個變數被用來尋找C/C++源檔案。在該例中,my-dir 是一個由編譯系統提供的宏函數,用於返回Android.mk所在目錄的路徑。
include $(CLEAR_VARS)
CLEAR_VARS是編譯系統預定義的一個變數,它指向一個特殊的Makefile,這個Makefile負責清除 LOCAL_xxx 的變數(例如 LOCAL_MODULE, LOCAL_SRC_FILES, LOCAL_STATIC_LIBRARIES 等)但不會清除 LOCAL_PATH。之所以需要清理這些變數是因為所有的編譯控制檔案是在一趟make執行過程中完成的,而所有的變數都是全域的,會對其他Android.mk檔案產生影響。
LOCAL_MODULE := hello-jni
LOCAL_MODULE 用來給每個模組定義一個名字,不同模組的名字不能相同,不能有空格。這裡的名字會傳給NDK編譯系統,然後加上lib首碼和.so尾碼 (例如,變成libhello-jni.so)。注意,如果你在LOCAL_MODULE定義中自己加上了lib首碼,則ndk在處理的時候就不會再加上lib首碼了(為了相容Android系統的一些源碼)。
LOCAL_SRC_FILES := hello-jni.c
在LOCAL_SRC_FILES 變數裡面列舉出對應於同一個模組的、要編譯的那些檔案,這裡不要把標頭檔加進來,編譯系統可以自動檢測標頭檔依賴關係。預設情況下,C++源碼檔案的副檔名應該是cpp,如果想修改的話,將變數LOCAL_CPP_EXTENSION修改為你想要的副檔名,注意句點。例如:LOCAL_CPP_EXTENSION := .cxx
include $(BUILD_SHARED_LIBRARY)
這個 BUILD_SHARED_LIBRARY也是預定義的變數,也是指向一個Makefile,負責將你在 LOCAL_XXX 等變數中定義資訊收集起來,確定要編譯的檔案,如何編譯。如果要編譯的是靜態庫而不是動態庫,則可以用 BUILD_STATIC_LIBRARY。
在NDK安裝目錄的samples目錄下有更加豐富的例子,裡面都有詳細的注釋。
3、變數名的限制
下面這些變數是你可以直接使用或者應該由你來定義的。你也可以定義自己的變數,但是不能用以下NDK所保留的變數名:
以 LOCAL_ 開頭的名字(例如,LOCAL_MODULE)
以 PRIVATE_, NDK_,APP_ 開頭的名字(供NDK內部使用)
小寫字母的變數名也不能使用(供NDK內部使用,例如 my-dir)
例如,你可以隨便用 MY_ 開頭的變數名:
[plain] view plaincopy
- MY_SOURCES := foo.c
- ifneq ($(MY_CONFIG_BAR),)
- MY_SOURCES += bar.c
- endif
-
- LOCAL_SRC_FILES += $(MY_SOURCES)
4、NDK預定義變數
下面這些變數是ndk提前定義好的變數。有時ndk會解析同一個Android.mk檔案多次,每次解析時,這些變數的值可能不相同。
CLEAR_VARS
指向一個特殊的Makefile,負責清理 LOCAL_XXX 變數(LOCAL_PATH除外)。一般在定義新模組之前使用這個變數,用法:
include $(CLEAR_VARS)
BUILD_SHARED_LIBRARY
該變數實際指向了一個Makefile,用來把所有名為 LOCAL_XXX的變數中的資訊收集起來,然後確定如何把你提供的源碼編譯成目標模組。用法:include $(BUILD_SHARED_LIBRARY) 預設檔案名稱:lib<LOCAL_MODULE>.so
BUILD_STATIC_LIBRARY
類似於BUILD_SHARED_LIBRARY,不過它用來編譯靜態庫。靜態庫不會被拷貝到你的安裝包中去,它往往用來編譯其他動態庫。用法:include $(BUILD_STATIC_LIBRARY) 預設檔案名稱:lib<LOCAL_MODULE>.a
PREBUILT_SHARED_LIBRARY
該變數指向一個已編譯好的共用庫。與BUILD_SHARED_LIBRARY和BUILD_STATIC_LIBRARY不同,此時相應的LOCAL_SRC_FILES不再指定源檔案,而是指向這個先行編譯共用庫檔案(例如 foo/libfoo.so)。可以在其他模組中,通過使用LOCAL_PREBUILTS變數來引用這個先行編譯模組。參考Prebuilt。
PREBUILT_STATIC_LIBRARY
與PREBUILT_SHARED_LIBRARY相同,只不過這裡是靜態庫。 參考 Prebuilt。
TARGET_ARCH
目標CPU架構的名字,與Android作業系統的CPU架構名一致。如果想相容所有ARM的CPU,可以用 “arm” 這個名字。
TARGET_PLATFORM
目標Android平台的名字。例如 android-3 對應的是 Android 1.5 系統鏡像(Cupcake)。所有系統鏡像的名字和相應的系統鏡像可參考Stable APIs。
TARGET_ARCH_ABI
目標CPU和ABI組合的名字,目前只有2個值可以用:
armeabi 對於ARMv5TE
armeabi-v7a
注意,一直到Android NDK 1.6_r1,這裡的值都是用“arm”。然而,該值已被重新定義以更好地匹配Android平台內部所使用的。
關於架構和ABI及相容性問題,參考文檔 Cpu Arch ABIs。
其他的目標ABI會在將來的NDK版本中增加,並且是不同的名字。注意,所有相容ARM的ABI的TARGET_ARCH都是arm,但是TARGET_ARCH_ABI不同。
TARGET_ABI
目標平台和ABI的組合,定義為 $(TARGET_PLATFORM)-$(TARGET_ARCH_ABI)。當你想在真機上測試一種系統鏡像時有用。該變數的預設值是android-3-armeabi。
直到Android NDK 1.6_r1,這個值一直是 android-3-arm。
5、NDK預定義的宏函數
下面是NDK預定義的“函數”宏,用法是 $(call <function>) ,返回的是文本資訊
my-dir
返回上一個被包含的Makefile的路徑,典型情況是Android.mk檔案所在的路徑。這個函數對於定義LOCAL_PATH特別有用,例如:
LOCAL_PATH := $(call my-dir)
注意:由於make的工作原理,該函數返回的確實是上一個被包含的Makefile的路徑(也就是說返回的結果可能不是Android.mk所在目錄)。所以,包含了另外一個檔案之後,就不要再使用 my-dir 了。
例如,下面的例子:
[plain] view plaincopy
- LOCAL_PATH := $(call my-dir)
- 該模組其他聲明
-
- include $(LOCAL_PATH)/foo/Android.mk
- LOCAL_PATH := $(call my-dir)
- 另一個模組的聲明
上面的問題就是第二次調用my-dir的時候,得到的是 $PATH/foo 而不是 $PATH,因為它前面有一個include語句。
因此,最好在所有include語句之前,把LOCAL_PATH定義好:
[plain] view plaincopy
- LOCAL_PATH := $(call my-dir)
- ... declare one module
- LOCAL_PATH := $(call my-dir)
- ... declare another module
- # extra includes at the end of the Android.mk
- include $(LOCAL_PATH)/foo/Android.mk
如果覺得這樣做不方便,可以把第一次調用的結果儲存到變數中,例如:
[plain] view plaincopy
- MY_LOCAL_PATH := $(call my-dir)
- LOCAL_PATH := $(MY_LOCAL_PATH)
- ... declare one module
- include $(LOCAL_PATH)/foo/Android.mk
- LOCAL_PATH := $(MY_LOCAL_PATH)
- ... declare another module
all-subdir-makefiles
返回當前的my-dir目錄下的所有子目錄的Android.mk檔案的列表。例如,檔案組織如下:
sources/foo/Android.mk sources/foo/lib1/Android.mk sources/foo/lib2/Android.mk
如果 sources/foo/Android.mk 包含如下行:
include $(call all-subdir-makefiles)
那麼,該檔案將自動把 sources/foo/lib1/Android.mk 和 sources/foo/lib2/Android.mk 包含進來。當源碼被組織成很多層次時可以利用該函數,預設情況下,NDK只會尋找 sources/*/Android.mk。
this-makefile
返回當前的Makefile的路徑(即該函數調用時的位置)
parent-makefile
如果當前這個Makefile被另一個Makefile包含,則返回那個包含了自己的Makefile的路徑(即parent)
grand-parent-makefile
(你猜猜......)
import-module
該函數用於按模組名尋找另一個模組的Android.mk檔案,並包含進來。用法如下:
$(call import-module,<name>)
上面將在 NDK_MODULE_PATH變數所指定的目錄列表中尋找名為<name>的模組,找到之後將包含進來。
可以參考 Import Module。
6、模組描述變數
下面這些變數用於對模組進行描述,這些變數應該在 include $(CLEAR_VARS) 和 include $(BUILD_XXXX) 之間定義好。
LOCAL_PATH (必須)
這個變數表示當前檔案(一般是Android.mk)所在的路徑,該變數很重要,必須定義(在Android.mk檔案的開頭處定義)。常見寫法如下:
LOCAL_PATH := $(call my-dir)
該變數不會被 include $(CLEAR_VARS) 清空,所以不論Android.mk定義了幾個模組,一個Android.mk只需要在開頭定義一次即可。
LOCAL_MODULE (必須)
該變數定義當前模組的名字,名字必須唯一,不能有空格。這個變數必須在 include $(BUILD_XXX) 之前定義好。預設情況下,這裡的名字會用來得到輸出檔案的名字。例如模組名為foo,則得到的輸出檔案為libfoo.so。但是,如果你要在其他模組的Android.mk檔案或Application.mk中引用這個模組,應該用foo這個模組名,而不要用libfoo.so這個檔案名稱。
LOCAL_MODULE_FILENAME (可選)
該變數可以用來重定義輸出檔案的名字。預設情況下,foo模組得到的靜態庫的名字為 libfoo.a,動態庫的名字為libfoo.so(UNIX規範)。當定義了LOCAL_MODULE_FILENAME之後,輸出檔案名就是這個變數指定的名字,例如:
[plain] view plaincopy
- LOCAL_MODULE := foo-version-1
- LOCAL_MODULE_FILENAME := libfoo
注意: LOCAL_MODULE_FILENAME不支援檔案路徑(所以不能有斜杠),不要寫副檔名(檔案路徑和副檔名是由編譯工具自動加上的)
LOCAL_SRC_FILES (必須)
該變數用來指定該模組對應的源檔案,只把需要傳給編譯器的源檔案名稱加進LOCAL_SRC_FILES,編譯系統會自動處理標頭檔依賴。這裡的檔案名稱都是以 LOCAL_PATH 作為目前的目錄的(即相對於LOCAL_PATH目錄),例如:
LOCAL_SRC_FILES := foo.c toto/bar.c
注意:必須使用Unix風格的斜杠,Windows風格的斜杠不能正確處理。
LOCAL_CPP_EXTENSION (可選)
用來定義C++代碼檔案的副檔名。必須以句點開頭(即 “.”),預設值是“.cpp”,可以修改,例如:
LOCAL_CPP_EXTENSION := .cxx
從 NDK r7 這個版本開始,該變數可以支援多個副檔名了,例如:
LOCAL_CPP_EXTENSION := .cxx .cpp .cc
LOCAL_CPP_FEATURES (可選)
該變數用來指定C++代碼所依賴的特殊C++特性。例如,如果要告訴編譯器你的C++代碼使用了RTTI(RunTime Type Information):
LOCAL_CPP_FEATURES := rtti
如果要指定你的C++代碼使用了C++異常,則:
LOCAL_CPP_FEATURES := exceptions
該變數可以同時指定多個特性。例如: LOCAL_CPP_FEATURES := rtti features
這個變數的作用就是在編譯模組的時候,開啟相應的編譯器/連結器標誌。對於先行編譯的檔案,該變數表明該先行編譯的庫依賴了這些特性,從而確保最後的連結工作正確進行。與該變數等價的做法是在LOCAL_CPPFLAGS中寫上 -frtti -fexceptions 等標誌選項。但是,推薦用這裡的方法。
LOCAL_C_INCLUDES
一個路徑的列表,是NDK根目錄的相對路徑(LOCAL_SRC_FILES中的檔案相對於LOCAL_PATH)。當編譯C/C++、彙編檔案時,這些路徑將被追加到標頭檔搜尋路徑列表中。例如:
LOCAL_C_INCLUDES := sources/foo
或者, LOCAL_C_INCLUDES := $(LOCAL_PATH)/../foo
這裡的搜尋路徑會放在LOCAL_CFLAGS/LOCAL_CPPFALGS等標誌的前面。 當使用ndk-gdb的時候,LOCAL_C_INCLUDES中的路徑也會被用到。
LOCAL_CFLAGS
指定當編譯C/C++源碼的時候,傳給編譯器的標誌。它一般用來指定其他的編譯選項和宏定義。
注意:盡量不要在Android.mk中修改最佳化/調試等級,因為在Application.mk中定義了相關資訊之後編譯系統會自動處理這些問題。
LOCAL_CXXFLAGS (廢除, LOCAL_CPPFLAGS的別名)LOCAL_CPPFLAGS (可選)
編譯C++代碼的時候傳遞給編譯器的選項(編譯C代碼不會用這裡的選項)。最後得到的命令列選項中,這裡指定的選項在 LOCAL_CFLAGS 指定的選項的後面。
LOCAL_STATIC_LIBRARIES
指定應該連結到當前模組的靜態庫(可指定多個)。當前模組是動態庫時,該選項才有意義。
LOCAL_SHARED_LIBRARIES
指定的是運行時該模組所依賴共用庫(可指定多個)。這些資訊是連結階段必須的。
LOCAL_WHOLE_STATIC_LIBRARIES
它是LOCAL_STATIC_LIBRARIES的變體,用來表示它對應的模組對於linker來說應該是一個“whole archive”(見GNU linker 文檔,關於 --whole-archive的資料)。當靜態庫之間有循環相依性時,會用到這個選項。注意,當編譯動態庫時,這個選項會強行把所有的對象檔案組裝到一起;不過,在編譯可執行檔的時候情況不是這樣的。
LOCAL_LDLIBS
用來指定模組編譯時間的其餘連接器標誌。例如:
LOCAL_LDLIBS := -lz
告訴連結器在載入該共用庫的時候必須連結 /system/lib/libz.so 這個共用庫。
如果想知道Android系統中有哪些共用庫可以連結,參考 Stable APIs。
LOCAL_ALLOW_UNDEFINED_SYMBOLS
預設情況下,當編譯一個共用庫的時候,遇到未定義符號引用就會報告一個“undefined symbol”錯誤。這有助於修複你的代碼中存在的bug。
如果因為某種原因,必須禁止該檢測,可以把這個變數設定為true。注意,編譯出的共用庫有可能在載入的時候就報錯導致程式退出。
LOCAL_ARM_MODE
LOCAL_ARM_NEON
LOCAL_DISABLE_NO_EXECUTE
Android NDK r4增加了對“NX bit“安全特性的支援。它是預設開啟的,如果你確定自己不需要該特性,你可以將它關閉,即:
LOCAL_DISABLE_NO_EXECUTE := true
該變數不會修改ABI,只會在 ARMv6以上的CPU的核心上啟用。開啟該特性編譯出的代碼無需修改可運行在老的CPU上(也就是說所有ARM的CPU都能運行)。
參考資訊:
http://en.wikipedia.org/wiki/NX_bit
http://www.gentoo.org/proj/en/hardened/gnu-stack.xml
LOCAL_EXPORT_CFLAGS
這個變數定義一些C/C++編譯器flags。這些flags(標誌)會被追加到使用了這個模組(利用LOCAL_STATIC_LIBRARIES和LOCAL_SHARED_LIBRARIES)的模組的LOCAL_CFLAGS 定義中去。
假如foo模組的聲明如下:
[plain] view plaincopy
- include $(CLEAR_VARS)
- LOCAL_MODULE := foo
- LOCAL_SRC_FILES := foo/foo.c
- LOCAL_EXPORT_CFLAGS := -DFOO=1
- include $(BUILD_STATIC_LIBRARY)
bar模組依賴foo模組,聲明如下:
[plain] view plaincopy
- include $(CLEAR_VARS)
- LOCAL_MODULE := bar
- LOCAL_SRC_FILES := bar.c
- LOCAL_CFLAGS := -DBAR=2
- LOCAL_STATIC_LIBRARIES := foo
- include $(BUILD_SHARED_LIBRARY)
因此在編譯bar模組的時候,它的編譯器標誌就是 “-DFOO=1 -DBAR=2”。 匯出的flags 加上本模組的 LOCAL_CFLAGS,成為最後傳給編譯器的flags。這樣修改起來就很容易。這種依賴關係是可傳遞的,例如,如果zoo依賴bar,bar依賴foo,那麼zoo就會同時有bar和foo的匯出flags。
注意,編譯模組自身時,不會使用它所匯出的flags。例如在編譯上面的foo模組時, -DFOO=1 不會傳遞給編譯器。
LOCAL_EXPORT_CPPFLAGS
與 LOCAL_EXPORT_CFLAGS 相同,是跟C++相關的標誌。
LOCAL_EXPORT_C_INCLUDES
與 LOCAL_EXPORT_CFLAGS 相同,但是只用於標頭檔搜尋路徑。當你的共用庫有多個模組,而且互相之間有標頭檔依賴時有用。用法詳見 Import Module。
LOCAL_EXPORT_LDLIBS
與 LOCAL_EXPORT_CFLAGS 相同,但是只用於連接器的flag。注意這裡被導人的連結器標誌將追加到模組的 LOCAL_LDLIBS。
例如當foo模組是一個靜態庫並且代碼依賴於系統庫時,該變數非常有用。 LOCAL_EXPORT_LDLIBS 可以用於匯出該依賴:
[plain] view plaincopy
- include $(CLEAR_VARS)
- LOCAL_MODULE := foo
- LOCAL_SRC_FILES := foo/foo.c
- LOCAL_EXPORT_LDLIBS := -llog
- include $(BUILD_STATIC_LIBRARY)
-
- include $(CLEAR_VARS)
- LOCAL_MODULE := bar
- LOCAL_SRC_FILES := bar.c
- LOCAL_STATIC_LIBRARIES := foo
- include $(BUILD_SHARED_LIBRARY)
此處在編譯bar模組的時候,它的連結器標誌將加上一個 -llog,表示它依賴於系統提供的 liblog.so,因為它依賴 foo 模組。
LOCAL_FILTER_ASM
這個變數指定一個shell命令,用於過濾LOCAL_SRC_FILES 中列出的彙編檔案或者LOCAL_SRC_FILES列出的檔案所編譯出的彙編檔案。定義該變數後,將導致以下行為:
1)所有的C/C++源碼首先翻譯為臨時彙編檔案(如果不定義LOCAL_FILTER_ASM,則C/C++源碼直接編譯為 obj 檔案)
2)這些彙編檔案被傳給 LOCAL_FILTER_ASM 所指定的shell命令處理,得到一批新的彙編檔案。
3)這些新的彙編檔案再被編譯成obj檔案。
換句話說,如果你定義:
[plain] view plaincopy
- LOCAL_SRC_FILES := foo.c bar.S
- LOCAL_FILTER_ASM := myasmfilter
則foo.c首先傳給編譯器(gcc),得到 foo.S.orignal,然後這個 foo.S.original 被傳給你指定的過濾器(LOCAL_ASM_FILTER),得到 foo.S,然後再傳給彙編器(例如as),得到 foo.o。 bar.S 直接傳給過濾器得到 bar.S.new,然後再傳給彙編器,得到 bar.o。即在從*.S到*.o的編譯流程中增加了一個過濾的環節。
過濾器必須是獨立的shell命令,輸入檔案作為它的第一個命令列參數,輸出檔案作為第二個命令列參數,例如:
[plain] view plaincopy
- myasmfilter $OBJS_DIR/foo.S.original $OBJS_DIR/foo.S
- myasmfilter bar.S $OBJS_DIR/bar.S
【轉】Android.mk檔案文法規範(Android.mk File)