ndk-build 相关细说

上官兴昌
2023-12-01

目录

内部原理

从命令行调用

选项

可调试 build 与发布 build

要求

JSON 编译数据库

Android.mk

概览

基础知识

变量和宏

NDK 定义的 include 变量

CLEAR_VARS

BUILD_EXECUTABLE

BUILD_SHARED_LIBRARY

BUILD_STATIC_LIBRARY

PREBUILT_SHARED_LIBRARY

PREBUILT_STATIC_LIBRARY

目标信息变量

TARGET_ARCH

TARGET_PLATFORM

TARGET_ARCH_ABI

TARGET_ABI

模块描述变量

LOCAL_PATH

LOCAL_MODULE

LOCAL_MODULE_FILENAME

LOCAL_SRC_FILES

LOCAL_CPP_EXTENSION

LOCAL_CPP_FEATURES

LOCAL_C_INCLUDES

LOCAL_CFLAGS

LOCAL_CPPFLAGS

LOCAL_STATIC_LIBRARIES

LOCAL_SHARED_LIBRARIES

LOCAL_WHOLE_STATIC_LIBRARIES

LOCAL_LDLIBS

LOCAL_LDFLAGS

LOCAL_ALLOW_UNDEFINED_SYMBOLS

LOCAL_ARM_MODE

LOCAL_ARM_NEON

LOCAL_DISABLE_FORMAT_STRING_CHECKS

LOCAL_EXPORT_CFLAGS

LOCAL_EXPORT_CPPFLAGS

LOCAL_EXPORT_C_INCLUDES

LOCAL_EXPORT_LDFLAGS

LOCAL_EXPORT_LDLIBS

LOCAL_SHORT_COMMANDS

LOCAL_THIN_ARCHIVE

LOCAL_FILTER_ASM

NDK 提供的函数宏

my-dir

all-subdir-makefiles

this-makefile

parent-makefile

grand-parent-makefile

import-module

Application.mk

概览

变量

APP_ABI

APP_ASFLAGS

APP_ASMFLAGS

APP_BUILD_SCRIPT

APP_CFLAGS

APP_CLANG_TIDY

APP_CLANG_TIDY_FLAGS

APP_CONLYFLAGS

APP_CPPFLAGS

APP_CXXFLAGS

APP_DEBUG

APP_LDFLAGS

APP_MANIFEST

APP_MODULES

APP_OPTIM

APP_PLATFORM

APP_PROJECT_PATH

APP_SHORT_COMMANDS

APP_STL

APP_STRIP_MODE

APP_THIN_ARCHIVE

APP_WRAP_SH

目录

内部原理

从命令行调用

选项

可调试 build 与发布 build

要求

JSON 编译数据库

Android.mk

概览

基础知识

变量和宏

NDK 定义的 include 变量

CLEAR_VARS

BUILD_EXECUTABLE

BUILD_SHARED_LIBRARY

BUILD_STATIC_LIBRARY

PREBUILT_SHARED_LIBRARY

PREBUILT_STATIC_LIBRARY

目标信息变量

TARGET_ARCH

TARGET_PLATFORM

TARGET_ARCH_ABI

TARGET_ABI

模块描述变量

LOCAL_PATH

LOCAL_MODULE

LOCAL_MODULE_FILENAME

LOCAL_SRC_FILES

LOCAL_CPP_EXTENSION

LOCAL_CPP_FEATURES

LOCAL_C_INCLUDES

LOCAL_CFLAGS

LOCAL_CPPFLAGS

LOCAL_STATIC_LIBRARIES

LOCAL_SHARED_LIBRARIES

LOCAL_WHOLE_STATIC_LIBRARIES

LOCAL_LDLIBS

LOCAL_LDFLAGS

LOCAL_ALLOW_UNDEFINED_SYMBOLS

LOCAL_ARM_MODE

LOCAL_ARM_NEON

LOCAL_DISABLE_FORMAT_STRING_CHECKS

LOCAL_EXPORT_CFLAGS

LOCAL_EXPORT_CPPFLAGS

LOCAL_EXPORT_C_INCLUDES

LOCAL_EXPORT_LDFLAGS

LOCAL_EXPORT_LDLIBS

LOCAL_SHORT_COMMANDS

LOCAL_THIN_ARCHIVE

LOCAL_FILTER_ASM

NDK 提供的函数宏

my-dir

all-subdir-makefiles

this-makefile

parent-makefile

grand-parent-makefile

import-module

Application.mk

概览

变量

APP_ABI

APP_ASFLAGS

APP_ASMFLAGS

APP_BUILD_SCRIPT

APP_CFLAGS

APP_CLANG_TIDY

APP_CLANG_TIDY_FLAGS

APP_CONLYFLAGS

APP_CPPFLAGS

APP_CXXFLAGS

APP_DEBUG

APP_LDFLAGS

APP_MANIFEST

APP_MODULES

APP_OPTIM

APP_PLATFORM

APP_PROJECT_PATH

APP_SHORT_COMMANDS

APP_STL

APP_STRIP_MODE

APP_THIN_ARCHIVE

APP_WRAP_SH

使用预构建库

声明预构建库

从其他模块引用预构建库

导出预构建库的头文件

调试预构建库

为预构建库选择 ABI



ndk-build 脚本使用 NDK 的基于 Make 的构建系统构建项目。

内部原理

运行 ndk-build 脚本相当于运行以下命令:

$GNUMAKE -f <ndk>/build/core/build-local.mk
<parameters>

$GNUMAKE 指向 GNU Make 3.81 或更高版本,<ndk> 则指向 NDK 安装目录。您可以根据这些信息从其他 Shell 脚本(甚至是您自己的 Make 文件)中调用 ndk-build。

从命令行调用

ndk-build 脚本位于 NDK 安装目录顶层。如需从命令行运行该脚本,请在应用项目目录或其子目录中进行调用。例如:

$ cd <project>
$ <ndk>/ndk-build

在此示例中,<project> 指向项目的根目录,<ndk> 则是您安装 NDK 的目录。

选项

ndk-build 的所有参数都将直接传递到运行 NDK 构建脚本的底层 GNU make 命令。请将 ndk-build 和表单 ndk-build <option> 中的选项结合使用。例如:

$ ndk-build clean

您可以使用以下选项:

clean

移除之前生成的所有二进制文件。

注意:在 Mac OS X 上,运行 ndk-build clean 时若有大量并行执行,可能会导致出现包含以下消息的构建错误:

rm: fts_read: No such file or directory

为了避免出现此问题,请考虑不使用 -jN 修饰符,或为 N 选择较小的值(例如 2)。

V=1

启动构建,并显示构建命令。

-B

强制执行完整的重新构建。

-B V=1

强制执行完整的重新构建,并显示构建命令。

NDK_LOG=1

显示内部 NDK 日志消息(用于调试 NDK 本身)。

NDK_DEBUG=1

强制执行可调试 build(详见表 1)。

NDK_DEBUG=0

强制执行发布 build(详见表 1)。

NDK_HOST_32BIT=1

始终使用 32 位模式下的工具链。

NDK_APPLICATION_MK=<file>

使用 NDK_APPLICATION_MK 变量指向的特定 Application.mk 文件进行构建。

-C <project>

构建位于 <project> 的项目路径的原生代码。如果您不想在终端通过 cd 切换到该路径,此选项会非常有用。

可调试 build 与发布 build

使用 NDK_DEBUG 选项(在特定情况下结合 AndroidManifest.xml)指定调试 build 或发布 build、与优化相关的行为以及是否包含符号。表 1 显示了每个可能的设置组合的结果。

表 1. NDK_DEBUG(命令行)和 android:debuggable(清单)组合的结果。

清单设置NDK_DEBUG=0NDK_DEBUG=1未指定 NDK_DEBUG
android:debuggable="true"调试;符号;已优化*1调试;符号;未优化*2(与 NDK_DEBUG=1 相同)
android:debuggable="false"发布;符号;已优化发布;符号;未优化发布;无符号;已优化*3

*1:有助于进行剖析。
*2:用于运行 ndk-gdb 的默认值。
*3:默认模式。

注意:“NDK_DEBUG=0”相当于“APP_OPTIM=release”,并用“-O2”编译。“NDK_DEBUG=1”相当于“Application.mk”中的“APP_OPTIM=debug”,并用“-O0”编译。如需详细了解“APP_OPTIM”,请参阅 Application.mk

例如,命令行上的语法如下:

$ ndk-build NDK_DEBUG=1

要求

一般来说,您需要使用 GNU Make 4 才能使用 ndk-build 或 NDK。NDK 包含其自己的 GNU Make 副本,除非您将 $GNUMAKE 环境变量设置为指向不适当的 Make,否则 NDK 将使用该副本。

JSON 编译数据库

在 NDK r18 及更高版本中,ndk-build 可以生成 JSON 编译数据库

您可以使用 ndk-build compile_commands.json 生成数据库而不构建代码,也可以使用 ndk-build GEN_COMPILE_COMMANDS_DB=true 来构建代码,同时生成数据库。

Android.mk

概览

Android.mk 文件位于项目 jni/ 目录的子目录中,用于向构建系统描述源文件和共享库。它实际上是一个微小的 GNU makefile 片段,构建系统会将其解析一次或多次。Android.mk 文件用于定义 Application.mk、构建系统和环境变量所未定义的项目级设置。它还可替换特定模块的项目级设置。

Android.mk 的语法支持将源文件分组为“模块”。模块是静态库、共享库或独立的可执行文件。您可在每个 Android.mk 文件中定义一个或多个模块,也可在多个模块中使用同一个源文件。构建系统只将共享库放入您的应用软件包。此外,静态库可生成共享库。

除了封装库之外,构建系统还可为您处理各种其他事项。例如,您无需在 Android.mk 文件中列出头文件或生成的文件之间的显式依赖关系。NDK 构建系统会自动计算这些关系。因此,您应该能够享受到未来 NDK 版本中支持的新工具链/平台功能带来的益处,而无需处理 Android.mk 文件。

此文件的语法与随整个 Android 开源项目分发的 Android.mk 文件中使用的语法非常接近。虽然使用这些语法的构建系统实现并不相同,但通过有意将语法设计得相似,可使应用开发者更轻松地将源代码重复用于外部库。

基础知识

在详细了解语法之前,最好先了解 Android.mk 文件所含内容的基本信息。为此,本部分使用 Hello-JNI 示例中的 Android.mk 文件解释文件中每一行的作用。

Android.mk 文件必须先定义 LOCAL_PATH 变量:

LOCAL_PATH := $(call my-dir)

此变量表示源文件在开发树中的位置。在上述命令中,构建系统提供的宏函数 my-dir 将返回当前目录(Android.mk 文件本身所在的目录)的路径。

下一行声明 CLEAR_VARS 变量,其值由构建系统提供。

include $(CLEAR_VARS)

CLEAR_VARS 变量指向一个特殊的 GNU Makefile,后者会为您清除许多 LOCAL_XXX 变量,例如 LOCAL_MODULELOCAL_SRC_FILES 和 LOCAL_STATIC_LIBRARIES。请注意,GNU Makefile 不会清除 LOCAL_PATH。此变量必须保留其值,因为系统在单一 GNU Make 执行上下文(其中的所有变量都是全局变量)中解析所有构建控制文件。在描述每个模块之前,您必须声明(重新声明)此变量。

接下来,LOCAL_MODULE 变量存储您要构建的模块的名称。请在应用的每个模块中使用一次此变量。

LOCAL_MODULE := hello-jni

每个模块名称必须唯一,且不含任何空格。构建系统在生成最终共享库文件时,会对您分配给 LOCAL_MODULE 的名称自动添加正确的前缀和后缀。例如,上述示例会生成名为 libhello-jni.so 的库。

注意:如果模块名称的开头已经是 lib,构建系统不会添加额外的 lib 前缀;而是按原样采用模块名称,并添加 .so 扩展名。因此,比如原来名为 libfoo.c 的源文件仍会生成名为 libfoo.so 的共享对象文件。此行为是为了支持 Android 平台源文件根据 Android.mk 文件生成的库;所有这些库的名称都以 lib 开头。

下一行会列举源文件,以空格分隔多个文件:

LOCAL_SRC_FILES := hello-jni.c

LOCAL_SRC_FILES 变量必须包含要构建到模块中的 C 和/或 C++ 源文件列表。

最后一行帮助系统将一切连接到一起:

include $(BUILD_SHARED_LIBRARY)

BUILD_SHARED_LIBRARY 变量指向一个 GNU Makefile 脚本,该脚本会收集您自最近 include 以来在 LOCAL_XXX 变量中定义的所有信息。此脚本确定要构建的内容以及构建方式。

在示例目录中有更为复杂的示例,包括带有注释的 Android.mk 文件供您查看。此外,示例:native-activity 详细介绍了该示例的 Android.mk 文件。最后,变量和宏提供了关于本部分中变量的更多信息。

变量和宏

构建系统提供许多可在 Android.mk 文件中使用的变量。其中的许多变量已预先赋值。另一些变量由您赋值。

除了这些变量之外,您还可以自己定义任意变量。在定义变量时请注意,NDK 构建系统保留了下列变量名称:

  • 以 LOCAL_ 开头的名称,例如 LOCAL_MODULE
  • 以 PRIVATE_NDK_ 或 APP 开头的名称。构建系统在内部使用这些变量名。
  • 小写名称,例如 my-dir。构建系统也是在内部使用这些变量名。

如果您需要在 Android.mk 文件中定义您自己的便利变量,建议在名称前附加 MY_

NDK 定义的 include 变量

本部分探讨了构建系统在解析 Android.mk 文件前定义的 GNU Make 变量。在某些情况下,NDK 可能会多次解析 Android.mk 文件,每次使用其中某些变量的不同定义。

CLEAR_VARS

此变量指向的构建脚本用于取消定义下文“开发者定义的变量”部分中列出的几乎所有 LOCAL_XXX 变量。在描述新模块之前,请使用此变量来包含此脚本。使用它的语法为:

include $(CLEAR_VARS)

BUILD_EXECUTABLE

此变量指向的构建脚本会收集您在 LOCAL_XXX 变量中提供的模块的所有相关信息,以及确定如何根据您列出的源文件构建目标可执行文件。请注意,使用此脚本要求您至少已经为 LOCAL_MODULE 和 LOCAL_SRC_FILES 赋值;如需详细了解这些变量,请参阅模块描述变量

使用此变量的语法为:

include $(BUILD_EXECUTABLE)

注意:大多数 Android 应用不包含可执行文件,但它们对于创建单元测试和其他调试工具很有用。

BUILD_SHARED_LIBRARY

此变量指向的构建脚本会收集您在 LOCAL_XXX 变量中提供的模块的所有相关信息,以及确定如何根据您列出的源文件构建目标共享库。请注意,使用此脚本要求您至少已经为 LOCAL_MODULE 和 LOCAL_SRC_FILES 赋值;如需详细了解这些变量,请参阅模块描述变量

使用此变量的语法为:

include $(BUILD_SHARED_LIBRARY)

共享库变量会导致构建系统生成扩展名为 .so 的库文件。

BUILD_STATIC_LIBRARY

用于构建静态库的 BUILD_SHARED_LIBRARY 的变体。构建系统不会将静态库复制到您的项目/软件包中,但可以使用静态库构建共享库(请参阅下文的 LOCAL_STATIC_LIBRARIES 和 LOCAL_WHOLE_STATIC_LIBRARIES)。使用此变量的语法为:

include $(BUILD_STATIC_LIBRARY)

静态库变量会导致构建系统生成扩展名为 .a 的库。

PREBUILT_SHARED_LIBRARY

指向用于指定预构建共享库的构建脚本。与 BUILD_SHARED_LIBRARY 和 BUILD_STATIC_LIBRARY 的情况不同,这里的 LOCAL_SRC_FILES 值不能是源文件,而必须是指向预构建共享库的单一路径,例如 foo/libfoo.so。使用此变量的语法为:

include $(PREBUILT_SHARED_LIBRARY)

您也可以使用 LOCAL_PREBUILTS 变量引用另一个模块中的预构建库。如需详细了解如何使用预构建库,请参阅使用预构建库

PREBUILT_STATIC_LIBRARY

与 PREBUILT_SHARED_LIBRARY 相同,但用于预构建静态库。如需详细了解如何使用预构建库,请参阅使用预构建库

目标信息变量

构建系统会根据 APP_ABI 变量所指定的每个 ABI 分别解析 Android.mk 一次,该变量通常在 Application.mk 文件中定义。如果 APP_ABI 为 all,构建系统会根据 NDK 支持的每个 ABI 分别解析 Android.mk 一次。本部分介绍构建系统每次解析 Android.mk 时定义的变量。

TARGET_ARCH

构建系统解析此 Android.mk 文件时指向的 CPU 系列。此变量将是下列其中一项:armarm64x86 或 x86_64

TARGET_PLATFORM

构建系统解析此 Android.mk 文件时指向的 Android API 级别号。例如,Android 5.1 系统映像对应于 Android API 级别 22:android-22。如需查看平台名称和相应 Android 系统映像的完整列表,请参阅原生 API。以下示例展示了使用此变量的语法:

ifeq ($(TARGET_PLATFORM),android-22)
    # ... do something ...
endif

TARGET_ARCH_ABI

构建系统解析此 Android.mk 文件时指向的 ABI。表 1 显示了用于每个受支持 CPU 和架构的 ABI 设置。

表 1. 不同 CPU 和架构的 ABI 设置。

CPU 和架构设置
ARMv7armeabi-v7a
ARMv8 AArch64arm64-v8a
i686x86
x86-64x86_64

以下示例演示了如何检查 ARMv8 AArch64 是否为目标 CPU 与 ABI 的组合:

ifeq ($(TARGET_ARCH_ABI),arm64-v8a)
  # ... do something ...
endif

如需详细了解架构 ABI 和相关兼容性问题,请参阅 Android ABI

未来的新目标 ABI 将使用不同的值。

TARGET_ABI

目标 Android API 级别与 ABI 的串联,特别适用于要针对实际设备测试特定目标系统映像的情况。例如,要检查在 Android API 级别 22 上运行的 64 位 ARM 设备:

ifeq ($(TARGET_ABI),android-22-arm64-v8a)
  # ... do something ...
endif

模块描述变量

本部分中的变量会向构建系统描述您的模块。每个模块描述都应遵守以下基本流程:

  1. 使用 CLEAR_VARS 变量初始化或取消定义与模块相关的变量。
  2. 为用于描述模块的变量赋值。
  3. 使用 BUILD_XXX 变量设置 NDK 构建系统,使其将适当的构建脚本用于该模块。

LOCAL_PATH

此变量用于指定当前文件的路径。必须在 Android.mk 文件开头定义此变量。以下示例演示了如何定义此变量:

LOCAL_PATH := $(call my-dir)

CLEAR_VARS 所指向的脚本不会清除此变量。因此,即使 Android.mk 文件描述了多个模块,您也只需定义此变量一次。

LOCAL_MODULE

此变量用于存储模块名称。指定的名称在所有模块名称中必须唯一,并且不得包含任何空格。您必须先定义该名称,然后才能添加任何脚本(CLEAR_VARS 的脚本除外)。无需添加 lib 前缀或 .so 或 .a 文件扩展名;构建系统会自动执行这些修改。在整个 Android.mk 和 Application.mk 文件中,请用未经修改的名称引用模块。例如,以下行会导致生成名为 libfoo.so 的共享库模块:

LOCAL_MODULE := "foo"

如果您希望生成的模块使用除“lib + LOCAL_MODULE 的值”以外的名称,可以使用 LOCAL_MODULE_FILENAME 变量为生成的模块指定自己选择的名称。

LOCAL_MODULE_FILENAME

此可选变量使您能够替换构建系统为其生成的文件默认使用的名称。例如,如果 LOCAL_MODULE 的名称为 foo,您可以强制系统将其生成的文件命名为 libnewfoo。以下示例演示了如何完成此操作:

LOCAL_MODULE := foo
LOCAL_MODULE_FILENAME := libnewfoo

对于共享库模块,此示例将生成一个名为 libnewfoo.so 的文件。

注意:您无法替换文件路径或文件扩展名。

LOCAL_SRC_FILES

此变量包含构建系统生成模块时所用的源文件列表。只列出构建系统实际传递到编译器的文件,因为构建系统会自动计算所有相关的依赖项。请注意,您可以使用相对(相对于 LOCAL_PATH)和绝对文件路径。

建议您避免使用绝对文件路径;相对路径可以提高 Android.mk 文件的移植性。

注意:务必在构建文件中使用 Unix 样式的正斜杠 (/)。构建系统无法正确处理 Windows 样式的反斜杠 (\)。

LOCAL_CPP_EXTENSION

可以使用此可选变量为 C++ 源文件指定 .cpp 以外的文件扩展名。例如,以下行将扩展名更改为 .cxx(设置必须包含点)。

LOCAL_CPP_EXTENSION := .cxx

您可以使用此变量指定多个扩展名。例如:

LOCAL_CPP_EXTENSION := .cxx .cpp .cc

LOCAL_CPP_FEATURES

您可使用此可选变量指明您的代码依赖于特定 C++ 功能。它会在构建过程中启用正确的编译器标记和链接器标记。对于预构建的二进制文件,此变量还会声明二进制文件依赖于哪些功能,从而确保最终链接正常运行。我们建议您使用此变量,而不要直接在 LOCAL_CPPFLAGS 定义中启用 -frtti 和 -fexceptions

使用此变量可让构建系统对每个模块使用适当的标记。使用 LOCAL_CPPFLAGS 会导致编译器将所有指定的标记用于所有模块,而不管实际需求如何。

例如,如需指明您的代码使用 RTTI(运行时类型信息),请写入:

LOCAL_CPP_FEATURES := rtti

如需指明您的代码使用 C++ 异常,请输入:

LOCAL_CPP_FEATURES := exceptions

您还可以为此变量指定多个值。例如:

LOCAL_CPP_FEATURES := rtti features

描述值的顺序无关紧要。

LOCAL_C_INCLUDES

您可使用此可选变量指定相对于 NDK root 目录的路径列表,以便在编译所有源文件(C、C++ 和 Assembly)时添加到 include 搜索路径中。例如:

LOCAL_C_INCLUDES := sources/foo

或者甚至:

LOCAL_C_INCLUDES := $(LOCAL_PATH)/<subdirectory>/foo

请在通过 LOCAL_CFLAGS 或 LOCAL_CPPFLAGS 设置任何对应的包含标记前定义此变量。

在使用 ndk-gdb 启动原生调试时,构建系统也会自动使用 LOCAL_C_INCLUDES 路径。

LOCAL_CFLAGS

此可选变量用于设置在构建 C 和 C++ 源文件时构建系统要传递的编译器标记。这样,您就可以指定额外的宏定义或编译选项。可以使用 LOCAL_CPPFLAGS 仅为 C++ 指定标记。

请勿尝试在 Android.mk 文件中更改优化/调试级别。构建系统可以使用 Application.mk 文件中的相关信息自动处理此设置。这样,构建系统就可以生成供调试期间使用的有用数据文件。

您可通过输入以下代码指定额外的 include 路径:

LOCAL_CFLAGS += -I<path>,

但是,最好使用 LOCAL_C_INCLUDES,因为这样也可以使用可用于 ndk-gdb 原生调试的路径。

LOCAL_CPPFLAGS

只构建 C++ 源文件时将传递的一组可选编译器标记。它们将出现在编译器命令行中的 LOCAL_CFLAGS 后面。使用 LOCAL_CFLAGS 为 C 和 C++ 指定标记。

LOCAL_STATIC_LIBRARIES

此变量用于存储当前模块依赖的静态库模块列表。

如果当前模块是共享库或可执行文件,此变量将强制这些库链接到生成的二进制文件。

如果当前模块是静态库,此变量只是指出依赖于当前模块的其他模块也会依赖于列出的库。

LOCAL_SHARED_LIBRARIES

此变量会列出此模块在运行时依赖的共享库模块。此信息是链接时必需的信息,用于将相应的信息嵌入到生成的文件中。

LOCAL_WHOLE_STATIC_LIBRARIES

此变量是 LOCAL_STATIC_LIBRARIES 的变体,表示链接器应将相关的库模块视为完整归档。如需详细了解完整归档,请参阅有关 --whole-archive 标记的 GNU Id 文档

多个静态库之间存在循环依赖关系时,此变量十分有用。使用此变量构建共享库时,它将强制构建系统将静态库中的所有对象文件添加到最终二进制文件。但是,生成可执行文件时不会发生这种情况。

LOCAL_LDLIBS

此变量列出了在构建共享库或可执行文件时使用的额外链接器标记。利用此变量,您可使用 -l 前缀传递特定系统库的名称。例如,以下示例指示链接器生成在加载时链接到 /system/lib/libz.so 的模块:

LOCAL_LDLIBS := -lz

如需查看此 NDK 版本中可以链接的公开系统库列表,请参阅原生 API

注意:如果您为静态库定义此变量,构建系统会忽略此变量,并且 ndk-build 显示一则警告。

LOCAL_LDFLAGS

此变量列出了构建系统在构建共享库或可执行文件时使用的其他链接器标记。例如,若要在 ARM/X86 上使用 ld.bfd 链接器:

LOCAL_LDFLAGS += -fuse-ld=bfd

注意:如果您为静态库定义此变量,构建系统会忽略此变量,并且 ndk-build 会显示一则警告。

LOCAL_ALLOW_UNDEFINED_SYMBOLS

默认情况下,如果构建系统在尝试构建共享库时遇到未定义的引用,将会抛出“未定义的符号”错误。此错误可帮助您捕获源代码中的错误。

如需停用此检查,请将此变量设置为 true。请注意,此设置可能会导致共享库在运行时加载。

注意:如果您为静态库定义此变量,构建系统会忽略此变量,并且 ndk-build 会显示一则警告。

LOCAL_ARM_MODE

默认情况下,构建系统会以 thumb 模式生成 ARM 目标二进制文件,其中每条指令都是 16 位宽,并与 thumb/ 目录中的 STL 库链接。将此变量定义为 arm 会强制构建系统以 32 位 arm 模式生成模块的对象文件。以下示例演示了如何执行此操作:

LOCAL_ARM_MODE := arm

您也可以对源文件名附加 .arm 后缀,指示构建系统仅以 arm 模式构建特定的源文件。例如,以下示例指示构建系统始终以 ARM 模式编译 bar.c,但根据 LOCAL_ARM_MODE 的值构建 foo.c

LOCAL_SRC_FILES := foo.c bar.c.arm

注意:您也可以在 Application.mk 文件中将 APP_OPTIM 设置为 debug,强制构建系统生成 ARM 二进制文件。指定 debug 会强制构建 ARM,因为工具链调试程序无法正确处理 Thumb 代码。

LOCAL_ARM_NEON

此变量仅在以 armeabi-v7a ABI 为目标时才有意义。它允许在 C 和 C++ 源文件中使用 ARM Advanced SIMD (NEON) 编译器内建函数,以及在 Assembly 文件中使用 NEON 指令。

请注意,并非所有基于 ARMv7 的 CPU 都支持 NEON 扩展指令集。因此,必须执行运行时检测,以便在运行时安全地使用此代码。如需了解详情,请参阅 Neon 支持和 CPU 功能

此外,您也可以使用 .neon 后缀,指定构建系统仅以 NEON 支持来编译特定源文件。在以下示例中,构建系统以 Thumb 和 NEON 支持编译 foo.c,以 Thumb 支持编译 bar.c,并以 ARM 和 NEON 支持编译 zoo.c

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

如果同时使用这两个后缀,.arm 必须在 .neon 前面。

LOCAL_DISABLE_FORMAT_STRING_CHECKS

默认情况下,构建系统会在编译代码时保护格式字符串。这样的话,如果 printf 样式的函数中使用了非常量格式的字符串,就会强制引发编译器错误。此保护默认启用,但您也可通过将此变量的值设置为 true 将其停用。如果没有必要的原因,我们不建议停用。

LOCAL_EXPORT_CFLAGS

此变量用于记录一组 C/C++ 编译器标记,这些标记将添加到通过 LOCAL_STATIC_LIBRARIES 或 LOCAL_SHARED_LIBRARIES 变量使用此模块的任何其他模块的 LOCAL_CFLAGS 定义中。

例如,假设有以下模块对:foo 和 bar,它们依赖于 foo

include $(CLEAR_VARS)
LOCAL_MODULE := foo
LOCAL_SRC_FILES := foo/foo.c
LOCAL_EXPORT_CFLAGS := -DFOO=1
include $(BUILD_STATIC_LIBRARY)

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,而后者依赖于 foo,那么 zoo 也会继承从 foo 导出的所有标记。

最后,构建系统在执行局部构建时(即,构建要导出标记的模块时),不使用导出的标记。因此,在以上示例中,构建系统在构建 foo/foo.c 时不会将 -DFOO=1 传递到编译器。如需执行局部构建,请改用 LOCAL_CFLAGS

LOCAL_EXPORT_CPPFLAGS

此变量与 LOCAL_EXPORT_CFLAGS 相同,但仅适用于 C++ 标记。

LOCAL_EXPORT_C_INCLUDES

此变量与 LOCAL_EXPORT_CFLAGS 相同,但适用于 C include 路径。例如,当 bar.c 需要包括模块 foo 的头文件时,此变量很有用。

LOCAL_EXPORT_LDFLAGS

此变量与 LOCAL_EXPORT_CFLAGS 相同,但适用于链接器标记。

LOCAL_EXPORT_LDLIBS

此变量与 LOCAL_EXPORT_CFLAGS 相同,用于指示构建系统将特定系统库的名称传递到编译器。请在您指定的每个库名称前附加 -l

请注意,构建系统会将导入的链接器标记附加到模块的 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。这样就会告知链接器,由于 libbar.so 依赖于 foo,因此它也依赖于系统日志记录库。

LOCAL_SHORT_COMMANDS

当您的模块有很多源文件和/或依赖的静态或共享库时,请将此变量设置为 true。这样会强制构建系统将 @ 语法用于包含中间对象文件或链接库的归档。

此功能在 Windows 上可能很有用,在 Windows 上,命令行最多只接受 8191 个字符,这对于复杂的项目来说可能太少。它还会影响个别源文件的编译,而且将几乎所有编译器标记都放在列表文件内。

请注意,true 以外的任何值都将恢复为默认行为。您也可以在 Application.mk 文件中定义 APP_SHORT_COMMANDS,对项目中的所有模块强制实施此行为。

我们不建议默认启用此功能,因为它会减慢构建速度。

LOCAL_THIN_ARCHIVE

构建静态库时,请将此变量设置为 true。这样会生成一个瘦归档,即一个库文件,其中不含对象文件,而只包含它通常包含的实际对象的文件路径。

这对于减小构建输出的大小非常有用。但缺点是,这样的库无法移至其他位置(其中的所有路径都是相对路径)。

有效值为 truefalse 或空白。您可在 Application.mk 文件中通过 APP_THIN_ARCHIVE 变量来设置默认值。

注意:在非静态库模块或预构建的静态库模块中,将会忽略此变量。

LOCAL_FILTER_ASM

请将此变量定义为一个 shell 命令,供构建系统用于过滤根据您为 LOCAL_SRC_FILES 指定的文件提取或生成的汇编文件。定义此变量会导致发生以下情况:

  1. 构建系统从任何 C 或 C++ 源文件生成临时汇编文件,而不是将它们编译到对象文件中。
  2. 构建系统在任何临时汇编文件以及 LOCAL_SRC_FILES 中所列任何汇编文件的 LOCAL_FILTER_ASM 中执行 shell 命令,因此会生成另一个临时汇编文件。
  3. 构建系统将这些过滤的汇编文件编译到对象文件中。

例如:

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

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

NDK 提供的函数宏

本部分介绍了 NDK 提供的 GNU Make 函数宏。使用 $(call <function>) 可以对其进行求值;其返回文本信息。

my-dir

这个宏返回最后包括的 makefile 的路径,通常是当前 Android.mk 的目录。my-dir 可用于在 Android.mk 文件开头定义 LOCAL_PATH。例如:

LOCAL_PATH := $(call my-dir)

由于 GNU Make 的工作方式,这个宏实际返回的是构建系统解析构建脚本时包含的最后一个 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 的第二次调用将 LOCAL_PATH 定义为 $PATH/foo,而不是 $PATH,因为这是其最近的 include 所指向的位置。

在 Android.mk 文件中的任何其他内容后指定额外的 include 可避免此问题。例如:

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 file
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 文件列表。

利用此函数,您可以为构建系统提供深度嵌套的源目录层次结构。默认情况下,NDK 只在 Android.mk 文件所在的目录中查找文件。

this-makefile

返回当前 makefile(构建系统从中调用函数)的路径。

parent-makefile

返回包含树中父 makefile 的路径(包含当前 makefile 的 makefile 的路径)。

grand-parent-makefile

返回包含树中祖父 makefile 的路径(包含当前父 makefile 的 makefile 的路径)。

import-module

此函数用于按模块名称来查找和包含模块的 Android.mk 文件。典型的示例如下所示:

$(call import-module,<name>)

在此示例中,构建系统在 NDK_MODULE_PATH 环境变量所引用的目录列表中查找具有 <name> 标记的模块,并且自动包括其 Android.mk 文件。

Application.mk

概览

Application.mk 指定 ndk-build 的项目级设置。默认情况下,它位于应用项目目录中的 jni/Application.mk 下。

注意:其中许多参数也具有模块等效项。例如,APP_CFLAGS 对应于 LOCAL_CFLAGS。无论何种情况下,特定于模块的选项都将优先于应用级选项。对于标记,两者都使用,但特定于模块的标记将后出现在命令行中,因此它们可能会替换项目级设置。

变量

APP_ABI

默认情况下,NDK 构建系统会为所有非弃用 ABI 生成代码。您可以使用 APP_ABI 设置为特定 ABI 生成代码。表 1 显示了不同指令集的 APP_ABI 设置。

表 1. 不同指令集的 APP_ABI 设置。

指令集
32 位 ARMv7APP_ABI := armeabi-v7a
64 位 ARMv8 (AArch64)APP_ABI := arm64-v8a
x86APP_ABI := x86
x86-64APP_ABI := x86_64
所有支持的 ABI(默认)APP_ABI := all

您也可以指定多个值,方法是将它们放在同一行上,中间用空格分隔。例如:

APP_ABI := armeabi-v7a arm64-v8a x86

注意:Gradle 的 externalNativeBuild 会忽略 APP_ABI。请在 splits 块内部使用 abiFilters 块或(如果使用的是“多个 APK”)abi 块。

如需查看所有受支持 ABI 的列表以及详细了解其用法和限制,请参阅 Android ABI

APP_ASFLAGS

要传递给项目中每个汇编源文件(.s 和 .S 文件)的编译器的标记。

注意ASFLAGS 与 ASMFLAGS 不同。后者专用于 YASM 源文件(请参阅关于 APP_ASMFLAGS 的部分)。

APP_ASMFLAGS

对于所有 YASM 源文件(.asm,仅限 x86/x86_64),要传递给 YASM 的标记。

APP_BUILD_SCRIPT

默认情况下,ndk-build 假定 Android.mk 文件位于项目根目录的相对路径 jni/Android.mk 中。

如需从其他位置加载 Android.mk 文件,请将 APP_BUILD_SCRIPT 设置为 Android.mk 文件的绝对路径。

注意:Gradle 的 externalNativeBuild 将根据 externalNativeBuild.ndkBuild.path 变量自动配置此路径。

APP_CFLAGS

要为项目中的所有 C/C++ 编译传递的标记。

注意:Include 路径应使用 LOCAL_C_INCLUDES 而不是显式 -I 标记。

另请参阅:APP_CONLYFLAGSAPP_CPPFLAGS

APP_CLANG_TIDY

若要为项目中的所有模块启用 clang-tidy,请将此标记设置为“True”。默认处于停用状态。

APP_CLANG_TIDY_FLAGS

要为项目中的所有 clang-tidy 执行传递的标记。

APP_CONLYFLAGS

要为项目中的所有 C 编译传递的标记。这些标记不会用于 C++ 代码。

另请参阅:APP_CFLAGSAPP_CPPFLAGS

APP_CPPFLAGS

要为项目中的所有 C++ 编译传递的标记。这些标记不会用于 C 代码。

另请参阅:APP_CFLAGSAPP_CONLYFLAGS

APP_CXXFLAGS

注意APP_CPPFLAGS 应优先于 APP_CXXFLAGS

与 APP_CPPFLAGS 相同,但在编译命令中将出现在 APP_CPPFLAGS 之后。例如:

APP_CPPFLAGS := -DFOO
APP_CXXFLAGS := -DBAR

以上配置将导致编译命令类似于 clang++ -DFOO -DBAR,而不是 clang++ -DBAR -DFOO

APP_DEBUG

若要构建可调试的应用,请将此标记设置为“True”。

APP_LDFLAGS

关联可执行文件和共享库时要传递的标记。

注意:这些标记对静态库没有影响。不会关联静态库。

APP_MANIFEST

AndroidManifest.xml 文件的绝对路径。

默认情况下将使用 $(APP_PROJECT_PATH)/AndroidManifest.xml)(如果存在)。

注意:使用 externalNativeBuild 时,Gradle 不会设置此值。

APP_MODULES

要构建的模块的显式列表。此列表的元素是模块在 Android.mk 文件的 LOCAL_MODULE 中显示的名称。

默认情况下,ndk-build 将构建所有共享库、可执行文件及其依赖项。仅当项目使用静态库、项目仅包含静态库或者在 APP_MODULES 中指定了静态库时,才会构建静态库。

注意:将不会构建导入的模块(在使用 $(call import-module) 导入的构建脚本中定义的模块),除非要在 APP_MODULES 中构建或列出的模块依赖导入的模块。

APP_OPTIM

将此可选变量定义为 release 或 debug。默认情况下,将构建发布二进制文件。

发布模式会启用优化,并可能生成无法与调试程序一起使用的二进制文件。调试模式会停用优化,以便可以使用调试程序。

请注意,您可以调试发布二进制文件或调试二进制文件。但是,发布二进制文件在调试期间提供的信息较少。例如,变量可能会被优化掉,导致无法检查代码。此外,代码重新排序会使单步调试代码变得更加困难;堆栈轨迹更可能不可靠。

在应用清单的 <application> 标记中声明 android:debuggable 将导致此变量默认为 debug,而不是 release。通过将 APP_OPTIM 设置为 release 可替换此默认值。

注意:使用 externalNativeBuild 进行构建时,Android Studio 将根据您的构建风格适当地设置此标记。

APP_PLATFORM

APP_PLATFORM 会声明构建此应用所面向的 Android API 级别,并对应于应用的 minSdkVersion

如果未指定,ndk-build 将以 NDK 支持的最低 API 级别为目标。最新 NDK 支持的最低 API 级别总是足够低,可以支持几乎所有有效设备。

警告:将 APP_PLATFORM 设置为高于应用的 minSdkVersion 可能会生成一个无法在旧设备上运行的应用。在大多数情况下,库将无法加载,因为它们引用了在旧设备上不可用的符号。

例如,值 android-16 指定库使用在 Android 4.1(API 级别 16)以前的版本中不可用的 API,并且无法在运行较低平台版本的设备上使用。如需查看平台名称和相应 Android 系统映像的完整列表,请参阅 Android NDK 原生 API

使用 Gradle 和 externalNativeBuild 时,不应直接设置此参数。而应在模块级别 build.gradle 文件的 defaultConfig 或 productFlavors 块中设置 minSdkVersion 属性。这样就能确保只有在运行足够高 Android 版本的设备上安装的应用才能使用您的库。

请注意,NDK 不包含 Android 每个 API 级别的库,省略了不包含新的原生 API 的版本以节省 NDK 中的空间。ndk-build 按以下优先级降序使用 API:

  1. 匹配 APP_PLATFORM 的平台版本。
  2. 低于 APP_PLATFORM 的下一个可用 API 级别。例如,APP_PLATFORM 为 android-20 时,将使用 android-19,因为 android-20 中没有新的原生 API。
  3. NDK 支持的最低 API 级别。

APP_PROJECT_PATH

项目根目录的绝对路径。

APP_SHORT_COMMANDS

LOCAL_SHORT_COMMANDS 的项目级等效项。如需了解详情,请参阅 Android.mk 中有关 LOCAL_SHORT_COMMANDS 的文档。

APP_STL

用于此应用的 C++ 标准库。

默认情况下使用 system STL。其他选项包括 c++_sharedc++_static 和 none。请参阅 NDK C++ 运行时和功能

APP_STRIP_MODE

要为此应用中的模块传递给 strip 的参数。默认为 --strip-unneeded。若要避免剥离模块中的所有二进制文件,请将其设置为 none。如需了解其他剥离模式,请参阅剥离文档

APP_THIN_ARCHIVE

要为项目中的所有静态库使用瘦归档,请将此变量设置为“True”。如需了解详情,请参阅 Android.mk 中有关 LOCAL_THIN_ARCHIVE 的文档。

APP_WRAP_SH

要包含在此应用中的 wrap.sh 文件的路径。

每个 ABI 都存在此变量的变体,ABI 通用变体也是如此:

  • APP_WRAP_SH
  • APP_WRAP_SH_armeabi-v7a
  • APP_WRAP_SH_arm64-v8a
  • APP_WRAP_SH_x86
  • APP_WRAP_SH_x86_64

注意APP_WRAP_SH_<abi> 可能无法与 APP_WRAP_SH 结合使用。如果有任何 ABI 使用特定于 ABI 的 wrap.sh,所有 ABI 都必须使用该 wrap.sh。

使用预构建库

NDK 支持使用预构建库(同时支持静态库和共享库)。此功能有以下两个主要用例:

  • 向第三方 NDK 开发者分发您自己的库,而不分发您的源代码。
  • 使用您自己的库的预构建版本来提升构建速度。

本页将介绍如何使用预构建库。

声明预构建库

您必须将自己使用的每个预构建库声明为一个独立模块。为此,请执行以下步骤:

  1. 为模块提供名称。此名称不需要与预构建库本身的名称相同。
  2. 在模块的 Android.mk 文件中,将指向您提供的预构建库的路径分配到 LOCAL_SRC_FILES。指定 LOCAL_PATH 变量的值的相对路径。

    注意:您必须确保选择与您的目标 ABI 对应的预构建库版本。如需了解有关确保库支持 ABI 的详细信息,请参阅为预构建库选择 ABI

  3. 根据您使用的是共享库 (.so) 还是静态库 (.a),添加 PREBUILT_SHARED_LIBRARY 或 PREBUILT_STATIC_LIBRARY

下面这个简单的示例假设预构建库 libfoo.so 与描述它的 Android.mk 文件位于同一个目录中。

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE := foo-prebuilt
LOCAL_SRC_FILES := libfoo.so
include $(PREBUILT_SHARED_LIBRARY)

在此示例中,模块名称与预构建库的名称相同。

构建系统会将您的预构建共享库副本放入 $PROJECT/obj/local 中,而将另一个提取的调试信息的副本放入 $PROJECT/libs/<abi> 中。此处,$PROJECT 是项目的根目录。

从其他模块引用预构建库

如需从其他模块引用预构建库,请在与这些模块关联的 Android.mk 文件中,将该预构建库的名称指定为 LOCAL_STATIC_LIBRARIES 或 LOCAL_SHARED_LIBRARIES 变量的值。

例如,使用 libfoo.so 的模块的说明可能类似于以下内容:

include $(CLEAR_VARS)
LOCAL_MODULE := foo-user
LOCAL_SRC_FILES := foo-user.c
LOCAL_SHARED_LIBRARIES := foo-prebuilt
include $(BUILD_SHARED_LIBRARY)

此处,LOCAL_MODULE 是引用预构建库的模块的名称;LOCAL_SHARED_LIBRARIES 是预构建库本身的名称。

导出预构建库的头文件

foo-user.c 中的代码取决于通常位于随预构建库分发的头文件(如 foo.h)中的特定声明。例如,foo-user.c 中可能会有类似于以下内容的一行代码:

#include <foo.h>

在这种情况下,如果您构建 foo-user 模块,需要提供头文件及其指向编译器的 include 路径。完成此任务的一个简单方法是在预构建模块定义中使用导出变量。例如,只要头文件 foo.h 位于与预构建模块关联的 include 目录下,您就可以按以下方式对其进行声明:

include $(CLEAR_VARS)
LOCAL_MODULE := foo-prebuilt
LOCAL_SRC_FILES := libfoo.so
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)

此处的 LOCAL_EXPORT_C_INCLUDES 定义会确保构建系统导出指向预构建库的 include 目录的路径,针对依赖于预构建库的模块将该路径附加到 LOCAL_C_INCLUDES 的值开头。

此操作可让编译系统查找必需的标头。

调试预构建库

建议您提供包含调试符号的预构建共享库。NDK 构建系统总是会从其安装到 $PROJECT/libs/<abi>/ 的那个版本的库中删除这些符号,但您可以使用调试版本通过 ndk-gdb 进行调试。

为预构建库选择 ABI

请务必为您的目标 ABI 选择正确版本的预构建共享库。Android.mk 文件中的 TARGET_ARCH_ABI 变量可以将构建系统指向适当版本的库。

例如,假设您的项目包含库 libfoo.so 的以下两个版本:

armeabi/libfoo.so
x86/libfoo.so

以下代码段显示了如何使用 TARGET_ARCH_ABI,以便构建系统选择适当版本的库:

include $(CLEAR_VARS)
LOCAL_MODULE := foo-prebuilt
LOCAL_SRC_FILES := $(TARGET_ARCH_ABI)/libfoo.so
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)

如果您已将 armeabi 指定为 TARGET_ARCH_ABI 的值,构建系统便会使用 armeabi 目录中的 libfoo.so 版本。如果您已将 x86 指定为 TARGET_ARCH_ABI 的值,构建系统便会使用 x86 目录中的版本。

 类似资料: