NDK学习笔记:Android.mk

最近在搞NDK编程时发现一个比较棘手的问题,就是关于编写Android.mk文件时,如果工程比较复杂,我们自己编写的Android.mk就会比较复杂。关于这个makefile的编写,我们可以参考http://android.mk/查找Android.mk的一些编写规则,在此我将http://android.mk/中的内容做了一些简单的翻译,其中也加入了一些自己的理解,可能有不妥之处,欢迎大家及时指正。

简介

Android.mk用于描述ndk项目构建的makefile的一部分。Android.mk与我们所了解的其他.mk文件一样,是makefile的一个片段,会被编译系统解析,官方推荐我们不要定义过多变量或者,同时需要明确哪些变量已经被系统所定义。

Android.mk最终要做的事情是把我们的源文件组织起来,编译生成modules,这些modules分两种形式,即动态链接库和静态链接库。只有动态链接库会被打包到我们的apk中,而静态链接库可以用来生成其他的动态链接库。

ndk会为我们做很多事情,比如我们在写Android.mk文件时不需要加入.h,ndk会自动帮助我们构建相关依赖文件的。这样即使更换新版本的ndk,也不必去修改Android.mk文件。

例子

在讲解语法之前先举一个简单的例子 “helloJNI”

其中 src文件夹中存放java源文件,而jni文件夹中存放c文件,此处为hello-jni.c

下面时jni/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开头都是这句话,用来指定当前的工作路径,my-dir是系统为我们提供的一个宏,就是当前执行的这个Android.mk所在的目录。后面我们要查找的源文件都是相对于这个路径的。

include $(CLEAR_VARS)

CLEAR_VARS是ndk给我们提供的一个宏,它指向一个特殊的makefile,用来清除我们之前设置过的LOCAL_XXX中的值。这个东西��️什么用呢,举个例子,假如我们需要编译多个不同的模块,最后将这些模块都打包到我们的apk中,那么我们在编译完毕第一个模块后,编译第二个模块时,很多ndk提供变量还是第一个模块的值,这样就没办法保证正确编译了。所以,我们需要将这些变量清除,重新开始配置第二个模块的变量。

LOCAL_MODULE := hello-jni

我们所编译的每个模块都要指定这个参数,这是这个模块的名字,ndk往往会根据生成的类型自动为这个模块加上前缀和后缀。一般前缀都是lib,后缀根据你所指定的参数,生成动态库加.so,生成静态库加.a,这点和普通的linux系统规则是一样的。

LOCAL_SRC_FILES := hello-jni.c

指定了当前模块依赖的本地源文件的路径,此处只需指定c与c++文件,头文件系统会自动为我们寻找,如果需要添加多个源文件只需要在后面加空格然后再写文件名即可。需要注意的是LOCAL_CPP_EXTENSION变量可以指定c++文件的后缀名,写法是’.cxx’,注意不要落下那个点啊。

include $(BUILD_SHARED_LIBRARY)

这一步决定了你所编译模块的性质,是直接打包的到apk中的动态库,还是其他文件依赖的静态库,或者是直接引入别人编译好的库,都是最后一句话来指定。此处BUILD_SHARED_LIBRARY也会指向一个特殊的makefile文件,里面是用来生成动态共享库的方法,当然,如果我们指定为BUILD_STATIC_LIBRARY,那么最终将生成一个静态链接库。

这里还需要补充一下,我们在makefile中引用子makefile一般使用include命令,’$’符号用来取变量的值。

扩展

下面介绍一些NDK的内置变量,一般内置变量的命名遵循以下规则

1.以 ‘LOCAL_’ 开头 (e.g. LOCAL_MODULE)
2.以 ‘PRIVATE_’, ‘NDK_’ or ‘APP_’开头
3.小写字母 (e.g. ‘my-dir’)

官方推荐使用’MY_’作为自定义变量的前缀,下面是官方文档给出的例子:

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

LOCAL_SRC_FILES += $(MY_SOURCES)

NDK内置变量宏

CLEAR_VARS
清空所有LOCAL_XXX变量的值,在编译每个新的模块之前都调用一次

include $(CLEAR_VARS)

BUILD_SHARED_LIBRARY
将所指定的模块编译为动态共享库,我们必须使用LOCAL_SRC_FILES指定需要的源代码文件,LOCAL_MODULE指定模块名。

include $(BUILD_SHARED_LIBRARY)

最终将生成lib$(LOCAL_MODULE).so文件

BUILD_STATIC_LIBRARY
这个变量是将目标编译为一个静态库,我们可以把静态库理解为将代码编译后再使用ar工具打包,这里的内容并不会被直接运行,而是通过在编译时静态链接的方式生成可执行文件或者动态共享库,所以,这里生成的静态链接库不会被打包进最终的可执行文件。

include $(BUILD_STATIC_LIBRARY)

最终将生成lib$(LOCAL_MODULE).a

PREBUILT_SHARED_LIBRARY
试想这样一种情景,别人已经编译好了一个动态共享库文件,而我们需要直接把这个文件打包到apk中进行调用。前面提到使用BUILD_SHARED_LIBRARY必须指定源代码文件,显然我们这里没有源代码,遇到这种问题,我们就可以将这个编译好的文件使用PREBUILT_SHARED_LIBRARY打包的我们的apk中了。

PREBUILT_STATIC_LIBRARY
作用同PREBUILT_SHARED_LIBRARY,就是直接编译的时候直接拿来别人的静态库文件。比如我们在使用OpenCV提供的Android开发包时,我们会发现都是已经编译好的.a文件,而OpenCV.mk文件中使用这些文件就是用的PREBUILT_STATIC_LIBRARY,代码如下:

define add_opencv_module
include $(CLEAR_VARS)
LOCAL_MODULE:=opencv_$1
    LOCAL_SRC_FILES:=$(OPENCV_LIBS_DIR)/libopencv_$1.$(OPENCV_LIB_SUFFIX)
include $(PREBUILT_$(OPENCV_LIB_TYPE)_LIBRARY)
endef

TARGET_ARCH
cpu架构名称,如果这个值为’arm’,则说明兼容arm指令集

TARGET_PLATFORM
指定目标平台的Android版本号

TARGET_ARCH_ABI
指定当前cpu的abi。这个变量比较重要,如果我们想同时编译多种abi的文件,那么就可以使用这个变量。举个例子,我们现在有armeabi-v7a和x86的两种编译好的.so文件,现在我们要使用这两种.so文件编译出两个最终的文件,我们可以写个for循环分别寻找动态库进行编译,但是有了这个变量就可以直接使用,定义几个量,编译器会为我们自动生成几种abi的文件。示例如下:

include $(CLEAR_VARS)
LOCAL_MODULE := target
LOCAL_SRC_FILES := ../jniLibs/$(TARGET_ARCH_ABI)/libtarget.so
include $(PREBUILT_SHARED_LIBRARY)

使用上述代码,编译器会自动到TARGET_ARCH_ABI的每个值的文件中寻找对应的.so,最终生成多种abi的.so文件。

TARGET_ABI
(TARGETPLATFORM) (TARGET_ARCH_ABI),目前还没有领会这个变量的用法。

NDK内置方法宏

方法宏的调用方式为 ‘$(call )’ ,可以返回对应的文本值。

my-dir
返回当前最后一个运行着的makefile文件所在的路径,一般用来在编译模块的开头指定LOCAL_PATH.

LOCAL_PATH := $(call my-dir)

官方文档提示我们如果include了其他makefile之后,最好不要调用my-dir,因为这个时候my-dir可能时被调用的makefile所在文件的路径。我们看下面这个例子

LOCAL_PATH := $(call my-dir)

... declare one module

include $(LOCAL_PATH)/foo/Android.mk

LOCAL_PATH := $(call my-dir)

... declare another module

由于中途include了一个.mk文件,第二次调用my-dir后返回的路径是$(LOCAL_PATH)/foo/。

避免这种问题出现,我们最好将调用其他makefile文件的步骤放到最后,代码如下

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_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
返回第一个makefile中的my-dir路径下所有的(包括文件夹中)的makefile文件。
我们可以使用如下代码

include $(call all-subdir-makefiles)

这样就把所有的工作目录下所有的makefile文件都引入了。

this-makefile
返回当前makefile文件所在路径

parent-makefile
调用当前makefile的makfile的路径

grand-parent-makefile

import-module
从其他module中通过module名拿到Android.mk文件。

$(call import-module,<name>)

这句话将在NDK_MODULE_PATH环境变量中找名叫的模块,找到后将它的Android.mk文件include进来.

编译模块变量

下面的变量用来描述模块的构建,一般定义在’include (CLEARVARS)include (BUILD_XXXXX)’语句之间。’include $(CLEAR_VARS)’会清除大多数这些变量的值。

LOCAL_PATH
这个变量放在Android.mk文件的开头,用来指明当前的工作目录。一般定义如下

LOCAL_PATH := $(call my-dir)

这个变量是不会被$(CLEAR_VARS)所清除,在Android.mk中只需要定义一次就可以了。

LOCAL_MODULE
这个变量用于指定模块的名字,每个模块的名字不能相同,同时这个名字也不能包含空格,每次编译一个模块,也就是使用$(BUILD_XXXX)语句时都必须指定这个变量。
举个例子,生成 lib.so 类型的模块的名字,我们需要将LOCAL_MODULE指定为’foo’。当然我们可以自己指定生成文件的名字,LOCAL_MODULE_FILENAME变量。下面我们就介绍这个变量。

LOCAL_MODULE_FILENAME
这个变量不是必须指定的。LOCAL_MODULE默认会将文件名变成Unix标准形式的库,我们可以通过指定LOCAL_MODULE_FILENAME的值,指定最终生成模块的名字,用法如下

LOCAL_MODULE := foo-version-1
LOCAL_MODULE_FILENAME := libfoo

LOCAL_SRC_FILES
这个变量用来指定生成模块的源文件。这个变量指定的文件列表将传递给ndk,由ndk为我们自动构建依赖关系。
用法如下

LOCAL_SRC_FILES := foo.c \
                   toto/bar.c

一般Unix类系统可以’/’作为文件分割符,注意在windows下用这样的文件分隔符可能会有问题。

LOCAL_CPP_EXTENSION
可选变量,用来指定c++文件的扩展名。用法如下

LOCAL_CPP_EXTENSION := .cxx .cpp .cc

LOCAL_CPP_FEATURES
可选变量,用来指定c++特性。
加入rtti特性

LOCAL_CPP_FEATURES := rtti

加入exceptions

LOCAL_CPP_FEATURES := exceptions

官方推荐使用在这里指定这些编译器相关的选项,而不是在LOCAL_CPPFLAGS中指定。

LOCAL_C_INCLUDES
可选变量,指定头文件所在路径,这个变量的值在编译时会被追加到编译器的include路径中。
使用方法如下

LOCAL_C_INCLUDES := sources/foo

LOCAL_C_INCLUDES := $(LOCAL_PATH)/../foo

这个变量需要声明在 LOCAL_CFLAGS / LOCAL_CPPFLAGS 之前。

LOCAL_CFLAGS
编译器的编译选项,也可以在此增加宏定义。
官方文档中不推荐在此修改默认的优化级别,这些选项最好使用默认,另外这样ndk也会生成debug用的文件。
这个变量也可以指定头文件路径,用法如下

LOCAL_CFLAGS += -I<path>

但是我们推荐使用LOCAL_C_INCLUDES指定头文件,因为这个路径会被用作ndk-gdb调试。

LOCAL_CXXFLAGS
LOCAL_CPPFLAGS变量的别名,这个变量已经废弃。

LOCAL_CPPFLAGS
可选变量,用来设置c++文件的编译选项。如果想同时设置c和c++文件的选项,可以使用LOCAL_CFLAGS。

LOCAL_STATIC_LIBRARIES
将静态链接库链接到当前的模块中,这个变量仅在要编译的模块为动态共享库时有效。

LOCAL_SHARED_LIBRARIES
指定这个模块在运行时依赖的动态共享库。

LOCAL_WHOLE_STATIC_LIBRARIES
LOCAL_STATIC_LIBRARIES变量的一个衍生变量,表示这个模块被用作”whole archives”, 相当于 –whole-archive 选项。这里指定的静态库中的函数会被包含到生成的动态库中,其会将静态库的符号导入动态库的符号表中。
这个参数用于多个静态库相互依赖的情况,这个参数会使静态库整个被链接到生成的动态共享库中,但是不适用于生成可执行文件。

LOCAL_LDLIBS
链接选项,用于指定链接系统的库,需要加前缀”-l”,举个例子,我们在加载时要链接” /system/lib/libz.so”文件,使用如下语句:

LOCAL_LDLIBS := -lz

LOCAL_ALLOW_UNDEFINED_SYMBOLS
正常情况下,在编译动态链接库时遇到未定义参数时会报错。但是如果我们知道在什么时候加载这个为定义的变量,我们就可以屏蔽这个错误,将这个变量赋值为true。

LOCAL_ARM_MODE
对于arm,编译默认使用thumb指令集,如果需要强制使用32位宽度arm的指令集,使用如下语句:

LOCAL_ARM_MODE := arm

也可以将某些源文件单独编译为32位arm指令集的目标文件,使用如下语句:

LOCAL_SRC_FILES := foo.c bar.c.arm

这样bar.c就被强制编译为arm指令集的目标文件。

LOCAL_ARM_NEON
这个指令赋值为true泽使用NEON指令集编译。部分armv7的处理器支持NEON指令。同样,我们可以将指定文件编译为NEON指令的目标,使用’.neon’后缀,如下:

LOCAL_SRC_FILES = foo.c.neon bar.c zoo.c.arm.neon

注意,如果arm与neno都要添加,.neon必须放在.arm后面

LOCAL_DISABLE_NO_EXECUTE
开启”NX bit”安全选项,默认是开启的,如果确实需要关闭cpu的”禁止执行”模式,可以将这个变量设置为true。这个变量会作用于armv6以上的cpu。

LOCAL_DISABLE_RELRO
加载动态链接库时不允许修改其全局重定位表(? Global Offset Table), 用于保护系统。

LOCAL_EXPORT_CFLAGS
这个变量的值将被赋予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’都将被定义,但是在编译foo.c时则不会有定义FOO。

LOCAL_EXPORT_CPPFLAGS
作用同上,用于c++文件编译。

LOCAL_EXPORT_C_INCLUDES
这个变量类似LOCAL_EXPORT_CFLAGS,用于当bar.c希望饮用foo模块中的头文件时导入头文件。

LOCAL_EXPORT_LDLIBS
用作链接选项。值将会被追加到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)

此时,生成的libbar.so在动态链接时会去链接系统的log。

LOCAL_SHORT_COMMANDS
如果源文件或者链接库过多则将这个变量置为true,这样编译器会生成一些使用@$(listfile)语法中间文件,用于减少指令长度,主要用在win下。

LOCAL_FILTER_ASM
这个指令会让我们的c/c++文件先生成汇编文件,然后汇编文件再进行编译。

LOCAL_SRC_FILES  := foo.c bar.S
 LOCAL_FILTER_ASM := myasmfilter

会导致如下编译结果,汇编文件bar.s不变,foo.c多生成一个汇编文件。

foo.c –1–> OBJSDIR/foo.S.original2> OBJS_DIR/foo.S –3–> $OBJS_DIR/foo.o

bar.S –2–> OBJSDIR/bar.S3> OBJS_DIR/bar.o

这个变量的值必须与shell中的变量名不同,其后面第一个参数是输入文件,第二个参数是输出文件,示例如下:

myasmfilter $OBJS_DIR/foo.S.original $OBJS_DIR/foo.S
myasmfilter bar.S $OBJS_DIR/bar.S

NDK_TOOLCHAIN_VERSION
指定gcc工具链的版本。

Android.mk构建apk文件

原文给出了使用Android.mk构建apk的例子,记录如下:

Building a simple APK

 LOCAL_PATH := $(call my-dir)
  include $(CLEAR_VARS)

  # Build all java files in the java subdirectory
  LOCAL_SRC_FILES := $(call all-subdir-java-files)

  # Name of the APK to build
  LOCAL_PACKAGE_NAME := LocalPackage

  # Tell it to build an APK
  include $(BUILD_PACKAGE)

Building a APK that depends on a static .jar file

LOCAL_PATH := $(call my-dir)
  include $(CLEAR_VARS)

  # List of static libraries to include in the package
  LOCAL_STATIC_JAVA_LIBRARIES := static-library

  # Build all java files in the java subdirectory
  LOCAL_SRC_FILES := $(call all-subdir-java-files)

  # Name of the APK to build
  LOCAL_PACKAGE_NAME := LocalPackage

  # Tell it to build an APK
  include $(BUILD_PACKAGE)

Building a APK that should be signed with the platform key

LOCAL_PATH := $(call my-dir)
  include $(CLEAR_VARS)

  # Build all java files in the java subdirectory
  LOCAL_SRC_FILES := $(call all-subdir-java-files)

  # Name of the APK to build
  LOCAL_PACKAGE_NAME := LocalPackage

  LOCAL_CERTIFICATE := platform

  # Tell it to build an APK
  include $(BUILD_PACKAGE)

Building a APK that should be signed with a specific vendor key

LOCAL_PATH := $(call my-dir)
  include $(CLEAR_VARS)

  # Build all java files in the java subdirectory
  LOCAL_SRC_FILES := $(call all-subdir-java-files)

  # Name of the APK to build
  LOCAL_PACKAGE_NAME := LocalPackage

  LOCAL_CERTIFICATE := vendor/example/certs/app

  # Tell it to build an APK
  include $(BUILD_PACKAGE)

Adding a prebuilt APK

LOCAL_PATH := $(call my-dir)
  include $(CLEAR_VARS)

  # Module name should match apk name to be installed.
  LOCAL_MODULE := LocalModuleName
  LOCAL_SRC_FILES := $(LOCAL_MODULE).apk
  LOCAL_MODULE_CLASS := APPS
  LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)

  include $(BUILD_PREBUILT)

Adding a Static Java Library

  LOCAL_PATH := $(call my-dir)
  include $(CLEAR_VARS)

  # Build all java files in the java subdirectory
  LOCAL_SRC_FILES := $(call all-subdir-java-files)

  # Any libraries that this library depends on
  LOCAL_JAVA_LIBRARIES := android.test.runner

  # The name of the jar file to create
  LOCAL_MODULE := sample

  # Build a static jar file.
  include $(BUILD_STATIC_JAVA_LIBRARY)
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值