CMake

Android NDK 支援使用 CMake 為應用程式編譯 C 和 C++ 程式碼。本頁面說明如何透過 Android Gradle 外掛程式的 ExternalNativeBuild,或透過直接叫用 CMake,將 NDK 與 CMake 搭配使用。

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 檔案中明確設定這個引數。如要控管 ABI Gradle 的目標,請依照 Android ABI 中所述使用 abiFilters

指令列

CMake 針對每項建構作業的單一目標進行建構。如要設定多個 Android ABI 目標,則必須為每個 ABI 建構一次。建議針對每個 ABI 使用不同的建構目錄,以免建構作業之間發生衝突。

附註
armeabi-v7a
armeabi-v7a with NEON 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) 中套用標記。

對於較少數的編譯器標記,您也可以使用 cppFlags 或類似的屬性,在 build.gradle 檔案中設定這些標記。請勿這麼做。從 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 建構問題偵錯,建議您先瞭解為 Android 執行跨平台程式碼編譯作業時,Gradle 使用哪些特定建構引數。

Android Gradle 外掛程式會針對每個 ABI 及與 build_command.txt 配對的建構類型的 CMake 建構作業,儲存執行此類作業時使用的建構引數。這些檔案位於以下目錄中:

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

您可以使用 CMake 引數建構 hello-jni 範例的可偵錯版本並指定 armeabi-v7a 架構,如以下程式碼片段範例所示:

                    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 使用手冊中有關 IMPORTED 目標的 add_library 說明文件,瞭解如何透過 CMake 使用預先建構的程式庫。

建構第三方程式碼

在 CMake 專案中建構第三方程式碼的實用方法有很多,哪種方法最合適需視情況而定,但最佳做法通常是完全不建構第三方程式碼。請改為針對程式庫建構 AAR,並在應用程式中取用。您不必「發布」AAR,AAR 可位於 Gradle 專案內部。

如果不適合這種做法:

  • 將第三方來源提供 (即複製) 到存放區,並使用 add_subdirectory 建構。只有在其他程式庫也使用 CMake 來建構時,才能使用這種做法。
  • 定義 ExternalProject
  • 從專案中單獨建構程式庫,並按照「使用預先建構的程式庫」所述,將其匯入為預先建構程式庫。

CMake 中的 YASM 支援

NDK 針對建構以 YASM 編寫的組譯程式碼提供 CMake 支援,以便在 x86 和 x86-64 架構上執行。YASM 是 x86 和 x86-64 架構的開放原始碼組譯工具,以 NASM 組譯工具為基礎。

如要使用 CMake 建構組譯程式碼,請在專案的 CMakeLists.txt 中進行下列變更:

  1. 呼叫 enable_language,並將值設為 ASM_NASM
  2. 如要建構共用程式庫,請呼叫 add_library;如要建構可執行的二進位檔,則呼叫 add_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 錯誤