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++ 源文件,对应于众所周知的 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 安装的 GCC 和 Clang [5]。要使用 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 Module Metadata 的形式——至关重要。特别是,库的消费者应该能够区分两种不同类型的依赖项:一种是仅编译您的库所需的依赖项,另一种是编译消费者也需要的依赖项。

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 未获官方支持