Gradle 在建置原生專案時採用慣例優於配置的方法。如果您來自其他原生建置系統,這些概念起初可能不太熟悉,但它們旨在簡化建置腳本的編寫。

在本章中,我們將詳細探討 C++ 專案,但大多數主題也適用於其他支援的原生語言。如果您對使用 Gradle 建置原生專案沒有太多經驗,請查看 C++ 教學,其中包含關於如何建置各種類型基本 C++ 專案以及一些常見用例的逐步說明。

本章涵蓋的 C++ 外掛程式是 在 2018 年推出的,我們建議使用者使用這些外掛程式,而不是您可能會找到參考資料的較舊的 Native 外掛程式

簡介

C++ 專案最簡單的建置腳本是套用 C++ 應用程式外掛程式或 C++ 函式庫外掛程式,並選擇性地設定專案版本

build.gradle.kts
plugins {
    `cpp-application` // or `cpp-library`
}

version = "1.2.1"
build.gradle
plugins {
    id 'cpp-application' // or 'cpp-library'
}

version = '1.2.1'

透過套用任一 C++ 外掛程式,您將獲得許多功能

  • compileDebugCppcompileReleaseCpp 任務,分別編譯 src/main/cpp 下的 C++ 原始碼檔案,用於眾所周知的 debug 和 release 建置類型。

  • linkDebuglinkRelease 任務,將編譯後的 C++ 物件檔案連結成應用程式的可執行檔,或用於具有共享連結的函式庫的共享函式庫,適用於 debug 和 release 建置類型。

  • createDebugcreateRelease 任務,將編譯後的 C++ 物件檔案組合成靜態函式庫,用於具有靜態連結的函式庫,適用於 debug 和 release 建置類型。

對於任何非簡單的 C++ 專案,您可能會有某些檔案相依性和特定於您的專案的其他配置。

C++ 外掛程式也將上述任務整合到標準的 生命週期任務 中。產生開發二進制檔的任務附加到 assemble。預設情況下,開發二進制檔是 debug 變體。

本章的其餘部分說明在建置函式庫和應用程式時,根據您的需求自訂建置的不同方式。

介紹建置變體

原生專案通常可以產生幾個不同的二進制檔,例如 debug 或 release 版本,或針對特定平台和處理器架構的版本。Gradle 通過維度變體的概念來管理這一點。

維度只是一個類別,其中每個類別都與其餘類別正交。例如,「建置類型」維度是一個包含 debug 和 release 的類別。「架構」維度涵蓋處理器架構,如 x86-64 和 PowerPC。

變體是這些維度的值的組合,每個維度恰好包含一個值。您可能會有一個「debug x86-64」或「release PowerPC」變體。

Gradle 內建支援多個維度和每個維度內的多個值。您可以在原生外掛程式參考章節中找到它們的列表。

宣告您的原始碼檔案

Gradle 的 C++ 支援直接從 applicationlibrary 腳本區塊使用 ConfigurableFileCollection 來配置要編譯的原始碼集。

函式庫區分私有(實作細節)和公開(匯出給消費者)標頭。

您也可以為每個二進制建置配置原始碼,以應對僅在特定目標機器上編譯原始碼的情況。

cpp sourcesets compilation
圖 1. 原始碼和 C++ 編譯

測試原始碼在每個測試套件腳本區塊上配置。請參閱 測試 C++ 專案 章節。

管理您的相依性

絕大多數專案都依賴其他專案,因此管理專案的相依性是建置任何專案的重要組成部分。相依性管理是一個很大的主題,因此我們在這裡僅關注 C++ 專案的基礎知識。如果您想深入了解細節,請查看 相依性管理簡介

Gradle 支援從 Gradle 發布的 Maven 儲存庫 [1] 取用預先建置的二進制檔。

我們將介紹如何在多建置專案中新增專案之間的相依性。

為您的 C++ 專案指定相依性需要兩條資訊

  • 相依性的識別資訊(專案路徑、Maven GAV)

  • 它需要的用途,例如編譯、連結、執行階段或以上所有。

此資訊在 C++ applicationlibrary 腳本區塊的 dependencies {} 區塊中指定。例如,要告訴 Gradle 您的專案需要函式庫 common 來編譯和連結您的生產程式碼,您可以使用以下程式碼片段

範例 2. 宣告相依性
build.gradle.kts
application {
    dependencies {
        implementation(project(":common"))
    }
}
build.gradle
application {
    dependencies {
        implementation project(':common')
    }
}

以下是 Gradle 對於這三個元素的術語

  • 配置(例如:implementation)- 相依性的命名集合,為特定目標(例如編譯或連結模組)分組在一起

  • 專案參考(例如:project(':common'))- 由指定路徑參考的專案

您可以在這裡找到更全面的相依性管理術語詞彙表。

就配置而言,主要的關注點是

  • implementation - 用於編譯、連結和執行階段

  • cppCompileVariant - 用於編譯生產程式碼所需的相依性,但不應成為連結或執行階段過程的一部分

  • nativeLinkVariant - 用於連結程式碼所需的相依性,但不應成為編譯或執行階段過程的一部分

  • nativeRuntimeVariant - 用於執行元件所需的相依性,但不應成為編譯或連結過程的一部分

您可以在原生外掛程式參考章節中了解更多關於這些以及它們之間如何關聯的資訊。

請注意,C++ 函式庫外掛程式 會為編譯和連結模組以及任何依賴於它的模組所需的相依性建立一個額外的配置 — api

我們在這裡僅觸及皮毛,因此我們建議您在熟悉使用 Gradle 建置 C++ 專案的基礎知識後,閱讀 專門的相依性管理章節

一些需要進一步閱讀的常見場景包括

您會發現 Gradle 具有豐富的 API 用於處理相依性 — 需要時間才能掌握,但在常見場景中易於使用。

如果您遵循慣例,編譯您的程式碼可以非常容易

  1. 將您的原始碼放在 src/main/cpp 目錄下

  2. implementation 配置中宣告您的編譯相依性(請參閱上一節)

  3. 執行 assemble 任務

我們建議您盡可能遵循這些慣例,但您不必如此。

如您接下來所見,有幾個自訂選項。

所有 CppCompile 任務都是增量的且可快取的。

支援的工具鏈

Gradle 提供了使用不同工具鏈執行相同建置的能力。當您建置原生二進制檔時,Gradle 將嘗試在您的機器上找到可以建置二進制檔的工具鏈。Gradle 選擇第一個可以為目標作業系統和架構建置的工具鏈。未來,Gradle 在選擇工具鏈時將考慮原始碼和 ABI 相容性。

Gradle 一般支援主要作業系統上的三個主要工具鏈:Clang [2]、GCC [3] 和 Visual C++ [4](僅限 Windows)。據報告,使用 Macports 和 Homebrew 安裝的 GCC 和 Clang 可以正常工作,但這並未持續測試。

Windows

要在 Windows 上建置,請安裝相容版本的 Visual Studio。C++ 外掛程式將發現 Visual Studio 安裝並選擇最新版本。如果自動發現不起作用,您可以使用 toolChains 區塊配置 Visual Studio。

build.gradle.kts
toolChains{
    withType<VisualCpp>().configureEach {
        setInstallDir("C:\\Program Files (x86)\\Microsoft Visual Studio\\2022\\BuildTools")
    }
}
build.gradle
toolChains{
    withType(VisualCpp).configureEach {
        setInstallDir('C:\\Program Files (x86)\\Microsoft Visual Studio\\2022\\BuildTools')
    }
}

或者,您可以安裝帶有 GCC 的 Cygwin 或 MinGW。目前不支援 Clang。

macOS

要在 macOS 上建置,您應該安裝 Xcode。C++ 外掛程式將使用系統 PATH 發現 Xcode 安裝。

C++ 外掛程式也適用於使用 Macports 或 Homebrew [5] 安裝的 GCC 和 Clang。要使用 Macports 或 Homebrew 之一,您需要將 Macports/Homebrew 新增到系統 PATH。

Linux

要在 Linux 上建置,請安裝相容版本的 GCC 或 Clang。C++ 外掛程式將使用系統 PATH 發現 GCC 或 Clang。

自訂檔案和目錄位置

假設您有一個舊版函式庫專案,它使用 src 目錄用於生產程式碼和私有標頭,以及 include 目錄用於匯出的標頭。傳統的目錄結構將無法工作,因此您需要告訴 Gradle 在哪裡找到原始碼和標頭檔案。您可以通過 applicationlibrary 腳本區塊來做到這一點。

每個組件腳本區塊以及每個二進制檔都定義了其原始碼所在的位置。您可以使用以下語法覆蓋慣例值

build.gradle.kts
library {
    source.from(file("src"))
    privateHeaders.from(file("src"))
    publicHeaders.from(file("include"))
}
build.gradle
library {
    source.from file('src')
    privateHeaders.from file('src')
    publicHeaders.from file('include')
}

現在 Gradle 將僅直接在 src 中搜尋原始碼和私有標頭,在 include 中搜尋公開標頭。

大多數編譯器和連結器選項都可以通過相應的任務訪問,例如 compileVariantCpplinkVariantcreateVariant。這些任務的類型分別為 CppCompileLinkSharedLibraryCreateStaticLibrary。請閱讀任務參考以獲取最新的和全面的選項列表。

例如,如果您想更改編譯器為所有變體產生的警告級別,您可以使用此配置

build.gradle.kts
tasks.withType(CppCompile::class.java).configureEach {
    // Define a preprocessor macro for every binary
    macros.put("NDEBUG", null)

    // Define a compiler options
    compilerArgs.add("-W3")

    // Define toolchain-specific compiler options
    compilerArgs.addAll(toolChain.map { toolChain ->
        when (toolChain) {
            is Gcc, is Clang -> listOf("-O2", "-fno-access-control")
            is VisualCpp -> listOf("/Zi")
            else -> listOf()
        }
    })
}
build.gradle
tasks.withType(CppCompile).configureEach {
    // Define a preprocessor macro for every binary
    macros.put("NDEBUG", null)

    // Define a compiler options
    compilerArgs.add '-W3'

    // Define toolchain-specific compiler options
    compilerArgs.addAll toolChain.map { toolChain ->
        if (toolChain in [ Gcc, Clang ]) {
            return ['-O2', '-fno-access-control']
        } else if (toolChain in VisualCpp) {
            return ['/Zi']
        }
        return []
    }
}

也可以通過 applicationlibrary 腳本區塊上的 BinaryCollection 找到特定變體的實例

build.gradle.kts
application {
    binaries.configureEach(CppStaticLibrary::class.java) {
        // Define a preprocessor macro for every binary
        compileTask.get().macros.put("NDEBUG", null)

        // Define a compiler options
        compileTask.get().compilerArgs.add("-W3")

        // Define toolchain-specific compiler options
        when (toolChain) {
            is Gcc, is Clang -> compileTask.get().compilerArgs.addAll(listOf("-O2", "-fno-access-control"))
            is VisualCpp -> compileTask.get().compilerArgs.add("/Zi")
        }
    }
}
build.gradle
application {
    binaries.configureEach(CppStaticLibrary) {
        // Define a preprocessor macro for every binary
        compileTask.get().macros.put("NDEBUG", null)

        // Define a compiler options
        compileTask.get().compilerArgs.add '-W3'

        // Define toolchain-specific compiler options
        if (toolChain in [ Gcc, Clang ]) {
            compileTask.get().compilerArgs.addAll(['-O2', '-fno-access-control'])
        } else if (toolChain in VisualCpp) {
            compileTask.get().compilerArgs.add('/Zi')
        }
    }
}

選擇目標機器

預設情況下,Gradle 將嘗試為主機作業系統和架構建立 C++ 二進制變體。可以通過在 applicationlibrary 腳本區塊上指定 TargetMachine 集合來覆蓋此設定

build.gradle.kts
application {
    targetMachines = listOf(machines.windows.x86, machines.windows.x86_64, machines.macOS.x86_64, machines.linux.x86_64)
}
build.gradle
application {
    targetMachines = [
        machines.linux.x86_64,
        machines.windows.x86, machines.windows.x86_64,
        machines.macOS.x86_64
    ]
}

封裝和發布

您如何封裝和可能發布您的 C++ 專案在原生世界中差異很大。Gradle 帶有預設值,但可以毫無問題地實作自訂封裝。

  • 可執行檔直接發布到 Maven 儲存庫。

  • 共享和靜態函式庫檔案與公開標頭的 zip 檔一起直接發布到 Maven 儲存庫。

  • 對於應用程式,Gradle 還支援在已知位置安裝和執行可執行檔及其所有共享函式庫相依性。

清除建置

C++ 應用程式和函式庫外掛程式通過使用 基礎外掛程式 向您的專案新增 clean 任務。此任務只是刪除 layout.buildDirectory 目錄中的所有內容,這就是為什麼您應該始終將建置產生的檔案放在那裡的原因。該任務是 Delete 的實例,您可以通過設定其 dir 屬性來更改它刪除的目錄。

建置 C++ 函式庫

函式庫專案的獨特之處在於它們被其他 C++ 專案使用(或「取用」)。這意味著與二進制檔和標頭一起發布的相依性元數據 — 以 Gradle 模組元數據的形式 — 至關重要。特別是,您的函式庫的消費者應該能夠區分兩種不同類型的相依性:僅編譯您的函式庫所需的相依性和也需要編譯消費者的相依性。

Gradle 通過 C++ 函式庫外掛程式 管理這種區別,除了本章中涵蓋的 implementation 之外,它還引入了 api 配置。如果來自相依性的類型作為靜態函式庫的未解析符號或在公開標頭中出現,則該相依性通過您的函式庫的公開 API 公開,因此應將其新增到 api 配置中。否則,相依性是內部實作細節,應新增到 implementation 中。

如果您不確定 API 和實作相依性之間的區別,C++ 函式庫外掛程式 章節有詳細的解釋。此外,您可以在相應的 範例 中看到建置 C++ 函式庫的基本實務範例。

建置 C++ 應用程式

有關更多詳細資訊,請參閱 C++ 應用程式外掛程式 章節,但以下是您獲得的功能的快速摘要

  • install 建立一個目錄,其中包含運行它所需的一切

  • Shell 和 Windows Batch 腳本以啟動應用程式

您可以在相應的 範例 中看到建置 C++ 應用程式的基本範例。


1. 遺憾的是,Conan 和 Nuget 儲存庫尚不支援作為核心功能
2. 與 macOS 上的 Xcode 一起安裝
3. 通過 Cygwin 和 MinGW 安裝,用於 Windows 上 32 位和 64 位架構
4. 與 Visual Studio 2010 至 2019 一起安裝
5. Macports 和 Homebrew 安裝的 GCC 和 Clang 並非官方支援