It’s probably fair to say that Gradle has somewhat of a steep learning curve. Certainly compared to Maven, another popular build automation tool, it’s move flexible and less opinionated about how things should work.
Whether you’ve just made the jump into Gradle, or have been using it for a while, in this article you’ll discover seven things you wish you’d known about Gradle. With these pointers, you’ll be well on your way to writing more powerful, more concise build scripts for your application, following best practice all the way.
1. The Gradle build script isn’t as complex as it might seem
The build.gradle
can seem at first overwhelming, but it’s helpful to understand that it consists of nothing but these components:
-
buildscript
- here you can define additional dependencies of your build script -
plugins
- include plugins which add extra functionality to your buildscript -
repositories
- define where Gradle should fetch dependencies from -
configurations
- define how groups of dependencies interact with each other -
dependencies
- libraries that your application depends on for building or running -
task definitions - any additional tasks that you want to define, that haven’t already been added by a plugin
-
plugin configuration - most plugins allow you to configure them with a specific configuration block
Additionally, since Gradle is written in Groovy, in theory it can contain any additional Groovy or Java code. This should be kept to a minimum, with any extra functionality being extracted out into a plugin.
2. Gradle relies heavily on a few Groovy syntax features
For a Java developer, reading through a build.gradle
file might make you think that you’re reading through some kind of definition syntax, like JSON or YAML. In reality, build.gradle
files usually use some Groovy language features, which once explained, make things easier to understand:
Brackets are optional when calling methods
As long as the method has at least one parameter, you can leave out the brackets. This means we can have code like this in our build file:
setSourceCompatibility '11'
This in actual fact is calling a method with signature public void setSourceCompatibility(Object value)
.
The getter/setter method will be called automatically
Because Groovy will automatically call the setter (and getter) we can simplify the previous example to the following, which is more common in build.gradle
:
sourceCompatibility '11'
Closures are supported
In Groovy a closure is a block of code that can take arguments, return a value, and be passed around like a variable. This comes in really handy in our build.gradle
, as it allows us to write code like this:
repositories {
mavenCentral()
}
The signature of the method being called here is in fact void repositories(Closure configureClosure)
. So everything between the curly brackets is a closure, and can in fact contain any code we want.
With the knowledge of these three language features, it becomes easier to see that Gradle build scripts appear as a DSL (domain specific language) by making the most of the Groovy syntax.
3. Gradle uses its own cache for dependencies
Gradle can easily resolve dependencies from 2 popular repository locations using this syntax:
repositories {
mavenCentral()
google()
}
This means that Gradle will try to resolve any declared dependencies from these locations, in the order in which they’re defined. Once it’s fetched them, it will put them in its own cache which by default is located at ~/.gradle/caches
.
4. Gradle can publish to Maven’s local cache
Any Maven users will know that Maven uses a cache located at ~/.m2/repository
. By default, Gradle has nothing to do with this.
You may, however, want to use this location for interoperability with any Maven projects you have, or to pull an artifact published by project A into project B before pushing to a central repository.
Add Maven local as a repository with this definition in your build.gradle
:
repositories {
mavenLocal()
}
Now Gradle will try to resolve any dependencies from the local Maven cache. To push to this location you’ll also need to add the Maven Publish plugin:
plugins {
id 'maven-publish'
}
publishing {
publications {
maven(MavenPublication) {
from components.java
}
}
}
And then run ./gradlew publishToMavenLocal
. You can double check your ~/.m2/repository directory
to make sure it’s been published correctly,
5. You should always use the Gradle wrapper to execute tasks in a project
One problem that Maven developers may have encountered is inconsistent Maven versions between developers working on a project. Gradle completely removes this problem by introducing the Gradle wrapper.
The Gradle wrapper is a script that gets committed into your repository, meaning that:
-
whoever builds your project doesn’t need a local version of Gradle installed
-
whoever builds your project will always be using the same version of Gradle as everyone else
To setup the Gradle wrapper, you’ll need a version of Gradle installed on your machine, then just run gradle wrapper
. You can also update the version of Gradle which the wrapper uses using gradle wrapper --gradle-version <version>
. Easy!
It’s then very important to ensure anyone building your project does so using the wrapper:
./gradlew build
or on Windows,
gradle.bat build
6. Gradle has a way to convert a Maven project to Gradle
Time needn’t be a barrier to changing your Maven project to Gradle. Gradle includes a way to convert all your pom.xml
files to build.gradle
files. Any defined repositories, dependencies, and uses of the Maven Publish, Java, and War plugins will be converted. Sadly though, other plugins will have to be converted by hand.
Just run:
gradle init
You’ll now be able to benefit from Gradle’s claimed 2-10x build time speed improvements! It even works on multi-module projects, so check out the docs for full details.
7. Multi-module is easy with Gradle
Setting up a multi-module project with Gradle is straightforward and flexible. All you have to do is add a definition for it to settings.gradle
file in your project root.
e.g. if I want to create a database sub-module in my customer-service project, my settings.gradle
will looks like this:
rootProject.name = 'customer-service'
include 'database'
The surprising thing is that you don’t even need an additional directory or build.gradle
file for this to work (although you can, if you need specific configuration for your sub-module).
To prove this works, I can now add this code to my main build.gradle
:
def cl = { task -> println "I'm $task.project.name" } task('hello').doLast(cl)
Now if we run ./gradlew hello
, we see the following output:
$ ./gradlew hello
> Task :hello
I'm customer-service
> Task :database:hello
I'm database
As you can see it’s printed out the names of the modules, proving the multi-module structure works. By default when you execute a task, Gradle will execute it on all modules that have that task. For more advanced features check out the specific documentation on this topic.
Conclusion
These were the seven things that I wish I’d learnt about Gradle from the beginning, as they would have made the learning process a lot easier. And if you didn’t know some of these, make sure to tell your Mom.
The best place to start for more information on any of these topics, is the Gradle documentation. If you have any other tips along the same lines, please feel free to share below and maybe I’ll include them in a part 2 blog post.
Stop reading Gradle articles like these
This article helps you fix a specific problem, but it doesn't teach you the Gradle fundamentals you need to actually help your team succeed.
Instead, follow a step-by-step process that makes getting started with Gradle easy.