Android.mk基础
1、前言
Android.mk用于向编译系统描述源文件和共享库,它实际上是编译系统解析一次或多次的微小GNU makefile片段。它的语法支持将源文件分组为模块,模块是静态库、共享库或独立的可执行文件。
2、简单示例
首先来看一个最简单的Android.mk的例子,如下所示:
# A simple Android.mk LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES := hello.c LOCAL_MODULE := helloworld include $(BUILD_EXECUTABLE)
对该Android.mk文件进行解析,如下:
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_PATH,这个清理动作是必须的,因为所有的编译控制文件由同一个GNU Make解析和执行,其变量是全局的,清理后才能避免相互影响。
LOCAL_SRC_FILES := hello.c
LOCAL_SRC_FILES变量必须包含将要打包成模块的C/C++源文件,不必列出头文件,编译系统会自动找出依赖的文件,缺省的C++源码的拓展名为.cpp,也可以通过LOCAL_CPP_EXTENSION进行修改。
LOCAL_MODULE := helloworld
LOCAL_MODULE模块必须定义,以表示Android.mk中的每一个模块,名字必须唯一并且不包含空格,Build System会自动添加适当的前缀和后缀。
include $(BUILD_EXECUTABLE)
BUILD_EXECUTABLE是编译系统提供的一个变量,指向一个特定的GNU Makefile脚本,表示要编译成一个可执行的文件,如果想编译成动态库则可以用BUILD_SHARED_LIBRARY,如果想编译成静态库则可以用BUILD_STATIC_LIBRARY。
3、保留的变量
在编写Android.mk文件时应该使用或者定义的变量,可以视具体情况定义其他的变量,但是NDK编译环境保留以下变量名:
(1)LOCAL_开头的变量名(例如:LOCAL_MODULE)
(2)PRIVATE、NDK_以及APP_开头的变量名(内部使用)
(3)小写命名(内部使用,如my-dir)
如果要在Android.mk文件中定义自己的变量,建议使用前缀MY_开头,例如:
MY_SOURCES := foo.c ifneq ($(MY_CONFIG_BAR),) MY_SOURCES += bar.c LOCAL_SRC_FILES += $(MY_SOURCES)
4、NDK提供的变量
GNU Make在解析Android.mk文件之前由编译环境定义的一些变量,需要注意的是,在某些条件下,NDK可能会多次解析你的Android.mk文件,每一次都会为某些变量进行不同的定义。
(1)CLEAR_VARS
指向一个脚本,取消定义在模块变量下面几乎所有的LOCAL_XXX的定义,必须在一个新的模块下面包含这个脚本,如:
include $(CLEAR_VARS)
(2)BUILD_SHARED_LIBRARY
指向一个脚本,收集你提供的所有LOCAL_XXX变量的信息,决定如何根据你列出的源文件编译对应的共享库,注意,在包含这个脚本之前,你必须至少已经定义了LOCAL_MODULE和LOCAL_SRC_FILES,如:
include $(BUILD_SHARED_LIBRARY)
注意:这会生成一个名为lib$(LOCAL_MODULE).so文件。
(3)BUILD_STATIC_LIBRARY
该变量类似于BUILD_SHARED_LIBRARY,用来编译生成静态库,静态库不会拷贝到你的工程,但是可以用来生成共享库,如:
include $(BUILD_STATIC_LIBRARY)
注意:这会生成一个名为lib$(LOCLA_MODULE).a文件。
(4)PREBUILT_SHARED_LIBRARY
指定一个脚本,用来指定一个预置的共享库,和BUILD_SHARED_LIBRARY以及BUILD_SHARED_LIBRARY不同的是,LOCAL_SRC_FILES的值必须是一个预置共享库的路径,而不是一个源文件,如foo/libfoo.so文件,还可以在另一个模块中使用LOCAL_PREBUILTS变量来引用预置库。
(5)PREBUILT_STATIC_LIBRARY
和PREBUILT_SHARED_LIBRARY类似,但指定一个静态库。
(6)TARGET_ARCH
由AOSP指定的CPU架构名称,arm表示与ARM兼容。
(7)TARGET_PLATFORM
解析Android.mk时,目标Android平台的名称。
(8)TARGET_ARCH_ABI
解析Android.mk时,目标CPU+ABI的名称,如armeabi-v7a。
(9)TARGET_ABI
目标平台和abi的连接,由$(TARGET_PLATFORM)-$(TARGET_ARCH_ABI)定义。
5、NDK提供的函数宏
下面是GNU Make的宏方法,必须使用$(call <function>)的形式使用,返回文本信息。
(1)my-dir
返回上一个包含Makefile文件的路径,通常是当前Android.mk的目录,在Android.mk文件起始位置定义LOCAL_PATH时很有用,如:
LOCAL_PATH := $(call my-dir)
注意:由于GNU Make的工作方式,该方法返回在解析编译脚本时最后一次包含Makefile文件的路径,在包含其他文件之后不要调用my-dir,例如下面的例子:
# Android.mk LOCAL_PATH := $(call my-dir) # Declare one module ... include $(LOCAL_PATH)/foo/Android.mk # Declare another module ...
这里的问题是,第二次调用my-dir会将LOCAL_PATH定义为$(LOCAL_PATH)/foo/而不是$(LOCAL_PATH)。
基于上面的这个原因,在一个Android.mk文件中,将额外的包含动作放在所有内容的后面,如下:
# Android.mk LOCAL_PATH := $(call my-dir) # Declare one module ... LOCAL_PATH := $(call my-dir) # Declare another module ... # Extra include at the end of Android.mk include $(LOCAL_PATH)/foo/Android.mk
还可以用一个变量保存第一次调用my-dir的值,如下:
# Android.mk MY_LOCAL_PATH := $(call my-dir) LOCAL_PATH := $(MY_LOCAL_PATH) # Declare one module ... includ $(LOCAL_PATH)/foo/Android.mk LOCAL_PATH := $(MY_LOCAL_PATH) # Declare another module ...
(2)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)
那么,source/foo/lib1/Android.mk和source/foo/lib2/Android.mk会自动地被包含进来。
(3)this-makefile
返回当前Makefile文件的路径。
(4)parent-makefile
返回包含树中父Makefile文件的路径,如包含当前Makefile的Makefile的路径。
(5)grand-parent-makefile
与parent-makefile类似。
(6)import-module
该方法通过名字查找和包含另一个模块,通常的用法是:
$(call import-module,<name>)
该方法会在NDK_MODULE_PATH环境变量中查找标签为<name>的模块,然后自动地将它的Android.mk包含进来。
6、模块描述变量
下面的变量用来向编译环境描述模块,应该在include $(CLEAR_VARS)和include $(BUILD_XXX)之间定义这些变量,$(CLEAR_VARS)会取消定义或者清除所有这些变量。
(1)LOCAL_PATH
这个变量表示当前文件所在的路径,必须在Android.mk的开始处定义,可以由下面的命令来完成:
LOCAL_PATH := $(call my-dir)
$(CLEAR_VARS)不会清除该变量,因此,每个Android.mk只需定义一次即可。
(2)LOCAL_MODULE
模块的名字,每一个模块的名字必须唯一并且不含空格,必须在包含$(BUILD_XXX)脚本之前定义这个变量,默认的,这个名字决定了生成文件的名字,如名为<foo>的模块,共享库的名字为lib<foo>.so。
(3)LOCAL_MODULE_FILENAME
这个变量是可选的,帮助重新定义生成文件的名字,默认的情况下,<foo>模块会按照Unix的一般标准生成名为lib<foo>.a的静态库或者名为lib<foo>.so的共享库,可以通过LOCAL_MODULE_FILENAME重新定义生成文件的名字,如下:
# Android.mk LOCAL_MODULE := foo-version-1 LOCAL_MODULE_FILENAME := libfoo
注意:不要在LOCAL_MODULE_FILENAME中放路径或者拓展名,这些会被编译系统自动处理。
(4)LOCAL_SRC_FILES
用来编译的模块的源文件列表,只需要列出需要被传给编译器的文件,编译环境会自动计算依赖关系,所有的源文件名是相对LOCAL_PATH的,也可以使用路径分隔符,如:
LOCAL_SRC_FILES := foo.c \
fun/bar.c
(5)LOCAL_CPP_EXTENSIONS
这是一个可选的变量,用来指示C++的源文件的文件拓展名,默认的是.cpp,但是可以改变它,如:
LOCAL_CPP_EXTENSION := .cxx
(6)LOCAL_C_INCLUDES
这是可选的变量,相对NDK根目录的相对路径列表,在编译所有的源文件时会被添加到搜索路径后面,如:
LOCAL_C_INCLUDES := sources/foo
or
LOCAL_C_INCLUDES := $(LOCAL_PATH)/../foo
它们需要放置在LOCAL_CFLAGS/LOCAL_CPPFLAGS之前,在使用ndk-gdb进行native调试时将自动使用LOCAL_C_INCLUDES路径。
(7)LOCAL_CFLAGS
这是一个可选的变量,编译C/C++源文件时的编译器flags,可以用来指定额外的宏定义和编译选项。
(8)LOCAL_CXXFLAGS
LOCAL_CPPFLAGS的别名。
(9)LOCAL_CPPFLAGS
可选的变量,编译C++文件时编译器flags,在编译器命令行中,他们出现在LOCAL_CFLAGS之后。
(10)LOCAL_STATIC_LIBRARIES
需要链接到这个模块的静态库module(使用BUILD_STATIC_LIBRARY编译的)列表,只在共享库中有用。
(11)LOCAL_SHARED_LIBRARIES
该模块在运行时需要依赖的共享库列表,在链接时需要,并在生成文件中嵌入相应的信息。
(12)LOCAL_LDLIBS
编译模块时需要的额外链接flags列表,传递指定的以-l为前缀的系统库,例如,下面的指令会告诉链接器生成一个在加载的时候链接到/system/lib/libz.so的模块:
LOCAL_LDLIBS := -lz
(13)LOCAL_ALLOW_UNDEFINED_SYMBOLS
默认情况下,在编译共享数据库时会遇到未定义的引用,导致未定义符号的错误,这对在源码中抓取log的作用很大,但是,如果考虑到某些原因需要关闭这个选项,将变量设置为true即可。
(14)LOCAL_ARM_MODE
默认情况下,以thumb模式生成ARM目标二进制,每一个指令都是16比特宽,如果想强制生成arm(32比特指令)模式下的模块,可以定义这个变量为arm,如下:
LOCAL_ARM_MODE := arm
也可以通过在源文件名称后面添加后缀.arm的方式编译系统以指定的arm模式编译源文件,如:
LOCAL_SRC_FILES := foo.c bar.c.arm
这会告诉编译系统以arm模式编译bar.c,但根据LOCAL_ARM_MODE的值编译foo.c。
(15)LOCAL_ARM_NENO
定义这个变量为true允许在C/C++文件中使用ARM Advanced SIMD GCC(也叫NEON)指令,就像在程序集文件中使用NEON指令一样。
(16)LOCAL_CFLAGS
定义这个变量来记录一组C/C++编译flags,这些会被添加到使用这个变量的模块的LOCAL_STATIC_LIBRARIES或者LOCAL_SHARED_LIBRARIES的LOCAL_CFLAGS中,例如:
# Define "foo" module include $(CLEAR_VARS) LOCAL_MODULE := foo LOCAL_SRC_FILES := foo/foo.c include $(BUILD_STATIC_LIBRARY) # Define "bar" module include $(CLEAR_VARS) LOCAL_MODULE := bar LOCAL_SRC_FILES := bar.c LOCAL_CFLAGS := -DBAR=2 LOCAL_STATIC_LIBRARYS := foo include $(BUILD_SHARED_LIBRARY)
在编译bar.c时会将-DFOO=1 –DBAR=2传给编译器,exported flags会被一层一层的传递,如果zoo依赖于bar,而bar依赖于foo,那么foo中的所有flags会被传给zoo,exported flags在当前module编译时不会被使用,在上面,编译foo/foo.c时不会传递-DFOO=1。
(17)LOCAL_EXPORT_CPPFLAGS
和LOCAL_EXPORT_CFLAGS一样,但是只对C++文件。
(18)LOCAL_EXPORT_C_INCLUDES
和LOCAL_EXPORT_CFLAGS一样,但记录的是C头文件路径。
(19)LOCAL_EXPORT_LDLIBS
和LOCAL_EXPORT_CFLAGS一样,记录链接器flags,导入的链接器flags会被添加到模块的LOCAL_LDLIBS中,这由Unix链接器的工作方式决定,当foo是一个静态库并且部分代码依赖系统库,该变量将会很有用,LOCAL_EXPORT_LDLIBS可以被用来导出依赖,如:
# Define "foo" module include $(CLEAR_VARS) LOCAL_MODULE := foo LOCAL_SRC_FILES := foo/foo.c LOCAL_EXPORT_LDLIBS := -llog include $(BUILD_STATIC_LIBRARY) # Define "bar" module include $(CLEAR_VARS) LOCAL_MODULE := bar LOCAL_SRC_FILES := bar.c LOCAL_STATIC_LIBRARY := foo include $(BUILD_SHARED_LIBRARY)
这里,libbar.so在链接时编译-llog,表示它依赖于系统日志库,因为它依赖于foo。
(20)LOCAL_FILTER_ASM
使用一个shell命令定义这个变量,用来过滤LOCAL_SRC_FILES中的或者由其生成的程序集文件。