Loading...

November 12, 2009

Gradle Goodness: A Groovy Multi-project with Gradle

Gradle is a flexible build system that uses Groovy for build scripts. In this post we create a very simple application demonstrating a multi-project build. We create a Groovy web application with very simple domain, dataaccess, services and web projects. The sample is not to demonstrate Groovy but to show the multi-project build support in Gradle.

We start by creating a new application directory app and create two files settings.gradle and build.gradle:

$ mkdir app
$ cd app
$ touch settings.gradle
$ touch build.gradle

We open the file settings.gradle in a text editor. With the include method we define the subprojects for the application:

include 'domain', 'dataaccess', 'services', 'web'

Next we open build.gradle in the text editor. This build file is our main build file for the application. We can define all settings for the subprojects in this file:

subprojects {
    usePlugin 'groovy'
    version = '1.0.0-SNAPSHOT'
    group = 'com.mrhaki.blog'
    configurations.compile.transitive = true  // Make sure transitive project dependencies are resolved.
    
    repositories {
        mavenCentral()
    }
    
    dependencies {
        groovy 'org.codehaus.groovy:groovy:1.6.5'
    }
    
    task initProject(description: 'Initialize project') << { task ->
        task.project.sourceSets.all.groovy.srcDirs*.each { 
            println "Create $it"
            it.mkdirs() 
        }
    }
}

project(':dataaccess') {
    dependencies {
        compile project(':domain')
    }
}

project(':services') {
    dependencies {
        compile project(':dataaccess')
    }
}

project(':web') {
    usePlugin 'jetty'  // jetty plugin extends war plugin, so we get all war plugin functionality as well.
    
    dependencies {
        compile project(':services')  // Because configurations.compile.transitive = true we only have to specify services project, although we also reference dataaccess and domain projects.
    }
    
    // Add extra code to initProject task.
    initProject << { task ->
        def webInfDir = new File(task.project.webAppDir, '/WEB-INF')
        println "Create $webInfDir"
        webInfDir.mkdirs()
    }
}

The subprojects method accepts a closure and here we define common settings for all subprojects. The project method allows us to fine tune the definiton of a subproject. For each project we define project dependencies between the different projects for the compile configuration. This is a very powerful feature of Gradle, we define the project dependency and Gradle will make sure the dependent project is first build before the project that needs it. This even works if we invoke a build command from a subproject. For example if we run gradle build from the web project, all dependent projects are build first.

We also create a new task initProject for all subprojects. This task creates the Groovy source directories. In the web project we add an extra statement to the task to create the src/main/webapp/WEB-INF directory. This shows we can change a task definition in a specific subproject.

Okay it is time to let Gradle create our directories: $ gradle initProject. After the script is finished we have a new directory structure:

It is time to add some files to the different projects. As promised we keep it very, very simple. We define a domain class Language, a class in dataaccess to get a list of Language objects, a services class to filter out the Groovy language and a web component to get the name property for the Language object and a Groovlet to show it in the web browser. Finally we add a web.xml so we can execute the Groovlet.

// File: app/domain/src/main/groovy/com/mrhaki/blog/domain/Language.groovy
package com.mrhaki.blog.domain

class Language {
    String name
}
// File: app/dataaccess/src/main/groovy/com/mrhaki/blog/data/LanguageDao.groovy
package com.mrhaki.blog.data

import com.mrhaki.blog.domain.Language

class LanguageDao {
    List findAll() {
        [new Language(name: 'Java'), new Language(name: 'Groovy'), new Language(name: 'Scala')]
    }
}
// File: app/services/src/main/groovy/com/mrhaki/blog/service/LanguageService.groovy
package com.mrhaki.blog.service

import com.mrhaki.blog.domain.Language
import com.mrhaki.blog.data.LanguageDao

class LanguageService {
    def dao = new LanguageDao()
    
    Language findGroovy() {
       dao.findAll().find { it.name == 'Groovy' }
    }
}
// File: app/web/src/main/groovy/com/mrhaki/blog/web/LanguageHelper.groovy
package com.mrhaki.blog.web

import com.mrhaki.blog.service.LanguageService

class LanguageHelper {
    def service = new LanguageService()
    
    String getGroovyValue() {
        service.findGroovy()?.name ?: 'Groovy language not found'
    }
}
// File: app/web/src/main/webapp/language.groovy
import com.mrhaki.blog.web.LanguageHelper

def helper = new LanguageHelper()

html.html {
    head {
        title "Simple page"
    }
    body {
        h1 "Simple page"
        p "My favorite language is '$helper.groovyValue'."
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<!-- File: app/web/src/main/webapp/WEB-INF/web.xml -->
<web-app>
    <servlet>
        <servlet-name>Groovy</servlet-name>
        <servlet-class>groovy.servlet.GroovyServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>Groovy</servlet-name>
        <url-pattern>*.groovy</url-pattern>
    </servlet-mapping>
</web-app>

We have created all the files and it is time to see the result. Thanks to the Jetty plugin we only have to invoke the jettyRun tasks and all files (and dependent projects) are compiled and processed:

$ cd web
$ gradle jettyRun
:domain:compileJava
:domain:compileGroovy
:domain:processResources
:domain:classes
:domain:jar
:domain:uploadDefaultInternal
:dataaccess:compileJava
:dataaccess:compileGroovy
:dataaccess:processResources
:dataaccess:classes
:dataaccess:jar
:dataaccess:uploadDefaultInternal
:services:compileJava
:services:compileGroovy
:services:processResources
:services:classes
:services:jar
:services:uploadDefaultInternal
:web:compileJava
:web:compileGroovy
:web:processResources
:web:classes
:web:jettyRun

We open a web browser and go to http://localhost:8080/web/language.groovy and get a simple web page with the results of all our labour:

This concludes this blog about the multi-project support of Gradle. What we need to remember is Gradle is great in resolving dependencies between projects. If one project dependents on another we don't have to worry about first compiling the dependent project, Gradle does this for us. We can define tasks for each project, but still fine tune a task for a specific project. Also we have a certain freedom about the project structure, as long as we define the needed projects in the settings.gradle all will be fine. Also we only need one build.gradle (but can be more per project if we want) to configure all projects.