本章節所描述的外掛與組態快取不相容。

Gradle 使用慣例優先於組態的方式來建置原生專案。如果您來自其他原生建置系統,這些概念一開始可能很陌生,但它們的目的是簡化建置指令碼撰寫。

我們將在本章節詳細探討 C++ 專案,但大多數主題也適用於其他支援的原生語言。如果您沒有太多使用 Gradle 建置原生專案的經驗,請參閱 C++ 教學課程,以取得逐步說明,了解如何建置各種基本 C++ 專案以及一些常見的使用案例。

本章節介紹的 C++ 外掛於2018 年推出,我們建議使用者使用這些外掛,而非您可能找到參考的舊版原生外掛

簡介

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++ 原始檔。

  • linkDebuglinkRelease 任務,將已編譯的 C++ 物件檔連結成可執行檔(針對應用程式),或連結成共用函式庫(針對具有共用連結的函式庫),以供偵錯和發行建置類型使用。

  • createDebugcreateRelease 任務,將已編譯的 C++ 物件檔組裝成靜態函式庫,以供具有靜態連結的函式庫,使用偵錯和發行建置類型。

對於任何非平凡的 C++ 專案,您可能有一些檔案相依性,以及特定於您的專案的其他組態。

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

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

簡介建置變異

原生專案通常可以產生數個不同的二進位檔,例如偵錯或發行,或針對特定平台和處理器架構的二進位檔。Gradle 透過維度變異的概念來管理這項工作。

維度只是一個類別,其中每個類別都與其他類別正交。例如,「建置類型」維度是一個類別,其中包含偵錯和發行。「架構」維度涵蓋 x86-64 和 PowerPC 等處理器架構。

變異是這些維度的值組合,由每個維度的值組成。您可能有「偵錯 x86-64」或「發行 PowerPC」變異。

Gradle 內建支援數個維度,以及每個維度中的數個值。您可以在 原生外掛程式參考章節 中找到清單。

宣告您的原始檔

Gradle 的 C++ 支援直接從 應用程式函式庫 指令碼區塊使用 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 安裝並選取最新版本。無需設定環境變數或批次指令碼。這在 Cygwin shell 或 Windows 命令列中都能正常運作。

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

macOS

要在 macOS 上進行建置,您應該安裝 Xcode。C++ 外掛程式會使用系統路徑找出 Xcode 安裝。

C++ 外掛程式也可以搭配使用 Macports 或 Homebrew 安裝的 GCC 和 Clang [5]。若要使用 Macports 或 Homebrew,您需要將 Macports/Homebrew 加入系統路徑。

Linux

要在 Linux 上進行建置,請安裝相容版本的 GCC 或 Clang。C++ 外掛程式會使用系統路徑找出 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 批次指令碼

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


1. 很遺憾,Conan 和 Nuget 儲存庫尚未支援為核心功能
2. 在 macOS 上與 Xcode 一起安裝
3. 透過 Cygwin 和 MinGW 安裝在 Windows 上的 32 和 64 位元架構
4. 與 Visual Studio 2010 至 2019 一起安裝
5. Macports 和 Homebrew 安裝的 GCC 和 Clang 未獲得官方支援