Chapter 55. Building native binaries

The Gradle support for building native binaries is currently incubating. Please be aware that the DSL and other configuration may change in later Gradle versions.

The various native binary plugins add support for building native software components, such as executables or shared libraries, from code written in C++, C and other languages. While many excellent build tools exist for this space of software development, Gradle offers developers its trademark power and flexibility together with dependency management practices more traditionally found in the JVM development space.

55.1. Supported languages

The following source languages are currently supported:

  • C

  • C++

  • Objective-C

  • Objective-C++

  • Assembly

  • Windows resources

55.2. Tool chain support

Gradle offers the ability to execute the same build using different tool chains. You can control which tool chain will be used to build by changing the operating system PATH to include the desired tool chain compiler. Alternatively, you can configure the tool chains directly, as described in Section 55.13, “Tool chains”.

The following tool chains are supported:

Operating SystemTool ChainNotes
LinuxGCC
LinuxClang
Mac OS XXCodeUses the Clang tool chain bundled with XCode.
Mac OS XGCC
Mac OS XClang
WindowsVisual C++Windows XP and later, Visual C++ 2010 and later.
WindowsGCC with Cywin 32Windows XP and later. Cygwin 64 is currently not supported.
WindowsGCC with MinGWWindows XP and later. Mingw-w64 is currently not supported.

The native plugins should generally work on other UNIX-like platforms, but are currently officially supported on the above platforms only.

55.3. Component model

To build native binaries using Gradle, your project should define one or more native components. Each component represents either an executable or a library that Gradle should build. A project can define any number of components. Gradle does not define any components by default.

For each component, Gradle defines a source set for each language that the component can be built from. A source set is essentially just a set of source directories containing source files. For example, when you apply the c plugin and define a library called helloworld, Gradle will define, by default, a source set containing the C source files in the src/helloworld/c directory. It will use these source files to build the helloworld library. This is described in more detail below.

For each component, Gradle defines one or more binaries as output. To build a binary, Gradle will take the source files defined for the component, compile them as appropriate for the source language, and link the result into a binary file. For an executable component, Gradle can produce executable binary files. For a library component, Gradle can produce both static and shared library binary files. For example, when you define a library called helloworld and build on Linux, Gradle will, by default, produce libhelloworld.so and libhelloworld.a binaries.

In many cases, more than one binary can be produced for a component. These binaries may vary based on the tool chain used to build, the compiler/linker flags supplied, the dependencies provided, or additional source files provided. Each native binary produced for a component is referred to as variant. Binary variants are discussed in detail below.

55.4. Building a library

To build either a static or shared native library, you define a library component in the libraries container. The following sample defines a library called hello:

Example 55.1. Defining a library component

build.gradle

libraries {
    hello {}
}

A library component is represented using NativeLibrarySpec. Each library component can produce at least one shared library binary (SharedLibraryBinarySpec) and at least one static library binary (StaticLibraryBinarySpec).

55.5. Building an executable

To build a native executable, you define an executable component in the executables container. The following sample defines an executable called main:

Example 55.2. Defining executable components

build.gradle

executables {
    main {}
}

An executable component is represented using NativeExecutableSpec. Each executable component can produce at least one executable binary (NativeExecutableBinarySpec).

For each component defined, Gradle adds a FunctionalSourceSet with the same name. Each of these functional source sets will contain a language-specific source set for each of the languages supported by the project.

55.6. Tasks

For each NativeBinarySpec that can be produced by a build, a single lifecycle task is constructed that can be used to create that binary, together with a set of other tasks that do the actual work of compiling, linking or assembling the binary.

Component Type Native Binary Type Lifecycle task Location of created binary
NativeExecutableSpec NativeExecutableBinarySpec ${component.name}Executable ${project.buildDir}/binaries/${component.name}Executable/${component.name}
NativeLibrarySpec SharedLibraryBinarySpec ${component.name}SharedLibrary ${project.buildDir}/binaries/${component.name}SharedLibrary/lib${component.name}.so
NativeLibrarySpec StaticLibraryBinarySpec ${component.name}StaticLibrary ${project.buildDir}/binaries/${component.name}StaticLibrary/${component.name}.a

55.6.1. Working with shared libraries

For each executable binary produced, the cpp plugin provides an install${binary.name} task, which creates a development install of the executable, along with the shared libraries it requires. This allows you to run the executable without needing to install the shared libraries in their final locations.

55.7. Finding out more about your project

Gradle provides a report that you can run from the command-line that shows some details about the components and binaries that your project produces. To use this report, just run gradle components. Below is an example of running this report for one of the sample projects:

Example 55.3. The components report

Output of gradle components

> gradle components
:components

------------------------------------------------------------
Root project
------------------------------------------------------------

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

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

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

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

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

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

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

BUILD SUCCESSFUL

Total time: 1 secs

55.8. Language support

Presently, Gradle supports building native binaries from any combination of source languages listed below. A native binary project will contain one or more named FunctionalSourceSet instances (eg 'main', 'test', etc), each of which can contain LanguageSourceSets containing source files, one for each language.

  • C

  • C++

  • Objective-C

  • Objective-C++

  • Assembly

  • Windows resources

55.8.1. C++ sources

C++ language support is provided by means of the 'cpp' plugin.

Example 55.4. The 'cpp' plugin

build.gradle

apply plugin: 'cpp'

C++ sources to be included in a native binary are provided via a CppSourceSet, which defines a set of C++ source files and optionally a set of exported header files (for a library). By default, for any named component the CppSourceSet contains .cpp source files in src/${name}/cpp, and header files in src/${name}/headers.

While the cpp plugin defines these default locations for each CppSourceSet, it is possible to extend or override these defaults to allow for a different project layout.

Example 55.5. C++ source set

build.gradle

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

For a library named 'main', header files in src/main/headers are considered the “public” or “exported” headers. Header files that should not be exported should be placed inside the src/main/cpp directory (though be aware that such header files should always be referenced in a manner relative to the file including them).

55.8.2. C sources

C language support is provided by means of the 'c' plugin.

Example 55.6. The 'c' plugin

build.gradle

apply plugin: 'c'

C sources to be included in a native binary are provided via a CSourceSet, which defines a set of C source files and optionally a set of exported header files (for a library). By default, for any named component the CSourceSet contains .c source files in src/${name}/c, and header files in src/${name}/headers.

While the c plugin defines these default locations for each CSourceSet, it is possible to extend or override these defaults to allow for a different project layout.

Example 55.7. C source set

build.gradle

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

For a library named 'main', header files in src/main/headers are considered the “public” or “exported” headers. Header files that should not be exported should be placed inside the src/main/c directory (though be aware that such header files should always be referenced in a manner relative to the file including them).

55.8.3. Assembler sources

Assembly language support is provided by means of the 'assembler' plugin.

Example 55.8. The 'assembler' plugin

build.gradle

apply plugin: 'assembler'

Assembler sources to be included in a native binary are provided via a AssemblerSourceSet, which defines a set of Assembler source files. By default, for any named component the AssemblerSourceSet contains .s source files under src/${name}/asm.

55.8.4. Objective-C sources

Objective-C language support is provided by means of the 'objective-c' plugin.

Example 55.9. The 'objective-c' plugin

build.gradle

apply plugin: 'objective-c'

Objective-C sources to be included in a native binary are provided via a ObjectiveCSourceSet, which defines a set of Objective-C source files. By default, for any named component the ObjectiveCSourceSet contains .m source files under src/${name}/objectiveC.

55.8.5. Objective-C++ sources

Objective-C++ language support is provided by means of the 'objective-cpp' plugin.

Example 55.10. The 'objective-cpp' plugin

build.gradle

apply plugin: 'objective-cpp'

Objective-C++ sources to be included in a native binary are provided via a ObjectiveCppSourceSet, which defines a set of Objective-C++ source files. By default, for any named component the ObjectiveCppSourceSet contains .mm source files under src/${name}/objectiveCpp.

55.9. Configuring the compiler, assembler and linker

Each binary to be produced is associated with a set of compiler and linker settings, which include command-line arguments as well as macro definitions. These settings can be applied to all binaries, an individual binary, or selectively to a group of binaries based on some criteria.

Example 55.11. Settings that apply to all binaries

build.gradle

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"
    }
}

Each binary is associated with a particular ToolChain, allowing settings to be targeted based on this value.

It is easy to apply settings to all binaries of a particular type:

Example 55.12. Settings that apply to all shared libraries

build.gradle

// For any shared library binaries built with Visual C++,
// define the DLL_EXPORT macro
binaries.withType(SharedLibraryBinarySpec) {
    if (toolChain in VisualCpp) {
        cCompiler.args "/Zi"
        cCompiler.define "DLL_EXPORT"
    }
}

Furthermore, it is possible to specify settings that apply to all binaries produced for a particular executable or library component:

Example 55.13. Settings that apply to all binaries produced for the 'main' executable component

build.gradle

executables {
    main {
        binaries.all {
            if (toolChain in VisualCpp) {
                assembler.args "/Zi"
            } else {
                assembler.args "-g"
            }
        }
    }
}

sources {
    i386_masm {
        asm(AssemblerSourceSet) {
            source.srcDir "src/main/asm_i386_masm"
        }
    }
    i386_gcc {
        asm(AssemblerSourceSet) {
            source.srcDir "src/main/asm_i386_gcc"
        }
    }
}

The example above will apply the supplied configuration to all executable binaries built.

Similarly, settings can be specified to target binaries for a component that are of a particular type: eg all shared libraries for the main library component.

Example 55.14. Settings that apply only to shared libraries produced for the 'main' library component

build.gradle

libraries {
    main {
        binaries.withType(SharedLibraryBinarySpec) {
            // Define a preprocessor macro that only applies to shared libraries
            cppCompiler.define "DLL_EXPORT"
        }
    }
}

55.10. Windows Resources

When using the VisualCpp tool chain, Gradle is able to compile Window Resource (rc) files and link them into a native binary. This functionality is provided by the 'windows-resources' plugin.

Example 55.15. The 'windows-resources' plugin

build.gradle

apply plugin: 'windows-resources'

Windows resources to be included in a native binary are provided via a WindowsResourceSet, which defines a set of Windows Resource source files. By default, for any named component the WindowsResourceSet contains .rc source files under src/${name}/rc.

As with other source types, you can configure the location of the windows resources that should be included in the binary.

Example 55.16. Configuring the location of Windows resource sources

build-resource-only-dll.gradle

sources {
    helloRes {
        rc {
            source {
                srcDirs "src/hello/rc"
            }
            exportedHeaders {
                srcDirs "src/hello/headers"
            }
        }
    }
}

You are able to construct a resource-only library by providing Windows Resource sources with no other language sources, and configure the linker as appropriate:

Example 55.17. Building a resource-only dll

build-resource-only-dll.gradle

libraries {
    helloRes {
        binaries.all {
            rcCompiler.args "/v"
            linker.args "/noentry", "/machine:x86"
        }
    }
}

The example above also demonstrates the mechanism of passing extra command-line arguments to the resource compiler. The rcCompiler extension is of type PreprocessingTool.

55.11. Library Dependencies

Dependencies for native components are binary libraries that export header files. The header files are used during compilation, with the compiled binary dependency being used during linking and execution.

55.11.1. Dependencies within the same project

A set of sources may depend on header files provided by another binary component within the same project. A common example is a native executable component that uses functions provided by a separate native library component.

Such a library dependency can be added to a source set associated with the executable component:

Example 55.18. Providing a library dependency to the source set

build.gradle

sources {
    main {
        cpp {
            lib libraries.hello
        }
    }
}

Alternatively, a library dependency can be provided directly to the ExecutableBinary for the executable.

Example 55.19. Providing a library dependency to the binary

build.gradle

executables {
    main {
        binaries.all {
            // Each executable binary produced uses the 'hello' static library binary
            lib libraries.hello.static
        }
    }
}

55.11.2. Project Dependencies

For a component produced in a different Gradle project, the notation is similar.

Example 55.20. Declaring project dependencies

build.gradle

project(":lib") {
    apply plugin: "cpp"
    libraries {
        main {}
    }
    // 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"
        }
    }
}

project(":exe") {
    apply plugin: "cpp"

    executables {
        main {}
    }

    sources {
        main {
            cpp {
                lib project: ':lib', library: 'main'
            }
        }
    }
}

55.12. Native Binary Variants

For each executable or library defined, Gradle is able to build a number of different native binary variants. Examples of different variants include debug vs release binaries, 32-bit vs 64-bit binaries, and binaries produced with different custom preprocessor flags.

Binaries produced by Gradle can be differentiated on build type, platform, and flavor. For each of these 'variant dimensions', it is possible to specify a set of available values as well as target each component at one, some or all of these. For example, a plugin may define a range of support platforms, but you may choose to only target Windows-x86 for a particular component.

55.12.1. Build types

A build type determines various non-functional aspects of a binary, such as whether debug information is included, or what optimisation level the binary is compiled with. Typical build types are 'debug' and 'release', but a project is free to define any set of build types.

Example 55.21. Defining build types

build.gradle

model {
    buildTypes {
        debug
        release
    }
}

If no build types are defined in a project, then a single, default build type called 'debug' is added.

For a build type, a Gradle project will typically define a set of compiler/linker flags per tool chain.

Example 55.22. Configuring debug binaries

build.gradle

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'
    }
}

At this stage, it is completely up to the build script to configure the relevant compiler/linker flags for each build type. Future versions of Gradle will automatically include the appropriate debug flags for any 'debug' build type, and may be aware of various levels of optimisation as well.

55.12.2. Platform

An executable or library can be built to run on different operating systems and cpu architectures, with a variant being produced for each platform. Gradle defines each OS/architecture combination as a Platform, and a project may define any number of platforms. If no platforms are defined in a project, then a single, default platform 'current' is added.

Presently, a Platform consists of a defined operating system and architecture. As we continue to develop the native binary support in Gradle, the concept of Platform will be extended to include things like C-runtime version, Windows SDK, ABI, etc. Sophisticated builds may use the extensibility of Gradle to apply additional attributes to each platform, which can then be queried to specify particular includes, preprocessor macros or compiler arguments for a native binary.

Example 55.23. Defining platforms

build.gradle

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

For a given variant, Gradle will attempt to find a ToolChain that is able to build for the target platform. Available tool chains are searched in the order defined. See the tool chains section below for more details.

55.12.3. Flavor

Each component can have a set of named flavors, and a separate binary variant can be produced for each flavor. While the build type and target platform variant dimensions have a defined meaning in Gradle, each project is free to define any number of flavors and apply meaning to them in any way.

An example of component flavors might differentiate between 'demo', 'paid' and 'enterprise' editions of the component, where the same set of sources is used to produce binaries with different functions.

Example 55.24. Defining flavors

build.gradle

model {
    flavors {
        english
        french
    }
}

libraries {
    hello {
        binaries.all {
            if (flavor == flavors.french) {
                cppCompiler.define "FRENCH"
            }
        }
    }
}

In the example above, a library is defined with a 'english' and 'french' flavor. When compiling the 'french' variant, a separate macro is defined which leads to a different binary being produced.

If no flavor is defined for a component, then a single default flavor named 'default' is used.

55.12.4. Selecting the build types, platforms and flavors for a component

For a default component, Gradle will attempt to create a native binary variant for each and every combination of buildType, platform and flavor defined for the project. It is possible to override this on a per-component basis, by specifying the set of targetBuildTypes, targetPlatforms and/or targetFlavors.

Example 55.25. Targeting a component at particular platforms

build.gradle

executables {
    main {
        targetPlatforms "x86", "x64"
    }
}

Here you can see that the TargetedNativeComponent.targetPlatforms() method is used to select the set of platforms to target for executables.main.

A similar mechanism exists for selecting TargetedNativeComponent.targetBuildTypes() and TargetedNativeComponent.targetFlavors().

55.12.5. Building all possible variants

When a set of build types, target platforms, and flavors is defined for a component, a NativeBinarySpec model element is created for every possible combination of these. However, in many cases it is not possible to build a particular variant, perhaps because no tool chain is available to build for a particular platform.

If a binary variant cannot be built for any reason, then the NativeBinarySpec associated with that variant will not be buildable. It is possible to use this property to create a task to generate all possible variants on a particular machine.

Example 55.26. Building all possible variants

build.gradle

task buildAllExecutables {
    dependsOn binaries.withType(ExecutableBinary).matching {
        it.buildable
    }
}

55.13. Tool chains

A single build may utilize different tool chains to build variants for different platforms. To this end, the core 'native-binary' plugins will attempt to locate and make available supported tool chains. However, the set of tool chains for a project may also be explicitly defined, allowing additional cross-compilers to be configured as well as allowing the install directories to be specified.

55.13.1. Defining tool chains

The supported tool chain types are:

Example 55.27. Defining tool chains

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)
    }
}

Each tool chain implementation allows for a certain degree of configuration (see the API documentation for more details).

55.13.2. Using tool chains

It is not necessary or possible to specify the tool chain that should be used to build. For a given variant, Gradle will attempt to locate a ToolChain that is able to build for the target platform. Available tool chains are searched in the order defined.

When a platform does not define an architecture or operating system, the default target of the tool chain is assumed. So if a platform does not define a value for operatingSystem, Gradle will find the first available tool chain that can build for the specified architecture.

The core Gradle tool chains are able to target the following architectures out of the box. In each case, the tool chain will target the current operating system. See the next section for information on cross-compiling for other operating systems.

Tool ChainArchitectures
GCCx86, x86_64
Clangx86, x86_64
Visual C++x86, x86_64, ia-64

So for GCC running on linux, the supported target platforms are 'linux/x86' and 'linux/x86_64'. For GCC running on Windows via Cygwin, platforms 'windows/x86' and 'windows/x86_64' are supported. (The Cygwin POSIX runtime is not yet modelled as part of the platform, but will be in the future.)

If no target platforms are defined for a project, then all binaries are built to target a default platform named 'current'. This default platform does not specify any architecture or operatingSystem value, hence using the default values of the first available tool chain.

Gradle provides a hook that allows the build author to control the exact set of arguments passed to a tool chain executable. This enables the build author to work around any limitations in Gradle, or assumptions that Gradle makes. The arguments hook should be seen as a 'last-resort' mechanism, with preference given to truly modelling the underlying domain.

Example 55.28. Reconfigure tool arguments

build.gradle

model {
    toolChains {
        visualCpp(VisualCpp) {
            cppCompiler.withArguments { args ->
                args << "-DFRENCH"
            }
        }
        clang(Clang){
            cCompiler.withArguments { args ->
                Collections.replaceAll(args, "CUSTOM", "-DFRENCH")
            }
            linker.withArguments { args ->
                args.remove "CUSTOM"
            }
            staticLibArchiver.withArguments { args ->
                args.remove "CUSTOM"
            }
        }

    }
}

55.13.3. Cross-compiling with GCC

Cross-compiling is possible with the Gcc and Clang tool chains, by adding support for additional target platforms. This is done by specifying a target platform for a toolchain. For each targetted platform a custom configuration can be specified.

Example 55.29. Defining target platforms

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"
        }
    }
}

55.14. Visual Studio IDE integration

Gradle has the ability to generate Visual Studio project and solution files for the native components defined in your build. This ability is added by the visual-studio plugin. For a multi-project build, all projects with native components should have this plugin applied.

When the visual-studio plugin is applied, a task name ${component.name}VisualStudio is created for each defined component. This task will generate a Visual Studio Solution file for the named component. This solution will include a Visual Studio Project for that component, as well as linking to project files for each depended-on binary.

The content of the generated visual studio files can be modified via API hooks, provided by the visualStudio extension. Take a look at the 'visual-studio' sample, or see VisualStudioExtension.getProjects() and VisualStudioExtension.getSolutions() in the API documentation for more details.

55.15. CUnit support

The Gradle cunit plugin provides support for compiling and executing CUnit tests in your native-binary project. For each NativeExecutableSpec and NativeLibrarySpec defined in your project, Gradle will create a matching CUnitTestSuiteSpec component, named ${component.name}Test.

55.15.1. CUnit sources

Gradle will create a CSourceSet named 'cunit' for each CUnitTestSuiteSpec component in the project. This source set should contain the cunit test files for the component sources. Source files can be located in the conventional location (src/${component.name}Test/cunit) or can be configured like any other source set.

Gradle initialises the CUnit test registry and executes the tests, utilising some generated CUnit launcher sources. Gradle will expect and call a function with the signature void gradle_cunit_register() that you can use to configure the actual CUnit suites and tests to execute.

Example 55.30. Registering CUnit tests

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);
}

Due to this mechanism, your CUnit sources may not contain a main method since this will clash with the method provided by Gradle.

55.15.2. Building CUnit executables

A CUnitTestSuiteSpec component has an associated NativeExecutableSpec or NativeLibrarySpec component. For each NativeBinarySpec configured for the main component, a matching CUnitTestSuiteBinarySpec will be configured on the test suite component. These test suite binaries can be configured in a similar way to any other binary instance:

Example 55.31. Registering CUnit tests

build.gradle

binaries.withType(CUnitTestSuiteBinarySpec) {
    lib library: "cunit", linkage: "static"

    if (flavor == flavors.failing) {
        cCompiler.define "PLUS_BROKEN"
    }
}

Both the CUnit sources provided by your project and the generated launcher require the core CUnit headers and libraries. Presently, this library dependency must be provided by your project for each CUnitTestSuiteBinarySpec.

55.15.3. Running CUnit tests

For each CUnitTestSuiteBinarySpec, Gradle will create a task to execute this binary, which will run all of the registered CUnit tests. Test results will be found in the ${build.dir}/test-results directory.

Example 55.32. Running CUnit tests

build.gradle

apply plugin: "c"
apply plugin: "cunit"

model {
    flavors {
        passing
        failing
    }
    repositories {
        libs(PrebuiltLibraries) {
            cunit {
                headers.srcDir "lib/cunit/2.1-2/include"
                binaries.withType(StaticLibraryBinary) {
                    staticLibraryFile =
                        file("lib/cunit/2.1-2/lib/" +
                             findCUnitLibForPlatform(targetPlatform))
                }
            }
        }
    }
}

libraries {
    operators {}
}
binaries.withType(CUnitTestSuiteBinarySpec) {
    lib library: "cunit", linkage: "static"

    if (flavor == flavors.failing) {
        cCompiler.define "PLUS_BROKEN"
    }
}

Note: The code for this example can be found at samples/native-binaries/cunit which is in both the binary and source distributions of Gradle.

Output of gradle -q runFailingOperatorsTestCUnitExe

> gradle -q runFailingOperatorsTestCUnitExe
There were test failures:
  1. /home/user/gradle/samples/native-binaries/cunit/src/operatorsTest/c/test_plus.c:6  - plus(0, -2) == -2
  2. /home/user/gradle/samples/native-binaries/cunit/src/operatorsTest/c/test_plus.c:7  - plus(2, 2) == 4

The current support for CUnit is quite rudimentary. Plans for future integration include:

  • Allow tests to be declared with javadoc-style annotations.

  • Improved HTML reporting, similar to that available for JUnit.

  • Real-time feedback for test execution.

  • Support for additional test frameworks.