简介
最简单的 C++ 项目构建脚本应用 C++ 应用程序插件或 C++ 库插件,并可选地设置项目版本
plugins {
`cpp-application` // or `cpp-library`
}
version = "1.2.1"
plugins {
id 'cpp-application' // or 'cpp-library'
}
version = '1.2.1'
通过应用任一 C++ 插件,您可以获得一系列功能:
-
compileDebugCpp
和compileReleaseCpp
任务,分别编译 src/main/cpp 下的 C++ 源文件,用于知名的调试和发布构建类型。 -
linkDebug
和linkRelease
任务,将编译后的 C++ 对象文件链接成可执行文件(适用于应用程序)或共享库(适用于具有共享链接的库),用于调试和发布构建类型。 -
createDebug
和createRelease
任务,将编译后的 C++ 对象文件组装成静态库(适用于具有静态链接的库),用于调试和发布构建类型。
对于任何非平凡的 C++ 项目,您可能有一些文件依赖项和特定于您项目的额外配置。
C++ 插件还将上述任务集成到标准生命周期任务中。生成开发二进制文件的任务附加到 assemble
。默认情况下,开发二进制文件是调试变体。
本章的其余部分解释了在构建库和应用程序时,根据您的要求自定义构建的不同方法。
引入构建变体
原生项目通常可以生成几种不同的二进制文件,例如调试或发布版本,或者针对特定平台和处理器架构的版本。Gradle 通过“维度”和“变体”的概念来管理这一点。
维度只是一个类别,每个类别都与其他类别正交。例如,“构建类型”维度是一个包含调试和发布类别的类别。“架构”维度涵盖 x86-64 和 PowerPC 等处理器架构。
变体是这些维度的值组合,每个维度恰好包含一个值。您可能有一个“调试 x86-64”或“发布 PowerPC”变体。
Gradle 内置支持多个维度和每个维度内的多个值。您可以在原生插件参考章中找到它们的列表。
管理依赖项
绝大多数项目都依赖于其他项目,因此管理项目的依赖项是构建任何项目的重要组成部分。依赖项管理是一个很大的主题,因此我们在此仅关注 C++ 项目的基础知识。如果您想深入了解细节,请查看依赖项管理简介。
Gradle 支持从 Gradle [1] 发布的 Maven 仓库中使用预构建的二进制文件。
我们将介绍如何在多项目构建中的项目之间添加依赖项。
指定 C++ 项目的依赖项需要两条信息:
-
依赖项的标识信息(项目路径、Maven GAV)
-
它需要什么,例如编译、链接、运行时或以上所有。
此信息在 C++ application
或 library
脚本块的 dependencies {}
块中指定。例如,要告诉 Gradle 您的项目需要库 common
来编译和链接生产代码,您可以使用以下片段:
application {
dependencies {
implementation(project(":common"))
}
}
application {
dependencies {
implementation project(':common')
}
}
Gradle 中这三个元素的术语如下
-
配置(例如:
implementation
) - 命名依赖项集合,为特定目标(例如编译或链接模块)分组 -
项目引用(例如:
project(':common')
) - 由指定路径引用的项目
您可以在此处找到更全面的依赖管理术语词汇表。
就配置而言,最主要的是
-
implementation
- 用于编译、链接和运行时 -
cppCompileVariant
- 编译生产代码所必需但不需要作为链接或运行时过程一部分的依赖项 -
nativeLinkVariant
- 链接代码所必需但不需要作为编译或运行时过程一部分的依赖项 -
nativeRuntimeVariant
- 运行组件所必需但不需要作为编译或链接过程一部分的依赖项
您可以在原生插件参考章中了解有关这些以及它们之间关系的更多信息。
请注意,C++ 库插件创建了一个额外的配置 — api
— 用于编译和链接模块以及依赖于它的任何模块所需的依赖项。
我们在这里只触及了皮毛,因此我们建议您在熟悉使用 Gradle 构建 C++ 项目的基础知识后,阅读专门的依赖项管理章节。
一些需要进一步阅读的常见场景包括:
-
定义自定义Maven 兼容仓库
-
声明兄弟项目作为依赖项
-
通过复合构建测试对第三方依赖项的修复(发布到和消费本地 Maven 仓库的更好替代方案)
您会发现 Gradle 拥有丰富的依赖项处理 API — 掌握它需要时间,但在常见场景下使用起来很简单。
编译和链接代码
如果您遵循约定,编译代码可以非常简单:
-
将源代码放在 src/main/cpp 目录下
-
在
implementation
配置中声明您的编译依赖项(参见上一节) -
运行
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。
toolChains{
withType<VisualCpp>().configureEach {
setInstallDir("C:\\Program Files (x86)\\Microsoft Visual Studio\\2022\\BuildTools")
}
}
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 在哪里找到源文件和头文件。您可以通过 application
或 library
脚本块来完成此操作。
每个组件脚本块以及每个二进制文件都定义了其源代码的所在位置。您可以使用以下语法覆盖约定值:
library {
source.from(file("src"))
privateHeaders.from(file("src"))
publicHeaders.from(file("include"))
}
library {
source.from file('src')
privateHeaders.from file('src')
publicHeaders.from file('include')
}
现在 Gradle 将只在 src 中直接搜索源文件和私有头文件,在 include 中搜索公共头文件。
更改编译器和链接器选项
大多数编译器和链接器选项都可以通过相应的任务访问,例如 compileVariantCpp
、linkVariant
和 createVariant
。这些任务的类型分别为 CppCompile、LinkSharedLibrary 和 CreateStaticLibrary。请阅读任务参考以获取最新和全面的选项列表。
例如,如果您想更改编译器为所有变体生成的警告级别,您可以使用此配置:
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()
}
})
}
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 []
}
}
也可以通过 application
或 library
脚本块上的 BinaryCollection
找到特定变体的实例:
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")
}
}
}
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++ 二进制变体。可以通过在 application
或 library
脚本块上指定 TargetMachine
集合来覆盖此设置:
application {
targetMachines = listOf(machines.windows.x86, machines.windows.x86_64, machines.macOS.x86_64, machines.linux.x86_64)
}
application {
targetMachines = [
machines.linux.x86_64,
machines.windows.x86, machines.windows.x86_64,
machines.macOS.x86_64
]
}
打包和发布
在原生世界中,如何打包并可能发布您的 C++ 项目差异很大。Gradle 附带默认值,但可以毫无问题地实现自定义打包。
-
可执行文件直接发布到 Maven 仓库。
-
共享库和静态库文件直接发布到 Maven 仓库,并附带一个包含公共头文件的 zip 包。
-
对于应用程序,Gradle 还支持将可执行文件及其所有共享库依赖项安装并运行到已知位置。
清理构建
C++ 应用程序和库插件通过使用基础插件为您的项目添加了 clean
任务。此任务只是删除 layout.buildDirectory
目录中的所有内容,因此您应该始终将构建生成的文件放在那里。该任务是 Delete 的一个实例,您可以通过设置其 dir
属性来更改它删除的目录。
构建 C++ 库
库项目的独特之处在于它们被其他 C++ 项目使用(或“消费”)。这意味着与二进制文件和头文件一起发布的依赖元数据——以 Gradle 模块元数据的形式——至关重要。特别是,库的消费者应该能够区分两种不同类型的依赖项:那些只要求编译您的库的依赖项,以及那些也要求编译消费者的依赖项。
Gradle 通过C++ 库插件管理这种区别,该插件在本章介绍的 implementation 之外引入了一个 api 配置。如果依赖项中的类型作为静态库的未解析符号或在公共头文件中出现,则该依赖项通过您的库的公共 API 公开,因此应添加到 api 配置中。否则,该依赖项是内部实现细节,应添加到 implementation 中。
构建 C++ 应用程序
有关更多详细信息,请参阅C++ 应用程序插件章节,但这里是您获得内容的快速摘要:
-
install
创建一个包含运行所需一切的目录 -
启动应用程序的 Shell 和 Windows 批处理脚本。
您可以在相应的示例中看到一个构建 C++ 应用程序的基本示例。