Loading...

November 7, 2009

Gradle Goodness: Using Gradle for a Mixed Java and Groovy Project

Gradle is a build system to build software projects. Gradle supports convention over configuration, build-in takss and dependency support. We write a build script in Groovy (!) to define our Gradle build. This means we can use all available Groovy (and Java) stuff we want, like control structures, classes and methods. In this post we see how we can use Gradle to build a very simple mixed Java and Groovy project.

To get started we must first have installed Gradle on our computers. We can read the manual to see how to do that. To check Gradle is installed correctly and we can run build script we type $ gradle -v at our shell prompt and we must get a result with the versions of Java, Groovy, operating system, Ivy and more.

It is time to create our Groovy/Java project. We create a new directory mixed-project:

$ mkdir mixed-project
$ cd mixed-project
$ touch build.gradle

Gradle uses plugins to define taks and conventions for certain type of projects. The plugins are distributed with Gradle and not (yet) downloaded from the internet. One of the plugins is the Groovy plugin. This plugin is extended from the Java plugin, so if we use the Groovy plugin we also have all functionality of the Java plugin. And that is exactly what we need for our project. The plugin provides a set of tasks like compile, build, assemble, clean and a directory structure convention. The plugin assumes we save our source files in src/main/java and src/main/groovy for example. The structure is similar to Maven conventions. As a matter of fact Gradle also has a Maven plugin that add Maven tasks like build, install to our build. For now we just need the Groovy plugin, so we open the file build.gradle in a text editor and add the following line:

usePlugin 'groovy'

To see the lists of tasks we can now execute by just including this one line we return to our shell and type $ gradle -t and we get the following list of tasks:

------------------------------------------------------------
Root Project
------------------------------------------------------------
:assemble - Builds all Jar, War, Zip, and Tar archives.
   -> :jar
:build - Assembles and tests this project.
   -> :assemble, :check
:buildDependents - Assembles and tests this project and all projects that depend on it.
   -> :build
:buildNeeded - Assembles and tests this project and all projects it depends on.
   -> :build
:check - Runs all checks.
   -> :test
:classes - Assembles the main classes.
   -> :compileGroovy, :compileJava, :processResources
:clean - Deletes the build directory.
:compileGroovy - Compiles the main Groovy source.
   -> :compileJava
:compileJava - Compiles the main Java source.
:compileTestGroovy - Compiles the test Groovy source.
   -> :classes, :compileTestJava
:compileTestJava - Compiles the test Java source.
   -> :classes
:groovydoc - Generates the groovydoc for the source code.
:jar - Generates a jar archive with all the compiled classes.
   -> :classes
:javadoc - Generates the javadoc for the source code.
   -> :classes
:processResources - Processes the main resources.
:processTestResources - Processes the test resources.
:test - Runs the unit tests.
   -> :classes, :testClasses
:testClasses - Assembles the test classes.
   -> :compileTestGroovy, :compileTestJava, :processTestResources
rule - Pattern: build<ConfigurationName>: Builds the artifacts belonging to the configuration.
rule - Pattern: upload<ConfigurationName>Internal: Uploads the project artifacts of a configuration to the internal Gradle repository.
rule - Pattern: upload<ConfigurationName>: Uploads the project artifacts of a configuration to a public Gradle repository.

We don't have any code yet in our project so we don't have any task to run right now, but it is good to know all these tasks can be executed once we have our code. Okay, we have to create our directory structure according to the conventions to make it all work without to much configuration. We can do this all by hand but we can also use a trick described in the Gradle cookbook. We add a new task to our build.gradle file to create all necessary directories for us.

task initProject(description: 'Initialize project directory structure.') << {
    // Default package to be created in each src dir.
    def defaultPackage = 'com/mrhaki/blog'
    
    ['java', 'groovy', 'resources'].each {
        // convention.sourceSets contains the directory structure
        // for our Groovy project. So we use this struture
        // and make a directory for each node.
        convention.sourceSets.all."${it}".srcDirs*.each { dir ->
            def newDir = new File(dir, defaultPackage)
            logger.info "Creating directory $newDir"  // gradle -i shows this message.
            newDir.mkdirs()  // Create dir.
        }
    }
}

At the command prompt we type $ gradle initProject and the complete source directory struture is now created. Let's add some Java and Groovy source files to our project. We keep it very simple, because this post is about Gradle and not so much about Java and Groovy. We create a Java interface in src/main/java/com/mrhaki/blog/GreetingService.java:

package com.mrhaki.blog;

public interface GreetingService {
    String greet(final String name);
}

We provide a Java implementation for this interface in src/main/java/com/mrhaki/blog/JavaGreetingImpl.java:

package com.mrhaki.blog;

public class JavaGreetingImpl implements GreetingService {
    public String greet(final String name) {
        return "Hello " + (name != null ? name : "stranger") + ". Greeting from Java.";
    }
}

And a Groovy implementation in src/main/groovy/com/mrhaki/blog/GroovyGreetingImpl.groovy:

package com.mrhaki.blog

class GroovyGreetingImpl implements GreetingService {
    String greet(String name) {
        "Hello ${name ?: 'stranger'}. Greeting from Groovy"
    }
}

We have learned Gradle uses Groovy to define and execute the build script. But this bundled Groovy is not available for our project. We can choose which version of Groovy we want and don't have to rely on the version that is shipped with Gradle. We must define a dependency to the Groovy library version we want to use in our project in build.gradle. So we must add the following lines to the build.gradle file:

repositories {
    mavenCentral()  // Define Maven central repository to look for dependencies.
}

dependencies {
    groovy 'org.codehaus.groovy:groovy:1.6.5'  // group:name:version is a nice shortcut notation for dependencies. 
}

In our shell we type $ gradle compileJava compileGroovy to compile the source files we just created. If we didn't make any typos we should see the message BUILD SUCCESSFUL at the command prompt. Let's add some test classes to our project to test our simple implementations. We create src/test/java/com/mrhaki/blog/JavaGreetingTest.java:

package com.mrhaki.blog;

import static org.junit.Assert.*;
import org.junit.Test;

public class JavaGreetingTest {
    final GreetingService service = new JavaGreetingImpl();

    @Test public void testGreet() {
        assertEquals("Hello mrhaki. Greeting from Java.", service.greet("mrhaki"));
    }
    
    @Test public void testGreetNull() {
        assertEquals("Hello stranger. Greeting from Java.", service.greet(null));
    }
}

And we create a Groovy test class in src/test/groovy/com/mrhaki/blog/GroovyGreetingTest.groovy:

package com.mrhaki.blog;

import static org.junit.Assert.*;
import org.junit.Test;

public class JavaGreetingTest {
    final GreetingService service = new JavaGreetingImpl();

    @Test public void testGreet() {
        assertEquals("Hello mrhaki. Greeting from Java.", service.greet("mrhaki"));
    }
    
    @Test public void testGreetNull() {
        assertEquals("Hello stranger. Greeting from Java.", service.greet(null));
    }
}

We add a dependency to build.gradle for JUnit:

dependencies {
    groovy 'org.codehaus.groovy:groovy:1.6.5'  // group:name:version is a nice shortcut notation for dependencies. 
    testCompile 'junit:junit:4.7'
}

We return to the command prompt and type $ gradle test. Gradle compiles the code and runs the JUnit tests. The results of the test are stored in build/reports/tests. We can see the results in a web browser if we open index.html:

Let's leave the coding part for now. It it time to package our code. We can use $ gradle build to create a JAR file with the compiled classes from the src/main directories. But first we make a change to build.gradle to include a version number. If we run $ gradle -r we get an overview of all properties for our project. Among them is the version property. We can set a value for the version property in the build.gradle file. We also set the basename for the archive:

version = "1.0-${new Date().format('yyyyMMdd')"  // The script is all Groovy, so we make use of all methods and features.
archivesBaseName = 'greeting'

We return to the command prompt and type $ gradle build. Gradle runs and if all is successful we see in build/libs the file greeting-1.0-20091107.jar. Here is the complete build.gradle with all changes we made:

usePlugin 'groovy'

version = "1.0-${new Date().format('yyyyMMdd')}" // The script is all Groovy, so we make use of all methods and features.
archivesBaseName = 'greeting'

repositories {
    mavenCentral()  // Define Maven central repository to look for dependencies.
}

dependencies {
    groovy 'org.codehaus.groovy:groovy:1.6.5'  // group:name:version is a nice shortcut notation for dependencies. 
    testCompile 'junit:junit:4.7'
}

task initProject(description: 'Initialize project directory structure.') << {
    // Default package to be created in each src dir.
    def defaultPackage = 'com/mrhaki/blog'
    
    ['java', 'groovy', 'resources'].each {
        // convention.sourceSets contains the directory structure
        // for our Groovy project. So we use this struture
        // and make a directory for each node.
        convention.sourceSets.all."${it}".srcDirs.each { dirs ->
            dirs.each { dir ->
                def newDir = new File(dir, defaultPackage)
                logger.info "Creating directory $newDir"  // gradle -i shows this message.
                newDir.mkdirs()  // Create dir.
            }
        }
    }
}

We learned how we can start a new project from scratch and with little coding get a compiled and tested archive wth our code. In future blog posts we learn more about Gradle, for example the multi-project support.