Android NDK开发之Android.mk文件

时间:2024-04-07 10:35:00

Android NDK开发指南---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