Gradle’s C++ plugins add support for building applications and libraries written in C++. These plugins were introduced in 2018 and we recommend new projects to use these plugins over the older Native plugins. This chapter makes heavy use of a large collection of samples.

Features

The C++ plugins provide:

  • Support for building C++ libraries and applications on Windows, Linux, and macOS.

  • Support for building variants of the same project for different architectures and operating systems.

  • Incremental and parallel compilation.

  • Cacheable compilation with Gradle’s build cache.

  • Dependency management features like any Java project built with Gradle.

  • Publishing of libraries and applications to repositories.

  • Unit test execution.

  • Integration with various IDEs (Xcode, Visual Studio, CLion).

  • Automatic discovery of popular tool chains (Visual C++, GCC, Clang).

  • Integration with build scans.

Plugin overview

Unlike the software model native plugins, the new C++ plugins rely on a familiar DSL and configuration model.

When the C++ plugin is applied to the project, Gradle registers an extension for the type of project (application or library). All configuration is done through this extension or the tasks registered by the plugin.

Please be aware that the DSL and APIs may change in later Gradle versions as we add new features with respect to our incubating feature lifecycle.

Building an application

Apply the cpp-application plugin. You can configure a CppApplication in a application block.

By default, when building a C++ application, the project name will be used as the name of the executable. For Windows, .exe will be added.

Building a library

Apply the cpp-library plugin. You can configure a CppLibrary in a library block.

By default, when building a C++ library, the project name will be used as the name of the library. Depending on the operating system, Gradle will add .so, .dylib or .dll for shared libraries or .a or .lib for static libraries. Gradle will assume you only want a shared library unless otherwise configured.

If you only want static libraries, you can do that too.

C++ source layout

By default, Gradle assumes a C++ library or application’s sources are in src/main/cpp.

For a library, Gradle assumes its public headers are in src/main/public. Header files can be mixed into src/main/cpp, but these headers will be treated as private and not exposed to downstream consumers.

Gradle assumes C++ source files end in .cpp, .c++ or .cc.

Declaring dependencies

Dependencies can be declared in a similar way to other Gradle plugins, like Java, with the main difference being that the dependencies block is inside the application or library block.

C++ applications only have implementation level dependencies.

C++ libraries have api and implementation dependencies.

Dependencies that are implementation only are not shared with downstream consumers. api dependencies form a part of the public API for a library.

Gradle automatically knows when to use the headers, link time or runtime usage of a dependency.

Project Dependencies

Project dependencies are just like project dependencies in Java projects. Gradle will export public header paths to downstream consumers and build libraries when required.

External Dependencies

External dependencies are like external dependencies in Java projects. Gradle will automatically fetch a dependency’s headers and link/runtime files. The compile and link tasks will be configured to search for headers or libraries in Gradle’s dependency cache.

Publishing

The Gradle metadata format is unstable and likely to change in incompatible ways. This will cause previously published artifacts to no longer be resolved properly. We will announce when this format is stable.

For C++ applications, Gradle will publish the executable.

For C++ libraries, Gradle will publish a header zip and any supporting binaries (shared or static).

Gradle also publishes additional information about the artifacts (a "Gradle metadata" file) that will be used when resolving dependencies. Gradle needs the additional metadata to select the appropriate variant (e.g., macOS vs Windows).

Configure for different targets

From a C++ library or application, Gradle generates what we call a binary. This binary is a variant of a component which has been assigned a particular build type (e.g., debug) and target machine (e.g., 32-bit Windows). Only binaries that may be built on the current host are generated by Gradle.

You can configure variant specific things, such as dependencies.

Debug and Release

Gradle creates two build types:

  • Debug - variants of this type are debuggable and not optimized

  • Release - variant of this type are debuggable and optimized

Under the covers, Gradle passes the appropriate compiler arguments for these build types.

Debugging symbols are extracted and stored in a separate file for the release build type, if necessary.

Target Machine

Gradle assumes the C++ application or library only targets the current host unless told otherwise. When building for multiple target machines, you need to declare this in the build configuration so the published metadata is created correctly.

IDE integration

Gradle provides plugins for Visual Studio and Xcode.

When used with the C++ plugins, Gradle will generate metadata files used by these IDEs. As much as possible, we’ve tried to make the IDE delegate to Gradle when compiling and linking C++ applications and libraries.

JetBrains maintains a separate integration with CLion.

Unit Testing support

Unit testing support is very limited right now. Gradle makes no assumptions about the type of unit testing framework being used. Gradle automatically links the test executable with the object files produced for the development binary. If the binary is an executable, Gradle will relocate the main symbol so you can test code in an application without conflicting with the test executable’s main symbol.

In the future, Gradle make come with support out-of-the-box for a particular testing framework or make it easier to integrate your own.

Incremental Compilation

Before invoking the compiler, Gradle analyzes the dependencies between source and header files. When a file changes, Gradle uses this dependency information to select which files need to be recompiled. When macros or compiler arguments change, Gradle must recompile all source files.

Gradle tracks the contents of files, so changes to just the timestamp of a header file (i.e., touching a header file) will not cause Gradle to recompile the affected source files.

If Gradle is unable to determine the relationship between source files and header files, it will fall back to compiling everything to be safe.

Parallel Compilation

Gradle uses a single build worker pool to concurrently compile and link C++ binaries by default. No special configuration is required to enable parallel compilation.

The worker pool size is determined by the number of available processors on the build machine (as reported to the build JVM). To explicitly set the number of workers use the --max-workers command-line option or org.gradle.workers.max system property. There is generally no need to change this setting from its default.

This build worker pool is shared across all tasks. This means that when using parallel project execution, the maximum number of concurrent compilation operations does not increase. For example, if the build machine has 4 processing cores and 10 projects are compiling in parallel, Gradle will only try to compile 4 files at a time and not 40.

Cached Compilation

Gradle uses the inputs to a compilation task to calculate a build cache key. This key uniquely identifies the output of the compilation task. When the build cache is enabled, Gradle will first check for a match of the build cache key in a local or remote cache. When there is a hit, Gradle extracts the compilation output into the expected location.

The build cache key takes into consideration:

  • Version and type of the compiler

  • Any macros or compiler arguments

  • Contents of the source files

  • Contents of the header files

  • Transitive headers used by the source or header files

  • Version of Gradle

Tool chain support

Gradle offers the ability to execute the same build using different tool chains. When you build a native binary, Gradle will attempt to locate a tool chain installed on your machine that can build the binary.

The following tool chains are supported:

Operating System Tool Chain Notes

Linux

GCC

Linux

Clang

macOS

XCode

Uses the Clang tool chain bundled with XCode.

Windows

Visual C++

Windows XP and later, Visual C++ 2010 to 2017.

Windows

GCC with Cygwin 32 and Cygwin 64

Windows XP and later.

Windows

GCC with MinGW and MinGW64

Windows XP and later.

Tool chain installation

To build C++ projects, you will need to have a compatible tool chain installed:

Windows

To build on Windows, install a compatible version of Visual Studio. The C++ plugins will discover the Visual Studio installations and select the latest version. There is no need to mess around with environment variables or batch scripts. This works fine from a Cygwin shell or the Windows command-line.

Alternatively, you can install Cygwin with GCC or MinGW. Clang is currently not supported.

macOS

To build on macOS, you should install XCode. The C++ plugins will discover the XCode installation using the system PATH.

The native plugins also work with GCC and Clang bundled with Macports. To use one of the Macports tool chains, you will need to make the tool chain the default using the port select command and add Macports to the system PATH.

Linux

To build on Linux, install a compatible version of GCC or Clang. The C++ plugins will discover GCC or Clang using the system PATH.

Limitations

The C++ plugins come with a few caveats and limitations:

  • While you can publish executable and shared libraries to Maven repositories, you cannot publish to Ivy repositories.

  • The metadata format used by the C++ plugin is unstable and may change between Gradle versions in incompatible ways. This limitation is expected to be removed in the next Gradle release (5.3).

  • C++ libraries are expected to have all of the same headers for all variants.

  • Support for "pre-built" dependencies is not a first class citizen, but there are several approaches to dealing with them:

  • The default set of C++ source extensions is not configurable.

  • Currently, the plugins only support building C++ out of the box. It’s possible to build C applications with some effort.

  • Pre-compiled header support is not supported out of the box, but it’s possible.

  • Cross-compilation is not supported out of the box.

Feedback and Contributing

Issues specific to the C++ plugins and Gradle-Native related features are tracked on a separate repository. If you run into problems or have a feature request, please open an issue up over here.

If you’re interested in contributing to Gradle-Native development and the C++ plugins, please contact us through a GitHub issue.