软件模型 正在被弃用,本章中提到的插件最终将被弃用并删除。我们建议新的项目想要构建 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

GCCCygwin 32 和 Cygwin 64

Windows XP 及更高版本。

Windows

GCCMinGWMinGW64

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 或 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。每个功能源集将包含针对项目支持的每种语言的特定于语言的源集。

组装或构建依赖项

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

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

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

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

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

在接下来的部分中,我们将演示使用assembleDependents*buildDependents*dependentComponents 任务,并使用包含 CUnit 测试套件的示例构建。示例的构建脚本如下:

示例:示例构建

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,都会构建一个用于组装和检查该二进制文件的单个检查任务

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

NativeExecutableSpec

原生二进制文件类型

NativeExecutableBinarySpec

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

NativeLibrarySpec

原生二进制文件类型

SharedLibraryBinarySpec

check${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 组件生成的全部二进制文件的设置

示例:应用于为“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 二进制文件。

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

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

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,它能够为目标平台构建。可用的工具链按定义的顺序进行搜索。有关更多详细信息,请参阅下面的 工具链 部分。

风格

每个组件都可以有一组命名的 flavors,并且可以为每个风格生成一个单独的二进制变体。虽然 build typetarget platform 变体维度在 Gradle 中具有定义的含义,但每个项目都可以自由地定义任意数量的风格,并以任何方式为它们应用含义。

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

示例:定义风格

build.gradle
model {
    flavors {
        english
        french
    }
    components {
        hello(NativeLibrarySpec) {
            binaries.all {
                if (flavor == flavors.french) {
                    cppCompiler.define "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)

Clang

x86、x86_64、arm64(仅限 macOS)

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 提供的报告。

  • 测试执行的实时反馈。

  • 支持其他测试框架。