软件模型正在弃用中,本章中提到的插件最终也将弃用并移除。我们建议寻求构建 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

来自 Macports 的 GCC

macOS

来自 Macports 的 Clang

类 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) 和至少一个静态库二进制文件 (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 将能够跳过依赖组件的编译,并且只需与新库重新链接。组装组件时不会运行测试。

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

在以下部分中,我们将通过一个包含 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" 变体的 "static" 库二进制文件的依赖项,您可以运行 assembleDependentsOperatorsPassingStaticLibrary 任务

示例:组装依赖于 operators 组件的 passing/static 二进制文件的组件

gradle assembleDependentsOperatorsPassingStaticLibrary --max-workers=1 的输出
> gradle assembleDependentsOperatorsPassingStaticLibrary --max-workers=1
> Task :compileOperatorsTestPassingCUnitExeOperatorsC
> Task :operatorsTestCUnitLauncher
> Task :compileOperatorsTestPassingCUnitExeOperatorsTestC
> Task :compileOperatorsTestPassingCUnitExeOperatorsTestCunitLauncher
> Task :linkOperatorsTestPassingCUnitExe
> Task :operatorsTestPassingCUnitExe
> Task :assembleDependentsOperatorsTestPassingCUnitExe
> Task :compileOperatorsPassingStaticLibraryOperatorsC
> Task :createOperatorsPassingStaticLibrary
> Task :operatorsPassingStaticLibrary
> Task :assembleDependentsOperatorsPassingStaticLibrary

BUILD SUCCESSFUL in 0s
7 actionable tasks: 7 executed

在上面的输出中,目标二进制文件以及依赖于它的测试套件二进制文件都已组装。

您还可以使用相应的组件任务(例如 assembleDependentsOperators)来组装组件的所有依赖项(即所有二进制文件/变体)。如果您有许多构建类型、变体和平台的组合,并且想要组装所有这些组合,这将非常有用。

构建依赖项

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

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

例如,要构建 "operators" 组件的 "passing" 变体的 "static" 库二进制文件的依赖项,您可以运行 buildDependentsOperatorsPassingStaticLibrary 任务

示例:构建依赖于 operators 组件的 passing/static 二进制文件的组件

gradle buildDependentsOperatorsPassingStaticLibrary --max-workers=1 的输出
> gradle buildDependentsOperatorsPassingStaticLibrary --max-workers=1
> Task :compileOperatorsTestPassingCUnitExeOperatorsC
> Task :operatorsTestCUnitLauncher
> Task :compileOperatorsTestPassingCUnitExeOperatorsTestC
> Task :compileOperatorsTestPassingCUnitExeOperatorsTestCunitLauncher
> Task :linkOperatorsTestPassingCUnitExe
> Task :operatorsTestPassingCUnitExe
> Task :installOperatorsTestPassingCUnitExe
> Task :runOperatorsTestPassingCUnitExe
> Task :checkOperatorsTestPassingCUnitExe
> Task :buildDependentsOperatorsTestPassingCUnitExe
> Task :compileOperatorsPassingStaticLibraryOperatorsC
> Task :createOperatorsPassingStaticLibrary
> Task :operatorsPassingStaticLibrary
> Task :buildDependentsOperatorsPassingStaticLibrary

BUILD SUCCESSFUL in 0s
9 actionable tasks: 9 executed

在上面的输出中,目标二进制文件以及依赖于它的测试套件二进制文件都已构建,并且测试套件已运行。

您还可以使用相应的组件任务(例如 buildDependentsOperators)来构建组件的所有依赖项(即所有二进制文件/变体)。

任务

对于构建可以生成的每个 NativeBinarySpec,都会构建一个单独的生命周期任务,该任务可用于创建该二进制文件,以及一组执行编译、链接或组装二进制文件的实际工作的其他任务。

${component.name}Executable
组件类型

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 任务依赖于项目中二进制文件的所有 check tasks。如果没有 CUnitGoogleTest 插件,二进制检查任务仅依赖于组装二进制文件的 lifecycle task,请参阅 原生任务

当应用 CUnitGoogleTest 插件时,执行组件测试套件的任务会自动连接到适当的 check task

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

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

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 二进制文件的任何 check tasks 都将运行自定义检查任务

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

gradle checkHelloSharedLibrary 的输出
> gradle checkHelloSharedLibrary

> Task :myCustomCheck
Executing my custom check

> Task :checkHelloSharedLibrary

BUILD SUCCESSFUL in 0s
1 actionable task: 1 executed

使用共享库

对于生成的每个可执行二进制文件,cpp 插件提供了一个 install${binary.name} 任务,该任务会创建可执行文件的开发安装,以及它所需的共享库。这允许您运行可执行文件而无需将共享库安装到其最终位置。

了解有关项目的信息

Gradle 提供了一个您可以从命令行运行的报告,该报告显示了有关项目生成的组件和二进制文件的一些详细信息。要使用此报告,只需运行 gradle components。下面是运行其中一个示例项目的此报告的示例

示例:组件报告

gradle components 的输出
> gradle components

> Task :components

------------------------------------------------------------
Root project 'cpp'
------------------------------------------------------------

Native library 'hello'
----------------------

Source sets
    C++ source 'hello:cpp'
        srcDir: src/hello/cpp

Binaries
    Shared library 'hello:sharedLibrary'
        build using task: :helloSharedLibrary
        build type: build type 'debug'
        flavor: flavor 'default'
        target platform: platform 'current'
        tool chain: Tool chain 'clang' (Clang)
        shared library file: build/libs/hello/shared/libhello.dylib
    Static library 'hello:staticLibrary'
        build using task: :helloStaticLibrary
        build type: build type 'debug'
        flavor: flavor 'default'
        target platform: platform 'current'
        tool chain: Tool chain 'clang' (Clang)
        static library file: build/libs/hello/static/libhello.a

Native executable 'main'
------------------------

Source sets
    C++ source 'main:cpp'
        srcDir: src/main/cpp

Binaries
    Executable 'main:executable'
        build using task: :mainExecutable
        install using task: :installMainExecutable
        build type: build type 'debug'
        flavor: flavor 'default'
        target platform: platform 'current'
        tool chain: Tool chain 'clang' (Clang)
        executable file: build/exe/main/main

Note: currently not all plugins register their components, so some components may not be visible here.

BUILD SUCCESSFUL in 0s
1 actionable task: 1 executed

语言支持

目前,Gradle 支持从下面列出的任何源语言组合构建原生软件。一个原生二进制项目将包含一个或多个命名的 FunctionalSourceSet 实例(例如“main”、“test”等),每个实例都可以包含 LanguageSourceSet,其中包含源文件,每种语言一个。

  • C

  • C++

  • Objective-C

  • Objective-C++

  • 汇编

  • Windows 资源

C++ 源文件

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

示例:'cpp' 插件

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 资源源的位置

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

您可以通过提供 Windows 资源源而不提供其他语言源,并相应地配置链接器来构建一个仅包含资源的库

示例:构建仅包含资源的 DLL

only-dll/build.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 将每个 OS/架构组合定义为 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"
                }
            }
        }
    }
}

在上面的示例中,定义了一个具有“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 }
        }
    }
}

工具链

单个构建可能利用不同的工具链来为不同的平台构建变体。为此,核心“原生二进制”插件将尝试定位并提供受支持的工具链。但是,项目的工具链集也可以明确定义,从而允许配置额外的交叉编译器以及指定安装目录。

定义工具链

支持的工具链类型有

示例:定义工具链

build.gradle
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
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
toolChains {
    gcc(Gcc) {
        target("arm"){
            cppCompiler.withArguments { args ->
                args << "-m32"
            }
            linker.withArguments { args ->
                args << "-m32"
            }
        }
        target("sparc")
    }
}
model {
    platforms {
        arm {
            architecture "arm"
        }
        sparc {
            architecture "sparc"
        }
    }
    components {
        main(NativeExecutableSpec) {
            targetPlatform "arm"
            targetPlatform "sparc"
        }
    }
}

Visual Studio IDE 集成

Gradle 能够为构建中定义的原生组件生成 Visual Studio 项目和解决方案文件。此功能由 visual-studio 插件添加。对于多项目构建,所有具有原生组件的项目(和根项目)都应应用此插件。

visual-studio 插件应用于根项目时,将创建一个名为 visualStudio 的任务,该任务将生成一个包含构建中所有组件的 Visual Studio 解决方案文件。此解决方案将包含每个组件的 Visual Studio 项目,并配置每个组件以使用 Gradle 进行构建。

当项目是根项目时,visual-studio 插件还会创建一个名为 openVisualStudio 的任务。此任务生成 Visual Studio 解决方案,然后将其在 Visual Studio 中打开。这意味着您只需从根项目运行 gradlew openVisualStudio,即可在一个便捷的步骤中生成并打开 Visual Studio 解决方案。

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

CUnit 支持

Gradle cunit 插件提供了在您的原生二进制项目中文编译和执行 CUnit 测试的支持。对于您项目中定义的每个 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 generate a Build Scan (Powered by Develocity).
> 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 中可用的报告。

  • 测试执行的实时反馈。

  • 支持其他测试框架。