1. 程式人生 > >Android.mk語法解釋

Android.mk語法解釋

大家在編寫Android的Native程式碼時,經常會接觸到一個叫做Android.mk的檔案。

雖然編譯的時候都用到的是make,但是這個Android.mk檔案裡的語法還跟一般的make檔案語法不太一樣。

本質上,Android.mk只是GNU MakeFile的一個片段,編譯系統在編譯的時候有可能會多次解釋Android.mk檔案,所以要儘量少在腳本里面申明變數,也不要假設任何沒有在指令碼中定義的條件。

Android.mk檔案是用來讓你把原始碼組織成各個“模組”。所謂模組,由以下三種構成:

  • 靜態庫
  • 共享庫
  • 獨立的可執行檔案

只有共享庫可以被包含到apk應用程式包裡,但是靜態庫可以被用來生成共享庫。

可以在一個Android.mk檔案中定義一個或者多個模組,並且可以多個模組複用同樣的原始碼。

編譯系統已經替你處理了很多瑣碎的事情。例如,你不需要在Android.mk檔案中羅列.h標頭檔案和顯式宣告生成檔案之間的依賴關係。NDK編譯系統會自動為你計算出來。

這也意味著,當升級到新版的NDK時,不需要更改Android.mk檔案就可以相互相容。

NDK中的Android.mk檔案語法和Android原始碼中的Android.mk檔案語法非常相近。但是其實編譯系統實現是不一樣的,這是有意這樣設計的,為了讓應用程式開發者可以更加方便的複用第三方庫的原始碼。

簡單的例子

在正式描述語法細節之前,讓我們來看一個簡單的例子“hello JNI”,這個例子包含在NDK裡的以下目錄中:

samples/hello-jni

在這個目錄裡,我們可以看到

  • src目錄,裡面包含了例子用到的Java程式碼
  • jni目錄,裡面包含了例子用到的Native程式碼(jni/hello-jni.c)
  • jni/Android.mk檔案,描述了要NDK編譯系統編譯出來的共享庫。內容如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello-jni
LOCAL_SRC_FILES := hello-jni.c
include $(BUILD_SHARED_LBRARY)

下面來稍微解釋一下:

LOCAL_PATH := $(call my-dir)

所有的Android.mk檔案必須要以LOCAL_PATH變數定義開頭。它用來定位要編譯的原始碼在程式碼樹中的位置。在本例中,巨集函式“my-dir”是由編譯系統提供的,用來返回當前目錄的路徑(也就是包含此Android.mk檔案的目錄)。

include $(CLEAR_VARS)

CLEAR_VARS變數也是由編譯系統提供的,它指向了一個特殊的GNU MakeFile檔案,這個檔案的用處是為你清理許多LOCAL_XXX變數(例如,LOCAL_MODULE,LOCAL_SRC_FILES,LOCAL_STATIC_LIBRARY),除了前面才定義的LOCAL_PATH變數。必須要這樣做的目的是,所有的編譯指令碼都在同一個GNU Make執行環境中分析,所有這些變數都是全域性的。如果不及時清理,前面編譯指令碼定義的變數會對當前編譯指令碼產生影響。

LOCAL_MODULE := hello-jni

LOCAL_MODULE變數必須定義,用來標識你在Android.mk檔案中描述的每一個模組。名字必須唯一,並且不能包含空格。注意,編譯系統會自動給生成的檔案加上適當的字首和字尾。也就是說,如果一個共享模組名是“foo”的話,那麼生成的檔案就是“libfoo.so”。

重要提示:如果你把你的模組命名成“libfoo”的話,編譯系統是不會再加上“lib”字首的,還是會生成“libfoo.so”檔案。這樣設計的目的是為了支援從AOSP那移植過來的程式碼。

LOCAL_SRC_FILES := hello-jni.c

變數LOCAL_SRC_FILES必須包含編譯模組必須要的C或者C++程式碼原始檔。注意,請不要在此列出標頭檔案和其它的各種包含檔案,因為編譯系統會自動幫你算出依賴關係,請只列出需要編譯器編譯的程式碼原始檔。

注意,預設的C++程式碼原始檔的副檔名是“.cpp”。但是,可以通過定義變數LOCAL_CPP_EXTENTION來指定成其它的名字。定義的時候不要忘記起始的點(例如“.cxx”可以,但是“cxx”就不行)。

include $(BUILD_SHARED_LIBRARY)

變數BUILD_SHARED_LIBRARY也是由編譯系統提供的,指向了一個GNU MakeFile指令碼檔案。這個指令碼檔案是用來負責收集所有你從“include $(CLEAR_VARS)”開始定義的所有LOCAL_XXX變數中包含的資訊,來決定如何編譯,編譯成什麼。相應的還有BUILD_STATIC_LIBRARY變數,用來生成一個靜態庫。

在samples目錄下,還有很多更加複雜的例子,每個Android.mk都包含註釋。

自定義變數

NDK編譯系統預留了如下的變數名:

  • 所有以LOCAL_開頭的變數(如LOCAL_MODULE)
  • 所有以PRIVATE_、NDK_或者APP_開頭的變數(供內部使用)
  • 小寫字母構成的變數(內部使用,例如my-dir)

只要符合以上三個規則,其它的你就可以自由定義了。如果你要定義的話,我們建議用MY_字首,下面是一個簡單的例子:

MY_SOURCES := foo.c
ifneq ($(MY_CONFIG_BAR),)
    MY_SOURCES += bar.c
endif

LOCAL_SRC_FILES += $(MY_SOURCES)

NDK提供的變數

這些GNU Make變數是在解析你的Android.mk檔案之前就有編譯系統定義好的。注意,在某些特定的情況下,NDK可能會多次解析你的Android.mk檔案,並且每次預先定義的變數值會不一樣。

CLEAR_VARS

指向一個編譯指令碼。這個編譯指令碼的目的是清空所有接下來指令碼中會用到的LOCAL_XXX變數。這個指令碼必須要在定義一個新模組之前被包含進來

include $(CLEAR_VARS)

BUILD_SHARED_LIBRARY

指向一個編譯指令碼,這個編譯指令碼可以收集所有你定義的LOCAL_XXX變數中提供的資訊,然後決定如何編譯目標共享庫。注意,最少你要在包含這個指令碼之前定義好LOCAL_MODULE和LOCAL_SRC_FILES變數。用法如下:

include $(BUILD_SHARED_LIBRARY)

這將會生成一個名字為lib$(LOCAL_MODULE).so的目標檔案。

BUILD_STATIC_LIBRARY

變數BUILD_STATIC_LIBRARY是專門用來編譯靜態庫的。靜態庫是不能直接用在應用程式中的,但是可以用來構建共享庫(參照下面的對LOCAL_STATIC_LIBRARIES和LOCAL_WHOLE_STATIC_LIBRARIES變數的說明)。示例用法如下:

include $(BUILD_STATIC_LIBRARY)

這將會生成一個名為lib$(LOCAL_MODULE).a的目標檔案。

PREBUILT_SHARED_LIBRARY

指向一個編譯指令碼,該指令碼用來指定一個預先編譯好的共享庫。這時候變數LOCAL_SRC_FILES值的含義,就和在BUILD_SHARED_LIBRARY和BUILD_STATIC_LIBRARY裡面的不同。前者要設定成一個指向預編譯好的共享庫檔案的路徑,而後者是要編譯的原始檔。

PREBUILD_STATIC_LIBRARY

含義基本和PREBUILD_SHARED_LIBRARY相同,只不過這是指定一個靜態庫檔案。

TARGET_ARCH

目標 CPU架構的名字。如果是“arm”,表示要生成 ARM 相容的指令,與 CPU架構的修訂版無關。

TARGET_PLATFORM

目標平臺的名字。表示要編譯的這個模組,將來要跑在哪個Android目標平臺上,例如“android-18”代表Android 4.3系統。 當然,Android系統提供向上相容性,即使指定了android-18,編譯出來的程式還是可以在android-19平臺上跑的。

TARGET_ARCH_ABI

目標ABI(Application Binary Interface)的名字。目前支援四個值: 1)armeabi:表示對ARMv5TE的支援; 2)armeabi-v7a:表示對ARMv7的支援; 3)x86:表示對i686的支援; 4)mips:表示對於mips32(r1)的支援。 注意,在未來NDK中還會引入更多的ABI,它們的名字各不相同。但是所以基於ARM的ABI,儘管它們的ABI名字不一樣,但是它們的TARGET_ARCH變數都會被定義成“arm”。

TARGET_ABI

目標平臺和 ABI 的組合。它實際上被定義成$(TARGET_PLATFORM)-$(TARGET_ARCH_ABI)  。 在預設的情況下,它會是“android-3-armeabi”。

NDK提供的巨集函式

本節將介紹編譯系統預先定義好了的GNU Make巨集函式,這些函式必須要像“$(call)”這樣呼叫。它們會返回文字型別的資訊。

my-dir

返回最近一次包含的MakeFile的目錄位置,通常這就是當前Android.mk檔案所在的目錄。它可以用來在Android.mk檔案的開頭定義LOCAL_PATH變數,如下:

LOCAL_PATH := $(call my-dir)

特別要注意的是,該函式確切的說是返回最近一次包含的MakeFile檔案的位置。請不要在包含了另外一個檔案後呼叫my-dir巨集函式。

例如,考慮以下這種情況:

LOCAL_PATH := $(call my-dir)

... declare one module

include $(LOCAL_PATH)/foo/`Android.mk`

LOCAL_PATH := $(call my-dir)

... declare another module

這裡存在的問題是,由於在第二次呼叫my-dir之前包含了另一個Android.mk檔案,就會將LOCAL_PATH設定成$PATH/foo,而不是$PATH。

由於這個原因,如果要包含另外的檔案的話,最好將其放在Android.mk檔案的最後面,如:

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`

如果這樣做不方便的話,在第一次呼叫my-dir之後,將其值儲存在另外一個變數中,例如:

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檔案。

該函式可以在多級巢狀的目錄結構中,幫助編譯系統羅列出裡面所有包含的Android.mk檔案。而在預設情況下,NDK只會尋找sources/*/Android.mk檔案,再下面就不會去查找了。

this-makefile

返回當前MakeFile的路徑(這個函式是在哪個MakeFile中呼叫的)

parent-makefile

返回父MakeFile的路徑,也就是包含當前呼叫這個函式的MakeFile的那個MakeFile。

grand-parent-makefile

不解釋了,我想大家應該從名字就能猜到其意思了。

import-module

該函式用於按指定的名字,查詢另一個模組的Android.mk檔案,幷包含到當前的Android.mk中來。用法如下:

$(call import-module,<name>)

上面的巨集函式呼叫,會在 NDK_MODULE_PATH變數裡所指定的目錄列表的每一個目錄中,尋找指定名字的模組,並且找到之後將其包含進來。


模組描述變數

下面介紹的這些變數,專門用來向編譯系統描述你模組的一些特性。如果要使用的話,請確保將它們定義在“include $(CLEAR_VARS)”和“include $(BUILD_XXXXX)”之間。而除了一些下面說明的特例,“$(CLEAR_VARS)”會將這些變數都清理掉。

LOCAL_PATH

這個變數用來告訴編譯系統當前編譯路徑是什麼,必須要在Android.mk檔案的一開頭就定義,像這樣:

LOCAL_PATH := $(call my-dir)

這個變數不會被“$(CLEAR_VARS)”給清理掉,所以一般情況下每個Android.mk檔案只要定義一次就可以了(除非你在一個Android.mk檔案中定義了多個模組)。

LOCAL_MODULE

這個變數用來告訴編譯系統當前編譯模組的名字。這個名字必須唯一,不能和別的模組名相同,並且不能包含空格。必須在“$(BUILD_XXXXX)”之前定義。 預設情況下,編譯生成的檔名是由模組的名字決定的。就是在模組名前面加上“lib”,再在後面加上“.so”(動態庫)或者“.a”(靜態庫)。例如如果一個共享模組名是“foo”的話,那麼生成的檔案就是“libfoo.so”。 如果想自己制定生成的檔名字,而不用這種命名規則的話,可以通過修改變數LOCAL_MODULE_FILENAME的值來達到。

LOCAL_MODULE_FILENAME

這個變數是可選的,它允許你自己指定編譯過後生成的檔案的名字。預設情況下,編譯系統使用LOCAL_MODULE變數的值來計算最後生成的檔名。 如果不想這樣,而是自己制定,可以定義LOCAL_MODULE_FILENAME變數,例如:
LOCAL_MODULE := foo-version-1
LOCAL_MODULE_FILENAME := libfoo
這樣的話,最終編譯生成的檔案就不是libfoo-version-1.so了,而是libfoo.so。 注意,定義這個變數的時候,請不要包含路徑和副檔名。

LOCAL_SRC_FILES

這個變數用來指定,編譯生成模組所需要的所有原始碼檔案。請只包含原始碼檔案,不要包含標頭檔案,編譯系統會自動替你計算依賴關係。 如果不指定路徑的話,編譯系統會預設在當前路徑下(即變數LOCAL_PATH中指定的路徑)搜尋原始碼檔案。如果原始碼檔案不在當前路徑下的話,請指定路徑名,例如:
LOCAL_SRC_FILES := foo.c \
                   toto/bar.c
除了上面例子中這種相對路徑外,也可以使用絕對路徑:
LOCAL_SRC_FILES := /home/user/mysources/foo.c
如必要,請儘量不要使用絕對路徑,會導致相容性問題,可移植性會變差。 請注意,即使是在Windows系統上編譯,指定路徑的時候也請使用正斜槓(“/”),而不要使用反斜槓(“\”)。

LOCAL_CPP_EXTENSION

這個變數是可選的,預設情況下,編譯系統如果看到一個檔案是以“.cpp”結尾的話,會認為其裡面包含C++的程式碼;如果不是以“.cpp”結尾的話,則認為這不是一個包含C++程式碼的檔案。如果你的原始碼確實是用C++編寫,但是不是以“.cpp”結尾的檔案儲存的話,可以通過指定LOCAL_CPP_EXTENSION變數,讓編譯系統知道其也是用C++編寫的。 例如,如果你的C++原始碼檔案是以“.cxx”結尾的話,則可以這樣定義:
LOCAL_CPP_EXTENSION := .cxx
注意,指定副檔名的時候,要加上點(“.”)。 另外,從NDK r7開始,可以指定一串擴充套件檔案列表給這個變數:
LOCAL_CPP_EXTENSION := .cxx .cpp .cc
這樣的話,編譯系統就碰到以這些名字結尾的檔案的話都會知道它們是用C++編寫的。

LOCAL_CPP_FEATURES

這個變數是可選的,用來告訴編譯系統,你的程式碼會依賴哪些C++專有的特性。主要有一下幾個: 1)RTTI(RunTime Type Information,即動態型別識別) 如果想告訴編譯系統,你的程式碼使用了C++中的動態型別識別特性,可以這樣:
LOCAL_CPP_FEATURES := rtti
2)C++ exceptions(C++異常) 如果想告訴編譯系統,你的程式碼使用了C++的異常特性,可以這樣:
LOCAL_CPP_FEATURES := exceptions
也可以同時指定多個特性(順序無所謂):
LOCAL_CPP_FEATURES := rtti exceptions
通過設定這個變數,在編譯的時候,可以傳遞相應的選項給編譯器或連結器。 請不要在LOCAL_CPPFLAGS變數中定義-frtti或-fexceptions選項,而是在LOCAL_CPP_FEATURES變數中指定。

LOCAL_C_INCLUDES

這個變數是可選的,預設情況下,編譯系統會在當前路徑下(即在LOCAL_PATH變數中指定的路徑)搜尋相關的標頭檔案,可以通過設定這個變數來增加搜尋路徑。 例如:
LOCAL_C_INCLUDES := sources/foo
或者,也可以這樣:
LOCAL_C_INCLUDES := $(LOCAL_PATH)/../foo
如果要在LOCAL_CFLAGS或者LOCAL_CPPFLAGS中使用任何包含選項的話,請確保要包含檔案所在的目錄已經在LOCAL_C_INCLUDES變數中定義過了。 如果使用ndk-gdb對程式進行除錯的話,在變數LOCAL_C_INCLUDES中定義的目錄也會自動被包含進來。

LOCAL_CFLAGS

這個變數定義了,當要編譯C程式檔案的時候,要傳遞給系統編譯器的一組選項。 如果要通知編譯器包含一個目錄的話,也可以通過下面的變數定義實現:
LOCAL_CFLAGS += -I<path>
但是,請儘量不要這麼做,還是使用前面介紹的LOCAL_C_INCLUDES變數來指定特殊的包含路徑,以保證後面用ndk-gdb對程式進行除錯的時候,這些特殊路徑能自動被包含進來。

LOCAL_CPPFLAGS / LOCAL_CXXFLAGS

這個變數定義了,當要編譯C++程式檔案的時候,要傳遞給系統編譯器的一組選項。
一般情況下,它是接著LOCAL_CFLAGS變數後面定義的。 LOCAL_CXXFLAGS是LOCAL_CPPFLAGS的一個別名。請儘量使用LOCAL_CPPFLAGS,因為LOCAL_CXXFLAGS已經過時,以後的NDK中可能會不再提供支援。

LOCAL_STATIC_LIBRARIES

列出所有當前模組所要依賴的靜態庫。如果當前待編譯模組是一個共享庫或者一個可執行檔案的話,這會強制讓這些靜態庫連結進最終編譯出來的二進位制檔案中。如果當前待編譯模組也是一個靜態庫的話,則不會對編譯出來的檔案造成什麼影響,只是如果以後有別的模組要包含當前這個靜態庫的時候,通過這個變數就知道它也要依賴這些靜態庫。

LOCAL_SHARED_LIBRARIES

列出了所有當前模組所要在執行時依賴的動態庫。它的作用是在連結的時候,在最終生成的檔案中包含相應的資訊,並不會把這些動態庫的二進位制程式碼包含進來。

LOCAL_LDLIBS

這個變數用來告訴連結器,在將各個模組連結成最終的檔案時,需要哪些特定的系統預設提供的庫,這些庫要以“-l”開頭。 因為是給連結器用的,所以只在編譯動態庫或者可執行檔案的時候,這個變數才有效。 例如:
LOCAL_LDLIBS := -lz
這樣的話,會告訴連結器,在生成最終的二進位制檔案中包含執行時將動態連結/system/lib/libz.so模組的資訊。 和在LOCAL_SHARED_LIBRARIES中指定的動態庫不同,LOCAL_LDLIBS是列出系統的動態庫,而LOCAL_SHARED_LIBRARIES是列出自己編譯出的動態庫。 注意,在編譯靜態庫的時候,這個變數即使設定了也會被忽略,並且在這種情況下ndk-build會打印出警告資訊。

LOCAL_LDFLAGS

列出其它的一些(除去在LOCAL_LDLIBS中傳給連結器的系統動態庫的資訊)要傳給連結器的引數,同樣只針對動態庫和可執行檔案有效。 在編譯靜態庫的時候會被忽略,並且打出警告資訊。

LOCAL_ALLOW_UNDEFINED_SYMBOLS

預設情況下,在編譯動態庫的時候,如果遇到了任何無法解析的符號,都會直接報“undefined symbol”的錯誤。 這樣做可以極大的幫助開發者儘早發現程式中的錯誤。 但是,如果出於某些原因,你想在連結的時候忽略這些檢查,可以將變數LOCAL_ALLOW_UNDEFINED_SYMBOLS設定成“true”。 但是,這樣做的話,如果在載入的時候還是無法找到對應的符號,則程式還是會直接報錯退出。 注意,這個選項對編譯靜態庫的時候沒有任何作用,如果編譯系統在編譯靜態庫時發現定義了這個變數,則會給出錯誤提示資訊。

LOCAL_ARM_MODE

預設情況下,如果編譯目標是ARM平臺的話,編譯系統會將程式編譯成Thumb指令集(每條指令長16位元)的。可以通過將這個變數設定成“arm”來告訴編譯系統,必須將程式碼編譯成ARM指令集(每條指令32位元)的:
LOCAL_ARM_MODE := arm
這樣的話,這個整個這個模組都會編譯成ARM指令集的。 而如果只想讓模組中某幾個原始檔內的程式碼被編譯成ARM指令集的,而剩下的程式碼任然用Thumb指令集編譯的話,可以在原始碼的檔名後面加上“.arm”字尾。 例如:
LOCAL_SRC_FILES := foo.c bar.c.arm
這將告訴編譯系統,將“bar.c”編譯成ARM指令集的。而對於“foo.c”來說,任然用LOCAL_ARM_MODE變數指定的方式(如果未指定則預設用Thumb指令集)編譯。

LOCAL_ARM_NEON

可以通過定義LOCAL_ARM_NEON變數,並將其值設定成“true”,開啟GCC編譯器對ARM中NEON指令集的支援。 注意,並不是所有ARM處理器都支援NEON指令集的。即使處理器支援比較新的ARMv7指令集,也不一定包含對NEON指令集的支援。所以,為了使得程式碼能夠正確的執行,需要在執行時進行動態判斷處理器是否支援NEON指令集。 通過設定LOCAL_ARM_NEON變數,編譯器會將模組中所有的程式碼都編譯成支援NEON指令集的形式。而如果你只想讓模組中某幾個原始檔中的程式碼編譯成支援NEON指令集,而其它的不要支援NEON指令集,可以將那些需要支援NEON指令集的原始碼檔案的名字後面加上“.neon”字尾。 例如:
LOCAL_SRC_FILES = foo.c.neon bar.c zoo.c.arm.neon
本例中,“foo.c”會被編譯成Thumb指令集加上NEON指令集的形式(預設情況下,所有原始碼會被編譯成Thumb指令集的形式),“bar.c”會被編譯成Thumb指令集的形式,而“zoo.c”會被編譯成ARM指令集加上NEON指令集的形式。 還有一點要說明,如果“.arm”字尾和“.neon”字尾要同時使用的話,請保證“.arm”出現在“.neon”之前(對於前例中的“zoo.c.arm.neon”,如果改成“zoo.c.neon.arm”,則會出錯)。

LOCAL_DISABLE_NO_EXECUTE

從Android NDK r4開始,加入了一個新的安全功能,叫做“NX bit”(No-eXecute)。就是將記憶體中的某些區域標記成不能執行的,如果PC暫存器移動到這些區域的話就會報錯,從而讓利用溢位漏洞變得更加困難。 預設的情況下,這個功能是被開啟的,如果想關閉編譯出來程式的“NX bit”功能的話,可以定義LOCAL_DISABLE_NO_EXECUTE變數,並將其值設定成“true”。 注意,這個功能只在支援ARMv6及以上的處理器上才能支援。但是編譯出來的程式保持向下相容性,即在ARMv5的處理器上也能執行,但保護功能失效。

LOCAL_DISABLE_RELRO

預設情況下,NDK編譯出來的程式碼,會自帶對重定位表和GOT表的保護功能。也就是告訴Android的動態連結器(linker),在程式載入進記憶體,並完成了重定位之後,將某些特定區域的記憶體標記為只讀。這樣,可以有效防止別的程式通過修改GOT表來達到hook程式中某些重要函式的目的,提高了程式的安全性(其實沒什麼用,只要在修改之前用mprotect()函式將GOT先改成可寫的就可以了)。 如果處於某些特殊的目的,你想關閉這個功能的話,可以定義變數LOCAL_DISABLE_RELRO,並將其值設定成“true”。 注意,這個所謂的GOT保護功能,只在Android 4.3(Jelly Bean)及以後的版本中有效。而對於之前的Android版本,程式碼仍然能執行,但保護功能將不起作用。

LOCAL_EXPORT_CFLAGS

這個變數比較有意思,當要編譯C語言編寫的程式時,前面的LOCAL_CFLAGS是要編譯本模組的時候傳給編譯器的引數列表,而這個LOCAL_EXPORT_CFLAGS是要傳給別的依賴於當前模組的其它模組的編譯器引數列表。 也就是說,如果一個模組自己用LOCAL_STATIC_LIBRARIES或者LOCAL_SHARED_LIBRARIES引用了一個別的靜態或動態模組,而那個模組的Android.mk檔案中定義了LOCAL_EXPORT_CFLAGS變數的話,則在編譯自己模組的時候,傳給編譯器的選項還要包括那個引用模組中在LOCAL_EXPORT_CFLAGS變數裡定義的選項。 好拗口,舉個例子,假設有一個模組叫“foo”,它的定義如下:
include $(CLEAR_VARS)
LOCAL_MODULE := foo
LOCAL_SRC_FILES := foo/foo.c
LOCAL_EXPORT_CFLAGS := -DFOO=1
include $(BUILD_STATIC_LIBRARY)
而同時還有另外一個模組,叫做“bar”,它依賴於這個“foo”模組,其定義如下:
include $(CLEAR_VARS)
LOCAL_MODULE := bar
LOCAL_SRC_FILES := bar.c
LOCAL_CFLAGS := -DBAR=2
LOCAL_STATIC_LIBRARIES := foo
include $(BUILD_SHARED_LIBRARY)
那麼,對於“bar”來說,編譯的時候傳給編譯器的選項就不光是“-DBAR=2”了。由於依賴的模組“foo”中定義了LOCAL_EXPORT_CFLAGS選項,所以編譯選項要加上“-DFOO=1”。因此,最終傳給編譯器的選項是“-DFOO=1 -DBAR=2”。 是定義在自己模組的LOCAL_CFLAGS之前的。 同時,這個變數也是可傳遞的。假設另有一個模組“zoo”依賴於“bar”,而前面也看到了“bar”是依賴於“foo”的,那麼在編譯“zoo”模組時也會使用在“foo”模組中匯出的引數。
另外,這個變數對編譯包含這個變數的自己模組是沒有作用的。例如,前例中,在編譯“foo.c”時,並不會將引數“-DFOO=1”傳給編譯器。

LOCAL_EXPORT_CPPFLAGS

功能和前面的LOCAL_EXPORT_CFLAGS一樣,但只是對於編譯C++的程式碼有效。

LOCAL_EXPORT_C_INCLUDES

這個變數也是匯出到別的依賴模組的,但是匯出的是包含路徑。 還是用前面的例子,如果在“bar.c”中,要包含模組“foo”的標頭檔案,有兩種做法: 1)可以在“bar”模組中定義LOCAL_C_INCLUDES變數,將“foo”模組的路徑賦值給它; 2)可以在“foo”自己模組中定義LOCAL_EXPORT_C_INCLUDES變數,將“foo”模組自己所在的路徑賦值給它。

LOCAL_EXPORT_LDFLAGS

這個變數也是匯出到別的依賴模組的,但是匯出的是傳給連結器的選項。

LOCAL_EXPORT_LDLIBS

這個變數也是匯出到別的依賴模組的,但是匯出的是用“-l”字首來表示的,模組所需特定系統庫的名字。
注意,這種通過其它模組的LOCAL_EXPORT_LDLIBS變數匯入進來的連結器選項,會新增到在本模組LOCAL_LDLIBS變數中宣告的選項之後。 通常這個變數用於這種場景,即有一個模組要編譯成靜態庫,而這個模組又要依賴於一個系統提供的動態庫。那麼這時,就可以用LOCAL_EXPORT_LDLIBS變數把這個依賴關係匯出出去,而不是在別的依賴這個靜態庫的模組中用LOCAL_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引數,並且這個引數是在所有其它引數的最後。也就是表示“bar”模組是依賴於系統所提供的日誌動態庫的,因為“bar”模組依賴於“foo”模組,而“foo”模組依賴於系統的日誌模組。