请注意,软件模型正在退役,本章中提到的插件最终将被弃用并移除。我们建议寻求构建 C++ 应用和库的新项目使用较新的替代插件

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

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

功能特性

原生软件插件提供

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

  • 支持多种源语言。

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

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

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

  • 单元测试执行。

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

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

支持的语言

当前支持以下源语言

  • C

  • C++

  • Objective-C

  • Objective-C++

  • 汇编

  • 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

GCC with Cygwin 32 和 Cygwin 64

Windows XP 及更高版本。

Windows

GCC with MinGWMinGW64

Windows XP 及更高版本。

以下工具链非官方支持。它们通常工作正常,但并未持续测试

操作系统 工具链 说明

macOS

GCC 来自 Macports

macOS

Clang 来自 Macports

类 UNIX

GCC

类 UNIX

Clang

工具链安装

请注意,如果您使用 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.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) 和至少一个静态库二进制文件 (StaticLibrarySpec)。

构建可执行文件

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

示例:定义可执行文件组件

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

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

对于定义的每个组件,Gradle 会添加一个同名的 FunctionalSourceSet。每个功能源集将包含一个项目支持的每种语言的语言特定源集。

组装或构建依赖项

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

在以下示例中,构建文件定义 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" 组件的 "static" 库二进制文件 "passing" 变体的依赖项,您可以运行 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" 组件的 "static" 库二进制文件 "passing" 变体的依赖项,您可以运行 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}可执行文件
组件类型

NativeExecutableSpec

原生二进制文件类型

NativeExecutableBinarySpec

创建的二进制文件位置

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

${component.name}共享库
组件类型

NativeLibrarySpec

原生二进制文件类型

SharedLibraryBinarySpec

创建的二进制文件位置

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

${component.name}静态库
组件类型

NativeLibrarySpec

原生二进制文件类型

StaticLibraryBinarySpec

创建的二进制文件位置

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

检查任务

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

检查${component.name}可执行文件
组件类型

NativeExecutableSpec

原生二进制文件类型

NativeExecutableBinarySpec

检查${component.name}共享库
组件类型

NativeLibrarySpec

原生二进制文件类型

SharedLibraryBinarySpec

检查${component.name}静态库
组件类型

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

  • 汇编

  • Windows 资源

C++ 源码

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

示例:'cpp' 插件

build.gradle
plugins {
    id 'cpp'
}

要包含在原生二进制文件中的 C++ 源码通过 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 提供,它定义了一组 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' 插件提供。

示例:'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 组件生成的所有二进制文件的设置。

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

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 中的原生二进制支持,平台的概念将扩展到包括 C 运行时版本、Windows SDK、ABI 等。复杂的构建可以使用 Gradle 的可扩展性为每个平台应用额外的属性,然后可以查询这些属性来为原生二进制文件指定特定的包含文件、预处理器宏或编译器参数。

示例:定义平台

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

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

风格

每个组件可以有一组命名的风格,并且可以为每种风格生成一个单独的二进制变体。虽然构建类型目标平台变体维度在 Gradle 中有明确的含义,但每个项目可以自由定义任意数量的风格并以任何方式赋予它们含义。

组件风格的一个示例可以是区分组件的“演示版”、“付费版”和“企业版”,其中使用同一组源文件生成具有不同功能的二进制文件。

示例:定义风格

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 将不是 buildable(可构建的)。可以使用此属性创建一个任务,以便在特定机器上生成所有可能的变体。

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

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 解决方案。

生成的 Visual Studio 文件的内容可以通过 visualStudio 扩展提供的 API 钩子进行修改。请查看“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 可用的报告。

  • 测试执行的实时反馈。

  • 支持其他测试框架。