Chapter 54. 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 from C++, C and Assembler sources. While many excellent build tools exist for this space of software development, Gradle offers developers it's trademark power and flexibility together with the dependency management practices more traditionally found in the JVM development space.

54.1. 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 the `Native Binary Variants` section, below.

The following tool chains are supported:

Operating SystemTool ChainNotes
LinuxGCC
LinuxClang
Mac OS XGCCUsing GCC distributed with XCode.
Mac OS XClangUsing Clang distributed with XCode.
WindowsVisual C++Windows XP and later, Visual C++ 2010 and later.
WindowsGCCWindows XP and later, using GCC distributed with Cygwin.
WindowsMinGWWindows XP and later.

54.2. Component model

A native binary project defines a set of Executable and Library components, each of which Gradle maps to a number of NativeBinary outputs. For each executable or library 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.

To build either a static or shared native library binary, a Library component is added to the libraries container. Each library component can produce at least one SharedLibraryBinary and at least one StaticLibraryBinary.

Example 54.1. Defining a library component

build.gradle

libraries {
    hello {}
}

To build an executable binary, an Executable component is added to the executables container and associated with a set of sources.

Example 54.2. Defining executable components

build.gradle

executables {
    main {}
}

In many cases, more than one native 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.

54.3. Tasks

For each NativeBinary 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 TypeNative Binary TypeLifecycle taskLocation of created binary
ExecutableExecutableBinary$component.nameExecutable$buildDir/binaries/$binary.name/$component.name
LibrarySharedLibraryBinary$component.nameSharedLibrary$buildDir/binaries/$binary.name/lib$component.name.so
LibraryStaticLibraryBinary$component.nameStaticLibrary$buildDir/binaries/$binary.name/$component.name.a

54.3.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.

54.4. Core language support: C, C++ and Assembler

Presently, Gradle supports building native binaries from any combination of C++, C and Assembler sources. A native binary project will contain one or more named FunctionalSourceSet instances (eg 'main', 'test', etc), each of which can contain LanguageSourceSets containing C++, C or Assembler source files.

54.4.1. C++ sources

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

Example 54.3. 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 54.4. C++ source set

build.gradle

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

For a library named 'main', files in src/main/headers are considered the “public” or “exported” headers. Header files that should not be exported (but are used internally) 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).

54.4.2. C sources

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

Example 54.5. 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 54.6. C source set

build.gradle

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

For a library named 'main', files in src/main/headers are considered the “public” or “exported” headers. Header files that should not be exported (but are used internally) 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).

54.4.3. Assembler sources

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

Example 54.7. 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.

54.5. 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 54.8. 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 "-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 54.9. 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(SharedLibraryBinary) {
    if (toolChain in VisualCpp) {
        cCompiler.args "/Zi"
        cCompiler.define "DLL_EXPORT"
    }
}

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

Example 54.10. 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"
            }
        }
    }
}

The above example 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 54.11. Settings that apply only to shared libraries produced for the 'main' library component

build.gradle

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

54.6. 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 54.12. 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 in included in the binary.

Example 54.13. Configuring the location of Windows resource sources

build-resource-only-dll.gradle

sources {
    helloResources {
        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 54.14. Building a resource-only dll

build-resource-only-dll.gradle

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

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

54.7. Library Dependencies

Dependencies for C++ projects are binary libraries that export header files. The header files are used during compilation, with the compiled binary dependency being used during the linking.

54.7.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 easily provided to source set associated with the executable component:

Example 54.15. 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 54.16. 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
        }
    }
}

54.7.2. Project Dependencies

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

Example 54.17. Declaring project dependencies

build.gradle

project(":lib") {
    apply plugin: "cpp-lib"
}

project(":exe") {
    apply plugin: "cpp-exe"
    evaluationDependsOn(":lib")

    sources {
        main {
            cpp {
                lib project(":lib").libraries.main
            }
        }
    }
}

54.8. 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 by GCC vs binaries produced by Clang.

Binaries produced by Gradle can be differentiated on build type, target platform, flavor and tool chain.

54.8.1. Build type

The 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 54.18. Defining build types

build.gradle

buildTypes {
    debug {}
    release {}
}

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

Example 54.19. 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.

If no build types are defined for a component, then all binaries are built with a single, default build type called 'debug'.

54.8.2. Target platform

An executable or library can be built to run on different operating systems and cpu architectures, with a variant being produced for each target platform. Gradle defines each target OS/architecture combination as a Platform, and a project may be configured with different targetPlatforms.

Example 54.20. Defining target platforms

build.gradle

targetPlatforms {
    x86 {
        architecture "x86"
    }
    x64 {
        architecture "x86_64"
    }
    itanium {
        architecture "ia-64"
    }
}

When a platform does not define an architecture or operating system, the default target of the tool chain is assumed. In the example above, the operatingSystem is not defined, so Gradle will assume that the tool chain is configured to build for the correct operating system, and will not supply any specific compiler/linker flags to target a particular operating system.

The core Gradle tool chains support the following architectures out of the box. There is not currently any core support for building for different operating systems.

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

Cross-compiling is possible with the Gcc and Clang tool chains, by programmatically adding support for additional target platforms. This is done using the PlatformConfigurableToolChain API. Each added TargetPlatformConfiguration defines support for a particular target platform, and supplies additional tool arguments that are required to target this platform.

There is no direct support for cross platform source configuration (à la autoconf) at this time.

If no target platforms are defined for a project, then all binaries are built to target a default platform which does not specify an architecture or operatingSystem, hence using the tool chain default.

54.8.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 54.21. Defining component flavors

build.gradle

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

In the above example, 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.

54.8.4. Tool chain

Within a single build it is possible to build the same component with different tool chains. To do so, the set of tool chains for a project must be explicitly configured, and a separate variant will be produced for each available tool chain. Attempting to build the variant for a tool chain that is not available on the current machine will result in an error.

Example 54.22. Defining tool chains

build.gradle

toolChains {
    visualCpp(VisualCpp) {
        // Specify the installDir if Visual Studio cannot be located by default
        // 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)
}

The supported tool chain types are:

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

54.8.5. Building all possible variants

When a set of build types, target platforms, flavors and tool chains is defined for a component, a NativeBinary 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 the tool chain for that variant is not available on the current machine, or the tool chain is not able to build for the specified target architecture.

If a binary variant cannot be built for any reason, then the NativeBinary 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 54.23. Building all possible variants

build.gradle

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