Android NDK的构建系统简介

  Android NDK的构建系统是基于GNU Make的。该构建系统的主要目的是使开发人员能够用很短的构建文档来描述原生的Android应用程序;该构建系统还处理了包括替开发人员指定工具链、平台、CPU和ABI等很多细节。封装该构建过程可以在不改变构建文件的情况下,使Android NDK的后续更新添加更多对工具链、平台以及系统接口的支持。

  Android NDK构建系统是由多种GNU Makefile片段构成的。该构建系统包括基于渲染构建过程的不同类型NDK项目所需要的必要片段。这些构建系统片段可以在Android NDK安装程序的build/core子目录中找到。虽然开发人员并不会直接接触到这些文件,但知道它们的位置对与构建系统相关的故障很有帮助。

  除了这些片段,Android NDK构建系统还要依赖另外两个文件:Android.mk和Application.mk,这两个文件应该作为NDK项目的一部分由开发人员提供。

关于Android.mk

Android.mk

  Android.mk是一个向Android NDK构建系统描述NDK项目的GUN Makefile片段。它是每一个NDK项目的必备组件。构建系统希望它出现在jni子目录中。hello-jni项目中Android.mk文件的内容:

  # Copyright (C) 2009 The Android Open Source Project
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  #

  LOCAL_PATH := $(call my-dir)

  include $(CLEAR_VARS)

  LOCAL_MODULE := hello-jni
  LOCAL_SRC_FILES := hello-jni.c

  include $(BUILD_SHARED_LIBRARY)

为了更好的理解它的句法,我们逐行分析。因为这个是一个GNU Makefile片段,所以它的句法和其他Makefile是一样的。

每行都包含一个单独的指令,以“#”开头的是注释行,GNU Make工具不处理它们。根据命名规范,变量名要大写。

第一行:

注释块后的第一条指令是用来定义LOCAL_PATH变量的。根据Android构建系统的要求,Android.mk文档必须以LOCAL_PATH变量的定义开头。

LOCAL_PATH := $(call my-dir)

Android构建系统利用LOCAL_PATH来定位源文件。因为将该变量设置为硬编码值并不合适,所以Android构建系统提供了一个名为my-dir的宏功能。通过将该变量设置为my-dir宏功能的返回值,可以将其放在当前目录下。

第二行:

Android构建系统将CLEAR_VARS变量设置为clear-vars.mk片段的位置。包含Makefile片段可以清除除了LOCAL_PATH以外的LOCAL_<name>变量,例如LOCAL_MODULE与LOCAL_SRC_FILES等。

Include $(CLEAR_VARS)

这样做是因为Android构建系统在单次执行中解析多个构建文件和模块定义,而LOCAL_<name>是全局变量。清除它们可以避免冲突,每一个原生组件被称为一个模块。

第三行:

LOCAL_MODULE变量用来给这些模块设定一个唯一的名称。下面的代码将该模块的名称设为hello-jni:

LOCAL_MODULE := hello-jni

因为模块名称也被用于给构建过程所生产的文件命名,所以构建系统给该文件添加了适当的前缀和后缀。本例中,hello-jni模块会自动生成一个共享库文件且构建系统会将它命名为libhello-jni.so。

第四行:

用LOCAL_SRC_FILES变量定义用来建立和组装这个模块的源文件列表。

LOCAL_SRC_FILES := hello-jni.c

这里,hello-jni模块只由一个源文件生成,而LOCAL_SRC_FILES变量可以包含用空格分开的多个原文件名。

总结:

Android.mk文件中定义的构建系统变量简单描述了原生项目。编译和生成实际模块的构建系统还需要包含合适的构建系统片段,具体需要包含哪些片段取决于想要生成模块的类型。

1.构建共享库

为了建立可供主应用程序使用的模块,必须将该模块变成共享库。Android NDK构建系统将BUILD_SHARED_LIBRARY变量设置成build-shared-library.mk文件的保存位置。该Makefile片段包含了将源文件构建和组装成共享库的必要过程:

include $(BUILD_SHARED_LIBRARY)

hello-jni是一个简单的模块;然而,除非你的模块需要特殊处理,否则Android.mk文档将会包含一模一样的流程和指令。

2.构建多个共享库

基于不同的应用程序的体系结构,一个单独的Android.mk文档可能产生多个共享库模块。为了达到这个目的,需要在Android.mk文档中定义多个模块。如下所示:

LOCAL_PATH := $(call my-dir)

#

#模块 1

#

include $(CLEAR_VARS)

LOCAL_MODULE := module1

LOCAL_SRC_FILES := module1.c

include $(BUILD_SHARED_LIBRARY)

#

#模块2

#

include $(CLEAR_VARS)

LOCAL_MODULE := modele2

LOCAL_SRC_FILES := module2.c

include $(BUILD_SHARED_LIBRARY)

在处理完这个Android.mk构建文档之后,Android NDK构建系统会产生libmodule1.so和libmodule2.so两个共享库。

3.构建静态库

 Android NDK构建系统也支持静态库。实际的Android应用程序并不直接使用静态库,并且应用程序包中也不包含静态库。静态库可以用来构建共享库。例如,在将第三方代码添加到现有原生项目中时,不用直接将第三方源代码包括在原生项目中,而是将第三方代码编译成静态库后然后并入共享库,如下所示:

LOCAL_PATH := $(call my-dir)

#

#第三方AVI库

#

include $(CLEAR_VARS)

LOCAL_MODULE := avilib

LOCAL_SRC_FILES := avilib.c platform_posix.c

include $(BUILD_STATIC_LIBRARY)

#

#原生模块

#

include $(CLEAR_VARS)

LOCAL_MODULE := module

LOCAL_SRC_FILES := module.c

LOCAL_STATIC_LIBRARIES := avilib

include $(BUILD_SHARED_LIBRARY)

将第三方代码模块生成静态库之后,共享库就可以通过将它的模块名添加到LOCAL_STATIC_LIBRARIES变量中来使用该模块。

4.用共享库共享通用模块

静态库可以保证源代码模块化;但是,当静态库与共享库相连时,它就变成了共享库的一部分。在多个共享库的情况下,多个共享库与同一个静态库连接时,需要将通用模块的多个副本与不同共享库重复相连,这样就增加了应用程序的大小。在这种情况下,不用构建静态库,而是将通用模块作为共享库建立起来,而动态连接依赖模块以便消除重复的副本,如下所示:

 LOCAL_PATH := $(call my-dir)

#

#第三方AVI库

#

include $(CLEAR_VARS)

LOCAL_MODULE := avilib

LOCAL_SRC_FILES := avilib.c platform_posix.c

include $(BUILD_SHARED_LIBRARY)

#

#原生模块 1
#

include $(CLEAR_VARS)

LOCAL_MODULE := module1

LOCAL_SRC_FILES := module1.c

LOCAL_SHARED_LIBRARIES := avilib

include $(BUILD_SHARED_LIBRARY)

#

#原生模块2
#

include $(CLEAR_VARS)

LOCAL_MODULE := module2

LOCAL_SRC_FILES := module2.c

LOCAL_SHARED_LIBRARIES := avilib

include $(BUILD_SHARED_LIBRARY)

5.在多个NDK项目间共享模块

同时使用静态库和共享库时,可以在模块间共享通用模块。但要说明的是,所有这些模块必须属于同一个NDK项目。从R5版本开始,AndroidNDK也允许在NDK项目间共享和重用模块。考虑前面讲过的示例,可以通过以下步骤在多个NDK项目间共享avilib模块:

(1)首先,将avilib源代码移动到NDK项目以外的位置,例如:C:androidshared-modulesavilib。为了避免命名冲突,目录结构也可以包含模块提供者的名字,例如:C:androidshared-modules ranscodeavilib。注意:在Android NDK构建系统中,共享模块路径不能包含空格。

(2)作为共享模块,avilib需要自己的Android.mk文件,如下所示:

LOCAL_PATH := $(call my-dir)

#

#第三方AVI库

#

include $(CLEAR_VARS)

LOCAL_MODULE := avilib

LOCAL_SRC_FILES := avilib.c platform_posix.c

include $(BUILD_SHARED_LIBRARY)

现在,可以将avilib模块从NDK项目的Android.mk文件中移除。为了使用这个共享模块,将以transcode/avilib为参数调用函数宏import-module部分添加在构建文档的末尾,将以transcode/avilib为参数调用函数宏import-module部分添加在构建文档的末尾。为了避免构建系统的冲突,应该将import-module函数宏调用放在Android.mk文档的末尾。为了避免构建系统的冲突,应该将import-module 函数宏调用放在Android.mk文档的末尾。如下所示:

#

#原生模块

#

include $(CLEAR_VARS)

LOCAL_MODULE := module

LOCAL_SRC_FILES := module.c

LOCAL_SHARED_LIBRARIES := avilib

include $(BUILD_SHARED_LIBRARY)

$(call import-module,transcode/avilib)

(3)import-module函数宏需要先定位分享模块,然后再将它导入到NDK项目中,默认情况下,import-module函数宏只搜索<Android NDK>/sources目录。为了搜索c:androidshared-modules目录,定义一个名为NDK_MODULE_PATH的新环境变量并将它设置成共享模块的根目录,例如:c:androidshared-modules。

6.用Prebuilt库

使用共享模块要求有共享模块的源代码,Android NDK构建系统简单地把这些源文件包含在NDK项目中并每次构建它们。自R5版本以后,Android NDK也提供对Prebuilt库的支持。在下面的情况下,Prebuilt库是非常有用的:

(1)想在不发布源代码的情况下将你的模块发布给他人。

(2)想使用共享模块的预建版来加速构建过程。

尽管已经被编译了,但预建模块仍需要一个Android.mk构建文档,如下所示:

LOCAL_PATH := $(call my-dir)

#

#第三方预构建AVI库

#

include $(CLEAR_VARS)

LOCAL_MODULE := avilib

LOCAL_SRC_FILES := libavilib.so

include $(PREBUILT_SHARED_LIBRARY)

LOCAL_SRC_FILES变量指向的不是源文件,而是实际Prebuilt库对于LOCAL_PATH的位置。

注意:Prebuilt库定义中不包含任何关于该库所构建的实际机器体系结构的信息。开发人员需要确保Prebuilt库是为与NDK项目相同的机器体系结构而构建的。

PREBUILT_SHARED_LIBRARY 变量指向prebuilt-shared-library.mk Makefile。它什么都没有构建,但是它将Prebuilt库复制到了NDK项目的libs目录下。通过使用PREBUILT_STATIC_LIBRARY变量,静态库可以像共享库一样被用作Prebuilt库,NDK项目可以像普通共享库一样使用Prebuilt库了。

...

LOCAL_SHAREd_LIBRARIES := avilib

7.构建独立的可执行文件

在Android平台上使用原生组件的推荐和支持的方法是将它们打包成共享库。但是,为了方便测试和进行快速原型设计,Android NDK也支持构建独立的可执行文件。这些独立的可执行文件是不用打包成APK文件就可以复制到Android设备上的常规Linux应用程序,而且它们可以直接执行,而不通过Java应用程序加载。生成独立可执行文件需要在Android.mk构建文档中导入BUILD_EXECUTABLE变量,而不是导入BUILD_SHARED_LIBRARY变量,如下所示:

#

# 独立可执行模块的Android.mk文件

#

include $(CLEAR_VARS)

LOCAL_MODULE := module

LOCAL_SRC_FILES := module.c

LOCAL_STATIC_LIBRARIES := avilib

include $(BUILD_EXECUTABLE)

BUILD_EXECUTABLE变量指向build-executable.mk Makefile片段,该片段包含了在Android平台上生成独立可执行文件的必要步骤。独立可执行文件以与模块相同的名称被放在libs/<machine architecture>目录下。尽管放在该目录下,但在打包阶段它并没有被包含在APK文件中。

8.其他构件系统变量

除了在前几节提到的变量之外,Android NDK构建系统还支持其他变量,本节将对这些变量进行简要说明。

构建系统定义的变量有:

TARGET_ARCH:目标CPU体系结构的名称,例如arm

TARGET_PLATFORM:目标Android平台的名称,例如:android-3

TARGET_ARCH_ABI:目标CPU体系结构和ABI的名称,例如:armeabi-v7a

TARGET_ABI:目标平台和ABI串联,例如:android-3-armeabi-v7a

可被定义为模块说明部分的变量有:

LOCAL_MODULE_FILENAME:可选变量,用来重新定义生成的输出文件名称。默认情况下,构建系统使用LOCAL_MODULE的值作为生成的输出文件名称,但变量LOCAL_MODULE_FILENAME可以覆盖LOCAL_MODULE的值。

LOCAL_CPP_EXTENSION:C++源文件的默认扩展名是.cpp。这个变量可以用来为C++源代码指定一个或多个文件扩展名。

...

LOCAL_CPP_EXTENSION := .cpp .cxx

...

LOCAL_CPP_FEATURES:可选变量,用来指明模块所依赖的具体C++特性,如RTTI、exception等。

...

LOCAL_CPP_FEATURES := rtti

...

LOCAL_C_INCLUDES:可选目录列表,NDK安装目录的相对路径,用来搜索头文件。

...

LOCAL_C_INCLUDES := sources/shared-module

LOCAL_C_INCLUDES := $(LOCAL_PATH)/include

LOCAL_CFLAGS: 一组可选的编译器标志,在编译C和C++源文件的时候会被传送给编译器。

...

LOCAL_CFLAGS := -NDDEBUG -DPORT = 1234

...

LOCAL_CPP_FLAGS:一组可选的编译标志,在只编译C++源文件时被传送给编译器。

LOCAL_WHOLE_STATIC_LIBRARIES:LOCAL_STATIC_LIBRARIES 的变体,用来指明应该被包含在生成的共享库中的所有静态内容。

当几个静态库之间有循环依赖时,LOCAL_WHOLE_STATIC_LIBRARIES很有用。

LOCAL_LDLIBS:链接标志的可选列表,当对目标文件进行链接以生成输出文件时该标志将被传送给链接器。它主要用于传送要进行动态链接的系统库列表。例如:要与Android NDK日志库链接,使用以下代码:

LOCAL_LDFLAGS := -llog

LOCAL_ALLOW_UNDEFINED_SYMBOLS:可选参数,它禁止在生成的文件中进行缺失符号检查。若没有定义,链接器会在符号缺失时生成错误信息。

LOCAL_ARM_MODE:可选参数,ARM机器体系结构特有变量,用于指定要生成的ARM二进制类型。默认情况下,构建系统在拇指模式下用16位指令生成,但该变量可以被设置为arm来指定使用32位指令。

LOCAL_ARM_MODE := arm

该变量改变了整个模块的构建系统行为;可以用.arm扩展名指定只在arm模式下构建特定文件。

LOCAL_SRC_FILES := file1.c  file2.c.arm

LOCAL_ARM_NEON:可选参数,ARM机器体系结构特有变量,用来指定在源文件中应该使用的ARM高级单指令流多数据流(Single Instruction Multiple Data,SIMD)(a.k.a. NEON)内联函数。

LOCAL_ARM_NEON := true

该变量改变了整个模块的构建系统行为;可以用.neon扩展名指定只构建带有NEON内联函数的特定文件。

LOCAL_SRC_FILES := file1.c file2.c.neon

LOCAL_DISABLE_NO_EXECUTE: 可选变量,用来禁用NX Bit 安全特性。NX Bit代表 Never Execute(永不执行),它是在CPU中使用的一项技术,用来隔离代码区和存储区。这样可以防止恶意软件通过将它的代码插入应用程序的存储区来控制应用程序。

LOCAL_DISABLE_NO_EXECUTE := true

LOCAL_EXPORT_CFLAGS:该变量记录一组编译器标志,这些编译器标志会被添加到通过变量LOCAL_STATIC_LIBRARIES或LOCAL_SHARED_LIBRARIES使用本模块的其他模块的LOCAL_CFLAGS定义中。

LOCAL_MODULE := avilib

...

LOCAL_EXPORT_LIBRARIES := avilib

编译器在构建module1时会以-DENABLE_AUDIO -DDEBUG标志执行。

LOCAL_EXPORT_CPPFLAGS:和LOCAL_EXPORT_CFLAGS一样,但是它是C++特定代码编译器标志。

LOCAL_EXPORT_LDFLAGS:和LOCAL_EXPORT_CFLAGS一样,但用作链接器标志。

LOCAL_EXPORT_C_INCLUDES:该变量允许记录路径集,这些路径会被添加到通过变量LOCAL_STATIC_LIBRARIES或LOCAL_SHARED_LIBRARIES使用该模块的LOCAL_C_INCLUDES定义中。

LOCAL_SHORT_COMMANDS:对于有大量资源或独立的静态/共享库的模块,该变量应该被设置为true。诸如Windows之类的操作系统只允许命令行最多输入8 191个字符;该变量通过分解构建命令使其长度小于8191个字符。在较小的模块中不推荐使用该方法,因为使用它会让构建过程变慢。

LOCAL_FILTER_ASM:该变量定义了用于过滤来自LOCAL_SRC_FILES变量的装配文件的应用程序。

9.其他的构建系统函数宏

all-subdir-makefiles:返回当前目录的所有子目录下的Android.mk构建文件列表。例如,调用以下命令可以将子目录下的所有Android.mk文件包含在构建过程中:include $(call all-subdir-makefiles)

this-makefile:返回当前Android.mk构建文件的路径

parent-makefile:返回包含当前构建文件的父Android.mk构建文件的路径。

grand-parent-makefile:和parent-makefile一样但用于祖父目录。

10.定义新变量

开发人员可以定义其他变量来简化他们的构建文件。以LOCAL_和NDK_前缀开头的名称预留给Android NDK构建系统使用。建议开发人员定义的变量以MY_开头,如下所示:

...

MY_SRC_FILES := avilib.c platform_posix.c

LOCAL_SRC_FILES := $(addprefix avilib/, $(MY_SRC_FILES))

...

11.条件操作

Android.mk构建文件也可以包含关于这些变量的条件操作,例如:在每个体系结构中包含一个不同的源文件集,如下:

...

ifeq ($(TARGET_ARCH), arm)

  LOCAL_SRC_FILES + = armonly.c

else

  LOCAL_SRC_FILES + = generic.c

endif

...

Application.mk

Application.mk是Android NDK构建系统使用的一个可选构建文件。和Android.mk文件一样,它也被放在jni目录下。Application.mk也是一个GUN Makefile片段。它的目的是描述应用程序需要哪些模块;它也定义所有模块的通用变量。以下是Application.mk构建文件支持的变量:

APP_MODULES:默认情况下,Android NDK构建系统构建Android.mk文件声明的所有模块。该变量可以覆盖上述行为并提供一个用空格分开的、需要被构建的模块列表。

APP_OPTIM:该变量可以被设置为release或debug以改变生成的二进制文件的优化级别。默认情况下使用的是release模式,并且此时生成的二进制文件被高度优化。该变量可以被设置为debug模式以生成更容易调试的未优化二进制文件。

APP_CLAGS:该变量列出了一些编译器标志,在编译任何模块的C和C++源文件时这些标志都会被传给编译器。

APP_CPPFLAGS:该变量列出了一些编译器标志,在编译任何模块的C++源文件时这些标志都会被传给编译器。

APP_BUILD_SCRIPT:默认情况下,Android NDK构建系统在项目的jni子目录下查找Android.mk构建文件。可以用该变量改变上述行为,并使用不同的生成文件。

APP_ABI:默认情况下,Android NDK构建系统为armeabi ABI生成二进制文件。可以用该变量改变上述行为,并为其他ABI生成二进制文件。可用该变量改变上述行为,并为其他ABI生成二进制文件,例如:

APP_ABI := mips

另外,可以设置多个ABI

APP_ABI := armeabi mips

为所有支持的ABI生成二进制文件

APP_ABI := all

APP_STL:默认情况下,Android NDK构建系统使用最小STL运行库,也被成为system库。可以用该变量选择不同的STL实现。

APP_STL := stlport_shared

APP_GNUSTL_FORCE_CPP_FEATURES:与LOCAL_CPP_EXTENSIONS变量相似,该变量表明所有模块都依赖于具体的C++特性,如RTTI、exceptions等。

APP_SHORT_COMMANDS:与LOCAL_SHORT_COMMANDS变量相似,该变量使得构建系统在有大量源文件的情况下可以在项目中使用更短的命令。

使用NDK-Build脚本

如前所述,可以通过执行ndk-build脚本启动Android NDK构建系统。该脚本用一组参数使维护和控制构建过程更容易。

(1)默认情况下,ndk-buid脚本应该在主项目目录中执行。-C参数可以用于指定命令行中NDK项目的位置,这样一来ndk-build脚本可以从任意的位置开始。

ndk-build -C /path/to/the/project

(2)如果源文件没被修改,Android NDK构建系统不会重构目标。可以用-B执行ndk-build脚本来强制重构建所有源码。

ndk-build -B

(3)为了清理生成的二进制文件和目标文件,可以在命令行执行ndk-build clean命令。Android NDK构建系统会删除生成的二进制文件。

ndk-build clean

(4)Android NDK构建系统依赖于GNU Make工具对模块进行构建。默认情况下,GNU Make工具一次执行一句构建命令,等这一句完成以后再执行下一句。如果在命令行使用-j参数,GNU Make就可以并行执行构建命令。另外,也可以通过指定该参数之后的数字来指定并行执行的命令总数。

ndk-build -j 4

排除构建系统故障

Android NDK构建系统有大量的日志以支持构建系统相关的故障排除,本节将简要阐述构建系统故障排除。

在命令行输入ndk-build NDK_LOG=1便可启用Android NDK 构建系统内部状态日志功能。Android NDK构建系统会产生大量的日志,日志消息的前缀是Android NDK,比如:

Android NDK:。。。。

Android NDK:。。。。

如果只想看实际执行的构建命令,可以在命令行输入 ndk-build V = 1。Android NDK将会只显示构建命令。

原文地址:https://www.cnblogs.com/coderbaker/p/coderbaker04.html