A Gradle build file describes what is needed to build our Java project. We apply one or more plugins, configure the plugins, declare dependencies and create and configure tasks. We have a lot of freedom to organize the build file as Gradle doesn’t really care. So to create maintainable Gradle build files we need to organize our build files and follow some conventions. In this post we focus on organizing the tasks and see if we can find a good way to do this.
It is good to have a single place where all the tasks are created and configured, instead of having all the logic scattered all over the build file. The TaskContainer
is a good place to put all the tasks. To access the TaskContainer
we can use the tasks
property on the Project
object. Within the scope of the tasks
block we can create and configure tasks. Now we have a single place where all the tasks are created and configured. This makes it easier to find the tasks in our project as we have a single place to look for the tasks.
Continue reading →
It is good practice in Gradle to use lazy configuration. This makes builds faster as only configuration values are evaluated when needed. We should try to not let Gradle spend time on evaluating configuration values that will not be used. For example tasks that are not executed could still be configured by Gradle. If we make sure the configuration of these tasks is lazy we can save time.
Gradle gives us a lazy way to get the value of a Java system property. In our build script we can use the providers
property of type ProviderFactory
and the method systemProperty(String)
. This method returns a Provider<String>
instance that can be used to get the value of a system property in a lazy way. The method systemProperty
can also be used with a Provider<String>
argument.
Continue reading →
It is good practice in Gradle to use lazy configuration. This makes builds faster as only configuration values are evaluated when needed. We should try to not let Gradle spend time on evaluating configuration values that will not be used. For example tasks that are not executed could still be configured by Gradle. If we make sure the configuration of these tasks is lazy we can save time.
Gradle gives us a lazy way to get the value of an environment variable. In our build script we can use the providers
property of type ProviderFactory
and the method environmentVariable(String)
. This method returns a Provider<String>
instance that can be used to get the value of an environment variable in a lazy way.
Continue reading →
The command line option --continuous
or the short version -t
enables Gradle’s continous build. For a continuous build Gradle will keep on running and will re-execute the tasks we invoked if the input or of the input of one of the depended tasks has changed. For a project with the java
plugin we can use this option for the test
task. Gradle will run the test
task and after the task has been executed Gradle will wait for any changes in the input of the task. This means if we change our Java test code in src/test/java
and save the source file Gradle will re-execute the test
task and show the output. But also if the input of other tasks changes, that the test
task depends on, the test
is re-executed. So also changes in source files in our src/main/java
directory will trigger a re-execute of the test
task, because the test
task depends on the compileJava
task, and the compileJava
task has the src/main/java
directory as input.
Continue reading →
With the java plugin we can configure a so-called Java toolchain. The toolchain configuration is used to define which Java version needs to be used to compile and test our code in our project. The location of the Java version can be determined by Gradle automatically. Gradle will look at known locations based on the operating system, package managers, IntellIJ IDEA installations and Maven Toolchain configuration.
But we can also define the locations of our Java installations ourselves using the project property org.gradle.java.installations.paths
. We provide the paths to the local Java installations as a comma separated list as value for this property. When we set this property we can also disable the Gradle toolchain detection mechanism, so only the Java installations we have defined ourselves are used. To disable the automatic detection we set the property org.gradle.java.installations.auto-detect
to false
. If we leave the value to the default value true
, then the locations we set via org.gradle.java.installations.paths
are added to the Java installations already found by Gradle.
The property org.gradle.java.installations.paths
is a project property we can set via the command line, but we can also set it in the gradle.properties
file in our GRADLE_USER_HOME
directory. Then the values we define will be used by all Gradle builds on our machine.
Continue reading →
When we apply the Java plugin to our Gradle project we can configure which Java version we want to use for compiling our source code and running our tests using a toolchain configuration. The benefit of having a toolchain configuration is that we can use a different Java version for compiling and running our code than the Java version that is used by Gradle to execute the build. Gradle will look for that Java version on our local computer or download the correct version if it is not available. To search for a local Java installation Gradle will look for operating system specific locations, installations by package managers like SKDMAN! and Jabba, IntelliJ IDEA installations and Maven Toolchain specifications. Maven Toolchain specifications is an XML file describing the location of local Java installation. Each Java installation is described by a version and optional vendor it provides and the location of the installation. Maven uses this information to find the correct Java installation when the maven-toolchain-plugin
is used in a Maven project. But Gradle can also utilize Maven Toolchain specifications to find local Java installations. This can be useful when we have to work on multiple projects where some use Maven and others use Gradle. We can place the Maven Toolchain specification file in our Maven home directory. This is also the default place where Gradle will look, but we can use a project property to override this location.
Continue reading →
A version catalog in Gradle is a central place in our project where we can define dependency references with their version or version rules. We can define a version catalog using an API in our build file, but we can also create an external file where we define our dependencies and version. In our dependencies
section we can refer to the names in the version catalog using a type-safe accessor (if we use Kotlin for writing our build script) with code completion in a supported IDE (IntelliJ IDEA). If we want to share a version catalog between projects we can publish a version catalog to a Maven repository with a groupId
, artifactId
and version
.
Continue reading →
The JVM Test Suite plugin is part of the Java plugin and provides a nice way to configure multiple test types in our build file. Even if we don’t have multiple test types we have a default test type, which is used when we run the Gradle test
task. Using the test suite DSL we can configure the task of type Test
that belongs to a test suite type. The current release of the JVM Test Suite plugin provides a single target for a test suite type with a single Test
task. This will probably change in future releases of the plugin so more task of type Test
can be created and configured.
Continue reading →
The version catalog in Gradle is very useful to have one place in our project to define our project and plugin dependencies with their versions. But we can also use it to define our project version and then refer to that version from the version catalog in our build script file. That way the version catalog is our one place to look for everything related to a version. In the version catalog we have a versions
section and there we can define a key with a version value. The name of the key could be our project or application name for example. We can use type safe accessors generated by Gradle in our build script to refer to that version.
Continue reading →
The JVM Test Suite plugin adds an extension to our build that allows us to configure test tasks. We always can access the default test
task and for example specify the test framework we want to use. Gradle will then automatically add the dependencies of that test framework to the testImplementation
configuration. If we want to add more dependencies to the testImplementation
configuration we don’t have to do that by explicitly mentioning the testImplementation
configuration. Instead we can also use a dependencies
block from within the JvmTestSuite
extension. Any extra dependencies we need to run our tests can be added using the configuration names without a test
prefix. Gradle will automatically add them to the correct test configuration for us so the dependencies are available when we compile and run our tests. This will also work for any other new test type we add to the test suites, e.g. for integration tests.
Continue reading →
Spock is an awesome test framework for testing our Java or Groovy code. Spock itself is written with Groovy and provides a nice syntax to define our tests, or specifications in Spock terminology. To configure support for using Spock in our Gradle build is very easy with the JVM Test Suite plugin (included with the Java plugin). The plugin gives us a nice syntax to define different types of tests, for example integration tests, with their own source set, dependencies and configuration. To use Spock as testing framework we only have to use the method useSpock
within a test configuration. The default version of Spock that is used is 2.1-groovy-3.0 when we use Gradle 7.6. If we want to use another version we can use a String
parameter when we use the useSpock
method with the version we want to use.
Continue reading →
Since Gradle 7.3 we can use the JVM Test Suite plugin to define in a declarative way tests for our build. For example adding integration tests with a new source set and dependencies becomes easier with this plugin. The plugin is automatically part of the Java plugin so we don’t have to define it explicitly in our build. Configuring the default test
task can also be done using the syntax of the JVM TestSuite plugin. We can use several methods from the JvmTestSuite
class in our configuration. For example if we want to use Spock as testing framework we can simply add the method useSpock
in our build script. Or if we want to use the JUnit 5 Jupiter engine we can use useJUnitJupiter
. These methods will add dependencies in the testImplementation
configuration. There is a default version for the dependencies if we use the method without arguments. But we can also define the version as String
argument for these methods. The version catalog for our project is the place to store version for our dependencies, so it would be nice if we could use the version defined in our version catalog as argument for the use<TestFramework>
methods. We can reference the version very simple by using libs.versions.<version-key>
. This will return the value we defined as version in our version catalog.
Continue reading →