Building with Gradle
Recently I was on a project that needed to chose a build system. We looked at the usual suspects, ant and maven and after having less then wonderful experiences with both I looked for an alternative. That’s where Gradle came in. In this post I’m going to go through getting started on a simple project. In subsequent posts I plan on increasing the complexity and offer solutions to common issues found on a project.
Why choose gradle
I’ll assume since you’re here that you have an interest in gradle and may have already scoured the internet for comparisons of it with other popular build tools (Ant and Maven). Below are some of the reasons why I chose Gradle for my project and not the others.
1. Gradle is built on top of a full fledged programming language (groovy) and has direct access to all of it’s goodies. No reliance on plugins for custom scripting (Maven, Ant plugin), no overhead of creating custom Java classes or plugins, and finally no angle bracket soup (ant scripting).
2. Gradle out of the box has support for dependency management via Ivy and is compatible with all Maven repositories including enterprise ones (Artifactory, Nexus, etc.).
3. Gradle supports multi-module projects without having to reinvent the wheel (ant).
4. Finally, Gradle is extremely flexible allowing for just about any build configuration you could imagine unlike other build tools (I’m looking at you Maven).
First thing first, install gradle. There is good online documentation on how this is done here http://gradle.org/installation.
So lets get started with our first project. For this post lets keep it simple with a single module. We’ll set it up though with the forethought that we may be adding more modules in the future.
I’ll use a flat layout since this is most compatible with the Eclipse IDE. The project layout will look thusly:
Gradle also allows for a hierarchical layout but I’ve found that Eclipse does not do well with projects that include sub-projects.
The modules are intended to be used in the following manner:
- Contains domain classes, services, and any data access objects. Really the essence of the whole system.
- Contains common configuration information for the entire projects (all modules). This is where our main build.gradle script will be found.
Most of our build configuration will be done in example-parent with details specific to modules found in the module directories. The module build files usually contain information on dependencies and artifacts. They’ll also have custom tasks that are specific to that module. But before we start our build file we need to setup one other piece of information, the settings.gradle file.
The settings.gradle file is responsible for letting gradle know what modules are part of the project and where they are. By default Gradle assumes that settings.gradle will be found in the directory in which the build is executed but if it’s not then gradle will look in the parent directory. If still not found in the parent it will continue going up the directory hierarchy until it’s found. There is one exception though, if there happens to be a directory named ‘master’ then it will look in there also. This facilitates flat layouts.
Here’s our settings.gradle file found in the trunk directory:
Now with that there we are ready to create our first build script in example-parent.
All default gradle build scripts should be named build.gradle, it’s the name that the gradle command will look for by default when run. Let’s look at our simple build script, I’ll go through the sections afterwards.
Now let’s walk through it. Remember that this script is really a groovy script with a very detailed and structured DSL, you can do anything in this DSL that you could do in groovy. An example is the SLF4J_VERSION variable set at the top. This is a regular old groovy variable and is used later in constructing the dependency strings (using GString replacement).
The group and version propeties set at the top are part of the Project object which is the groovy object that the DSL is delegating to by default. By setting these properties you tell gradle what group and version this project is and can be used for dependency management. If this project is pushed into a maven repository it will use these properties as part of the pom.xml.
Next is the allprojects block which is part of the DSL. This block will apply all configurations found in the block to each module in the project. For this example that would be example-parent and example-core. You are free to do any configuration you care to here as if this was a single module project.
Inside the allprojects block is the repositories block. This block is used to configure what repositories will be used to locate dependencies. Here I have used a convenient method to include the maven central repository. Gradle supports all kinds of different repositories from maven and ivy to even a file system. It is extremely flexible. In a previous project I was on we stored all of our dependenies in source control inside a subdirectory found in our project and Gradle used that as it’s repository.
The subprojects block is used to configure all projects except for the root project. Generally the root project does not produce any artifacts therefore won’t have a need for many configuration parts. Here we set up a couple of configurations.
Configurations are used to store sets of dependencies and files. In this example I’ve created two common configurations for common test dependencies and common compile dependencies later on we’ll use these in our other modules.
The dependencies block is where all dependencies for this project are declared. A dependency needs to be added to a configuration, here I’ve added JUnit to the commonTest configuration and slf4j with log4j to the common configuration. You’ll notice that the dependencies are declared using a string. Gradle supports a few different ways of doing this along with methods for excluding transitive dependencies but I didn’t do that here. The pattern I’m following is ‘group:name:version’.
The example-core project is going to be a simple java project with one main file that prints out “Hello World” (I know, I know). We’ll set this up so it can be run from the command line using gradle. We’ll also make it so we can create an archive for deployment, therefore everyone can enjoy our genius easily.
Here’s the build.gradle file found in example-core:
Besides the code for the HelloWorld class this is all you need to set it up. If you look at the top of the script you may notice the ‘apply plugin:’ stanza. This imports a plugin into this script and applies its conventions to the build. In this case it also imports the java plugin as part of it. You can apply as many plugins as you like and you can also use plugins that are imported from any URL (file, http, etc.), the application plugin is built into gradle by default.
I then set the version of this module. The group is inherited from the root project so we don’t need to set it here also, in fact the version was too but I overrode it.
For configurations I merely extended the compile and compileTest configurations to include the ones we set up in example-parent. You may be asking yourself where compileTest and compile came from. Well, those were brought in via the apply plugin statement. They are standard configurations used by the java plugin.
For this example I don’t require any additional dependencies so I left it empty for display purposes. In real use you would omit it all together.
Finally, I set the mainClassName property to the fully qualified name of the class that I want to be executed when this application is run. This property was added by the application plugin.
Performing a Build
First, open a command shell (cmd, bash, etc.) and change to the example-core directory. From there execute this command:
You should see gradle startup and then echo out the different parts of the build lifecycle it’s going through. In this case we don’t have any unit tests so that’ll go quickly but the java plugin brings in an entire build cycle that includes a test phase. The output should resemble this:
jholland-mac:example-core jholland$ gradle build :example-core:compileJava Download http://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.6.4/slf4j-api-1.6.4.pom Download http://repo1.maven.org/maven2/org/slf4j/slf4j-parent/1.6.4/slf4j-parent-1.6.4.pom Download http://repo1.maven.org/maven2/org/slf4j/slf4j-log4j12/1.6.4/slf4j-log4j12-1.6.4.pom Download http://repo1.maven.org/maven2/log4j/log4j/1.2.14/log4j-1.2.14.pom Download http://repo1.maven.org/maven2/log4j/log4j/1.2.16/log4j-1.2.16.pom Download http://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.6.4/slf4j-api-1.6.4.jar Download http://repo1.maven.org/maven2/org/slf4j/slf4j-log4j12/1.6.4/slf4j-log4j12-1.6.4.jar Download http://repo1.maven.org/maven2/log4j/log4j/1.2.16/log4j-1.2.16.jar :example-core:processResources :example-core:classes :example-core:jar :example-core:assemble :example-core:compileTestJava UP-TO-DATE :example-core:processTestResources UP-TO-DATE :example-core:testClasses UP-TO-DATE :example-core:test Download http://repo1.maven.org/maven2/junit/junit/4.8.2/junit-4.8.2.pom Download http://repo1.maven.org/maven2/junit/junit/4.8.2/junit-4.8.2.jar :example-core:check :example-core:build BUILD SUCCESSFUL Total time: 5.031 secs jholland-mac:example-core jholland$
As you can see Gradle downloaded the necessary dependencies and then compiled the code. If you build again you’ll find that it won’t download the dependencies again and it won’t recompile your code. Gradle fully supports incremental builds and will only process files that have changed.
Now if you run ‘gradle run’ it will execute our main class. You should see the following output, note that the logging is currently also going to stdout.
jholland-mac:example-core jholland$ gradle run :example-core:compileJava UP-TO-DATE :example-core:processResources UP-TO-DATE :example-core:classes UP-TO-DATE :example-core:run 21:54:47,089 DEBUG HelloWorld:10 - Executing... Hello World BUILD SUCCESSFUL Total time: 2.736 secs jholland-mac:example-core jholland$
Finally if you run ‘gradle distZip’ Gradle will produce a deployable zip file that you can use to execute the program. You can find the zip archive example-core-1.0-SNAPSHOT.zip in the build/distributions directory. The distZip and run tasks are a part of the application plugin.
And that about all for now. In the next post I’ll create an additional module to show how multi module builds work. In the meantime check out the Gradle documentation online at http://gradle.org/documentation