构建原生软件
特性
原生软件插件提供
-
支持在 Windows、Linux、macOS 和其他平台上构建原生库和应用程序。
-
支持多种源语言。
-
支持为不同的架构、操作系统或任何目的构建同一软件的不同变体。
-
增量并行编译、预编译头文件。
-
原生软件组件之间的依赖管理。
-
单元测试执行。
-
生成 Visual Studio 解决方案和项目文件。
-
与各种工具链的深度集成,包括已安装工具链的发现。
支持的语言
目前支持以下源语言
-
C
-
C++
-
Objective-C
-
Objective-C++
-
汇编
-
Windows 资源
工具链支持
Gradle 提供了使用不同工具链执行相同构建的能力。当您构建原生二进制文件时,Gradle 将尝试在您的机器上找到一个可以构建该二进制文件的已安装工具链。您可以精确地调整其工作方式,有关详细信息,请参阅工具链支持。
支持以下工具链
操作系统 | 工具链 | 备注 |
---|---|---|
Linux |
||
Linux |
||
macOS |
XCode |
使用 XCode 捆绑的 Clang 工具链。 |
Windows |
Windows XP 及更高版本,Visual C++ 2010/2012/2013/2015/2017/2019。 |
|
Windows |
Windows XP 及更高版本。 |
|
Windows |
Windows XP 及更高版本。 |
以下工具链非官方支持。它们通常工作良好,但并未持续测试
操作系统 | 工具链 | 备注 |
---|---|---|
macOS |
来自 Macports 的 GCC |
|
macOS |
来自 Macports 的 Clang |
|
类 UNIX |
||
类 UNIX |
工具链安装
请注意,如果您正在使用 GCC,则目前需要安装 C++ 支持,即使您不是从 C++ 源文件构建。此限制将在未来的 Gradle 版本中移除。 |
要构建原生软件,您需要安装兼容的工具链
Windows
要在 Windows 上构建,请安装兼容版本的 Visual Studio。原生插件将发现 Visual Studio 安装并选择最新版本。无需处理环境变量或批处理脚本。这在 Cygwin shell 或 Windows 命令行中工作良好。
或者,您可以安装带有 GCC 的 Cygwin 或 MinGW。Clang 目前不支持。
macOS
要在 macOS 上构建,您应该安装 XCode。原生插件将使用系统 PATH 发现 XCode 安装。
原生插件也适用于 Macports 捆绑的 GCC 和 Clang。要使用其中一个 Macports 工具链,您需要使用 port select
命令将该工具链设置为默认工具链,并将 Macports 添加到系统 PATH 中。
Linux
要在 Linux 上构建,请安装兼容版本的 GCC 或 Clang。原生插件将使用系统 PATH 发现 GCC 或 Clang。
原生软件模型
原生软件模型基于基本的 Gradle 软件模型构建。
要使用 Gradle 构建原生软件,您的项目应该定义一个或多个 原生组件。每个组件代表 Gradle 应该构建的可执行文件或库。一个项目可以定义任意数量的组件。Gradle 默认不定义任何组件。
对于每个组件,Gradle 为组件可以构建的每种语言定义一个 源集。源集本质上只是一组包含源文件的源目录。例如,当您应用 c
插件并定义一个名为 helloworld
的库时,Gradle 默认将定义一个包含 src/helloworld/c
目录中 C 源文件的源集。它将使用这些源文件构建 helloworld
库。这在下面有更详细的描述。
对于每个组件,Gradle 定义一个或多个 二进制文件 作为输出。为了构建二进制文件,Gradle 将获取为组件定义的源文件,根据源语言进行适当编译,并将结果链接到二进制文件中。对于可执行组件,Gradle 可以生成可执行二进制文件。对于库组件,Gradle 可以生成静态库和共享库二进制文件。例如,当您定义一个名为 helloworld
的库并在 Linux 上构建时,Gradle 默认将生成 libhelloworld.so
和 libhelloworld.a
二进制文件。
在许多情况下,一个组件可以生成多个二进制文件。这些二进制文件可能因用于构建的工具链、提供的编译器/链接器标志、提供的依赖项或提供的其他源文件而异。为组件生成的每个原生二进制文件都称为 变体。二进制变体在下面详细讨论。
并行编译
Gradle 默认使用单个构建工作池并行编译和链接原生组件。无需特殊配置即可启用并发构建。
默认情况下,工作池大小由构建机器上可用处理器数量(向构建 JVM 报告)决定。要显式设置工作进程数量,请使用 --max-workers
命令行选项或 org.gradle.workers.max
系统属性。通常无需将此设置从默认值更改。
构建工作池在所有构建任务之间共享。这意味着当使用并行项目执行时,并发单个编译操作的最大数量不会增加。例如,如果构建机器有 4 个处理核心,并且 10 个项目并行编译,Gradle 只会使用总共 4 个工作进程,而不是 40 个。
构建库
要构建静态或共享原生库,您需要在 components
容器中定义一个库组件。以下示例定义了一个名为 hello
的库
示例:定义库组件
model {
components {
hello(NativeLibrarySpec)
}
}
库组件使用 NativeLibrarySpec 表示。每个库组件可以至少生成一个共享库二进制文件 (SharedLibraryBinarySpec) 和至少一个静态库二进制文件 (StaticLibraryBinarySpec)。
构建可执行文件
要构建原生可执行文件,您需要在 components
容器中定义一个可执行组件。以下示例定义了一个名为 main
的可执行文件
示例:定义可执行组件
model {
components {
main(NativeExecutableSpec) {
sources {
c.lib library: "hello"
}
}
}
}
可执行组件使用 NativeExecutableSpec 表示。每个可执行组件可以至少生成一个可执行二进制文件 (NativeExecutableBinarySpec)。
对于定义的每个组件,Gradle 会添加一个同名的 FunctionalSourceSet。这些功能源集中的每一个都将包含一个特定于语言的源集,用于项目支持的每种语言。
组装或构建依赖项
有时,您可能需要组装(编译和链接)或构建(编译、链接和测试)一个组件或二进制文件及其依赖项(依赖于该组件或二进制文件的项)。原生软件模型提供了实现此功能的任务。首先,依赖组件报告提供了对每个组件之间关系的洞察。其次,构建和组装依赖项任务允许您一步组装或构建一个组件及其依赖项。
在以下示例中,构建文件将 OpenSSL
定义为 libUtil
的依赖项,将 libUtil
定义为 LinuxApp
和 WindowsApp
的依赖项。测试套件也类似处理。依赖项可以认为是反向依赖项。

通过反向跟踪依赖项,您可以看到 LinuxApp 和 WindowsApp 是 libUtil 的 依赖项。当 libUtil 更改时,Gradle 将需要重新编译或重新链接 LinuxApp 和 WindowsApp 。 |
当您组装组件的依赖项时,该组件及其所有依赖项都将被编译和链接,包括任何测试套件二进制文件。Gradle 的最新检查用于仅在发生更改时才进行编译或链接。例如,如果您以不影响项目头文件的方式更改了源文件,Gradle 将能够跳过依赖组件的编译,并且只需与新库重新链接。组装组件时不会运行测试。
当您构建组件的依赖项时,该组件及其所有依赖二进制文件都将被编译、链接和检查。检查组件意味着运行任何检查任务,包括执行任何测试套件,因此构建组件时会运行测试。
在以下部分中,我们将通过一个包含 CUnit 测试套件的示例构建来演示 assembleDependents*
、buildDependents*
和 dependentComponents
任务的使用。该示例的构建脚本如下所示
示例:示例构建
plugins {
id 'c'
id 'cunit-test-suite'
}
model {
flavors {
passing
failing
}
platforms {
x86 {
if (operatingSystem.macOsX) {
architecture "x64"
} else {
architecture "x86"
}
}
}
components {
operators(NativeLibrarySpec) {
targetPlatform "x86"
}
}
testSuites {
operatorsTest(CUnitTestSuiteSpec) {
testing $.components.operators
}
}
}
依赖组件报告
Gradle 提供了一个您可以从命令行运行的报告,该报告显示了项目中组件的图形以及依赖于它们的组件。以下是在示例项目上运行 gradle dependentComponents
的示例
示例:依赖组件报告
gradle dependentComponents
的输出> gradle dependentComponents > Task :dependentComponents ------------------------------------------------------------ Root project 'cunit' ------------------------------------------------------------ operators - Components that depend on native library 'operators' +--- operators:failingSharedLibrary +--- operators:failingStaticLibrary +--- operators:passingSharedLibrary \--- operators:passingStaticLibrary Some test suites were not shown, use --test-suites or --all to show them. BUILD SUCCESSFUL in 0s 1 actionable task: 1 executed
有关更多详细信息,请参阅 DependentComponentsReport API 文档。 |
默认情况下,不可构建的二进制文件和测试套件在报告中是隐藏的。dependentComponents
任务提供了选项,允许您使用 --all
选项查看所有依赖项
示例:依赖组件报告
gradle dependentComponents --all
的输出> gradle dependentComponents --all > Task :dependentComponents ------------------------------------------------------------ Root project 'cunit' ------------------------------------------------------------ operators - Components that depend on native library 'operators' +--- operators:failingSharedLibrary +--- operators:failingStaticLibrary | \--- operatorsTest:failingCUnitExe (t) +--- operators:passingSharedLibrary \--- operators:passingStaticLibrary \--- operatorsTest:passingCUnitExe (t) operatorsTest - Components that depend on Cunit test suite 'operatorsTest' +--- operatorsTest:failingCUnitExe (t) \--- operatorsTest:passingCUnitExe (t) (t) - Test suite binary BUILD SUCCESSFUL in 0s 1 actionable task: 1 executed
这是 operators
组件的相应报告,显示了其所有二进制文件的依赖项
示例:依赖于 operators 组件的组件报告
gradle dependentComponents --component operators
的输出> gradle dependentComponents --component operators > Task :dependentComponents ------------------------------------------------------------ Root project 'cunit' ------------------------------------------------------------ operators - Components that depend on native library 'operators' +--- operators:failingSharedLibrary +--- operators:failingStaticLibrary +--- operators:passingSharedLibrary \--- operators:passingStaticLibrary Some test suites were not shown, use --test-suites or --all to show them. BUILD SUCCESSFUL in 0s 1 actionable task: 1 executed
这是 operators
组件的相应报告,显示了其所有二进制文件的依赖项,包括测试套件
示例:依赖于 operators 组件的组件报告,包括测试套件
gradle dependentComponents --test-suites --component operators
的输出> gradle dependentComponents --test-suites --component operators > Task :dependentComponents ------------------------------------------------------------ Root project 'cunit' ------------------------------------------------------------ operators - Components that depend on native library 'operators' +--- operators:failingSharedLibrary +--- operators:failingStaticLibrary | \--- operatorsTest:failingCUnitExe (t) +--- operators:passingSharedLibrary \--- operators:passingStaticLibrary \--- operatorsTest:passingCUnitExe (t) (t) - Test suite binary BUILD SUCCESSFUL in 0s 1 actionable task: 1 executed
此外,--non-binaries
选项在报告中显示不可构建的二进制文件,--no-non-buildable
隐藏它们。类似地,--test-suites
选项显示测试套件,--no-test-suites
隐藏它们。选项 --no-all
从报告中隐藏不可构建的二进制文件和测试套件。
组装依赖项
对于每个 NativeBinarySpec,Gradle 将创建一个名为 assembleDependents${component.name}${binary.variant}
的任务,该任务组装(编译和链接)该二进制文件及其所有依赖二进制文件。
对于每个 NativeComponentSpec,Gradle 将创建一个名为 assembleDependents${component.name}
的任务,该任务组装组件的所有二进制文件及其所有依赖二进制文件。
例如,要组装 "operators" 组件的 "passing" 变体的 "static" 库二进制文件的依赖项,您可以运行 assembleDependentsOperatorsPassingStaticLibrary
任务
示例:组装依赖于 operators 组件的 passing/static 二进制文件的组件
gradle assembleDependentsOperatorsPassingStaticLibrary --max-workers=1
的输出> gradle assembleDependentsOperatorsPassingStaticLibrary --max-workers=1 > Task :compileOperatorsTestPassingCUnitExeOperatorsC > Task :operatorsTestCUnitLauncher > Task :compileOperatorsTestPassingCUnitExeOperatorsTestC > Task :compileOperatorsTestPassingCUnitExeOperatorsTestCunitLauncher > Task :linkOperatorsTestPassingCUnitExe > Task :operatorsTestPassingCUnitExe > Task :assembleDependentsOperatorsTestPassingCUnitExe > Task :compileOperatorsPassingStaticLibraryOperatorsC > Task :createOperatorsPassingStaticLibrary > Task :operatorsPassingStaticLibrary > Task :assembleDependentsOperatorsPassingStaticLibrary BUILD SUCCESSFUL in 0s 7 actionable tasks: 7 executed
在上面的输出中,目标二进制文件以及依赖于它的测试套件二进制文件都已组装。
您还可以使用相应的组件任务(例如 assembleDependentsOperators
)来组装组件的所有依赖项(即所有二进制文件/变体)。如果您有许多构建类型、变体和平台的组合,并且想要组装所有这些组合,这将非常有用。
构建依赖项
对于每个 NativeBinarySpec,Gradle 将创建一个名为 buildDependents${component.name}${binary.variant}
的任务,该任务构建(编译、链接和检查)该二进制文件及其所有依赖二进制文件。
对于每个 NativeComponentSpec,Gradle 将创建一个名为 buildDependents${component.name}
的任务,该任务构建组件的所有二进制文件及其所有依赖二进制文件。
例如,要构建 "operators" 组件的 "passing" 变体的 "static" 库二进制文件的依赖项,您可以运行 buildDependentsOperatorsPassingStaticLibrary
任务
示例:构建依赖于 operators 组件的 passing/static 二进制文件的组件
gradle buildDependentsOperatorsPassingStaticLibrary --max-workers=1
的输出> gradle buildDependentsOperatorsPassingStaticLibrary --max-workers=1 > Task :compileOperatorsTestPassingCUnitExeOperatorsC > Task :operatorsTestCUnitLauncher > Task :compileOperatorsTestPassingCUnitExeOperatorsTestC > Task :compileOperatorsTestPassingCUnitExeOperatorsTestCunitLauncher > Task :linkOperatorsTestPassingCUnitExe > Task :operatorsTestPassingCUnitExe > Task :installOperatorsTestPassingCUnitExe > Task :runOperatorsTestPassingCUnitExe > Task :checkOperatorsTestPassingCUnitExe > Task :buildDependentsOperatorsTestPassingCUnitExe > Task :compileOperatorsPassingStaticLibraryOperatorsC > Task :createOperatorsPassingStaticLibrary > Task :operatorsPassingStaticLibrary > Task :buildDependentsOperatorsPassingStaticLibrary BUILD SUCCESSFUL in 0s 9 actionable tasks: 9 executed
在上面的输出中,目标二进制文件以及依赖于它的测试套件二进制文件都已构建,并且测试套件已运行。
您还可以使用相应的组件任务(例如 buildDependentsOperators
)来构建组件的所有依赖项(即所有二进制文件/变体)。
任务
对于构建可以生成的每个 NativeBinarySpec,都会构建一个单独的生命周期任务,该任务可用于创建该二进制文件,以及一组执行编译、链接或组装二进制文件的实际工作的其他任务。
${component.name}Executable
-
- 组件类型
- 原生二进制类型
- 创建的二进制文件位置
-
${project.layout.buildDirectory}/exe/${component.name}/${component.name}
${component.name}SharedLibrary
-
- 组件类型
- 原生二进制类型
- 创建的二进制文件位置
-
${project.layout.buildDirectory}/libs/${component.name}/shared/lib${component.name}.so
${component.name}StaticLibrary
-
- 组件类型
- 原生二进制类型
- 创建的二进制文件位置
-
${project.layout.buildDirectory}/libs/${component.name}/static/${component.name}.a
检查任务
对于构建可以生成的每个 NativeBinarySpec,都会构建一个单独的检查任务,该任务可用于组装和检查该二进制文件。
check${component.name}Executable
-
- 组件类型
- 原生二进制类型
check${component.name}SharedLibrary
-
- 组件类型
- 原生二进制类型
check${component.name}StaticLibrary
-
- 组件类型
- 原生二进制类型
内置的 check
任务依赖于项目中二进制文件的所有 check tasks。如果没有 CUnit 或 GoogleTest 插件,二进制检查任务仅依赖于组装二进制文件的 lifecycle task,请参阅 原生任务。
当应用 CUnit 或 GoogleTest 插件时,执行组件测试套件的任务会自动连接到适当的 check task。
您还可以添加自定义检查任务,如下所示
示例:添加自定义检查任务
plugins {
id "cpp"
}
// You don't need to apply the plugin below if you're already using CUnit or GoogleTest support
apply plugin: TestingModelBasePlugin
tasks.register('myCustomCheck') {
doLast {
println 'Executing my custom check'
}
}
model {
components {
hello(NativeLibrarySpec) {
binaries.all {
// Register our custom check task to all binaries of this component
checkedBy $.tasks.myCustomCheck
}
}
}
}
现在,运行 check
或 hello
二进制文件的任何 check tasks 都将运行自定义检查任务
示例:运行给定二进制文件的检查
gradle checkHelloSharedLibrary
的输出> gradle checkHelloSharedLibrary > Task :myCustomCheck Executing my custom check > Task :checkHelloSharedLibrary BUILD SUCCESSFUL in 0s 1 actionable task: 1 executed
使用共享库
对于生成的每个可执行二进制文件,cpp
插件提供了一个 install${binary.name}
任务,该任务会创建可执行文件的开发安装,以及它所需的共享库。这允许您运行可执行文件而无需将共享库安装到其最终位置。
了解有关项目的信息
Gradle 提供了一个您可以从命令行运行的报告,该报告显示了有关项目生成的组件和二进制文件的一些详细信息。要使用此报告,只需运行 gradle components
。下面是运行其中一个示例项目的此报告的示例
示例:组件报告
gradle components
的输出> gradle components > Task :components ------------------------------------------------------------ Root project 'cpp' ------------------------------------------------------------ Native library 'hello' ---------------------- Source sets C++ source 'hello:cpp' srcDir: src/hello/cpp Binaries Shared library 'hello:sharedLibrary' build using task: :helloSharedLibrary build type: build type 'debug' flavor: flavor 'default' target platform: platform 'current' tool chain: Tool chain 'clang' (Clang) shared library file: build/libs/hello/shared/libhello.dylib Static library 'hello:staticLibrary' build using task: :helloStaticLibrary build type: build type 'debug' flavor: flavor 'default' target platform: platform 'current' tool chain: Tool chain 'clang' (Clang) static library file: build/libs/hello/static/libhello.a Native executable 'main' ------------------------ Source sets C++ source 'main:cpp' srcDir: src/main/cpp Binaries Executable 'main:executable' build using task: :mainExecutable install using task: :installMainExecutable build type: build type 'debug' flavor: flavor 'default' target platform: platform 'current' tool chain: Tool chain 'clang' (Clang) executable file: build/exe/main/main Note: currently not all plugins register their components, so some components may not be visible here. BUILD SUCCESSFUL in 0s 1 actionable task: 1 executed
语言支持
目前,Gradle 支持从下面列出的任何源语言组合构建原生软件。一个原生二进制项目将包含一个或多个命名的 FunctionalSourceSet
实例(例如“main”、“test”等),每个实例都可以包含 LanguageSourceSet
,其中包含源文件,每种语言一个。
-
C
-
C++
-
Objective-C
-
Objective-C++
-
汇编
-
Windows 资源
C++ 源文件
C++ 语言支持通过 'cpp'
插件提供。
示例:'cpp' 插件
plugins {
id 'cpp'
}
要包含在原生二进制文件中的 C++ 源文件通过 CppSourceSet 提供,它定义了一组 C++ 源文件和一组可选的导出头文件(用于库)。默认情况下,对于任何命名组件,CppSourceSet 包含 src/${name}/cpp
中的 .cpp
源文件,以及 src/${name}/headers
中的头文件。
虽然 cpp
插件为每个 CppSourceSet 定义了这些默认位置,但可以扩展或覆盖这些默认位置以允许不同的项目布局。
示例:C++ 源集
sources {
cpp {
source {
srcDir "src/source"
include "**/*.cpp"
}
}
}
对于名为“main”的库,src/main/headers
中的头文件被认为是“公共”或“导出”头文件。不应导出的头文件应放置在 src/main/cpp
目录中(但请注意,此类头文件应始终相对于包含它们的文件进行引用)。
C 源文件
C 语言支持通过 'c'
插件提供。
示例:'c' 插件
plugins {
id 'c'
}
要包含在原生二进制文件中的 C 源文件通过 CSourceSet 提供,它定义了一组 C 源文件和一组可选的导出头文件(用于库)。默认情况下,对于任何命名组件,CSourceSet 包含 src/${name}/c
中的 .c
源文件,以及 src/${name}/headers
中的头文件。
虽然 c
插件为每个 CSourceSet 定义了这些默认位置,但可以扩展或覆盖这些默认位置以允许不同的项目布局。
示例:C 源集
sources {
c {
source {
srcDir "src/source"
include "**/*.c"
}
exportedHeaders {
srcDir "src/include"
}
}
}
对于名为“main”的库,src/main/headers
中的头文件被认为是“公共”或“导出”头文件。不应导出的头文件应放置在 src/main/c
目录中(但请注意,此类头文件应始终相对于包含它们的文件进行引用)。
汇编器源文件
汇编语言支持通过 'assembler'
插件提供。
示例:'assembler' 插件
plugins {
id 'assembler'
}
要包含在原生二进制文件中的汇编源文件通过 AssemblerSourceSet 提供,它定义了一组汇编源文件。默认情况下,对于任何命名组件,AssemblerSourceSet 包含 src/${name}/asm
下的 .s
源文件。
Objective-C 源文件
Objective-C 语言支持通过 'objective-c'
插件提供。
示例:'objective-c' 插件
plugins {
id 'objective-c'
}
要包含在原生二进制文件中的 Objective-C 源文件通过 ObjectiveCSourceSet 提供,它定义了一组 Objective-C 源文件。默认情况下,对于任何命名组件,ObjectiveCSourceSet 包含 src/${name}/objectiveC
下的 .m
源文件。
Objective-C++ 源文件
Objective-C++ 语言支持通过 'objective-cpp'
插件提供。
示例:'objective-cpp' 插件
plugins {
id 'objective-cpp'
}
要包含在原生二进制文件中的 Objective-C++ 源文件通过 ObjectiveCppSourceSet 提供,它定义了一组 Objective-C++ 源文件。默认情况下,对于任何命名组件,ObjectiveCppSourceSet 包含 src/${name}/objectiveCpp
下的 .mm
源文件。
配置编译器、汇编器和链接器
每个要生成的二进制文件都与一组编译器和链接器设置相关联,其中包括命令行参数和宏定义。这些设置可以应用于所有二进制文件、单个二进制文件,或者根据某些条件选择性地应用于一组二进制文件。
示例:适用于所有二进制文件的设置
model {
binaries {
all {
// Define a preprocessor macro for every binary
cppCompiler.define "NDEBUG"
// Define toolchain-specific compiler and linker options
if (toolChain in Gcc) {
cppCompiler.args "-O2", "-fno-access-control"
linker.args "-Xlinker", "-S"
}
if (toolChain in VisualCpp) {
cppCompiler.args "/Zi"
linker.args "/DEBUG"
}
}
}
}
每个二进制文件都与特定的 NativeToolChain 相关联,从而允许根据此值定位设置。
为特定类型的所有二进制文件应用设置非常简单
示例:适用于所有共享库的设置
// For any shared library binaries built with Visual C++,
// define the DLL_EXPORT macro
model {
binaries {
withType(SharedLibraryBinarySpec) {
if (toolChain in VisualCpp) {
cCompiler.args "/Zi"
cCompiler.define "DLL_EXPORT"
}
}
}
}
此外,还可以指定适用于为特定 executable
或 library
组件生成的所有二进制文件的设置
示例:适用于为“main”可执行组件生成的所有二进制文件的设置
model {
components {
main(NativeExecutableSpec) {
targetPlatform "x86"
binaries.all {
if (toolChain in VisualCpp) {
sources {
platformAsm(AssemblerSourceSet) {
source.srcDir "src/main/asm_i386_masm"
}
}
assembler.args "/Zi"
} else {
sources {
platformAsm(AssemblerSourceSet) {
source.srcDir "src/main/asm_i386_gcc"
}
}
assembler.args "-g"
}
}
}
}
}
上面的示例将把提供的配置应用于所有构建的 executable
二进制文件。
同样,可以指定针对组件的特定类型二进制文件的设置:例如,主库组件的所有共享库。
示例:仅适用于为“main”库组件生成的共享库的设置
model {
components {
main(NativeLibrarySpec) {
binaries.withType(SharedLibraryBinarySpec) {
// Define a preprocessor macro that only applies to shared libraries
cppCompiler.define "DLL_EXPORT"
}
}
}
}
Windows 资源
当使用 VisualCpp 工具链时,Gradle 能够编译 Windows 资源 (rc
) 文件并将其链接到原生二进制文件中。此功能由 'windows-resources'
插件提供。
示例:'windows-resources' 插件
plugins {
id 'windows-resources'
}
要包含在原生二进制文件中的 Windows 资源通过 WindowsResourceSet 提供,它定义了一组 Windows 资源源文件。默认情况下,对于任何命名组件,WindowsResourceSet 包含 src/${name}/rc
下的 .rc
源文件。
与其他源类型一样,您可以配置应包含在二进制文件中的 Windows 资源的位置。
示例:配置 Windows 资源源的位置
sources {
rc {
source {
srcDirs "src/hello/rc"
}
exportedHeaders {
srcDirs "src/hello/headers"
}
}
}
您可以通过提供 Windows 资源源而不提供其他语言源,并相应地配置链接器来构建一个仅包含资源的库
示例:构建仅包含资源的 DLL
model {
components {
helloRes(NativeLibrarySpec) {
binaries.all {
rcCompiler.args "/v"
linker.args "/noentry", "/machine:x86"
}
sources {
rc {
source {
srcDirs "src/hello/rc"
}
exportedHeaders {
srcDirs "src/hello/headers"
}
}
}
}
}
}
上面的示例还演示了向资源编译器传递额外命令行参数的机制。rcCompiler
扩展类型为 PreprocessingTool。
库依赖项
原生组件的依赖项是导出头文件的二进制库。头文件在编译期间使用,编译后的二进制依赖项在链接和执行期间使用。头文件应组织到子目录中,以防止常用头文件命名冲突。例如,如果您的 mylib
项目有一个 logging.h
头文件,那么如果您将其包含为 "mylib/logging.h"
而不是 "logging.h"
,则更不可能使用错误的头文件。
同一项目内的依赖项
一组源文件可能依赖于同一项目中另一个二进制组件提供的头文件。一个常见的例子是使用由单独的原生库组件提供的函数的原生可执行组件。
这样的库依赖项可以添加到与 executable
组件关联的源集中
示例:为源集提供库依赖项
sources {
cpp {
lib library: "hello"
}
}
或者,库依赖项可以直接提供给 executable
的 NativeExecutableBinarySpec。
示例:为二进制文件提供库依赖项
model {
components {
hello(NativeLibrarySpec) {
sources {
c {
source {
srcDir "src/source"
include "**/*.c"
}
exportedHeaders {
srcDir "src/include"
}
}
}
}
main(NativeExecutableSpec) {
sources {
cpp {
source {
srcDir "src/source"
include "**/*.cpp"
}
}
}
binaries.all {
// Each executable binary produced uses the 'hello' static library binary
lib library: 'hello', linkage: 'static'
}
}
}
}
项目依赖项
对于在另一个 Gradle 项目中生成的组件,其表示法类似。
示例:声明项目依赖项
plugins {
id 'cpp'
}
model {
components {
main(NativeLibrarySpec)
}
// For any shared library binaries built with Visual C++,
// define the DLL_EXPORT macro
binaries {
withType(SharedLibraryBinarySpec) {
if (toolChain in VisualCpp) {
cppCompiler.define "DLL_EXPORT"
}
}
}
}
plugins {
id 'cpp'
}
model {
components {
main(NativeExecutableSpec) {
sources {
cpp {
lib project: ':lib', library: 'main'
}
}
}
}
}
预编译头文件
预编译头文件是一种性能优化,可减少多次编译广泛使用的头文件的成本。此功能会预编译头文件,以便编译后的对象文件可以在编译每个源文件时重复使用,而不是每次都重新编译头文件。此支持适用于 C、C++、Objective-C 和 Objective-C++ 构建。
要配置预编译头文件,首先需要定义一个包含所有应预编译的头文件的头文件。它必须在每个应使用预编译头文件的源文件中指定为第一个包含的头文件。假定此头文件及其包含的任何头文件都使用头文件防护,以便它们可以以幂等方式包含。如果头文件中未使用头文件防护,则该头文件可能会被多次编译,并可能导致构建损坏。
示例:创建预编译头文件
#ifndef PCH_H
#define PCH_H
#include <iostream>
#include "hello.h"
#endif
示例:在源文件中包含预编译头文件
#include "pch.h"
void LIB_FUNC Greeter::hello () {
std::cout << "Hello world!" << std::endl;
}
预编译头文件在源集上指定。给定源集上只能指定一个预编译头文件,并且将应用于所有将其声明为第一个包含的源文件。如果源文件不包含此头文件作为第一个头文件,则该文件将以正常方式编译(不使用预编译头对象文件)。提供的字符串应与源文件中“#include”指令中使用的字符串相同。
示例:配置预编译头文件
model {
components {
hello(NativeLibrarySpec) {
sources {
cpp {
preCompiledHeader "pch.h"
}
}
}
}
}
预编译头文件必须以相同的方式包含在所有使用它的文件中。通常,这意味着头文件应该存在于源集“headers”目录中或包含在编译器包含路径中的目录中。
原生二进制文件变体
对于定义的每个可执行文件或库,Gradle 能够构建多种不同的原生二进制文件变体。不同变体的示例包括调试与发布二进制文件、32 位与 64 位二进制文件以及使用不同自定义预处理器标志生成的二进制文件。
Gradle 生成的二进制文件可以通过构建类型、平台和风味进行区分。对于这些“变体维度”中的每一个,都可以指定一组可用值,并根据这些值将每个组件定位到其中一个、一些或所有。例如,插件可以定义一系列支持平台,但您可以选择仅针对特定组件的 Windows-x86。
构建类型
构建类型
决定了二进制文件的各种非功能性方面,例如是否包含调试信息,或者二进制文件的编译优化级别。典型的构建类型是“debug”和“release”,但项目可以自由定义任何一组构建类型。
示例:定义构建类型
model {
buildTypes {
debug
release
}
}
如果项目中没有定义构建类型,则会添加一个名为“debug”的单一默认构建类型。
对于构建类型,Gradle 项目通常会为每个工具链定义一组编译器/链接器标志。
示例:配置调试二进制文件
model {
binaries {
all {
if (toolChain in Gcc && buildType == buildTypes.debug) {
cppCompiler.args "-g"
}
if (toolChain in VisualCpp && buildType == buildTypes.debug) {
cppCompiler.args '/Zi'
cppCompiler.define 'DEBUG'
linker.args '/DEBUG'
}
}
}
}
在此阶段,完全由构建脚本配置每个构建类型的相关编译器/链接器标志。Gradle 的未来版本将自动为任何“debug”构建类型包含适当的调试标志,并且也可能知道各种优化级别。 |
平台
可执行文件或库可以构建为在不同的操作系统和 CPU 架构上运行,为每个平台生成一个变体。Gradle 将每个 OS/架构组合定义为 NativePlatform,并且项目可以定义任意数量的平台。如果项目中没有定义平台,则会添加一个名为“current”的单一默认平台。
目前, |
示例:定义平台
model {
platforms {
x86 {
architecture "x86"
}
x64 {
architecture "x86_64"
}
itanium {
architecture "ia-64"
}
}
}
对于给定的变体,Gradle 将尝试找到能够为目标平台构建的 NativeToolChain。可用工具链将按定义顺序搜索。有关详细信息,请参阅下面的工具链部分。
风味
每个组件可以有一组命名的 flavors
,并且可以为每个风味生成一个单独的二进制变体。虽然 build type
和 target platform
变体维度在 Gradle 中有明确的含义,但每个项目都可以自由定义任意数量的风味并以任何方式赋予它们含义。
组件风味的示例可能区分组件的“演示版”、“付费版”和“企业版”,其中使用同一组源文件生成具有不同功能的二进制文件。
示例:定义风味
model {
flavors {
english
french
}
components {
hello(NativeLibrarySpec) {
binaries.all {
if (flavor == flavors.french) {
cppCompiler.define "FRENCH"
}
}
}
}
}
在上面的示例中,定义了一个具有“english”和“french”风味的库。编译“french”变体时,会定义一个单独的宏,从而生成一个不同的二进制文件。
如果组件没有定义风味,则使用名为“default”的单一默认风味。
为组件选择构建类型、平台和风味
对于默认组件,Gradle 将尝试为项目中定义的每个 buildType
和 flavor
组合创建一个原生二进制变体。可以在每个组件的基础上覆盖此设置,方法是指定 targetBuildTypes
和/或 targetFlavors
的集合。默认情况下,Gradle 将为默认平台进行构建,请参阅上文,除非在每个组件的基础上明确指定 targetPlatforms
的集合。
示例:将组件定位到特定平台
model {
components {
hello(NativeLibrarySpec) {
targetPlatform "x86"
targetPlatform "x64"
}
main(NativeExecutableSpec) {
targetPlatform "x86"
targetPlatform "x64"
sources {
cpp.lib library: 'hello', linkage: 'static'
}
}
}
}
在此处您可以看到 TargetedNativeComponent.targetPlatform(java.lang.String) 方法用于指定名为 main
的 NativeExecutableSpec 应为其构建的平台。
构建所有可能的变体
当为组件定义了一组构建类型、目标平台和风味时,将为所有这些可能的组合创建一个 NativeBinarySpec 模型元素。然而,在许多情况下,可能无法构建特定的变体,这可能是因为没有可用的工具链来为特定平台进行构建。
如果由于任何原因无法构建二进制变体,则与该变体关联的 NativeBinarySpec 将不可 buildable
。可以使用此属性创建任务以在特定机器上生成所有可能的变体。
示例:构建所有可能的变体
model {
tasks {
buildAllExecutables(Task) {
dependsOn $.binaries.findAll { it.buildable }
}
}
}
工具链
单个构建可能利用不同的工具链来为不同的平台构建变体。为此,核心“原生二进制”插件将尝试定位并提供受支持的工具链。但是,项目的工具链集也可以明确定义,从而允许配置额外的交叉编译器以及指定安装目录。
示例:定义工具链
toolChains {
visualCpp(VisualCpp) {
// Specify the installDir if Visual Studio cannot be located
// installDir "C:/Apps/Microsoft Visual Studio 10.0"
}
gcc(Gcc) {
// Uncomment to use a GCC install that is not in the PATH
// path "/usr/bin/gcc"
}
clang(Clang)
}
每个工具链实现都允许一定程度的配置(有关详细信息,请参阅 API 文档)。
使用工具链
无需也无法指定应用于构建的工具链。对于给定变体,Gradle 将尝试定位能够为目标平台构建的 NativeToolChain。可用工具链将按定义顺序搜索。
当平台未定义架构或操作系统时,假定工具链的默认目标。因此,如果平台未定义 operatingSystem 的值,Gradle 将查找第一个可用的工具链,该工具链可以为指定的 architecture 构建。 |
核心 Gradle 工具链开箱即用,能够针对以下架构。在每种情况下,工具链将针对当前操作系统。有关针对其他操作系统进行交叉编译的信息,请参阅下一节。
工具链 | 架构 |
---|---|
GCC |
x86、x86_64、arm64(仅限 macOS 和 Linux) |
Clang |
x86、x86_64、arm64(仅限 macOS 和 Linux) |
Visual C++ |
x86、x86_64、ia-64 |
因此,对于在 Linux 上运行的 GCC,支持的目标平台是“linux/x86”和“linux/x86_64”。对于通过 Cygwin 在 Windows 上运行的 GCC,支持的平台是“windows/x86”和“windows/x86_64”。(Cygwin POSIX 运行时尚未作为平台的一部分建模,但将来会。)
如果项目没有定义目标平台,则所有二进制文件都将构建以目标名为“current”的默认平台。此默认平台不指定任何 architecture
或 operatingSystem
值,因此使用第一个可用工具链的默认值。
Gradle 提供了一个钩子,允许构建作者控制传递给工具链可执行文件的确切参数集。这使构建作者能够解决 Gradle 中的任何限制或 Gradle 所做的假设。参数钩子应被视为“最后手段”机制,优先考虑真正建模底层领域。
示例:重新配置工具参数
toolChains {
visualCpp(VisualCpp) {
eachPlatform {
cppCompiler.withArguments { args ->
args << "-DFRENCH"
}
}
}
clang(Clang) {
eachPlatform {
cCompiler.withArguments { args ->
Collections.replaceAll(args, "CUSTOM", "-DFRENCH")
}
linker.withArguments { args ->
args.remove "CUSTOM"
}
staticLibArchiver.withArguments { args ->
args.remove "CUSTOM"
}
}
}
}
示例:定义目标平台
toolChains {
gcc(Gcc) {
target("arm"){
cppCompiler.withArguments { args ->
args << "-m32"
}
linker.withArguments { args ->
args << "-m32"
}
}
target("sparc")
}
}
model {
platforms {
arm {
architecture "arm"
}
sparc {
architecture "sparc"
}
}
components {
main(NativeExecutableSpec) {
targetPlatform "arm"
targetPlatform "sparc"
}
}
}
Visual Studio IDE 集成
Gradle 能够为构建中定义的原生组件生成 Visual Studio 项目和解决方案文件。此功能由 visual-studio
插件添加。对于多项目构建,所有具有原生组件的项目(和根项目)都应应用此插件。
当 visual-studio
插件应用于根项目时,将创建一个名为 visualStudio
的任务,该任务将生成一个包含构建中所有组件的 Visual Studio 解决方案文件。此解决方案将包含每个组件的 Visual Studio 项目,并配置每个组件以使用 Gradle 进行构建。
当项目是根项目时,visual-studio
插件还会创建一个名为 openVisualStudio
的任务。此任务生成 Visual Studio 解决方案,然后将其在 Visual Studio 中打开。这意味着您只需从根项目运行 gradlew openVisualStudio
,即可在一个便捷的步骤中生成并打开 Visual Studio 解决方案。
生成的 Visual Studio 文件的内容可以通过 API 钩子进行修改,这些钩子由 visualStudio
扩展提供。请查看“visual-studio”示例,或参阅 API 文档中的 VisualStudioExtension.getProjects() 和 VisualStudioRootExtension.getSolution() 以获取更多详细信息。
CUnit 支持
Gradle cunit
插件提供了在您的原生二进制项目中文编译和执行 CUnit 测试的支持。对于您项目中定义的每个 NativeExecutableSpec 和 NativeLibrarySpec,Gradle 将创建一个匹配的 CUnitTestSuiteSpec 组件,名为 ${component.name}Test
。
CUnit 源文件
Gradle 将为项目中的每个 CUnitTestSuiteSpec 组件创建一个名为 'cunit' 的 CSourceSet。此源集应包含被测试组件的 cunit 测试文件。源文件可以位于约定位置 (src/${component.name}Test/cunit
) 或像任何其他源集一样进行配置。
Gradle 初始化 CUnit 测试注册表并执行测试,利用一些生成的 CUnit 启动器源文件。Gradle 将期望并调用一个名为 void gradle_cunit_register()
的函数,您可以使用它来配置要执行的实际 CUnit 套件和测试。
示例:注册 CUnit 测试
#include <CUnit/Basic.h>
#include "gradle_cunit_register.h"
#include "test_operators.h"
int suite_init(void) {
return 0;
}
int suite_clean(void) {
return 0;
}
void gradle_cunit_register() {
CU_pSuite pSuiteMath = CU_add_suite("operator tests", suite_init, suite_clean);
CU_add_test(pSuiteMath, "test_plus", test_plus);
CU_add_test(pSuiteMath, "test_minus", test_minus);
}
由于这种机制,您的 CUnit 源文件可能不包含 main 方法,因为它会与 Gradle 提供的方法发生冲突。 |
构建 CUnit 可执行文件
一个 CUnitTestSuiteSpec 组件有一个关联的 NativeExecutableSpec 或 NativeLibrarySpec 组件。对于主组件配置的每个 NativeBinarySpec,将在测试套件组件上配置一个匹配的 CUnitTestSuiteBinarySpec。这些测试套件二进制文件可以以类似于任何其他二进制实例的方式进行配置
示例:配置 CUnit 测试
model {
binaries {
withType(CUnitTestSuiteBinarySpec) {
lib library: "cunit", linkage: "static"
if (flavor == flavors.failing) {
cCompiler.define "PLUS_BROKEN"
}
}
}
}
您的项目提供的 CUnit 源文件和生成的启动器都需要核心 CUnit 头文件和库。目前,您的项目必须为每个 CUnitTestSuiteBinarySpec 提供此库依赖项。 |
运行 CUnit 测试
对于每个 CUnitTestSuiteBinarySpec,Gradle 将创建一个任务来执行此二进制文件,该任务将运行所有已注册的 CUnit 测试。测试结果将位于 ${build.dir}/test-results
目录中。
示例:运行 CUnit 测试
plugins {
id 'c'
id 'cunit-test-suite'
}
model {
flavors {
passing
failing
}
platforms {
x86 {
if (operatingSystem.macOsX) {
architecture "x64"
} else {
architecture "x86"
}
}
}
repositories {
libs(PrebuiltLibraries) {
cunit {
headers.srcDir "libs/cunit/2.1-2/include"
binaries.withType(StaticLibraryBinary) {
staticLibraryFile =
file("libs/cunit/2.1-2/lib/" +
findCUnitLibForPlatform(targetPlatform))
}
}
}
}
components {
operators(NativeLibrarySpec) {
targetPlatform "x86"
}
}
testSuites {
operatorsTest(CUnitTestSuiteSpec) {
testing $.components.operators
}
}
}
model {
binaries {
withType(CUnitTestSuiteBinarySpec) {
lib library: "cunit", linkage: "static"
if (flavor == flavors.failing) {
cCompiler.define "PLUS_BROKEN"
}
}
}
}
gradle -q runOperatorsTestFailingCUnitExe
的输出> gradle -q runOperatorsTestFailingCUnitExe There were test failures: 1. /home/user/gradle/samples/src/operatorsTest/c/test_plus.c:6 - plus(0, -2) == -2 2. /home/user/gradle/samples/src/operatorsTest/c/test_plus.c:7 - plus(2, 2) == 4 FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':runOperatorsTestFailingCUnitExe'. > There were failing tests. See the results at: file:///home/user/gradle/samples/build/test-results/operatorsTest/failing/ * Try: > Run with --stacktrace option to get the stack trace. > Run with --info or --debug option to get more log output. > Run with --scan to generate a Build Scan (Powered by Develocity). > Get more help at https://help.gradle.org. BUILD FAILED in 0s
目前对 CUnit 的支持还很基础。未来的集成计划包括
|
GoogleTest 支持
Gradle google-test
插件提供了在您的原生二进制项目中文编译和执行 GoogleTest 测试的支持。对于您项目中定义的每个 NativeExecutableSpec 和 NativeLibrarySpec,Gradle 将创建一个匹配的 GoogleTestTestSuiteSpec 组件,名为 ${component.name}Test
。
GoogleTest 源文件
Gradle 将为项目中的每个 GoogleTestTestSuiteSpec 组件创建一个名为 'cpp' 的 CppSourceSet。此源集应包含被测试组件的 GoogleTest 测试文件。源文件可以位于约定位置 (src/${component.name}Test/cpp
) 或像任何其他源集一样进行配置。
构建 GoogleTest 可执行文件
一个 GoogleTestTestSuiteSpec 组件有一个关联的 NativeExecutableSpec 或 NativeLibrarySpec 组件。对于主组件配置的每个 NativeBinarySpec,将在测试套件组件上配置一个匹配的 GoogleTestTestSuiteBinarySpec。这些测试套件二进制文件可以以类似于任何其他二进制实例的方式进行配置
示例:注册 GoogleTest 测试
model {
binaries {
withType(GoogleTestTestSuiteBinarySpec) {
lib library: "googleTest", linkage: "static"
if (flavor == flavors.failing) {
cppCompiler.define "PLUS_BROKEN"
}
if (targetPlatform.operatingSystem.linux) {
cppCompiler.args '-pthread'
linker.args '-pthread'
if (toolChain instanceof Gcc || toolChain instanceof Clang) {
// Use C++03 with the old ABIs, as this is what the googletest binaries were built with
cppCompiler.args '-std=c++03', '-D_GLIBCXX_USE_CXX11_ABI=0'
linker.args '-std=c++03'
}
}
}
}
}
您的项目提供的 GoogleTest 源文件需要核心 GoogleTest 头文件和库。目前,您的项目必须为每个 GoogleTestTestSuiteBinarySpec 提供此库依赖项。 |
运行 GoogleTest 测试
对于每个 GoogleTestTestSuiteBinarySpec,Gradle 将创建一个任务来执行此二进制文件,该任务将运行所有已注册的 GoogleTest 测试。测试结果将位于 ${build.dir}/test-results
目录中。
目前对 GoogleTest 的支持还很基础。未来的集成计划包括
|