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.
Gradle offers the ability to execute the same build using different tool chains. You can ontrol 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 sub-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 | mainExecutable |
|
Library | SharedLibraryBinary | mainSharedLibrary |
|
Library | StaticLibraryBinary | mainStaticLibrary |
|
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 and Assembler 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 or Assembler 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
.
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" } } }
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.12. 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.13. 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.14. 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 } } } }
External dependencies (i.e. from a repository, not a subproject) are specified using the following syntax:
Example 54.15. Declaring dependencies
build.gradle
sources { main { cpp { dependency group: "some-org", name: "some-lib", version: "1.0" } } }
Each dependency must be specified with the dependency
method as above and must be declared as part of the source set. The
group
, name
and version
arguments must be supplied.
For each declared dependency, two actual dependencies are created. One with the classifier “headers
” and extension
“zip
” which is a zip file of the exported headers, and another with the classifier “so
” and extension
“so
” which is the compiled library binary to link against (which is supplied as a direct input to the g++ link operation).
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.
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.
For a build type, a Gradle project will typically define a set of compiler/linker flags per tool chain.
Example 54.17. 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' } }
If no build types are defined for a component, then all binaries are built with a single, default build type called 'debug'.
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.18. Defining target platforms
build.gradle
targetPlatforms { x86 { architecture "x86" } x64 { architecture "x86_64" } itanium { architecture "ia-64" } }
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 Chain | Architectures |
GCC | x86, x86_64 |
Clang | x86, 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.
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.
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.19. 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.
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.20. 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).
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.21. Building all possible variants
build.gradle
task buildAllExecutables { dependsOn binaries.withType(ExecutableBinary).matching { it.buildable } }
The cpp-exe
and cpp-lib
plugins configure their respective output binaries to be publishable as part of the
archives
configuration. To publish, simply configure the uploadArchives
task as per usual.
Example 54.22. Uploading exe or lib
build.gradle
group = "some-org" archivesBaseName = "some-lib" version = 1.0 uploadArchives { repositories { mavenDeployer { repository(url: uri("${buildDir}/repo")) } } }
The cpp-exe
plugin publishes a single artifact with extension “exe
”. The cpp-lib
plugin
publishes two artifacts; one with classifier “headers
” and extension “zip
”, and one with classifier
“so
” and extension “so
” (which is the format used when consuming dependencies).
Currently, there is no support for publishing the dependencies of artifacts in POM or Ivy files. Future versions will support this.