Android.mk语法解释

  • Post author:
  • Post category:其他


大家在编写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”模块依赖于系统的日志模块。



版权声明:本文为Roland_Sun原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。