Introducing the new C++ plugins

This post introduces some new plugins for C++ that we’ve been working on. These plugins can build C++ libraries and applications. They work on macOS, Linux, and Windows with GCC, Clang and Visual C++/Visual Studio.

The plugins will eventually replace the software model plugins and take advantage of many new features baked into Gradle core, such as a rich dependency management engine, build cache, composite builds, finer grained parallel execution, build scans, and more. For background, see our post on the State and Future of the Gradle Software Model.

We welcome any feedback you may have about these plugins. You can leave feedback on the Gradle forums or raise issues on the Gradle native GitHub repository.

Building an application

You can find all of the samples from this post in the Gradle native samples GitHub repository. Let’s look at building a simple application.

The build script should look familiar to anyone who has used Gradle’s Java plugins:

plugins {
    id 'cpp-application'
}

This application has no dependencies, and the C++ source files and headers live in the default location: the src/main/cpp directory. Since this is Gradle, you can easily configure the source locations to match whatever layout your project has, including the common pattern of putting everything in one directory.

Here’s the result of running ./gradlew assemble on this sample:

./gradlew assemble

Take a look at the build scan for this build to see what happened in more detail.

compile debug timeline

The plugins automatically find the compiler, linker and other tools to build the application. The result ends up installed in the build/install directory ready to run.

IDE support

Xcode is currently supported for C++ projects. You can just run ./gradlew xcode and open the generated workspace. Support for generating Visual Studio solutions will be added early this year and support for other IDEs will be gradually added after that.

Here is the result of running ./gradlew xcode on the sample:

./gradlew xcode

This is how the workspace looks in Xcode:

Xcode integration

Dependencies

The plugin uses Gradle’s dependency management features, just like other plugins such as the Java or Android plugins. This means, for example, transitive dependencies work just fine.

Let’s add a dependency on a library to the application. In this sample, the C++ library is downloaded from a Maven repository. You don’t have to install the library anywhere manually, and everyone who runs the build will use the version specified in the build script, rather than whatever version happens to be installed on their machine.

The build script defines a Maven repository and declares a dependency on another sample C++ library:

repositories {
    maven {
        // In this sample, we used a local Maven repository, 
        // but Maven Central or Artifactory server can be used.
        url 'http://localhost:8000/'
    }
}

dependencies {
    implementation 'org.gradle.cpp-samples:math:1.5'
}

Here is the result of running ./gradlew assemble. Gradle downloads the headers and shared library binary and compiles and links against these:

./gradlew assemble

The build scan shows more detail, including the downloads.

app assemble build scan network activity

Here is how this project looks in Xcode:

Xcode integration

Tests

Basic unit testing is supported out of the box. Here is a sample that uses Google Test, downloaded from a Maven repository. We published the binaries using this fork of Google Test, which simply adds a Gradle build.

The build script declares a dependency on Google test and a Maven repository that can be used to locate the Google test binaries:

plugins {
    id 'cpp-unit-test'
}
repositories {
    maven {
        url 'https://repo.gradle.org/gradle/libs-snapshots-local/'
    }
}
dependencies {
    // Currently we have to encode the operating system and architecture in 
    // the dependency name. This will disappear in later releases
    unitTestImplementation 'org.gradle.cpp-samples:googletest_macosx_x86-64_4.5:1.9.0-SNAPSHOT'
}

Here is the result of running ./gradlew check. Gradle downloads the Google test library, compiles the C++ source and tests and then runs the tests:

./gradlew check

build scan for math check

Richer reporting, build scan support, parallel execution and filtering for Google Test will be added this year with support for other C++ testing frameworks after that.

Fast builds

The plugins can produce debug and release builds of the application or library using Gradle’s new variant-aware dependency management, so that debug builds are compiled and linked against debug library binaries, and release builds are compiled and linked against release library binaries. When you build the debug build, which is the default, Gradle builds only the debug builds of the libraries that you need, rather than building everything.

Developer and CI builds are fast. C++ compilation is a cacheable task, so you can avoid unnecessary and long compilation times when using the build cache. Gradle Enterprise comes with a build cache backend. You don’t need to use the --parallel option as Gradle does incremental and parallel compilation and linking by default.

Let’s run some clean builds that use the build cache:

./gradlew assemble with build cache

You can see that the second build is faster, as the result is fetched from the build cache rather than recompiled. Build scans for non-cached build and cached build.

cached vs non-cached assemble

Publishing C++ libraries

The new plugins can publish C++ libraries to a Maven or Ivy repository. Support for other kinds of repositories will be added later. Here is the build for the library we saw earlier.

The build script adds a Maven repository to publish the binaries to:

plugins {
    id 'cpp-library'
    id 'maven-publish'
}

group = 'org.gradle.cpp-samples'
version = '1.5'

publishing {
    repositories {
        maven {
            // In this sample, we used a local maven repository, 
            // but Maven Central or Artifactory server can be used.
            url 'http://localhost:8000/'
        }
    }
}

Here is the result of running ./gradlew publish:

./gradlew publish

Composite builds

Composite builds also work the same as in Java projects. This sample using a composite build combines builds so they can be worked on together:

rootProject.name = 'app'

includeBuild 'list-library'
includeBuild 'utilities-library'

Here’s the result in Xcode. The application and the libraries it uses are available to edit, build and test together:

Xcode integration

Finally, it’s easy to set up a CI build for our application. We’ve added configuration for Travis CI.

Native Samples Travis CI

Your Feedback Wanted

These plugins are a work in progress and have some limitations. For example, binary publishing doesn’t understand operating system or architecture yet. We’ll continue to improve these plugins, make them stable, and eventually will deprecate the software model plugins.

Please try these plugins out and let us know what you think. The easiest way to get started is to clone the native samples repository and follow the instructions. Our samples use a Gradle nightly build, so you’ll see the latest and greatest developments there. Some changes are already showing up in the 4.5 release candidates.

We’d love to hear what you think works well, what’s confusing, and what is missing that would block you from using Gradle to build C++ software. You can also leave feedback on the Gradle forums or raise issues on the Gradle native GitHub repository.

Multi-release JARs - Good or bad idea?

With Java 9 came a new feature of the Java runtime called multi-release jars. For us at Gradle, it’s probably one of the most controversial additions to the platform. TL/DR, we think it’s a wrong answer to a real problem. This post will explain why we think so, but also explain how you can build such jars if you really want to.

Multi-release JARs, aka MRJARs, are a new feature of the Java platform, included in the Java 9 JDK. In this post, we will elaborate on the significant risks of adopting this technology and provide how one can produce and consume multi-release JARs with Gradle, if desired.

In a nutshell, multi-release jars allow you to package several versions of the same class, for consumption by different runtimes. For example, if you run on JDK 8, the Java runtime would use the Java 8 version of the class, but if you run on Java 9, it would use the Java 9 specific implementation. Similarly, if a version is built for the upcoming Java 10 release, then the runtime would use it instead of the Java 9 and default (Java 8) versions.

Use Cases for multi-release JARs

  • Optimized runtime. This answers a problem that lots of developers have faced in real world: when you develop an application, you don’t know in what runtime it’s going to be executed. However, you know that for some runtimes you can implement optimized versions of the same class. For example, imagine that you want to display the Java version number that your application is currently executed on. For Java 9, you can use the Runtime.getVersion method. However, this is a new method only available if you run on Java 9+. If you target more runtimes, say, Java 8, then you need to parse the java.version property. So you end up with 2 different implementations of the same feature.

  • Conflicting APIs : Another common use case is to handle conflicting APIs. For example, you need to support 2 different runtimes, but one has deprecated APIs. There are currently 2 widely used solutions to this problem:

    • The first one is to use reflection. One could for example define a VersionProvider interface, then 2 concrete classes Java8VersionProvider and Java9VersionProvider, the right one being loaded at runtime (note that funnily to be able to choose between the 2 you might have to parse the version number!). A variant of this solution is just to have a single class, but different methods, accessing and calling different methods by reflection.
    • A more advanced solution would be to use method handles for this, if it is technically applicable. Most likely, you would see reflection as both painful to implement and slow, and you would most likely be right.

Well known alternatives to multi-release JARs

The second solution, easier to maintain and reason about, is to provide 2 different jars, aimed at 2 different runtimes. Basically, you would write the 2 implementations for the same class in your IDE, and it’s the build tool responsibility to compile, test and package them correctly into 2 different artifacts. This is the approach that some tools like Guava or Spock for example have been using for years. But it’s also what some languages like Scala need. Because there are so many variants of the compiler and the runtime, that binary compatibility is almost impossible to maintain.

But there are more reasons to prefer separate jars:

  • a jar is just packaging
    • it’s an artifact of the build that happens to package classes, but not only: resources would typically be bundled into a jar too. Packaging, as well as processing resources, has a cost. What we’re trying to do with Gradle is to improve the performance of builds, and reduce the amount of time a developer has to wait to see results of compilation, tests, and in general the whole build process. By forcing to build a jar too early in the process, you create a redundant point of synchronization. For example, to compile downstream consumers, the only thing the consumer needs is the .class files. It doesn’t need the jar, nor does it need the resources in the jar. Similarly, to execute tests, all Gradle needs is the class files, plus the resources. There’s no need to actually create the jar to execute tests. The jar is only needed once an external consumer will require it (in short, publishing). But as soon as you consider the artifact as a requirement, then you’re blocking some tasks from running concurrently, and you’re slowing down the whole build. While for small projects this might not be an issue, for enterprise scale builds, this is a major blocker.
  • more importantly, as an artifact, a jar shouldn’t carry information about dependencies.
    • There’s absolutely no reason why the runtime dependencies of your Java 9 specific class would be the same as the Java 8 one. In our very simplistic example they would, but for larger project this is wrong modeling: typically, users would import a backport library of a Java 9 feature, and use it to implement the Java 8 version of the class. However, if you package both versions in the same jar, then you’re mixing things that don’t have the same dependency trees into a single artifact. It means, typically, that if you happen to run on Java 9, you’re bringing a dependency that you would never ever use. Worse, it can (and will) pollute your classpath, possibly creating conflicts for consumers.

Eventually, for a single project, you can produce different jars, aimed at different usages:

  • one for the API
  • one for Java 8 runtime
  • one for Java 9
  • one with native bindings

Abuse of the classifier leads to inconsistent things being referred to using the same mechanism. Typically, the sources or javadocs jars are posted as classifiers, but don’t really have any dependency.

  • we don’t want to create a mismatch depending on how you get your classes. In other words, using multi-release jars have the side effect that consuming from a jar and consuming from a class directory are no longer equivalent. There’s a semantic difference between the 2, which is terrible!
  • depending on the tool that is going to create the jar, you may produce inconsistent jars! The only tool so far that guarantees that if you package the same class twice in a jar, both of them have the same public API, is the jar tool itself. Which, for lots of good reasons is not necessarily used by build tools, or even users. A jar, in practice, is just an envelope. It’s a zip in disguise. So depending on how you build it, you would have different behavior, or you could just produce wrong artifacts and never notice.

Better ways to manage separate JARs

The main reason developers don’t use separate jars is that they are impractical to produce and consume. The fault is on build tools, which, until Gradle, have dramatically failed at handling this. In particular, developers who have used this solution had no other choice than relying on the very poor classifier feature of Maven to publish additional artifacts. However, classifiers are very bad at modelling the complexity of the situation. They are used for a variety of different aspects, from publishing sources, documentation, javadocs, to publishing variants of a library (guava-jdk5, guava-jdk7, …) or different usages (api, fat jar, …). And in practice, there’s no way to indicate that the dependency tree of a classifier is not the one of the project itself. In other words, the POM is broken, as it represents both how the component is built, and what artifacts it produces. Say that you want to produce 2 different jars: one classic jar, and one fat jar that bundles all dependencies. In practice Maven would consider that the 2 artifacts have equal dependency trees, even if it’s plain wrong! It’s super obvious in this case, but the situation is exactly the same with multi-release jars!

The solution is to handle variants properly. That’s what we call variant-aware dependency management, and Gradle knows how to do it. So far this feature has only been enabled for Android development, but we’re currently developing it for Java and native too!

Variant-aware dependency management is the idea that modules and artifacts are different beasts. With the same source files, you can target different runtimes, with different requirements. For the native world it has been obvious for years: we compile for i386 and amd64, and there’s no way you can mix the dependencies of a i386 library with the ones of arm64! Transposed to the Java world, it means that if you target Java 8, you should produce a java 8 version of your jar, with classes targeting the Java 8 class format. This artifact would have metadata attached so that Java 8 consumers know what dependencies to use. And if you target Java 9, then the Java 9 dependencies would be selected. It’s as simple as that (well, in practice it’s not because the runtime is only one dimension of the variants, and you can combine multiple).

Of course, nobody has ever done this before because it’s complex to handle: Maven would for sure never let you do such complex thing. But Gradle makes it possible. And the good news is that we’re also developing a new metadata format that will let consumers know which variant they should use. Simply said, the build tool needs to deal with the complexity of compiling, testing, packaging, but also consuming such modules. For example, say that you want to support Java 8 and Java 9 as runtimes. Then, ideally, you need to compile 2 versions of your library. Which means 2 different compilers (to avoid using the Java 9 APIs while targeting Java 8), 2 different class directories, and 2 different jars in the end. But also, you will probably want to test the 2 different runtimes. Or, you might want to build the 2 jars, but still want to test what the behavior of the Java 8 version is when executed on a Java 9 runtime (because, it may happen in production!).

We’ve made significant progress towards modelling this, and even if we’re not ready yet, it explains why we are not so keen on using multi-release jars: while they fix a problem, they are fixing it the wrong way, and Maven Central is going to be bloated with libraries that do not declare their dependencies properly!

How to create a multi-release JAR with Gradle

It’s not ready so what should I do? The good news is that the path to generate correct artifacts is the same. Until this new feature is ready for the Java ecosystem, you have 2 different options:

  • do it the old way, using reflection or distinct jars.
  • use multi-release jars, (being aware that you may take the wrong decision here, even if there are good use cases)

Whatever solution you choose, separate jars route or the multi-release jars, both use the same setup. Multi-release jars are only the wrong (default) packaging: they should be an option, not a goal. Technically, the source layout is the same for both separate jars and external jars. This repository explains how you can create a multi-release jar with Gradle, but here is how it works in a nutshell.

First, you must understand that we as developers often have a very bad habit: we tend to run Gradle (or Maven) using the same Java version that the artifacts you want to produce. Sometimes it’s even worse, when we use a more recent version to run Gradle, and compile using an older API level. But there’s no good reason to do this. Gradle supports cross-compilation. It allows you to explain where a JDK is found, and fork compilation to use this specific JDK to compile a component. A reasonable way to setup different JDKs is to configure the path to the JDKs through environment variables, which is what we are doing in this file. Then we only need to configure Gradle to use the appropriate JDK based on the source/target compatibility. It’s worth noting that starting from JDK 9, it’s no longer necessary to provide older JDKs to perform cross-compilation. A new option, -release, does exactly that. Gradle will recognize this option and configure the compiler accordingly.

The second key concept is the notion of source set. A source set represents a set of sources that are going to be compiled together. A jar is built from the result of the compilation of one or more source sets. For each source set, Gradle will automatically create a corresponding compile task that you can configure. This means that if we have sources for Java 8 and sources for Java 9, then they should live in separate source sets. That’s what we do by creating a Java 9 specific source set that will contain the specialized version of our class. This matches reality, and doesn’t force you to create a separate project like Maven would require. But more importantly, it allows us to precisely configure how this source set is going to compile.

Part of the challenge of multiple versions of a single class is that it’s very rare that such a class is totally independent from the rest of the code (it has dependencies onto classes that are found in the main source set). For example, its API would use classes that don’t need to have Java 9 specific sources. Yet, you don’t want to recompile all those common classes, nor do you want to package Java 9 versions of all those classes. They are really shared and should stay separate. This is what this line is about: it will configure the dependency between the Java 9 source set and the main source set, making sure that when we compile the Java 9 specific version, all common classes are on compile classpath.

The next step is really simple: we need to explain to Gradle that the main source set is going to target Java 8 language level, and that the Java 9 source set is going to target Java 9 language level.

All the steps we have described so far allow you both approaches described previously: publishing separate jars, or publishing a multi-release jar. Since this is the topic of this blog post, let’s see how we can now tell Gradle that we will only generate a multi-release jar:


jar {
  into('META-INF/versions/9') {
     from sourceSets.java9.output
  }

  manifest.attributes(
     'Multi-Release': 'true'
  )
}

This configuration block does 2 separate things: bundle the Java 9 specific classes into the META-INF/versions/9 directory, which is expected in a MRJar add the multi-release flag to the manifest.

And that’s it, you’ve built your first MRJar! However we’re not done yet, unfortunately. If you are familiar with Gradle, you would know that if you apply the application plugin you can also run the application directly with a run task. However, because as usual Gradle tries to perform the minimal amount of work to do what you need, the run task is wired to use the class directories as well as the processed resources directories. And for multi-release jars, that’s a problem, because you need the jar now! So instead of relying on this plugin we have no choice but creating our own task, which is another reason why not use multi-release jars.

Last but not least, we said we probably also want to test the 2 versions of our class. For this, you have no choice but using forked VMs, because there’s no equivalent to the -release flag for the Java runtime. The idea, here, is that you write a single unit test, but it’s going to be executed twice: once with Java 8, the other with Java 9 runtime. This is the only way to make sure that your substituted classes work properly. By default, Gradle only creates a single test task, and it will also use the class directories instead of the jar. So we need to do two things: create a Java 9 specific test task configure both test tasks so that they use the jar and specific Java runtimes

This can be achieved simply by doing this:


test {
   dependsOn jar
   def jdkHome = System.getenv("JAVA_8")
   classpath = files(jar.archivePath, classpath) - sourceSets.main.output
   executable = file("$jdkHome/bin/java")
   doFirst {
       println "$name runs test using JDK 8"
   }
}

task testJava9(type: Test) {
   dependsOn jar
   def jdkHome = System.getenv("JAVA_9")
   classpath = files(jar.archivePath, classpath) - sourceSets.main.output
   executable = file("$jdkHome/bin/java")
   doFirst {
       println classpath.asPath
       println "$name runs test using JDK 9"
   }

}

check.dependsOn(testJava9)

Now if you run the check task, Gradle will compile each source set using the proper JDK, build a multi-release jar, then run unit tests using this jar on both JDKs. Future versions of Gradle will help you do this in a more declarative way.

Conclusion

In conclusion, we’ve seen that multi-release jars address a real problem that a significant number of library designers face. However, we think this is the wrong solution to the problem. Correct modeling of dependencies, as well as coupling of artifacts and variants, and not forgetting performance (ability to execute more tasks concurrently) make them a poor man’s solution to a problem we are fixing the right way, using variant-aware dependency management. However, we reckon that for simple use cases, knowing that variant-aware dependency management for Java is not yet completed, it may be convenient to produce such a jar. In that case, and only in that case, this post helped you understand how you can do this, and how the philosophy of Gradle differs from Maven in this case (source set vs project).

Finally, we don’t deny that there are cases where multi-release jars do make sense: applications for which the runtime is not known in advance, for example, but those are exceptional and should be considered as such. Most issues are for library designers: we’ve covered common problems they face, and how multi-release JARs attempt to solve some of them. Modeling dependencies correctly as variants improves performance (via finer-grained parallelism) and reduces maintenance overhead (avoiding accidental complexity) over the use of multi-release JARs. Your situation may dictate that MRJARs be used; rest assured that it’s still supported by Gradle. See this mrjar-gradle example project to try this today.

State of Gradle Java 9 Support

This post provides an overview of Gradle’s Java 9 support, touching on runtime, cross-compilation, MRJARs, and Jigsaw modules support. We’ve fielded lots of questions since Java 9 was released last month, and decided it best to answer here.

What Gradle supports as of version 4.2.1

As of Gradle 4.2.1, building and running Java applications using major distributions of JDK 9 such as Oracle JDK9, OpenJDK9 and Azul JDK9 is fully supported. Further, cross-compilation (built by JDK9 but runs on JDK8) is supported.

Some builds will break when upgrading to Java 9, regardless of build tool used. The Java team have made good and necessary changes to the JDK to facilitate better software architecture and security, but this has meant removing access to some APIs. Even if your project is ready, some tools and Gradle plugins have not yet been updated to work with Java 9.

There is no convenience methods for consuming and assembling Multi-Release JARs, but you can take a look at this MRJAR-gradle example if you desire to use them.

Java Modules aka Jigsaw Support

If you’re not yet familiar with the Java 9 Platform Module System, also known as Project Jigsaw, you should read Project Jigsaw: Module System Quick-Start Guide. The motivation and terminology are well explained in The State of the Module System.

A module is defined as “a named, self-describing collection of code and data” whereby packages are treated as code boundaries, and are explicitly exported and required. Non-exported packages are not visible to module consumers, and further 2 modules cannot export the same packages, nor can they have the same internal packages. This means that packages cannot be “split” or duplicated between multiple modules, or compilation will fail.

Here is a guide that shows how to use Java modules with Gradle today. It walks you through the steps necessary to tell Gradle to use the modulepath and not classpath when compiling Java sources and patch modules for testing purposes.

A bottom-up approach (convert libraries with no dependencies first) is recommended if you wish to incrementally convert to Java 9 modules. After all, modules are consumable as regular JARs. Be mindful of automatic modules when “legacy” JARs are added to the modulepath.

Achieving encapsulation with the Java Library Plugin

One of the 2 major goals of the Java 9 module system is to provide better software architecture through strong encapsulation. Gradle 3.4 introduced the Java Library Plugin that enforces strong encapsulation for libraries by separating api dependencies (those meant to be exposed to consumers) from implementation dependencies whose internals are not leaked to consumers.

This, of course, does not eliminate use of Java classpaths as is stated as another goal of Java modules. You can learn about the motivation and usage of the Java Library Plugin in this post. It’s worth noting that the Java Library Plugin is useful for projects using Java 7 and above — you do not need to migrate to Java 9 to have some stronger encapsulation.

Here’s what this means in practice, given this example library:

apply plugin: 'java-library'

name = 'mylibrary'
group = 'com.mycompany'

dependencies {
   api project(':model')
   implementation 'com.google.guava:guava:18.0'
}

Let’s presume we have an application that uses mylibrary.

public class MyApplication {
    
    public static void main(String... args) {
        // This does not compile using 'java-library' plugin
        Set<String> strings = com.google.common.collect.ImmutableSet.of("Hello", "Goodbye");
        
        // This compiles and runs
        Foo foo = com.mycompany.model.internal.Foo();
        
        // This also compiles and runs
        Class clazz = MyApplication.class.getClassLoader().loadClass("com.mycompany.model.internal.Foo");
        Foo foo = (Foo) clazz.getConstructor().newInstance();
    }
}

You can see that you get some of the benefits by adopting the Gradle’s Java Library plugin. If you are migrate to Java modules, you can use this rough mapping:

  • implementation dependency => requires module declaration
  • api dependency => requires transitive module declaration
  • runtimeOnly dependency => requires static module declaration

Next Steps

Stay tuned for updates on first-class Java modules support in Gradle.

You can use the Building Java 9 Modules guide to learn how to use Java modules with Gradle today.

Join Gradle at KotlinConf 2017

Kotlin is the #1 rising Gradle “related topic” according to Google Trends. We want to improve user experience developing Kotlin applications with Gradle, and that requires great guides — but we need your help! Tell us which unwritten Kotlin Gradle guide you think would be most useful, and you’ll have a chance to get a free ticket to KotlinConf! See instructions below.

KotlinConf 2-3 Nov 2017, San Francisco, Pier 27

We are proud to be a silver partner for KotlinConf, which is just 3 weeks away! Hans Dockter will be presenting Building Kotlin Applications at Scale and some of the Gradle Kotlin DSL team will be on-site to talk Kotlin with you.

About the KotlinConf 2017 ticket raffle

We are giving away 2 standard tickets to the conference. You’ll be responsible for getting to the conference, so please don’t accept a ticket if you cannot attend.

All you need to do is file an issue on the gradle/guides GitHub repo with a unique and useful suggestion for a Gradle guide around building Kotlin with Gradle. For inspiration, you can check out the proposal for the Building Kotlin JVM Libraries guide, which was published recently.

We’d be happy to get lots of good ideas from you, but we’ll enter your name once. Winners will be chosen at random on Monday, October 16th. We’ll contact you if you’ve been selected, and you’ll have 3 days to confirm you can attend.

UPDATE: Thank you everyone! The contest is now closed.

If you’re interested in contributing to the Kotlin/Gradle community, just adding 👍 s on ideas you think would be good is helpful, but even more so submitting a PR with a sample or typo fix is most welcome. Remember the goal: make Kotlin development awesome for everyone!

Good luck! We hope to see you at KotlinConf!

State and future of the Gradle Software Model

We’ve received many inquiries about the status and direction of Gradle’s Software Model, especially from users building native libraries and applications.

In this blog post, we will explain the current state and future of the Software Model, and in particular how it relates to native development with Gradle. A lot of exciting improvements are planned for the remainder of 2017; see the roadmap below.

Situation with the Software Model

In a nutshell, the Software Model is a very declarative way to describe how a piece of software is built and the other components it needs as dependencies in the process. It also provides a new, rule-based engine for configuring a Gradle build. When we started to implement the Software Model we set ourselves the following goals:

  • Improve configuration and execution time performance.
  • Make customizations of builds with complex tool chains easier.
  • Provide a richer, more standardized way to model different software ecosystems

As we were developing the Software Model, the Gradle engineering team constantly tried to dogfood the concepts into existing software ecosystems. The Gradle plugins for building native applications is currently fully-based on the Software Model. Similarly, experimental Software Model-based plugins were developed for ecosystems like Android and Java.

Gradle adoption in the native ecosystem is picking up, and so is our investment. Since it’s inception, Gradle’s native support has proved itself as a welcome alternative for builds using Make. With its declarative and expressive model, support for different tool chains and platforms as well as features like parallel compilations, it offers a revolutionary way for building native libraries and applications.

It took us longer than expected to evolve the new configuration and Software Model and make it as powerful as the current Gradle model for Java and Android. Meanwhile, Gradle adoption skyrocketed, there are many complex builds out there using the current model and a vibrant ecosystem of 1500+ community plugins as well. We underestimated the complexity for those builds and plugins to migrate to the new model and saw understandable resistance with many of our partners to undergo this migration.

In hindsight, the scope of the new software and configuration model was too big. That is why at the Gradle Summit 2016, Hans Dockter announced that we were backporting many of its features to the current model. One year later, most of the features for the Java and Android ecosystem have been backported. This includes variant-aware dependency resolution and separation of API and implementation for Java components. Those features were game changers in terms of work avoidance and performance. Furthermore, we found other ways to drastically improve Gradle configuration performance, with more to come. There is no longer any need for a drastic, incompatible change in how Gradle builds are configured.

A way forward

You may therefore be wondering what is happening to the Software Model. We’re in the process of porting the configuration DSL of the native support to the current model. So the declarative nature and strong modelling language will be the same. The rule engine that was part of the Software Model will be deprecated. Everything under the model block will be ported as extensions to the current model. Native users will no longer have a separate extension model compared to the rest of the Gradle community, and they will be able to make use of the new variant aware dependency management.

What does the roadmap look like? Here are the areas of focus until the end of the year:

  • Current model support for native. New sets of plugins based on the current model are in development and are improved with every nightly release. They still need more work to achieve feature parity and stability, but already provide a lot of functionality. Try them out and give us feedback.
  • Parallel-by-default for compile and link tasks. Performance improvements are planned for the native ecosystem by enabling parallelism-by-default to compile and link tasks. This will have a positive impact on everyone building native with Gradle.
  • Transitive dependency resolution. We are porting this powerful feature from our JVM ecosystem to help native developers declare rich dependencies between native projects.
  • New native plugins on current model. Our plan is to have plugins that have most of the functionality of the Software Model plugins and will also have substantial new features like build caching and external source dependencies for native.
  • Improved tool chain support. We are looking at ironing out some of the wrinkles with tool chain declaration which is particularly important for embedded development.

For the most complete and up-to-date progress, we recommend having a look at the gradle-native project, the home for the native ecosystem feature planning.

User migration from Software Model plugins to the new ones will be pretty seamless. All core native tasks will be reused and the tool chain concept will be ported to the current model. We expect that a lot of your build logic can be simply reused. We will support the Software Model-based plugins for an extended period of time to ensure everyone has a successful migration.

If you are currently using, or are planning to use, Gradle to build native projects, by all means keep doing so. Gradle’s native support has proven time and time again to be more performant, flexible, and easier to use than currently available tools.

Exciting things afoot

Today, we’re working on IDE integration and XCTest support, with out-of-the-box HTML report generation and full build scan support. Tool chain definition will also be improved to allow easier integration with alternative tool chain families; this is especially exciting for users invested in the embedded world.

For multi-repository developers, you will be happy to learn that composite builds will work for all native projects.

C++ Build Scan

The new plugins will integrate with the Kotlin DSL which gives Gradle users proper IDE support including auto-completion and refactoring.

We will first implement the complete workflow for native development in the current model without any customization - i.e. no platforms, build types or tool chains configuration. By workflow we mean everything related to building binaries, as well as testing, packaging, deploying and integration with your favorite IDE. At first, the workflow will work for the most common cases. In the subsequent releases, we will proceed by adding customization incrementally to the whole workflow.

Community involvement

Our native community is one of the most active and engaged on the forum, and we want to encourage and grow that engagement even more. Please keep helping each other find the answers you are seeking in the forum, but also engage with us by, trying the various native sample projects, subscribing to the gradle-native project and filing issues, voting on issues that are most important to you, and even consider submitting pull requests if you’re excited to roll up your sleeves and pitch in.

The best ways to stay passively up-to-date on native platform support are to subscribe to the monthly newsletter or more frequent announcements on Twitter.

We look forward to working with you to develop the best native build tool!