The 软件模型 is being retired and the plugins mentioned in this chapter will eventually be deprecated and removed. We recommend new projects looking to build C++ applications and libraries use the newer 替代插件

原生软件插件增加了对构建原生软件组件(例如可执行文件或共享库)的支持,这些组件来自用 C++、C 和其他语言编写的代码。虽然在这个软件开发领域已经存在许多优秀的构建工具,但 Gradle 为开发人员提供了其标志性的强大功能和灵活性,以及在 JVM 开发领域更常见的依赖管理实践。

原生软件插件利用 Gradle 软件模型。

功能特性

原生软件插件提供

  • 支持在 Windows、Linux、macOS 和其他平台上构建原生库和应用程序。

  • 支持多种源语言。

  • 支持为不同的架构、操作系统或任何目的构建同一软件的不同变体。

  • 增量并行编译、预编译头文件。

  • 原生软件组件之间的依赖管理。

  • 单元测试执行。

  • 生成 Visual studio 解决方案和项目文件。

  • 与各种工具链的深度集成,包括已安装工具链的发现。

支持的语言

目前支持以下源语言

  • C

  • C++

  • Objective-C

  • Objective-C++

  • Assembly

  • Windows 资源

工具链支持

Gradle 提供了使用不同工具链执行相同构建的能力。当您构建原生二进制文件时,Gradle 将尝试在您的机器上找到可以构建该二进制文件的工具链。您可以微调这具体是如何工作的,请参阅 工具链支持 以了解详细信息。

支持以下工具链

操作系统 工具链 说明

Linux

GCC

Linux

Clang

macOS

XCode

使用与 XCode 捆绑的 Clang 工具链。

Windows

Visual C++

Windows XP 及更高版本,Visual C++ 2010/2012/2013/2015/2017/2019。

Windows

GCCCygwin 32 和 Cygwin 64

Windows XP 及更高版本。

Windows

GCCMinGWMinGW64

Windows XP 及更高版本。

以下工具链为非官方支持。它们通常工作良好,但未进行持续测试

操作系统 工具链 说明

macOS

GCC 来自 Macports

macOS

Clang 来自 Macports

UNIX-like

GCC

UNIX-like

Clang

工具链安装

请注意,如果您正在使用 GCC,那么您当前需要安装对 C++ 的支持,即使您不是从 C++ 源代码构建。此限制将在未来的 Gradle 版本中删除。

要构建原生软件,您需要安装兼容的工具链

Windows

要在 Windows 上构建,请安装兼容版本的 Visual Studio。原生插件将发现 Visual Studio 安装并选择最新版本。无需摆弄环境变量或批处理脚本。这在 Cygwin shell 或 Windows 命令行中都能正常工作。

或者,您可以安装带有 GCC 或 MinGW 的 Cygwin。当前不支持 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.solibhelloworld.a 二进制文件。

在许多情况下,可以为一个组件生成多个二进制文件。这些二进制文件可能因用于构建的工具链、提供的编译器/链接器标志、提供的依赖项或提供的其他源文件而异。为一个组件生成的每个原生二进制文件都称为变体。二进制变体将在下面详细讨论。

并行编译

默认情况下,Gradle 使用单一构建工作池来并发地编译和链接原生组件。无需特殊配置即可启用并行构建。

默认情况下,工作池大小由构建机器上可用的处理器数量(报告给构建 JVM)确定。要显式设置工作线程数,请使用 --max-workers 命令行选项或 org.gradle.workers.max 系统属性。通常无需更改此设置的默认值。

构建工作池在所有构建任务之间共享。这意味着,当使用 并行项目执行 时,并发的单个编译操作的最大数量不会增加。例如,如果构建机器有 4 个处理核心,并且 10 个项目并行编译,Gradle 将仅使用总共 4 个工作线程,而不是 40 个。

构建库

要构建静态或共享原生库,您需要在 components 容器中定义一个库组件。以下示例定义了一个名为 hello 的库

示例:定义库组件

build.gradle
model {
    components {
        hello(NativeLibrarySpec)
    }
}

库组件使用 NativeLibrarySpec 表示。每个库组件至少可以生成一个共享库二进制文件 (SharedLibraryBinarySpec) 和至少一个静态库二进制文件 (StaticLibraryBinarySpec)。

构建可执行文件

要构建原生可执行文件,您需要在 components 容器中定义一个可执行组件。以下示例定义了一个名为 main 的可执行文件

示例:定义可执行组件

build.gradle
model {
    components {
        main(NativeExecutableSpec) {
            sources {
               c.lib library: "hello"
            }
        }
    }
}

可执行组件使用 NativeExecutableSpec 表示。每个可执行组件至少可以生成一个可执行二进制文件 (NativeExecutableBinarySpec)。

对于定义的每个组件,Gradle 都会添加一个同名的 FunctionalSourceSet。这些 functional source set 中的每一个都将包含一个特定于语言的源集,用于项目支持的每种语言。

组装或构建依赖项

有时,您可能需要组装(编译和链接)或构建(编译、链接和测试)组件或二进制文件及其依赖项(依赖于组件或二进制文件的内容)。原生软件模型提供了启用此功能的任务。首先,依赖组件报告提供了关于每个组件之间关系的洞察。其次,构建和组装依赖项任务允许您一步组装或构建组件及其依赖项。

在以下示例中,构建文件将 OpenSSL 定义为 libUtil 的依赖项,将 libUtil 定义为 LinuxAppWindowsApp 的依赖项。测试套件的处理方式类似。依赖项可以被认为是反向依赖。

nativeDependents
图 1. 依赖组件示例
通过反向跟踪依赖关系,您可以看到 LinuxAppWindowsApplibUtil依赖项。当 libUtil 更改时,Gradle 将需要重新编译或重新链接 LinuxAppWindowsApp

当您组装组件的依赖项时,组件及其所有依赖项都会被编译和链接,包括任何测试套件二进制文件。Gradle 的最新检查用于仅在发生更改时才进行编译或链接。例如,如果您更改了源文件,但这些更改不影响项目的头文件,Gradle 将能够跳过依赖组件的编译,而只需要重新链接到新的库。组装组件时不会运行测试。

当您构建组件的依赖项时,组件及其所有依赖二进制文件都会被编译、链接和检查。检查组件意味着运行任何 检查任务,包括执行任何测试套件,因此在构建组件时运行测试。

在以下部分中,我们将通过一个包含 CUnit 测试套件的示例构建来演示 assembleDependents*buildDependents*dependentComponents 任务的用法。示例的构建脚本如下

示例:示例构建

build.gradle
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" flavor 的 "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)组装组件的所有依赖项(即其所有二进制文件/变体的依赖项)。如果您有许多构建类型、flavor 和平台的组合,并且想要组装所有这些组合,这将非常有用。

构建依赖项

对于每个 NativeBinarySpec,Gradle 将创建一个名为 buildDependents${component.name}${binary.variant} 的任务,该任务构建(编译、链接和检查)二进制文件及其所有依赖二进制文件。

对于每个 NativeComponentSpec,Gradle 将创建一个名为 buildDependents${component.name} 的任务,该任务构建组件的所有二进制文件及其所有依赖二进制文件。

例如,要构建 "operators" 组件的 "passing" flavor 的 "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
组件类型

NativeExecutableSpec

原生二进制文件类型

NativeExecutableBinarySpec

创建的二进制文件的位置

${project.layout.buildDirectory}/exe/${component.name}/${component.name}

${component.name}SharedLibrary
组件类型

NativeLibrarySpec

原生二进制文件类型

SharedLibraryBinarySpec

创建的二进制文件的位置

${project.layout.buildDirectory}/libs/${component.name}/shared/lib${component.name}.so

${component.name}StaticLibrary
组件类型

NativeLibrarySpec

原生二进制文件类型

StaticLibraryBinarySpec

创建的二进制文件的位置

${project.layout.buildDirectory}/libs/${component.name}/static/${component.name}.a

检查任务

对于可以由构建生成的每个 NativeBinarySpec,都会构建一个检查任务,该任务可用于组装和检查该二进制文件。

check${component.name}Executable
组件类型

NativeExecutableSpec

原生二进制文件类型

NativeExecutableBinarySpec

check${component.name}SharedLibrary
组件类型

NativeLibrarySpec

原生二进制文件类型

SharedLibraryBinarySpec

check${component.name}StaticLibrary
组件类型

NativeLibrarySpec

原生二进制文件类型

SharedLibraryBinarySpec

内置的 check 任务依赖于项目中二进制文件的所有检查任务。如果没有 CUnitGoogleTest 插件,则二进制文件检查任务仅依赖于组装二进制文件的生命周期任务,请参阅 原生任务

当应用 CUnitGoogleTest 插件时,为组件执行测试套件的任务会自动连接到相应的检查任务

您还可以添加自定义检查任务,如下所示

示例:添加自定义检查任务

build.gradle
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
            }
        }
    }
}

现在,运行 checkhello 二进制文件的任何检查任务都将运行自定义检查任务

示例:运行给定二进制文件的检查

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++

  • Assembly

  • Windows 资源

C++ 源代码

C++ 语言支持由 'cpp' 插件提供。

示例:'cpp' 插件

build.gradle
plugins {
    id 'cpp'
}

要包含在原生二进制文件中的 C++ 源代码通过 CppSourceSet 提供,该 CppSourceSet 定义了一组 C++ 源文件,并且可以选择定义一组导出的头文件(对于库)。默认情况下,对于任何命名的组件,CppSourceSet 包含 src/${name}/cpp 中的 .cpp 源文件,以及 src/${name}/headers 中的头文件。

虽然 cpp 插件为每个 CppSourceSet 定义了这些默认位置,但可以扩展或覆盖这些默认值,以允许不同的项目布局。

示例:C++ 源集

build.gradle
sources {
    cpp {
        source {
            srcDir "src/source"
            include "**/*.cpp"
        }
    }
}

对于名为 'main' 的库,src/main/headers 中的头文件被认为是“公共”或“导出”的头文件。不应导出的头文件应放在 src/main/cpp 目录中(但请注意,此类头文件应始终以相对于包含它们的文件的方式引用)。

C 源代码

C 语言支持由 'c' 插件提供。

示例:'c' 插件

build.gradle
plugins {
    id 'c'
}

要包含在原生二进制文件中的 C 源代码通过 CSourceSet 提供,该 CSourceSet 定义了一组 C 源文件,并且可以选择定义一组导出的头文件(对于库)。默认情况下,对于任何命名的组件,CSourceSet 包含 src/${name}/c 中的 .c 源文件,以及 src/${name}/headers 中的头文件。

虽然 c 插件为每个 CSourceSet 定义了这些默认位置,但可以扩展或覆盖这些默认值,以允许不同的项目布局。

示例:C 源集

build.gradle
sources {
    c {
        source {
            srcDir "src/source"
            include "**/*.c"
        }
        exportedHeaders {
            srcDir "src/include"
        }
    }
}

对于名为 'main' 的库,src/main/headers 中的头文件被认为是“公共”或“导出”的头文件。不应导出的头文件应放在 src/main/c 目录中(但请注意,此类头文件应始终以相对于包含它们的文件的方式引用)。

Assembler sources

汇编语言支持由 'assembler' 插件提供。

示例:'assembler' 插件

build.gradle
plugins {
    id 'assembler'
}

要包含在原生二进制文件中的汇编器源文件通过 AssemblerSourceSet 提供,它定义了一组汇编器源文件。默认情况下,对于任何命名的组件,AssemblerSourceSet 包含 src/${name}/asm 下的 .s 源文件。

Objective-C 源文件

Objective-C 语言支持由 'objective-c' 插件提供。

示例:'objective-c' 插件

build.gradle
plugins {
    id 'objective-c'
}

要包含在原生二进制文件中的 Objective-C 源文件通过 ObjectiveCSourceSet 提供,它定义了一组 Objective-C 源文件。默认情况下,对于任何命名的组件,ObjectiveCSourceSet 包含 src/${name}/objectiveC 下的 .m 源文件。

Objective-C++ 源文件

Objective-C++ 语言支持由 'objective-cpp' 插件提供。

示例:'objective-cpp' 插件

build.gradle
plugins {
    id 'objective-cpp'
}

要包含在原生二进制文件中的 Objective-C++ 源文件通过 ObjectiveCppSourceSet 提供,它定义了一组 Objective-C++ 源文件。默认情况下,对于任何命名的组件,ObjectiveCppSourceSet 包含 src/${name}/objectiveCpp 下的 .mm 源文件。

配置编译器、汇编器和链接器

要生成的每个二进制文件都与一组编译器和链接器设置相关联,其中包括命令行参数以及宏定义。这些设置可以应用于所有二进制文件、单个二进制文件,或根据某些标准选择性地应用于一组二进制文件。

示例:应用于所有二进制文件的设置

build.gradle
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 相关联,从而允许根据此值定位设置。

可以轻松地将设置应用于特定类型的所有二进制文件

示例:应用于所有共享库的设置

build.gradle
// 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"
            }
        }
    }
}

此外,可以指定应用于为特定 executablelibrary 组件生成的所有二进制文件的设置

示例:应用于为 'main' 可执行组件生成的所有二进制文件的设置

build.gradle
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 二进制文件。

类似地,可以指定设置以定位组件的特定类型的二进制文件:例如,主库组件的所有共享库。

示例:仅应用于为主库组件生成的共享库的设置

build.gradle
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' 插件

build.gradle
plugins {
    id 'windows-resources'
}

要包含在原生二进制文件中的 Windows 资源通过 WindowsResourceSet 提供,它定义了一组 Windows 资源源文件。默认情况下,对于任何命名的组件,WindowsResourceSet 包含 src/${name}/rc 下的 .rc 源文件。

与其他源类型一样,您可以配置应包含在二进制文件中的 windows 资源的位置。

示例:配置 Windows 资源源文件的位置

build-resource-only-dll.gradle
sources {
    rc {
        source {
            srcDirs "src/hello/rc"
        }
        exportedHeaders {
            srcDirs "src/hello/headers"
        }
    }
}

您可以通过提供没有其他语言源文件的 Windows 资源源文件来构建仅资源的库,并根据需要配置链接器

示例:构建仅资源的 dll

build-resource-only-dll.gradle
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 组件关联的源集

示例:向源集提供库依赖项

build.gradle
sources {
    cpp {
        lib library: "hello"
    }
}

或者,可以将库依赖项直接提供给 executableNativeExecutableBinarySpec

示例:向二进制文件提供库依赖项

build.gradle
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 项目中生成的组件,表示法类似。

示例:声明项目依赖

lib/build.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"
            }
        }
    }
}
exe/build.gradle
plugins {
    id 'cpp'
}

model {
    components {
        main(NativeExecutableSpec) {
            sources {
                cpp {
                    lib project: ':lib', library: 'main'
                }
            }
        }
    }
}

预编译头文件

预编译头文件是一种性能优化,可减少多次编译广泛使用的头文件的成本。此功能预编译一个头文件,以便在编译每个源文件时可以重用编译后的对象文件,而不是每次都重新编译头文件。此支持适用于 C、C++、Objective-C 和 Objective-C++ 构建。

要配置预编译头文件,首先需要定义一个头文件,其中包含应预编译的所有头文件。它必须指定为每个应使用预编译头文件的源文件中的第一个包含的头文件。假定此头文件及其包含的任何头文件都使用头文件保护,以便它们可以以幂等方式包含。如果头文件保护未在头文件中使用,则头文件可能会被编译多次,并可能导致构建中断。

示例:创建预编译头文件

src/hello/headers/pch.h
#ifndef PCH_H
#define PCH_H
#include <iostream>
#include "hello.h"
#endif

示例:在源文件中包含预编译头文件

src/hello/cpp/hello.cpp
#include "pch.h"

void LIB_FUNC Greeter::hello () {
    std::cout << "Hello world!" << std::endl;
}

预编译头文件在源集上指定。在给定的源集上只能指定一个预编译头文件,并将应用于所有声明它作为第一个包含的源文件。如果源文件未将此头文件作为第一个头文件包含,则该文件将以正常方式(不使用预编译头文件对象文件)编译。提供的字符串应与源文件中的 "#include" 指令中使用的字符串相同。

示例:配置预编译头文件

build.gradle
model {
    components {
        hello(NativeLibrarySpec) {
            sources {
                cpp {
                    preCompiledHeader "pch.h"
                }
            }
        }
    }
}

预编译头文件必须以相同的方式包含在所有使用它的文件中。通常,这意味着头文件应存在于源集 "headers" 目录中,或存在于编译器包含路径中包含的目录中。

原生二进制变体

对于定义的每个可执行文件或库,Gradle 能够构建许多不同的原生二进制变体。不同变体的示例包括调试与发布二进制文件、32 位与 64 位二进制文件,以及使用不同的自定义预处理器标志生成的二进制文件。

Gradle 生成的二进制文件可以在 构建类型平台风味 上区分。对于这些“变体维度”中的每一个,都可以指定一组可用值,并将每个组件定位到一个、一些或所有这些值。例如,插件可能定义一系列支持平台,但您可能选择仅针对特定组件的 Windows-x86。

构建类型

构建类型 决定了二进制文件的各种非功能性方面,例如是否包含调试信息,或者二进制文件编译时使用的优化级别。典型的构建类型是“debug”和“release”,但项目可以自由定义任何一组构建类型。

示例:定义构建类型

build.gradle
model {
    buildTypes {
        debug
        release
    }
}

如果项目中未定义构建类型,则会添加一个名为“debug”的单个默认构建类型。

对于构建类型,Gradle 项目通常会为每个工具链定义一组编译器/链接器标志。

示例:配置调试二进制文件

build.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 将每个操作系统/架构组合定义为 NativePlatform,并且项目可以定义任意数量的平台。如果项目中未定义平台,则会添加一个名为“current”的单个默认平台。

目前,Platform 由定义的操作系统和架构组成。随着我们继续开发 Gradle 中的原生二进制文件支持,Platform 的概念将扩展到包括 C 运行时版本、Windows SDK、ABI 等。复杂的构建可以使用 Gradle 的可扩展性为每个平台应用其他属性,然后可以查询这些属性以指定特定于原生二进制文件的包含、预处理器宏或编译器参数。

示例:定义平台

build.gradle
model {
    platforms {
        x86 {
            architecture "x86"
        }
        x64 {
            architecture "x86_64"
        }
        itanium {
            architecture "ia-64"
        }
    }
}

对于给定的变体,Gradle 将尝试查找能够为目标平台构建的 NativeToolChain。将按定义的顺序搜索可用的工具链。有关更多详细信息,请参阅下面的 工具链 部分。

风味

每个组件都可以有一组命名的风味,并且可以为每个风味生成单独的二进制变体。虽然 构建类型目标平台 变体维度在 Gradle 中具有定义的含义,但每个项目都可以自由定义任意数量的风味,并以任何方式应用其含义。

组件风味的示例可能区分组件的“demo”、“paid”和“enterprise”版本,其中使用相同的源文件集来生成具有不同功能的二进制文件。

示例:定义风味

build.gradle
model {
    flavors {
        english
        french
    }
    components {
        hello(NativeLibrarySpec) {
            binaries.all {
                if (flavor == flavors.french) {
                    cppCompiler.define "FRENCH"
                }
            }
        }
    }
}

在上面的示例中,定义了一个具有“english”和“french”风味的库。编译“french”变体时,定义了一个单独的宏,这会导致生成不同的二进制文件。

如果未为组件定义风味,则使用名为“default”的单个默认风味。

为组件选择构建类型、平台和风味

对于默认组件,Gradle 将尝试为项目中定义的每个 buildTypeflavor 的组合创建原生二进制变体。可以通过指定 targetBuildTypes 和/或 targetFlavors 的集合来在每个组件的基础上覆盖此设置。默认情况下,Gradle 将为默认平台构建,请参阅上面的 平台,除非通过指定 targetPlatforms 集合在每个组件的基础上显式指定。

示例:将组件定位到特定平台

build.gradle
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) 方法用于指定应为名为 mainNativeExecutableSpec 构建的平台。

构建所有可能的变体

当为组件定义了一组构建类型、目标平台和风味时,将为这些可能的组合中的每一种创建一个 NativeBinarySpec 模型元素。但是,在许多情况下,无法构建特定的变体,可能是因为没有工具链可用于为特定平台构建。

如果由于任何原因无法构建二进制变体,则与该变体关联的 NativeBinarySpec 将不可构建。可以使用此属性创建一个任务,以在特定计算机上生成所有可能的变体。

示例:构建所有可能的变体

build.gradle
model {
    tasks {
        buildAllExecutables(Task) {
            dependsOn $.binaries.findAll { it.buildable }
        }
    }
}

工具链

单个构建可以使用不同的工具链来构建不同平台的变体。为此,核心“native-binary”插件将尝试查找并提供支持的工具链。但是,项目的工具链集也可以显式定义,从而允许配置其他交叉编译器以及允许指定安装目录。

定义工具链

支持的工具链类型有

示例:定义工具链

build.gradle
model {
    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”的默认平台为目标。此默认平台未指定任何 architectureoperatingSystem 值,因此使用第一个可用工具链的默认值。

Gradle 提供了一个钩子,允许构建作者控制传递给工具链可执行文件的确切参数集。这使构建作者能够解决 Gradle 中的任何限制或 Gradle 所做的假设。参数钩子应被视为“最后一招”机制,优先考虑真正建模底层域。

示例:重新配置工具参数

build.gradle
model {
    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"
                }
            }
        }
    }
}

使用 GCC 进行交叉编译

通过为其他目标平台添加支持,可以使用 GccClang 工具链进行交叉编译。这是通过为工具链指定目标平台来完成的。可以为每个目标平台指定自定义配置。

示例:定义目标平台

build.gradle
model {
    toolChains {
        gcc(Gcc) {
            target("arm"){
                cppCompiler.withArguments { args ->
                    args << "-m32"
                }
                linker.withArguments { args ->
                    args << "-m32"
                }
            }
            target("sparc")
        }
    }
    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 解决方案。

可以通过 visualStudio 扩展提供的 API 钩子修改生成的 visual studio 文件的内容。查看“visual-studio”示例,或查看 API 文档中的 VisualStudioExtension.getProjects()VisualStudioRootExtension.getSolution(),以获取更多详细信息。

CUnit 支持

Gradle cunit 插件提供了在您的原生二进制项目编译和执行 CUnit 测试的支持。对于您的项目中定义的每个 NativeExecutableSpecNativeLibrarySpec,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 测试

suite_operators.c
#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 组件具有关联的 NativeExecutableSpecNativeLibrarySpec 组件。对于为主组件配置的每个 NativeBinarySpec,将在测试套件组件上配置匹配的 CUnitTestSuiteBinarySpec。可以像配置任何其他二进制实例一样配置这些测试套件二进制文件

示例:配置 CUnit 测试

build.gradle
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 测试

build.gradle
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 get full insights.
> Get more help at https://help.gradle.org.

BUILD FAILED in 0s

当前对 CUnit 的支持还非常基础。未来集成的计划包括

  • 允许使用 Javadoc 样式的注释声明测试。

  • 改进的 HTML 报告,类似于 JUnit 的可用报告。

  • 测试执行的实时反馈。

  • 支持其他测试框架。

GoogleTest 支持

Gradle google-test 插件提供了在您的原生二进制项目编译和执行 GoogleTest 测试的支持。对于您的项目中定义的每个 NativeExecutableSpecNativeLibrarySpec,Gradle 将创建一个匹配的 GoogleTestTestSuiteSpec 组件,名为 ${component.name}Test

GoogleTest 源文件

Gradle 将为项目中的每个 GoogleTestTestSuiteSpec 组件创建一个名为 'cpp' 的 CppSourceSet。此源集应包含被测组件的 GoogleTest 测试文件。源文件可以位于传统位置 (src/${component.name}Test/cpp) 或像任何其他源集一样配置。

构建 GoogleTest 可执行文件

GoogleTestTestSuiteSpec 组件具有关联的 NativeExecutableSpecNativeLibrarySpec 组件。对于为主组件配置的每个 NativeBinarySpec,将在测试套件组件上配置匹配的 GoogleTestTestSuiteBinarySpec。可以像配置任何其他二进制实例一样配置这些测试套件二进制文件

示例:注册 GoogleTest 测试

build.gradle
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 的支持还非常基础。未来集成的计划包括

  • 改进的 HTML 报告,类似于 JUnit 的可用报告。

  • 测试执行的实时反馈。

  • 支持其他测试框架。