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, Objective-C, Objective-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.
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 System | Tool Chain | Notes |
Linux | GCC | |
Linux | Clang | |
Mac OS X | GCC | Using GCC distributed with XCode. |
Mac OS X | Clang | Using Clang distributed with XCode. |
Windows | Visual C++ | Windows XP and later, Visual C++ 2010 and later. |
Windows | GCC | Windows XP and later, using GCC distributed with Cygwin. |
Windows | MinGW | Windows XP and later. |
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
.
To build an executable binary,
an Executable
component is added to the executables
container
and associated with a set of sources.
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.
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 Type | Native Binary Type | Lifecycle task | Location of created binary |
Executable | ExecutableBinary |
|
|
Library | SharedLibraryBinary |
|
|
Library | StaticLibraryBinary |
|
|
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.
Presently, Gradle supports building native binaries from any combination of C++, C, Assembler, Objective-C and Objective-C++ sources.
A native binary project will contain one or more named FunctionalSourceSet
instances (eg 'main', 'test', etc),
each of which can contain LanguageSourceSet
s containing C++, C, Assembler, Objective-C or Objective-C++ source files.
C++ language support is provided by means of the 'cpp'
plugin.
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).
C language support is provided by means of the 'c'
plugin.
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).
Assembly language support is provided by means of the 'assembler'
plugin.
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
.
Objective-C language support is provided by means of the 'objective-c'
plugin.
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
.
Objective-C++ language support is provided by means of the 'objective-cpp'
plugin.
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
.
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.10. 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 54.11. 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.12. 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 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 54.13. 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" } } }
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.
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.15. 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 54.16. 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
.
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.
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.17. 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.18. 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 } } }
For a component produced in a different Gradle project, the notation is similar.
Example 54.19. Declaring project dependencies
build.gradle
project(":lib") { apply plugin: "cpp" libraries { main {} } } project(":exe") { apply plugin: "cpp" executables { main {} } sources { main { cpp { lib project: ':lib', library: 'main' } } } }
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.
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.
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 54.21. 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' } }
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.
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 54.22. 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 chain section below for more details.
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.23. Defining flavors
build.gradle
model { flavors { english french } } libraries { hello { binaries.all { if (flavor == flavors.french) { cppCompiler.define "FRENCH" } } source sources.lib } }
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.
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 54.24. 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()
.
When a set of build types, target platforms, and flavors 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
no tool chain is available to build for a particular platform.
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.25. Building all possible variants
build.gradle
task buildAllExecutables { dependsOn binaries.withType(ExecutableBinary).matching { it.buildable } }
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.
The supported tool chain types are:
Example 54.26. Defining tool chains
build.gradle
model { 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) } }
Each tool chain implementation allows for a certain degree of configuration (see the API documentation for more details).
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.
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 Chain | Architectures |
GCC | x86, x86_64 |
Clang | x86, 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 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.
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.
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 programmatic hooks, provided by the visualStudio
extension. Take a look at the 'visual-studio' sample, or see VisualStudioExtension.getProjects()
and VisualStudioExtension.getSolutions()
for more details.
The Gradle cunit
plugin provides support for compiling and executing CUnit tests in your native-binary project.
For each Executable
and Library
defined in your project, Gradle will create a matching CUnitTestSuite
component,
named ${component.name}Test
.
Gradle will create a CSourceSet
named 'cunit' for each CUnitTestSuite
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.
The job of initialising the CUnit test registry and executing the tests is performed by Gradle, via 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 54.27. 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); }
main
method since this will clash with the method provided by Gradle.
A CUnitTestSuite
component has an associated
Executable
or Library
component.
For each ProjectNativeBinary
configured for the main component, a matching
TestSuiteExecutableBinary
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 54.28. Registering CUnit tests
build.gradle
binaries.withType(TestSuiteExecutableBinary) { lib library: "cunit", linkage: "static" if (flavor == flavors.failing) { cCompiler.define "PLUS_BROKEN" } }
TestSuiteExecutableBinary
.
For each TestSuiteExecutableBinary
, Gradle will create a task to execute this binary,
which will run all of the registered CUnit tests.
The generated test results will be located in the
directory.
${build.dir}
/test-results
Example 54.29. 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(TestSuiteExecutableBinary) { 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.
> gradle -q runFailingOperatorsTestCUnitExe There were test failures: 1. /home/user/gradle/samples/native-binaries/cunit/src/operatorsTest/cunit/test_plus.c:6 - plus(0, -2) == -2 2. /home/user/gradle/samples/native-binaries/cunit/src/operatorsTest/cunit/test_plus.c:7 - plus(2, 2) == 4 :runFailingOperatorsTestCUnitExe FAILED BUILD FAILED Total time: 1 secs
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.