简介
android.mk主要描述了c或者c++文件时如何在ndk工程中被使用的,该小节主要描述了android.mk的构建规则
概览
- android.mk文件描述了你的源码是如何构建的,主要包括:该文件实际上是一个简化了的GNU makefile文件。该文件被构建系统解析一次或多次,因此你需要尽可能少得自定义变量。同样的,也不能在解析过程中认为未定义任何变量
- 该文件语法决定了如何把你的源码组织到“模块”中,“模块”的概念是:
- 静态库
- 动态库
- 可执行文件
编译器仅仅安装/拷贝动态库到你的apk包中去。此外,静态库可以生成动态库
在每个android.mk文件中可以定义一个或多个模块,在多个模块中可以共用一个源文件
- 编译器自动加上了更详细的信息,比如不需要列出所包含的头文件或者不需要写出很明确的文件以来关系,NDK编译器已经为你自动计算好了。
同样的,当更新到新发布的NDK后,不需要修改android.mk文件就可以自动使用新的工具链/平台进行编译
注意此语法非常接近完整的安卓开源项目中andriod.mk语法,但是编译器是以不同的方式执行的。为了让开发者更容易得重复使用编译底层库的源文件,它们被有意的设计成类似格式。
简单实例
在详细描述语法之前,有必要考虑一下Hello_JNI中的Android.mk
---------- cut here ------------------ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := hello-jni LOCAL_SRC_FILES := hello-jni.c include $(BUILD_SHARED_LIBRARY) ---------- cut here ------------------
现在,仔细分析每一行的意义
LOCAL_PATH := $(call my-dir)
一个Android.ml文件必须以LOCAL_PATH变量的定义开头,该变量主要用来定位源码位置。在这个例子中,宏定义“my-dir”由编译器提供,它返回当前文件夹路径(包含Android.mk的文件夹)
include $(CLEAR_VARS)
CLEAR_VARS变量由编译器定义,它指定了一个特殊的GNU makefile文件,该文件会清除除了LOCAL_PATH之外的LOCAL_XXX变量(比如,LOCAL_MODULE,LOCAL_SRC_FILES,LOCAL_STATIC_LIBRARIES等)。所有的构建控制文件都会被解析到一个GNU构建运行上下文中,在这里所有的变量都是全局的,因此该变量是很有价值的。
LOCAL_MODULE := hello-jni
LOCAL_MODULE变量用来定义所有需要被定义的模块名。模块名必须是唯一的而且不能包含空格。注意编译器会在生成的模块名称上添加前缀和后缀。比如一个叫‘foo’的模块会生成‘libfoo.so’
注意:如果定义一个名为“libfoo”的模块,编译器不会再次添加‘lib’前缀,同样生成了‘libfoo.so’。
LOCAL_SRC_FILES := hello-jni.c
LOCAL_SRC_FILES变量包含了构建模块所需要的所有C/C++源码文件。因为编译器自动计算了文件以来关系,因此在这里不需要添加头文件,只需要直接列出所有源文件即可。
注意默认c++文件的后缀名是“.cpp”。可以使用LOCAL_CPP_EXTENSION变量来修改c++文件所使用的后缀,注意一定要加上“.”(正确:‘.cxx’,错误:‘cxx’)。
include $(BUILD_SHARED_LIBRARY)
BUILD_SHARED_LIBRARY是一个指向GNU makefile脚本的系统变量,该变量负责收集所有定义在LOCAL_XXX中的信息并且决定决定构建的目标和如何构建该目标。使用BUILD_STATIC__LIBRARY来生成静态库。
在ndk的sample目录下你可以查看更复杂的Android.mk文件。
引用
下面列出的一些变量在Android.mk中是完全可靠的。你可以自己定义一些变量,但是NDK编译器保留了这些关键字:
- 以“LOCAL_”开头的(例:LOCAL_HOME)
- 以”PRIVATE_”,”NDK”,”APP_”开头的(内部使用)
- 小写的变量名(内部使用,例:my-dir)
如果需要自定义变量,推荐使用“MY_“作为前缀,例如:
---------- cut here ------------------ MY_SOURCES := foo.c ifneq ($(MY_CONFIG_BAR),) MY_SOURCES += bar.c endif LOCAL_SRC_FILES += $(MY_SOURCES) ---------- cut here ------------------
NDK提供的变量
这些GNU Make变量在Android.mk文件被解析之前已经被定义了。注意在某些情况下,NDK也许会在不同的GNU Make变量的定义下解析Android.mk好几次
CLEAR_VARS
指向清除大部分定义在”Module-description”节下LOCAL_XXX变量的构建脚本。它必须包含在开始说明一个新模块之前,使用实例:
include $(CLEAR_VARS)
BUILD_SHARED_LIBRARY
指向收集所有LOCAL_XXX变量提供的信息并且决定如何由源码文件构建动态库的脚本文件。注意在使用该变量之前必须定义LOCAL_MODULE和LOCAL_SRC_FILE。使用说明:
include $(BUILD_SHARED_LIBRARY)
这会生成一个明文libXXX.so的文件。
BUILD_STATIC_LIBRARY
BUILD_SHARED_LIBRARY是生成一个静态库的变种。静态库不会被拷贝/打包进apk包文件中,但是我们可以使用它来生成动态库(参照下面LOCAL_STATIC_LIBRARY和LOCAL_WHOLE_STATIC_LIBRARY的描述)。使用说明:
include $(BUILD_STATIC_LIBRARY)
PREBUILT_SHARED_LIBRARY
指向一个预编译动态库的构建脚本。和BUILD_SHARED_LIBRARY与BUILD_STATIC_LIBRARY不同,LOCAL_SRC_FILES只能是一个预编译动态库的路径而不是源文件(例如:foo/libfoo.so)。
可以在其他模块中使用LOCAL_PREBUILTS变量来引用该预编译库(详细请参照NDK Prebuilt Library Support)
PREBUILT_STATIC_LIBRAR
和PREBUILT_SHARED_LIBRARY类似,但是指定的是静态库。
TARGET_ARCH
目标CPU架构的名称,对所有的arm兼容的编译器来说目标架构名都是arm,独立的CPU架构将会不同
TARGET_PLATFORM
目标Android平台的名称。例如android-3对应于Android 1.5
TARGET_ARCH_ABI
目标CPU和ABI的名称,可以指定以下的一个或多个值
armeabi For ARMv5TE armeabi-v7a For ARMv7 arm64-v8a For ARMv8 AArch64 x86 For i686 x86_64 For x86-64 mips For mips32 (r1) mips64 For mips64 (r6)
注意:到Android NDK 1.6_r1为止,这个变量被定义为‘arm’.然而,这个值在内部使用时会被重定义成与Android平台更加匹配的值。
TARGET_ABI
目标平台和ABI之间的联系,它实际上被定义成由”-“连接,这将会在测试装有指定系统文件的实体机上显得很有用。
默认的数值是”android-3-armeabi“
NDK提供的函数
一下是GNU Make中的”函数”宏,必须以“$(call <function>)”的方式进行调用。返回文本的返回值。
my-dir
返回最后包含的Makefile路径,通常情况下是当前Android.mk的路径。它通常在Android.mk头文件用来定LOCAL_PATH:
LOCAL_PATH := $(call my-dir)
提示:基于GNU Make的工作方式,他实际上返回的最后包含的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之前,my-dir已经变成了$PATH/foo,所以第二个调用my-dir时会定义LOCAL_PATH为$PATH/foo而不是$PATH。因此,最好把包含文件的操作放在所有内容之后,例如:
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
如果source/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的目录(该函数被调用的makefile)
parent-makefile
返回在包含树中该Makefile的父Makefile文件。包含当前Makefile文件
grand-parent-makefile
猜猜这是啥
import-module
通过名称来包含另外一个模块的Android.mk。一个典型的例子:
$(call import-module,<name>)
它会通过查找当前设置的NDK_MODULE_PATH环境变量来查找模块,自动包含该模块的Android.mk文件
模块描述的变量
以下的变量用来在编译系统中描述自定义的模块。可以再两个‘include’之间包含它们。正如前面所写,除非明确得表示它们,一个脚本会全部清理掉它们。
LOCAL_PATH
该变量用来获得当前路径,必须定义在Android.mk文件的开头,例如:
LOCAL_PATH := $(call my-dir)
该变量不会被清理,所以只需要定义一次(也许你会在一个文件中定义多个模块)
LOCAL_MODULE
当前模块的名称。它的命名必须是唯一的,也不能包含空格。需要在定义在任何脚本之前。模块名称默认决定了生成文件的名称,比如lib<foo>.so是<foo>生成的动态库文件。但是你只需要在NDK编译系统中的其他文件中使用“正常”的名称(比如<foo>),比如说Android.mk或者Application.mk
LOCAL_MODULE_FILENAME
该变量是可选的,它允许你重命名生成文件的名称。块名称默认决定了生成文件的名称,比如lib<foo>.so是<foo>生成的动态库文件。
你可以通过定义LOCAL_MODULE_FILENAME来改变,例如:
LOCAL_MODULE := foo-version-1 LOCAL_MODULE_FILENAME := libfoo
注意:不能把一个文件的路径或者后缀定义到LOCAL_MODULE_FILENAME中,这些会由编译系统自动添加。
LOCAL_SRC_FILES
列出所有编译模块所需要的源码。在编译器计算出依赖关系之前,只有列出的文件才会参与编译。
注意:所有文件都是在LOCAL_PATH目录下的,可以添加子目录中的文件。例如:
LOCAL_SRC_FILES := foo.c toto/bar.c
也可以使用绝对路径。例如:
LOCAL_SRC_FILES := /home/user/mysources/foo.c
windows平台:
LOCAL_SRC_FILES := c:/Users/user/sources/foo.c
为了让开发环境能够更好得移植,最好不要使用绝对路径
注意:Unix风格通常使用斜杠(/)作为路径分隔符。windows风格的反斜杠不一定能够被很好得支持。
LOCAL_CPP_EXTENSION
自定义C++文件的后缀名。必须以”.“开头,默认的后缀名是”cpp“,举例:
LOCAL_CPP_EXTENSION := .cxx
NDK r7以后,可以定义多个后缀名:
LOCAL_CPP_EXTENSION := .cxx .cpp .cc
LOCAL_CPP_FEATURES
该可选变量可以指定特定C++特性。如果你的代码使用RTTI特性,可以定义:
LOCAL_CPP_FEATURES := rtti
显示代码使用异常处理:
LOCAL_CPP_FEATURES := exceptions
可以同时定义多个特性(顺序无关)
LOCAL_CPP_FEATURES := rtti features
该变量可以在编译模块时正确得设置编译/链接的标志。该变量可以保证进行正确的来链接生成二进制文件。
推荐使用该变量来替代在LOCAL_CPPFLAGS中直接定义 -frtti和-fexceptions
LOCAL_C_INCLUDES
包含文件路径的可选变量,该路径会在编译时添加到包含目录下,例如:
LOCAL_C_INCLUDES := sources/foo
甚至这样也是可以的:
LOCAL_C_INCLUDES := $(LOCAL_PATH)/../foo
这些定义被放在LOCAL_CFLAGS/LOCAL_CPPFLAGS之前
LOCAL_C_INCLUDE路径也被用在自动使用ndk-gdb调试之中。
LOCAL_CFLAGS
可选的编译标识,也可以用来添加额外的宏定义或者编译选项。
重要:不要修改任何Android.mk中的优化、调试级别,这些会在指定Andorid.mk文件中的目标信息时自动化处理,ndk也会在调试时自动生成调试文件。
注意:在android-ndk-1.5_r1中,只在C源代码中产生作用(现在可以使用LOCAL_CPPFLAG来指定编译选项)。
可以使用LOCAL_CFLAGS+=-I<path>来指定附加包含目录。由于ndk-gdb会使用该路径,最好使用LOCAL_C_INCLUDES来实现该功能。
LOCAL_CXXFLAGS
LOCAL_CPPFLAG的别名,注意该变量可能在未来的NDK版本中被抛弃。
LOCAL_CPPFLAGS
在编译C++源码时的一个可选编译选项的变量。该变量会出现在LOCAL_CFLAGS之后
注意:在android-ndk-1.5-r1中,该变量适用于C和C++格式的源码。(现在也可以使用LOCAL_CFLAGS来指定所有C/C++源码编译选项)
LOCAL_STATIC_LIBRARIES
列出所有当前模块所以来的静态库。
如果当前模块时一个动态库或者可运行文件,这些静态库会被强制连接到生成的二进制文件中。
如果当前文件是一个静态库,仅仅会通知其他模块该模块会依赖这些列出的静态库
LOCAL_SHARED_LIBRARIES
列出该模块在运行时所依赖的动态库。他会在链接时嵌入这些动态库的一些信息,因此这些信息时很有必要的。
LOCAL_WHOLE_STATIC_LIBRARIES
LOCAL_STATIC_LIBRARIES的一个变种,用来表达对应的二进制模块应该当做”完整的文件“来连接。
LOCAL_LDLIBS
创建动态库或者可运行文件时的一些其他链接选项。例如,当ld.gold为默认时,下面会在ARM/X86 GCC 4.6+下使用ld.bfd链接器链接
LOCAL_LDFLAGS += -fuse-ld=bfd
注意:该选项在编译静态库时会被忽略,ndk会在你定义该变量时发出一个警告信息
LOCAL_ALLOW_UNDEFINED_SYMBOLS
默认情况下,在动态库中引用一个未定义的符号时,会产生一个”undefined symbol“错误。这会对你在源码中找出错误有很大的帮助。
然而,如果一些原因让你需要禁用这个检查,设置这个值为‘true’即可。注意生成的二进制文件可能在运行时失败。
注意:该选项在编译静态库时会被忽略,ndk会在你定义该变量时发出一个警告信息
LOCAL_ARM_MODE
默认情况下,ARM的目标程序的指令是‘thumb’模式的,‘thumb’模式下的指令长度是16位的,并且链接了thumb STL库。可以使用该变量来生成’arm’指令。该指令是32位的。使用实例:
LOCAL_ARM_MODE := arm
注意:可以指定以’.arm’后缀源码生成arm格式的汇编指令,例如:
LOCAL_SRC_FILES := foo.c bar.c.arm
告诉编译器永远编译bar.成arm指令格式的,而foo.c的类型由LOCAL_ARM_MODE决定。
注意:在Android.mk中设置APP_OPTIM为‘debug’后,也会生成arm格式的文件。这是由于工具链中的调试工具对thumb指令不能很好得处理造成的。
LOCAL_ARM_NEON
当定义该变量时,允许C/C++/汇编代码使用NEON技术。
只能当使用基于ARMv7指令集的”armeabi-v7a”才能定义。注意并不是所有ARMv7系列的CPU都支持NEON扩展指令集的。
此外,还可以通过添加‘.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’后缀之后。
LOCAL_DISABLE_NO_EXECUTE
Android NDK r4添加了”NX bit“安全特性的支持。它默认时开启的,可以通过设置该值为‘true’来关闭它。
LOCAL_DISABLE_RELRO
By default, NDK compiled code is built with read-only relocations and GOT protection. This instructs the runtime linker to mark certain regions of memory as being read-only after relocation, making certain security exploits (such as GOT overwrites) harder to perform.
It is enabled by default, but you can disable it if you really need to by setting this variable to ‘true
‘.
NOTE: These protections are only effective on newer Android devices ("Jelly Bean" and beyond). The code will still run on older versions (albeit without memory protections).
参考http://blog.chinaunix.net/uid-24203478-id-3298210.html
LOCAL_DISABLE_FORMAT_STRING_CHECKS
By default, NDK compiled code is compiled with format string protection. This forces a compiler error if a non-constant format string is used in a printf style function.
It is enabled by default, but you can disable it if you really need to by setting this variable to ‘true
‘.
LOCAL_EXPORT_CFLAGS
该变量会记录当前模块中设定的C/C++编译选项。当其他模块使用LOCAL_STATIC_LIBRARIES或LOCAL_SHARED_LIBRARIES引用该模块时,LOCAL_CFLAGS会自动添加上述设定的C/C++编译选项。
include $(CLEAR_VARS) LOCAL_MODULE := foo LOCAL_SRC_FILES := foo/foo.c LOCAL_EXPORT_CFLAGS := -DFOO=1 include $(BUILD_STATIC_LIBRARY)
其他模块定义
include $(CLEAR_VARS) LOCAL_MODULE := bar LOCAL_SRC_FILES := bar.c LOCAL_CFLAGS := -DBAR=2 LOCAL_STATIC_LIBRARIES := foo include $(BUILD_SHARED_LIBRARY)
这样,下面模块的编译标识会被设置成‘-DFOO=1
-DBAR=2
‘
导出标识在内部表现为LOCAL_CFLAGS,因此可以很方便得使用。这种特性是可以传递的:比如‘zoo’依赖于‘bar’,而‘bar’依赖于‘foo’,那么‘zoo’也会继承‘foo’的导出符号。
LOCAL_EXPORT_CPPFLAGS
和LOCAL_EXPORT_CFLAGS类似,但是仅仅包含C++标识。
LOCAL_EXPORT_C_INCLUDES
和LOCAL_EXPORT_CFLAGS类似,但是只用来包含C头文件路径。当’bar.c’需要包含‘foo’所提供的头文件时使用
LOCAL_EXPORT_LDFLAGS
和LOCAL_EXPORT_CFLAGS类似,但是用来处理链接符号。
LOCAL_EXPORT_LDLIBS
和LOCAL_EXPORT_CFLAGS类似,通常会设置一个以‘-l’为前缀的指定系统库。由于Unix编译器的工作方式,导入的链接标识会添加到模块的LOCAL_LDLIBS中。
当‘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)
因为依赖于‘foo’,libbar.so编译时会在链接命令的末尾添加-llog选项用来显示它会依赖系统的日志库
LOCAL_SHORT_COMMANDS
当模块中由很多源文件生成或者依赖静态或者动态库时设置该标识为’true’,他会强制编译系统使用内部的列表文件,并且使用一个二进制压缩文件或者静态链接到@语法。
这在windows系统中非常有用,命令行最多接收8191个字节,对一个复杂的工程来说这是远远不够的。
这会影响每一个单独的文件,几乎在每一个内部列出的文件加上所有的编译标识。
LOCAL_THIN_ARCHIVE
在编译静态库的时候设置为‘true’。他会生成一个简单的归档文件。例如一个库文件(libfoo.a)不包含任何对象文件,只包含了它应该包含对象的文件路径。
他通常被用来减少编译输出的体积。它的缺点是这些包含的库文件不能被移动到其他路径下去(所有的路径都被内部依赖)。
有效的值是‘true’,‘false’或者为空。可以通过APP_THIN_ARCHIVE来设置默认值。
注意:该命令在非静态库或者预编译静态库中会被忽略。
LOCAL_FILTER_ASM
在命令行中定义该变量用来过滤来自LOCAL_SRC_FILES中定义的或者生成的汇编文件。
当它被定义后,将会发生以下的事情:
- 一些C或者C++源文件被生成到一个临时的汇编文件中(而不是编译成一个object文件)
- 一些临时汇编文件,和在LOCAL_SRC_FILES中列出的汇编文件被发送到LOCAL_FILTER_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“对应汇编器。过滤器是第一个输入的文件当做它第一个参数的命令,而输出文件的名称是它的第二个参数。
myasmfilter $OBJS_DIR/foo.S.original $OBJS_DIR/foo.S myasmfilter bar.S $OBJS_DIR/bar.S