使用 Ninja 整合自訂 C/C++ 建構系統 (實驗功能)

如果您沒有使用 CMake 或 ndk-build,但想要完全整合 Android Gradle 外掛程式 (AGP) C/C++ 版本和 Android Studio,您可以建立自訂的 C/C++ 建構系統,方法是打造出利用 Ninja 建構檔案格式寫入建構資訊的殼層指令碼。

Android Studio 和 AGP 新增了針對自訂 C/C++ 建構系統的實驗性支援。這項功能是從 Android Studio Dolphin | 2021.3.1 Canary 4 開始提供。

總覽

C/C++ 專案的常見模式 (尤其是指定多個平台的專案) 是透過一些基礎表示法為每個平台產生專案,CMake 便是該模式其中一個顯著的例子。CMake 可針對儲存在 CMakeLists.txt 檔案中的單一基礎表示法,為 Android、iOS 及其他平台產生專案。

雖然 AGP 直接支援 CMake,但還有其他不受直接支援的專案產生器:

這些類型的專案產生器支援 Ninja 做為 C/C++ 版本的後端表示法,或可被調整來產生 Ninja 做為後端表示法。

如果設定正確,含有整合式 C/C++ 專案系統產生器的 AGP 專案可讓使用者執行以下動作:

  • 透過指令列和 Android Studio 進行建構。

  • 在 Android Studio 中編輯具有完整語言服務 (例如前往定義) 的來源。

  • 使用 Android Studio 偵錯工具進行原生和混合程序偵錯。

如何修改建構作業,以便使用自訂 C/C++ 建構設定指令碼

本節逐步說明使用 AGP 的自訂 C/C++ 建構設定指令碼。

步驟 1:修改模組層級的 build.gradle 檔案,以參照設定指令碼

如要在 AGP 中啟用 Ninja 支援,請在模組層級 build.gradle 檔案中設定 experimentalProperties

android {
  defaultConfig {
    externalNativeBuild {
      experimentalProperties["ninja.abiFilters"] = [ "x86", "arm64-v8a" ]
      experimentalProperties["ninja.path"] = "source-file-list.txt"
      experimentalProperties["ninja.configure"] = "configure-ninja"
      experimentalProperties["ninja.arguments"] = [
            "\${ndk.moduleMakeFile}",
            "--variant=\${ndk.variantName}",
            "--abi=Android-\${ndk.abi}",
            "--configuration-dir=\${ndk.configurationDir}",
            "--ndk-version=\${ndk.moduleNdkVersion}",
            "--min-sdk-version=\${ndk.minSdkVersion}"
       ]
     }
   }

AGP 解讀屬性如下:

  • ninja.abiFilters 是待建構的 ABI 清單。有效值為 x86x86-64armeabi-v7aarm64-v8a

  • ninja.path 是 C/C++ 專案檔案的路徑。這個檔案的格式不限,且檔案變更會在 Android Studio 中觸發 Gradle 同步處理作業的提示。

  • ninja.configure 是指令碼檔案路徑,在需要設定 C/C++ 專案時,Gradle 將執行這個指令碼檔案。專案會在第一次建構期間、Android Studio 中的 Gradle 同步處理期間,或是其中一個設定指令碼輸入內容變更時設定。

  • ninja.arguments 這個引數清單會傳遞到由 ninja.configure 定義的指令碼。這份清單中的元素可以參照一組巨集,其值會受到 AGP 目前的設定內容影響:

    • ${ndk.moduleMakeFile}ninja.configure 檔案的完整路徑。在這個範例中,我們使用的是 C:\path\to\configure-ninja.bat

    • ${ndk.variantName} 是目前正在建構的 AGP 版本名稱。例如偵錯或發布版本。

    • ${ndk.abi} 是目前正在建構的 AGP ABI 的名稱。例如 x86arm64-v8a

    • ${ndk.buildRoot} 是由 AGP 產生的資料夾名稱,這是指令碼寫入其輸出內容的資料夾。詳細步驟請參閱步驟 2:建立設定指令碼

    • ${ndk.ndkVersion} 是要使用的 NDK 版本。通常是傳送至 build.gradle 檔案中的 android.ndkVersion 的值;如果不存在,則為預設值。

    • ${ndk.minPlatform} 是 AGP 要求的最低目標 Android 平台。

  • ninja.targets 是應建構的特定 Ninja 目標清單。

步驟 2:建立設定指令碼

設定指令碼 (前例中為 configure-ninja.bat) 的最低責任是產生 build.ninja 檔案,在使用 Ninja 建構時,會編譯並連結專案的所有原生輸出內容。通常是 .o (物件)、.a (封存) 和 .so (共用物件) 檔案。

設定指令碼可根據需求,在兩個不同位置撰寫 build.ninja 檔案。

  • 如果 AGP 能夠選擇位置,則設定指令碼就會在 ${ndk.buildRoot} 巨集中設定的位置寫入 build.ninja

  • 如果設定指令碼必須選擇 build.ninja 檔案的位置,那麼系統也會在 ${ndk.buildRoot} 巨集中設定的位置寫入名為 build.ninja.txt 的檔案。這個檔案內含設定指令碼所寫入的 build.ninja 檔案完整路徑。

build.ninja 檔案的結構

一般來說,大多數能夠準確反映 Android C/C++ 版本的結構通常都能正常運作。AGP 和 Android Studio 需要的主要元素包括:

  • C/C++ 來源檔案清單,以及 Clang 編譯所需的標記。

  • 輸出程式庫的清單。這通常是 .so (共用物件) 檔案,但也可以是 .a (封存) 或執行檔 (無副檔名)。

如果您需要產生 build.ninja 檔案的範例,可以在使用 build.ninja 產生器時查看 CMake 輸出結果。

以下列舉幾個最小限度 build.ninja 範本的範例。

rule COMPILE
   command = /path/to/ndk/clang -c $in -o $out {other flags}
rule LINK
   command = /path/to/ndk/clang $in -o $out {other flags}

build source.o : COMPILE source.cpp
build lib.so : LINK source.o

最佳做法

除了使用規定 (來源檔案和輸出程式庫清單),建議您參考下列最佳做法。

使用 phony 規則宣告已命名的輸出內容

建議您盡可能讓 build.ninja 結構使用 phony 規則,為建構作業輸出的名稱提供使用者可理解的名稱。舉例來說,如果輸出名稱是 c:/path/to/lib.so,您可以按照以下方式為其提供使用者可理解的名稱。

build curl: phony /path/to/lib.so

這麼做的好處是,您可以將這個名稱指定為 build.gradle 檔案中的建構目標。例如:

android {
  defaultConfig {
    externalNativeBuild {
      ...
      experimentalProperties["ninja.targets"] = [ "curl" ]

指定「全部」目標

如果指定 all 目標,在 build.gradle 檔案中未明確指定任何目標時,這是 AGP 建構的預設程式庫組合。

rule COMPILE
   command = /path/to/ndk/clang $in -o $out {other flags}
rule LINK
   command = /path/to/ndk/clang $in -o $out {other flags}

build foo.o : COMPILE foo.cpp
build bar.o : COMPILE bar.cpp
build libfoo.so : LINK foo.o
build libbar.so : LINK bar.o
build all: phony libfoo.so libbar.so

指定替代的建構方法 (選用)

更進階的用途是納入非以 Ninja 為基礎的現有建構系統。在這種情況下,您還是必須以所有標記和輸出程式庫來代表所有來源,以便 Android Studio 提供適當語言服務功能,例如「自動完成」和「前往定義」。不過,您希望 AGP 在實際建構期間推遲到基礎建構系統。

如要完成這項操作,您可以使用具備特定擴充功能 .passthrough 的 Ninja 建構作業輸出內容。

以更具體的例子來說,假設您想包裝 MSBuild。您的設定指令碼仍會照常產生 build.ninja,但也會新增可定義 AGP 叫用 MSBuild 的直通目標。

rule COMPILE
   command = /path/to/ndk/clang $in -o $out {other flags}
rule LINK
   command = /path/to/ndk/clang $in -o $out {other flags}

rule MBSUILD_CURL
  command = /path/to/msbuild {flags to build curl with MSBuild}

build source.o : COMPILE source.cpp
build lib.so : LINK source.o
build curl : phony lib.so
build curl.passthrough : MBSUILD_CURL

提供意見

這項功能仍在實驗階段,因此非常歡迎您提供意見。您可以透過下列管道提供意見:

  • 如需查看一般意見回饋,請在這個錯誤中新增註解。

  • 如要回報錯誤,請開啟 Android Studio,然後依序點選「Help」>「Submit Feedback」。請務必參閱「自訂 C/C++ 建構系統」,以利修正錯誤。

  • 如要回報錯誤,但您尚未安裝 Android Studio,請使用這個範本回報錯誤。