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.
Written with Gradle 0.8.