Android NDK 开发教程五:Android.mk文件

jerry Android 2015年08月24日 收藏

NDK項目一個重要組成是它的make 文件 –android.mk. 下面部分來自網路翻譯(省得我再翻譯了:-).

註:大部分情況只需參考HelloJni 和twoLibs 的android.mk 文件即可,如果你想搞清楚android.mk 中定義變數的具體含義,可以參考下面翻譯。

Android.mk文件語法詳述

介紹:
————

這篇文檔是用來描述你的C或C++源文件中Android.mk編譯文件的語法的,為了理解她們我們需要您先看完
docs/OVERVIEW.html(http://hualang.iteye.com/blog/1135105)文件來了解它的作用

概覽:
————
Android.mk文件是用來描述build system(編譯系統)的,更準確的說:

–該文件是一個微型的GNU Makefile片段,將由build system解析一次或者多次。這樣,您就可以盡量減少您聲明的變數,並且不要以為在解析過程中沒有任何定義。

–這個文件但語法是用來允許你將源文件組織成模塊,這個模塊中含有:
-一個靜態庫(.a文件)
-一個動態庫(.so文件)
只有動態庫才會被安裝/複製到你的應用程序包,儘管靜態庫可以被用來生成動態庫。你可以在每個模塊中  都定義一個Android.mk文件,你也可以讓多個模塊共用一個Android.mk文件。

–build system可以為你處理許多細節,例如:你不許要在Android.mk文件中列出頭文件或者其他的依賴關係,這些NDK的build system會自動為你計算並處理。

這也意味著,當更新到新版本的NDK的時候,你應該得益於新的toolchain/platform的支持,而無需修改你的Android.mk文件。

注意:這些語法非常接近於分布在完整的開源的Android源代碼中的Android.mk文件,儘管是build system實現的,但是它們的用法是不同的。這樣故意設計的決定是為了讓應用程序開發者重用「外部」庫的源代碼更容易。

簡單實例:
————-
再詳細講解語法之前,讓我們先看看一個簡單的例子”hello JNI”,它在apps/hello-jni/project下

–‘src’目錄下用於存放java源文件
–『jni』目錄下用於存放本地源文件,例如”jni/hello-jni.c”

這個源文件實現了一個簡單的共享庫(shared library):實現了一個本地方法,為VM應用程序返回一個字元串。

–『jni/Android.mk』文件描述了如何生成一個共享庫,它的內容是:
—————–Android.mk————————
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變數開始,它用於在樹中定位文件。在這個例子中,宏功能’my-dir’是由build system提供的,用於返回當前目錄路徑(包括Android.mk文件本身)

include $(CLEAR_VARS)

CLEAR_VARS變數是由build system提供的,並且指明了一個GNU makefile文件,這個功能會清理掉所有以LOCAL_開頭的內容(例如LOCAL_MODULE,LOCAL_SRC_FILES,LOCAL_STATIC_LIBRARIES等),除了LOCAL_PATH,這句話是必須的,因為如果所有的變數都是全局變數的話,所有的可控的編譯文件都需要在一個單獨的GNU中被解析並執行

LOCAL_MODULE :=hello-jni

LOCAL_MODULE變數必須被定義,用來區分Android.mk中的每一個模塊。文件名必須是唯一的,不能有空格。注意,這裡編譯器會為你自動加上一些前綴和後綴,來保證文件是一致的,比如:這裡表明一個動態連接庫模塊被命名為”hello-jni”,但是最後會生成為”libhello-jni.so”文件。但是在Java中裝載這個庫的時候還是使用”hello-jni”名稱。當然如果我們使用”IMPORTANT NOTE:”,編譯系統就不會為你加上前綴,但是為了支持Android平台源碼中的Android.mk文件,也同樣會生成libhello-jni.so這樣的文件。

重要提示:如果你將你的模塊命名為’libfoo’,編譯系統將不會將前綴’lib’加上去,並且也會生成libfoo.so文件。

LOCAL_SRC_FILES := hello-jni.c

LOCAL_SRC_FILES變數被需包括一個C和C++源文件的列表,這些會編譯並聚合到一個模塊中。
注意:這裡並不需要你列出頭文件和被包含的文件,因為編譯系統會自動為你計算相關的屬性,源代碼中的列表會直接傳遞給編譯器。

C++默認文件的擴展名是「.cpp」,我們可以通過定義一個LOCAL_DEFAULT_CPP_EXTENSION變數來定義一個不同的C文件。不要忘記在初始化前面的「.」點(也就是說”.cpp”可以正常工作,但是cpp不能正常工作)

include $(BUILD_SHARED_LIBRARY)
BUILD_SHARED_LIBRARY這個變數是由系統提供的,並且指定給GNU Makefile的腳本,它可以收集所有你定義的”include $(CLEAR_VARS)”中以LOCAL_開頭的變數,並且決定哪些要被編譯,哪些應該做的更加準確。編譯生成的是以”lib<module_name>.so”的文件,這個就是共享庫了。我們同樣也可以使用BUILD_STATIC_LIBRARY編譯系統便會生成一個以”lib<module_name>.a”的文件來供動態庫調用。

在samples目錄下有很多複雜的例子,那裡的Android.mk文件可以供我們參考

參考:
———————–
這是個變數的列表,你可以依賴或者定義它們到Android.mk中。你可以定義自己使用的其他變數,但是NDK辨析系統只保留了以下名字:

–以LOCAL_開頭的名稱(如:LOCAL_MODULE)
–以PRIVATE_、NDK_、或APP_(內部使用)開始的名稱
–小寫的名稱(例如’my-dir’,內部使用)

如果你需要在Android.mk中定義自己的變數的話,我們建議使用MY-前綴,一個簡單的例子:
—————————-
MY_SOURCES := foo.c
ifneq($(MY_CONFIG_BAR),)
MY_SOURCES += bar.c
endif

LOCAL_SRC_FILES +=$(MY_SOURCES)
—————————-

我們繼續:

NDK提供的變數:
在您的Android.mk文件被解析之前這些GNU Make變數由編譯系統定義,注意,在某些情況下,NDK可能被解析幾次,每次以不同的變數的定義解析的

CLEAR_VARS
CLEAR_VARS這個變數由系統提供,功能是清理掉所有以LOCAL_開頭的內容,再開始一個新的模塊之前,你必須包括這段腳本

include ($CLEAR_VARS)

BUILD_SHARED_LIBRARY

在編譯腳本中收集所有以LOCAL_開頭的信息並且決定從列出的源代碼中編譯一個目標共享庫。注意,你必須定義了LOCAL_MODULE和LOCAL_SRC_FILES變數,使用它的時候,可以這樣定義

include $(BUILD_SHARED_LIBRARY)

注意,我們會生成一個以lib<module_name>.so為名的文件

BUILD_STATIC_LIBRARY

用來構建一個靜態庫,該靜態庫將不會被拷貝到你的project/packages下,但是可以被用於動態庫
(看下面的LOCAL_STATIC_LIBRARY和LOCAL_WHOLE_STATIC_LIBRARY介紹)

例如:
include $(BUILD_STATIC_LIBRARY)

注意,這將生成一個lib<module_name>.a為名字的模塊

PREBUILD_SHARED_LIBRARY
在編譯腳本中用於指定一個預先編譯的動態庫,不像BUILD_SHARED_LIBRARY和BUILD_STATIC_LIBRARY,LOCAL_SRC_FILES的預先共享庫必須是一個單獨的路徑(如:foo/libfoo.so),而不是源文件。

你可以在另一個模塊中引用預編譯的庫(參見docs/pribuilds.html)

PRIBUILD_STATIC_LIBRARY
這個變數類似於PREBUILD_SHARED_LIBRARY,但是是針對靜態庫的,(詳見docs/prebuilds.html)

TARGET_ARCH
TARGET_ARCH指框架中CPU的名字已經被Android開源代碼明確指出了,這裡的arm包含了任何ARM-獨立結構的架構,以及每個獨立的CPU版本

TARGET_PLATFORM
Android平台的名字在Android.mk中被解析,比如”android-3″對應Android 1.5系統鏡像,對於平台的名稱對應Android系統的列表,請看docs/STABLE-APIS.html

TARGET_ARCH_ABI
在Android.mk中被解析時指CPU+ABI的名字。

目前支持的兩個值
armeabi for ARMv5TE
armeabi-v7a

注意,到Android NDK 1.6_r1,這個值被簡化為”arm”。然而,這個值被重定義可以更好的匹配Android平台內部使用的是什麼

更多的信息可以參見docs/CPU-ARCH-ABIS.html

未來的NDK版本中得到支持,它們會有一個不同的名字,注意所有基於ARM的ABI都會有一個”TARGET_ARCH”被定義給arm,但也有可能有不同的”TARGET_ARCH_ABI”

TARGET_ABI

目標平台和ABI的鏈接,這裡要定義$(TARGET_PLATFORM)-$(TARGET_ARCH_ABI)它們都非常有用,特別是當你想測試一下具體的系統鏡像在一個真實設備環境的時候

默認地,這個是”android-3-armeabi”

(到Android NDK 16_R1版本,使用”android-3-arm”作為默認)

NDK提供的宏功能
——————————–
以下是使用GNU make的宏功能,必須通過使用”$(call <function>)”,返回一個文本信息。

my-dir

返回最後包含的makefile的路徑,這通常是當前Android.mk所在目錄的路徑,在Android.mk開始之前定義
LOCAL——PATH是很有用的。

在Android.mk文件的開始位置定義
LOCAL_PATH :=$(call my-dir)

…聲明一個模塊
include $(LOCAL_PATH)/foo/Android.mk

LOCAL_PATH :=($call my-dir)

…聲明另一個模塊
這裡的問題是第二次調用”my-dir”定義LOCAL_PATH替換$PATH為$PATH/foo,由於在此之前執行過。

對於這個原因,最好是將額外的其他所有東西都在Android.mk中包含進來

LOCAL_PATH :=$(call my-dir)

…聲明一個模塊

LOCAL_PATH :=$(call my-dir)
…聲明另一個模塊

#在Android.mk的最後額外包括進來
include $(LOCAL_PATH)/foo/Android.mk

如果這樣不方便的話,保存第一個my-dir調用的值到另一個變數中,例如

MY_LOCAL_PATH :=$(call my-dir)
LOCAL_PATH :=$(MY_LOCAL_PATH)
…聲明一個模塊

include $(LOCAL_PATH)/foo/Android.mk
LOCAL_PATH :=$(MY_LOCAL_PATH)
…聲明另一個模塊

all-subdir-makefiles
返回一個Android.mk文件所在位置的列表,以及當前的my-dir的路徑。比如
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包含進來。

此功能可以用於提供深層嵌套的源代碼目錄build system的層次結構。請注意,默認情況下,NDK只會尋找
sources/*Android.mk

this-makefile
返回當前makefile的路徑(也就是那個功能被調用了)

parent-makefile
返回makefile的包含樹,也就是包含Makefile當前的文件

grand-parent-makefile
你猜?

import-module
一個允許你通過名字找到並包含另一個模塊的的Android.mk的功能,例如

$(call import-module,<name>)

這將會找到通過NDK_MODULE_PATH環境變數引用的模塊<name>的目錄列表,並且將其自動包含到
Android.mk中

詳細信息請參閱:docs/IMPORT-MODULE.html

模塊變數描述:
———————————-
下面的這些變數是用來描述怎樣用你的模塊來編譯系統的。你可以定義它們中的一些比如
“include $(CLEAR_VARS)”和”include $(BUILD_XXX)”,正如前面所寫的,$(CLEAR_VARS)是一個可以取消定義/清楚所有變數的腳本。

LOCAL_PATH
這個變數是用來給出當前文件的路徑。您比系再您的Android.mk開始位置定義:
LOCAL_PATH :=$(call my-dir)
注意,這個變數是不被$(CLEAR_VARS)清除的,其他的都要被清除(我們可以定義幾個模塊到一個文件中)

LOCAL_MODULE
這個是你模塊的名稱,它在你的所有模塊中名稱必須是唯一的,並且不能包含空格。你必須在包含任何
$(BUILD-XXX)腳本之前定義它。

默認情況下,模塊的名稱決定了生成的文件的名稱,例如lib<foo>.so,它是foo模塊的名字。

你可以用LOCAL_MODULE_FILENAME覆蓋默認的那一個

LOCAL_MODULE_FILENAME

這個變數是可選的,並且允許你重新定義生成文件的名字。默認的,模塊<foo>將始終生成lib<foo>.a或者lib<foo>.so文件,這是標準的UNIX公約

你可以通過LOCAL_MODULE_FILENAME覆蓋它

LOCAL_MODULE :=foo-version-1
LOCAL_MODULE_FILENAME :=libfoo
注意:你不能將文件路徑或者文件擴展名寫到LOCAL_MODULE_FILENAME里,這些將有build system自動處理。

LOCAL_SRC_FILES
這是你模塊中將要編譯的源文件列表。只列出將被傳遞到編譯器的文件,因為build system自動為您計算了它們的依賴。

注意:源文件的名稱都是相對LOCAL_PATH的,您可以使用路徑組件,例如
LOCAL_SRC_FILES :=foo.c\
toto/bar.c

注意:在build system時請務必使用UNIX風格的斜杠(/),windows風格的斜杠將不會得到處理

LOCAL_CPP_EXTENSION
這是個可選的變數,可以被定義為文件擴展名為c++的源文件,默認是”.cpp”,但是你可以改變它,比如

LOCAL_CPP_EXTENSION:=.cxx

LOCAL_C_INCLUDES

可選的路徑列表,相對於NDK的根目錄,當編譯所有的源文件(C、C++、或者彙編)時將被追加到搜索路徑中
例如:
LOCAL_C_INCLUDES:=sources/foo
或者
LOCAL_C_INCLUDES:=$(LOCAL_PATH)/../foo

這些都在任何相應列入標誌之前被放置在
LOCAL_CFLAGS / LOCAL_CPPFLAGS

當用用ndk-gdb啟動本機調試時,LOCAL_C_INCLUDES也會自動被使用到

LOCAL_CFLAGS

當編譯C/C++源文件時傳遞一個可選的編譯器標誌。
這對於指定額外的宏定義或編譯選項很有用

重要提示:盡量不要改變Android.mk中的優化/調試級別,這個可以通過在Application.mk中設置相應的信息來自動為你處理,並且會會讓NDK生成在調試過程中使用的有用的數據文件。

注意:在Android-ndk-1.5_r1中,只使用於C源文件,而不適用於C++源文件。在匹配所有Android build system的行為已經得到了糾正。(現在你可以為C++源文件使用LOCAL_CPPFLAGS來指定標誌)

它可以用LOCAL_CFLAGS += -I<path>來指定額外的包含路徑,然而,如果使用LOCAL_C_INCLUDES會更好,因為用ndk-gdk進行本地調試的時候,那些路徑依然是需要使用的

LOCAL_CXXFLAGS
LOCAL_CPPFLAGS的別名。請注意,這個標誌在NDK的未來的版本中將會消失

LOCAL_CPPFLAGS
當只編譯C++源代碼的時候,將傳遞一個可選的編譯器標誌。它們將會出現再LOCAL_CFLAGS之後。

注意:在Android NDK-1.5_r1版本中,相應的標誌可以應用於C或C++源文件上。在配合完整的Android build system的時候,這已經得到了糾正。(你可以使用LOCAL_CFLAGS去指定C或C++源文件)

LOCAL_STATIC_LIBRARIES

靜態庫模塊的列表(通過BUILD_STATIC_LIBRARY創建)應與此模塊鏈接。這僅僅是為了使動態庫敏感。

LOCAL_SHARED_LIBRARY
共享庫的列表「模塊」,這個模塊依賴於運行時.這在鏈接的時候和在生成的文件中嵌入相應的信息是非常必要的

LOCAL_WHOLE_STATIC_LIBRARIES

LOCAL_WHOLE_STATIC_LIBRARIES是一個用於表示相應的庫模塊被用作為「整個檔案」到鏈接程序的變數。

當幾個靜態庫之間有循環依賴關係的時候,通常是很有益的。注意,當用來編譯一個動態庫的時候,這將迫使你將所有的靜態庫中的對象文件添加到最終的二進位文件中。但生成可執行程序時,這是不確定的。

LOCAL_LDLIBS
當額外的鏈接標誌列表被用於在編譯你的模塊時,通過用”-l”前綴的特定系統庫傳遞名字是很有用的。例如,下面的舊愛哪個告訴你生成一個在載入時鏈接到/system/lib/libz.so的模塊。

LOCAL_LDLIBS :=-lz

LOCAL_ALLOW_UNDEFINED_SYMBOLS
默認情況下,當試圖編譯一個共享庫的時候遇到任何未定義的引用都可能導致”未定義符號”(undefined symbol)的錯誤。這在你的源代碼中捕獲bug會很有用。

然而,但是由於某些原因,你需要禁用此檢查的話,設置變數為”true”即可。需要注意的是,相應的共享庫在運行時可能載入失敗。

LOCAL_ARM_MODE
默認情況下,在”thumb”模式下會生成ARM目標二進位,其中每個指令都是16位寬。你可以定義這個變數為”arm”,如果你想在”arm”模式下(32位指令)強迫模塊對象文件的生成。例如:

LOCAL_ARM_MODE := arm

注意,你需要執行編譯系統為在ARM模式下通過文件的名字增加後綴的方式編譯指定的源文件。比如:

LOCAL_SRC_FILES :=foo.c bar.c.arm

這會告訴編譯系統一直以ARM模式編譯”bar.c”,並且通過LOCAL_ARM_MODE的值編譯foo.c。

注意:在Application.mk文件中設置APP_OPTIM為”debug”也會強制ARM二進位文件的生成。這是因為工具鏈調試其中的bug不會處理thumb代碼。

LOCAL_ARM_NEON

定義這個變數為”true”會允許在你的C或C++源文件的GCC的內部函數中使用ARM高級SIMD(又名NEON),以及在聚合文件中的NEON指令。

當針對”armeabi-v7a”ABI對應的ARMv7指令集時你應該定義它。注意,並不是所有的ARMv7都是基於NEON指令集擴展的CPU,你應該執行運行時來檢測在運行時中這段代碼的安全。

另外,你也可以指定特定的源文件,比如用支持NEON”.neon”後綴的源文件也可以被編譯。

LOCAL_SRC_FILES :=foo.c.neon bar.c zoo.c.arm.neon

在這個例子中,”foo.c”將會被編譯在thumb+neon模式中,”bar.c”以thumb模式編譯,zoo.c以arm+neon模式編譯。

注意,如果你使用兩個的話,”.neon”後綴必須出現在”.arm”後綴之後
(就是foo.c.arm.neon可以工作,但是foo.c.neon.arm不工作)

LOCAL_DISABLE_NO_EXECUTE

Android NDK r4開始添加了支持”NX位”安全功能特性。它是默認啟用的,如果你需要的話,可以通過設置變數為「true」來禁用它。

注意:此功能不修改ABI,並且只在ARMv6及以上的CPU設備的內核上被啟用。

更多信息,可以參見:
http://en.wikipedia.org/wiki/NX_bit
http://www.gentoo.org/proj/en/hardened/gnu-stack.xml

LOCAL_EXPORT_CFLAGS

定義這個變數用來記錄C/C++編譯器標誌集合,並且會被添加到其他任何以LOCAL_STATIC_LIBRARIES和LOCAL_SHARED_LIBRARIES的模塊的LOCAL_CFLAGS定義中。

例如:這樣定義”foo”模塊
include $(CLEAR_VARS)
LOCAL_MODULE :=foo
LOCAL_SRC_FILES :=foo/foo.c
LOCAL_EXPORT_CFLAGS :=-DFOO=1
include $(BUILD_STATIC_LIBRARY)

另一個模塊,叫做”bar”,並且依賴於上面的模塊
include $(CLEAR_VARS)
LOCAL_MODULE :=bar
LOCAL_SRC_FILES :=bar.c
LOCAL_CFLAGS:=-DBAR=2
LOCAL_STATIC_LIBRARIES:=foo
include $(BUILD_SHARED_LIBRARY)

然後,當編譯bar.c的時候,標誌”-DFOO=1 -DBAR=2″將被傳遞到編譯器。

輸出的標誌被添加到模塊的LOCAL_CFLAGS上,所以你可以很容易複寫它們。它們也有傳遞性:如果”zoo”依賴”bar”,「bar」依賴”foo”,那麼”zoo”也將繼承”foo”輸出的所有標誌。

最後,當編譯模塊輸出標誌的時候,這些標誌並不會被使用。在上面的例子中,當編譯foo/foo.c時,
-DFOO=1將不會被傳遞給編譯器。

LOCAL_EXPORT_CPPFLAGS

類似LOCAL_EXPORT_CFLAGS,但適用於C++標誌。

LOCAL_EXPORT_C_INCLUDES

類似LOCAL_EXPORT_C_CFLAGS,但是只有C能包含路徑,如果”bar.c”想包含一些由”foo”模塊提供的頭文件的時候這會很有用。

LOCAL_EXPORT_LDLIBS

類似於LOCAL_EXPORT_CFLAGS,但是只用於鏈接標誌。注意,引入的鏈接標誌將會被追加到模塊的LOCAL_LDLIBS,這是因為UNIX連接器的工作方式。

當模塊foo是一個靜態庫的時候並且代碼依賴於系統庫時會很有用的。LOCAL_EXPORT_LDLIBS可以用於輸出依賴,例如:

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)

這裡,在連接器命令最後,libbar.so將以-llog參數進行編譯來表明它依賴於系統日誌庫,因為它依賴於foo。

LOCAL_FILTER_ASM

這個變數定義了一個shell命令,將用於過濾,從你的LOCAL_SRC_FILES中產生的或者聚合文件。

當它被定義了,將會出現如下的情況:

–任何C或C++源文件將會生成到一個臨時的聚合的文件中(而不是被編譯成目標文件)
–任何臨時聚合文件,任何在LOCAL_SRC_FILES中列出的聚合文件將通過LOCAL_FILER_ASM命令生成另一個臨時聚合文件

–這些過濾聚合文件被編譯成目標文件。

換種說法,如果
LOCAL_SRC_FILES  := foo.c bar.S
LOCAL_FILTER_ASM := myasmfilter

foo.c –1–> $OBJS_DIR/foo.S.original –2–> $OBJS_DIR/foo.S –3–> $OBJS_DIR/foo.o
bar.S                                 –2–> $OBJS_DIR/bar.S –3–> $OBJS_DIR/bar.o

「1」對應的編譯器,「2」的過濾器,和「3」的彙編。過濾器必須是一個獨立的shell命令作為第一個參數輸入文件的名稱,和輸出的名稱第二,如文件為:

myasmfilter$ OBJS_DIR/ foo.S.original$ OBJS_DIR/ foo.S
myasmfilter bar.S$ OBJS_DIR/ bar.S