CMake

Android NDK 支持使用 CMake 编译应用的 C 和 C++ 代码。此页面介绍如何通过 Android Gradle 插件的 ExternalNativeBuild 或直接调用 CMake 来使用 CMake 与 NDK。

CMake 工具链文件

NDK 通过 工具链文件 支持 CMake。工具链文件是 CMake 文件,用于自定义跨编译工具链的行为。用于 NDK 的工具链文件位于 NDK 中的 <NDK>/build/cmake/android.toolchain.cmake

在调用 cmake 时,将在命令行上提供 ABI、minSdkVersion 等构建参数。有关支持的参数列表,请参阅 工具链参数 部分。

"新的"工具链文件

早期的 NDK 尝试了一种新的工具链文件实现,该实现可以减少使用 NDK 的工具链文件与使用内置 CMake 支持之间的行为差异。这最终需要大量的工作(尚未完成),但实际上并没有改善行为,因此我们不再追求这种方法。

“新”工具链文件与“传统”工具链文件相比存在行为回归。默认行为是推荐的工作流程。如果您正在使用 -DANDROID_USE_LEGACY_TOOLCHAIN_FILE=OFF,我们建议从您的构建中删除该标志。“新”工具链文件从未达到与传统工具链文件相同的水平,因此可能存在行为回归。

尽管我们不建议使用新工具链文件,但目前没有计划将其从 NDK 中移除。这样做会破坏依赖于新旧工具链文件之间行为差异的构建,并且不幸的是,将选项重命名以使其清楚地表明“传统”实际上是推荐的也会破坏该选项的用户。如果您正在愉快地使用新工具链文件,则无需迁移,但请注意,针对新工具链文件行为提交的任何错误可能都不会被修复,而是需要您进行迁移。

用法

Gradle

当使用 externalNativeBuild 时,CMake 工具链文件的用法是自动的。有关更多信息,请参阅 Android Studio 的在您的项目中添加 C 和 C++ 代码指南。

命令行

在 Gradle 之外使用 CMake 构建时,必须将工具链文件本身及其参数传递给 CMake。例如

$ cmake \
    -DCMAKE_TOOLCHAIN_FILE=$NDK/build/cmake/android.toolchain.cmake \
    -DANDROID_ABI=$ABI \
    -DANDROID_PLATFORM=android-$MINSDKVERSION \
    $OTHER_ARGS

工具链参数

以下参数可以传递给 CMake 工具链文件。如果使用 Gradle 构建,请根据 ExternalNativeBuild 文档中所述,将参数添加到 android.defaultConfig.externalNativeBuild.cmake.arguments 中。如果从命令行构建,请使用 -D 将参数传递给 CMake。例如,要强制 armeabi-v7a 不使用 Neon 支持进行构建,请传递 -DANDROID_ARM_NEON=FALSE

ANDROID_ABI

目标 ABI。有关支持的 ABI 信息,请参阅Android ABI

Gradle

Gradle 会自动提供此参数。请勿在您的 build.gradle 文件中显式设置此参数。要控制 Gradle 针对的 ABI,请使用 abiFilters,如 Android ABI中所述。

命令行

CMake 每次构建都会为一个目标构建。要定位多个 Android ABI,必须为每个 ABI 构建一次。建议为每个 ABI 使用不同的构建目录,以避免构建之间的冲突。

备注
armeabi-v7a
带有 NEON 的 armeabi-v7a armeabi-v7a 相同。
arm64-v8a
x86
x86_64

ANDROID_ARM_MODE

指定是否为 armeabi-v7a 生成 arm 或 thumb 指令。对其他 ABI 没有影响。有关更多信息,请参阅Android ABI文档。

备注
arm
thumb 默认行为。

ANDROID_NATIVE_API_LEVEL

ANDROID_PLATFORM 的别名。

ANDROID_PLATFORM

指定应用程序或库支持的最低 API 级别。此值对应于应用程序的 minSdkVersion

Gradle

使用 Android Gradle 插件时,此值会自动设置为与应用程序的 minSdkVersion 匹配,不应手动设置。

命令行

直接调用 CMake 时,此值默认为正在使用的 NDK 支持的最低 API 级别。例如,使用 NDK r20 时,此值默认为 API 级别 16。

此参数接受多种格式

  • android-$API_LEVEL
  • $API_LEVEL
  • android-$API_LETTER

$API_LETTER 格式允许您指定 android-N,而无需确定与该版本关联的数字。请注意,一些版本在没有字母增加的情况下增加了 API。可以通过附加 -MR1 后缀来指定这些 API。例如,API 级别 25 是 android-N-MR1

ANDROID_STL

指定要为此应用程序使用的 STL。有关更多信息,请参阅C++ 库支持。默认情况下,将使用 c++_static

备注
c++_shared libc++ 的共享库变体。
c++_static libc++ 的静态库变体。
none 没有 C++ 标准库支持。
system 系统 STL

管理编译器标志

如果您需要为构建传递特定的标志给编译器或链接器,请参阅 CMake 文档中有关set_target_compile_options以及相关选项系列的信息。该页面底部的“另请参阅”部分提供了一些有用的线索。

一般来说,最佳实践是将编译器标志应用于尽可能小的范围。您希望应用于所有目标的标志(例如 -Werror)在每个模块中重复使用起来很不方便,但仍然很少应该全局应用(CMAKE_CXX_FLAGS),因为这些标志可能会对项目中的第三方依赖项产生不希望有的影响。对于此类情况,可以在目录范围内应用标志(add_compile_options)。

对于一小部分编译器标志,也可以在您的 build.gradle 文件中使用 cppFlags 或类似的属性进行设置。您不应该这样做。从 Gradle 传递给 CMake 的标志将具有意外的优先级行为,在某些情况下会覆盖实现隐式传递的标志,而这些标志对于构建 Android 代码是必需的。始终优先在 CMake 中直接处理 CMake 行为。如果您需要根据 AGP buildType 控制编译器标志,请参阅在 CMake 中使用 AGP 构建类型

在 CMake 中使用 AGP 构建类型

如果您需要根据自定义 Gradle buildType 定制 CMake 行为,请使用该构建类型传递一个额外的 CMake 标志(而不是编译器标志),您的 CMake 构建脚本可以读取该标志。例如,如果您有“免费”和“高级”构建变体由您的 build.gradle.kts 控制,并且您需要将该数据传递给 CMake

android {
    buildTypes {
        free {
            externalNativeBuild {
                cmake {
                    arguments.add("-DPRODUCT_VARIANT_PREMIUM=OFF")
                }
            }
        }
        premium {
            externalNativeBuild {
                cmake {
                    arguments.add("-DPRODUCT_VARIANT_PREMIUM=ON")
                }
            }
        }
    }
}

然后,在您的 CMakeLists.txt 中

if (DPRODUCT_VARIANT_PREMIUM)
  # Do stuff for the premium build.
else()
  # Do stuff for the free build.
endif()

变量的名称由您决定,但请确保避免使用任何以 ANDROID_APP_CMAKE_ 开头的名称,以避免与现有标志发生冲突或混淆。

有关示例,请参阅Sanitizers NDK 示例

了解 CMake 构建命令

在调试 CMake 构建问题时,了解 Gradle 在交叉编译 Android 时使用的具体构建参数非常有用。

Android Gradle 插件会为每个 ABI 和构建类型对执行 CMake 构建时使用的构建参数保存到 build_command.txt 中。这些文件位于以下目录中

<project-root>/<module-root>/.cxx/cmake/<build-type>/<ABI>/

以下代码段显示了构建 hello-jni 示例的可调试版本的目标 armeabi-v7a 架构的 CMake 参数示例。

                    Executable : ${HOME}/Android/Sdk/cmake/3.10.2.4988404/bin/cmake
arguments :
-H${HOME}/Dev/github-projects/googlesamples/ndk-samples/hello-jni/app/src/main/cpp
-DCMAKE_FIND_ROOT_PATH=${HOME}/Dev/github-projects/googlesamples/ndk-samples/hello-jni/app/.cxx/cmake/universalDebug/prefab/armeabi-v7a/prefab
-DCMAKE_BUILD_TYPE=Debug
-DCMAKE_TOOLCHAIN_FILE=${HOME}/Android/Sdk/ndk/22.1.7171670/build/cmake/android.toolchain.cmake
-DANDROID_ABI=armeabi-v7a
-DANDROID_NDK=${HOME}/Android/Sdk/ndk/22.1.7171670
-DANDROID_PLATFORM=android-23
-DCMAKE_ANDROID_ARCH_ABI=armeabi-v7a
-DCMAKE_ANDROID_NDK=${HOME}/Android/Sdk/ndk/22.1.7171670
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON
-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=${HOME}/Dev/github-projects/googlesamples/ndk-samples/hello-jni/app/build/intermediates/cmake/universalDebug/obj/armeabi-v7a
-DCMAKE_RUNTIME_OUTPUT_DIRECTORY=${HOME}/Dev/github-projects/googlesamples/ndk-samples/hello-jni/app/build/intermediates/cmake/universalDebug/obj/armeabi-v7a
-DCMAKE_MAKE_PROGRAM=${HOME}/Android/Sdk/cmake/3.10.2.4988404/bin/ninja
-DCMAKE_SYSTEM_NAME=Android
-DCMAKE_SYSTEM_VERSION=23
-B${HOME}/Dev/github-projects/googlesamples/ndk-samples/hello-jni/app/.cxx/cmake/universalDebug/armeabi-v7a
-GNinja
jvmArgs :


                    Build command args: []
                    Version: 1

使用预构建库

如果需要导入的预构建库作为 AAR 分发,请按照Studio 的依赖项文档导入和使用这些库。如果您没有使用 AGP,可以按照 https://google.github.io/prefab/example-workflow.html 进行操作,但迁移到 AGP 可能更容易。

对于未作为 AAR 分发的库,有关在 CMake 中使用预构建库的说明,请参阅 CMake 手册中关于 IMPORTED 目标的 add_library 文档。

构建第三方代码

有几种方法可以将第三方代码作为 CMake 项目的一部分进行构建,哪种选项最有效取决于您的情况。最好的选择通常是根本不这样做。相反,构建库的 AAR并在您的应用程序中使用它。您不需要发布该 AAR。它可以是您 Gradle 项目内部的。

如果这不是一个选项

  • 将第三方源代码(即复制)引入您的存储库,并使用add_subdirectory构建它。这只在其他库也使用 CMake 构建时有效。
  • 定义一个ExternalProject
  • 单独构建库,然后按照使用预构建库将其作为预构建库导入。

CMake 中的 YASM 支持

NDK 提供了 CMake 支持,用于构建用YASM编写的汇编代码,以便在 x86 和 x86-64 架构上运行。YASM 是一个开源的 x86 和 x86-64 架构汇编程序,基于 NASM 汇编程序。

要使用 CMake 构建汇编代码,请在项目的 CMakeLists.txt 中进行以下更改

  1. 使用设置为 ASM_NASM 的值调用enable_language
  2. 根据您是构建共享库还是可执行二进制文件,调用add_libraryadd_executable。在参数中,传递一个源文件列表,该列表由 YASM 中汇编程序的 .asm 文件和关联的 C 库或函数的 .c 文件组成。

以下代码段显示了如何配置 CMakeLists.txt 以将 YASM 程序构建为共享库。

cmake_minimum_required(VERSION 3.6.0)

enable_language(ASM_NASM)

add_library(test-yasm SHARED jni/test-yasm.c jni/print_hello.asm)

有关如何将 YASM 程序构建为可执行文件的示例,请参阅 NDK git 存储库中的yasm 测试

报告问题

如果您在使用 NDK 或其 CMake 工具链文件时遇到任何问题,请通过 GitHub 上的android-ndk/ndk问题跟踪器报告。对于 Gradle 或 Android Gradle 插件问题,请报告 Studio 错误