Gradle Release Notes

Version 2.10

The Gradle team is pleased to bring you Gradle 2.10. This release delivers significant performance improvements for large Native software builds, together with a number of bug fixes and smaller improvements.

Major progress has been made on the Rule-based configuration and Software Model infrastructure. We are excited about the opportunities this work will bring, and the progress that has been made. With full support for dependency management, an improved DSL and better support for developing plugins, the Gradle 2.10 release brings this revolutionary way of building software closer to the mainstream.

Table Of Contents

New and noteworthy

Here are the new features introduced in this Gradle release.

Performance improvements for native compilation

Gradle needs to know all input properties, input files and output files of a task to perform incremental build checks. When no input or output files have changed, Gradle can skip executing a task.

For native compilation tasks, Gradle used to consider the contents of all include directories as inputs to the task. This had performance problems when there were many include directories or when the project root directory was used as an include directory. To speed up up-to-date checks, Gradle now treats the include path as an input property and only considers those header files that are actually referenced by a source file as a inputs. Our performance benchmarks have demonstrated this to have a large positive impact on incremental builds for very large native projects.

For C++ and related languages, Gradle uses source file parsing to determine the set of included headers for a source file. If Gradle cannot determine the set of included headers, it will fall back to the old mechanism of including all files in all include directories as inputs. This means that using a macro to define an #include file is not recommended, as it can prevent Gradle from taking advantage of this optimization.

TestKit dependency is decoupled from Gradle core classes and dependencies

Declaring a dependency on gradleTestKit() includes all of the Gradle TestKit runtime classpath. In previous versions of Gradle this TestKit dependency also included transitive dependencies on some Gradle core classes and external libraries. This could lead to version conflicts between the TestKit runtime classpath and user-defined dependency declarations.

Gradle TestKit now avoids polluting the runtime classpath by using a fat and shaded JAR file for any required Gradle core classes and external dependencies.

New task for visualising a buildScript dependencies

The new buildEnvironment task can be used to obtain better insight into the buildscript dependencies of a Gradle project. This task is implicitly available for all projects, much like the existing dependencies task.

Use the buildEnvironment task to gain an understanding of how the declared dependencies of project's build script are actually resolved, and to debug issues with plugin loading and classloading.

The feature was kindly contributed by Ethan Hall.

HTML report for Checkstyle

The Checkstyle task now produces a HTML report on failure in addition to the existing XML report. This more human friendly HTML report is generated by default, and when available will be advertised in preference to the XML report.

This feature was kindly contributed by Sebastian Schuberth.

Tooling API exposes Java source language level for Eclipse projects

The Tooling API now exposes the Java source language level that should be used for an Eclipse project via the javaSourceSettings property.

This allows Buildship and other IDE integrations to automatically configure the source language level in Eclipse, so that users no longer need to configure this themselves.

Dependency management for the Java Software Model incubating feature

This release adds some important new features for dependency management for Java libraries in the incubating Java software model.

Component level dependencies for Java libraries

In most cases it is more natural and convenient to define dependencies on a component rather than on each of its source sets and it is now possible to do so when defining a Java library in the software model.

Example:

apply plugin: "jvm-component"

model {
  components {
    main(JvmLibrarySpec) {
      dependencies {
        library "core"
      }
    }

    core(JvmLibrarySpec) {
    }
  }
}

Dependencies declared this way will apply to all source sets for the component.

External dependencies for Java libraries

It is now possible to declare dependencies on external modules for a Java library in the software model:

repositories {
    jcenter()
}

model {
    components {
        main(JvmLibrarySpec) {
            dependencies {
                // external module dependency can start with either group or module
                group 'com.acme' module 'artifact' version '1.0'
                module 'artifact' group 'com.acme' version '1.0'

                // shorthand module notation also works, version number is optional
                module 'com.acme:artifact:1.42'
            }
        }
    }
}

Module dependencies declared this way will be resolved against the configured repositories as usual. External dependencies can be declared for a Java library, Java source set or Java library API specification.

DSL improvements for the Software Model incubating feature

This release includes a number of improvements to the model DSL, which is the DSL you use to define and configure the software model from a build script.

Nested rules in the DSL

The ModelMap creation and configuration DSL syntax now defines nested rules, each with its own inputs. For example, this means that an element of a ModelMap can now be configured using the configuration of a sibling as input:

model {
    components {
        mylib { ... }
        test {
            // Use `mylib` as input. When this code runs, it has been fully configured and will not change any further
            // Previously, this would have been treated as an input of the `components` rule, resulting in a dependency cycle
            targetPlatform = $.components.mylib.targetPlatform
        }
    }
}

And because tasks is a ModelMap, this means that a task can be configured using another task as input using the same syntax:

model {
    tasks {
        jar { ... }
        dist(Zip) {
            // Use the `jar` task as input. It has been fully configured and will not change any further
            def jar = $.tasks.jar
            from jar.output
            into someDir
        }
    }
}

This is also available for the various methods of ModelMap, such as all or withType:

model {
    components {
        all {
            // Adds a rule for each component
            ...
        }
        withType(JvmLibrarySpec) {
            // Adds a rule for each JvmLibrarySpec component
            ...
        }
    }
}

Configure the properties of a @Managed type

The properties of a @Managed type can now be configured using nested configure methods:

model {
    components {
        mylib {
            sources {
                // Adds a rule to configure `mylib.sources`
                ...
            }
            binaries {
                // Adds a rule to configure `mylib.binaries`
                ...
            }
        }
    }
}

This is automatically added for any property whose type is @Managed, or a ModelMap<T> or ModelSet<T>. Note that for this release, these nested closures do not define a nested rule, and the closure is executed as soon as it is encountered in the containing closure. This will be improved in the next Gradle release.

See the model DSL user guide section for more details and examples.

Convenient configuration of scalar properties

The model DSL now supports automatic conversions between various scalar types, making it very easy to use one type for another. In particular, you can use a String wherever a scalar type is expected. For example:

enum FailType {
   FAIL_BUILD,
   WARNING
}

@Managed
interface CoverageConfiguration {
   double getMinClassCoverage()
   void setMinClassCoverage(double minCoverage)

   double getMinPackageCoverage()
   void setMinPackageCoverage(double minCoverage)

   FailType getFailType()
   void setFailType(FailType failType)

   File getReportTemplateDir()
   void setReportTemplateDir(File templateDir)
}

model {
    coverage {
       minClassCoverage = '0.7' // can use a `String` where a `double` was expected
       minPackageCoverage = 1L // can use a `long` where a `double` was expected
       failType = 'WARNING' // can use a `String` instead of an `Enum`
       templateReportDir = 'src/templates/coverage' // creates a `File` which path is relative to the current project directory
    }
}

Better support for developing plugins with the Software Model

This release includes some major capabilities to allow plugin authors to extend the software model

Support for LanguageSourceSet model elements

This release allows source sets (subtypes of LanguageSourceSet) to be added to arbitrary locations in the managed model. A LanguageSourceSet can be attached to any @Managed type as a property, or used for the elements of a ModelSet or ModelMap, or as a top level model element.

Managed binary and component types

The BinarySpec and ComponentSpec types can now be extended via @Managed subtypes, allowing for declaration of @Managed components and binaries without having to provide a default implementation. LibrarySpec and ApplicationSpec can also be extended in this manner.

Example:

@Managed
interface SampleLibrarySpec extends LibrarySpec {
    String getPublicData()
    void setPublicData(String publicData)
}

class RegisterComponentRules extends RuleSource {
    @ComponentType
    void register(ComponentTypeBuilder<SampleLibrarySpec> builder) {
    }
}
apply plugin: RegisterComponentRules

model {
    components {
        sampleLib(SampleLibrarySpec) {
            publicData = "public"
        }
    }
}

Managed internal views for binaries and components

Now it is possible to attach a @Managed internal view to any BinarySpec or ComponentSpec type. This allows plugin authors to attach extra properties to already registered binary and component types like JarBinarySpec.

Example:

@Managed
interface MyJarBinarySpecInternal extends JarBinarySpec {
    String getInternal()
    void setInternal(String internal)
}

class CustomPlugin extends RuleSource {
    @BinaryType
    public void register(BinaryTypeBuilder<JarBinarySpec> builder) {
        builder.internalView(MyJarBinarySpecInternal)
    }

    @Mutate
    void mutateInternal(ModelMap<MyJarBinarySpecInternal> binaries) {
        // ...
    }
}

apply plugin: "jvm-component"

model {
    components {
        myComponent(JvmLibrarySpec) {
            binaries.withType(MyJarBinarySpecInternal) { binary ->
                binary.internal = "..."
            }
        }
    }
}

Note: @Managed internal views registered on unmanaged types (like JarBinarySpec) are not yet visible in the top-level binaries container, and thus it's impossible to do things like:

// This won't work:
model {
    binaries.withType(MyJarBinarySpecInternal) {
        // ...
    }
}

This feature is available for subtypes of BinarySpec and ComponentSpec.

Default implementation for unmanaged base binary and component types

It is now possible to declare a default implementation for a base component or a binary type, and extend it via further managed subtypes.

interface MyBaseBinarySpec extends BinarySpec {}

class MyBaseBinarySpecImpl extends BaseBinarySpec implements MyBaseBinarySpec {}

class BasePlugin extends RuleSource {
    @ComponentType
    public void registerMyBaseBinarySpec(ComponentTypeBuilder<MyBaseBinarySpec> builder) {
        builder.defaultImplementation(MyBaseBinarySpecImpl.class);
    }
}

@Managed
interface MyCustomBinarySpec extends BaseBinarySpec {
    // Add some further managed properties
}

class CustomPlugin extends RuleSource {
    @ComponentType
    public void registerMyCustomBinarySpec(ComponentTypeBuilder<MyCustomBinarySpec> builder) {
        // No default implementation required
    }
}

This functionality is available for unmanaged types extending ComponentSpec and BinarySpec.

Internal views for unmanaged binary and component types

The goal of the new internal views feature is for plugin authors to be able to draw a clear line between public and internal APIs of their plugins regarding model elements. By declaring some functionality in internal views (as opposed to exposing it on a public type), the plugin author can let users know that the given functionality is intended for the plugin's internal bookkeeping, and should not be considered part of the public API of the plugin.

Internal views must be interfaces, but they don't need to extend the public type they are registered for.

Example: A plugin could introduce a new binary type like this:

/**
 * Documented public type exposed by the plugin
 */
interface MyBinarySpec extends BinarySpec {
    // Functionality exposed to the public
}

// Undocumented internal type used by the plugin itself only
interface MyBinarySpecInternal extends MyBinarySpec {
    String getInternalData();
    void setInternalData(String internalData);
}

class MyBinarySpecImpl implements MyBinarySpecInternal {
    private String internalData;
    String getInternalData() { return internalData; }
    void setInternalData(String internalData) { this.internalData = internalData; }
}

class MyBinarySpecPlugin extends RuleSource {
    @BinaryType
    public void registerMyBinarySpec(BinaryTypeBuilder<MyBinarySpec> builder) {
        builder.defaultImplementation(MyBinarySpecImpl.class);
        builder.internalView(MyBinarySpecInternal.class);
    }
}

With this setup the plugin can expose MyBinarySpec to the user as the public API, while it can attach some additional information to each of those binaries internally.

Internal views registered for an unmanaged public type must be unmanaged themselves, and the default implementation of the public type must implement the internal view (as MyBinarySpecImpl implements MyBinarySpecInternal in the example above).

It is also possible to attach internal views to @Managed types as well:

@Managed
interface MyManagedBinarySpec extends MyBinarySpec {}

@Managed
interface MyManagedBinarySpecInternal extends MyManagedBinarySpec {}

class MyManagedBinarySpecPlugin extends RuleSource {
    @BinaryType
    public void registerMyManagedBinarySpec(BinaryTypeBuilder<MyManagedBinarySpec> builder) {
        builder.internalView(MyManagedBinarySpecInternal.class);
    }
}

Internal views registered for a @Managed public type must themselves be @Managed.

This functionality is available for types extending ComponentSpec and BinarySpec.

Binary names are scoped to the owning component

Binary names are now scoped to the component they belong to. This means multiple components can have binaries with a given name. For example, several library components might have a jar binary. This allows binaries to have names that reflect their relationship to the component, rather than their absolute location in the software model.

Fixed issues

Potential breaking changes

Changes to the runtime classpath provided by TestKit

Previous versions of Gradle TestKit included a number of additional dependencies in the test runtime classpath. These dependencies, such as Google Guava, are no longer automatically usable by functional test code. If an external dependency is required by test code, it must be explicitly declared in order to be available on the test classpath.

Native header files as inputs to compile task

Previously, Gradle considered all files in all include path directories as inputs to a compile task. This had performance problems and could cause tasks to be out of date when they should not be. This has been fixed but may cause some subtle differences to the way changes are detected for compilation tasks.

Gradle now considers a compile task to be out-of-date (and require full recompilation) when the include path is changed. In older releases, Gradle would only recompile source files if the resolved set of headers changed. This meant that you could reorder the include path without necessarily causing any files to be recompiled.

Once Gradle has compiled a file once, it will only be recompiled if it or one of the included headers has changed. This means that Gradle will not detect a new header file added to the include path, where that file that should be used in preference to an existing header file. If a compilation task has an include path of [ first/, second/ ] and a source file includes header.h from second/, if a new file called header.h is added to first/, Gradle will not detect the change, and will consider the source file compilation to be up to date.

We plan to address this limitation in a future version of Gradle.

Changes to model rules DSL

Changes to incubating software model

Changes to incubating Java Software Model

Changes to incubating Native Software Model

External contributions

We would like to thank the following community members for making contributions to this release of Gradle.

We love getting contributions from the Gradle community. For information on contributing, please see gradle.org/contribute.

Known issues

Known issues are problems that were discovered post release that are directly related to changes made in this release.