Show Navigation

Grails Multi-Project Build

Learn how to leverage Gradle capabilities in a Grails 3 application to create Multi-Project builds

Authors: Sergio del Amo

Grails Version: 3.3.1

1 Grails Training

Grails Training - Developed and delivered by the folks who created and actively maintain the Grails framework!.

2 Getting Started

In this guide, you are going to create a multi-project build from scratch. We are going to combine a Grails App, a Grails Plugin, and simple Groovy Lib.

2.1 What you will need

To complete this guide, you will need the following:

  • Some time on your hands

  • A decent text editor or IDE

  • JDK 1.7 or greater installed with JAVA_HOME configured appropriately

2.2 How to complete the guide

To get started do the following:

or

The Grails guides repositories contain two folders:

  • initial Initial project. Often a simple Grails app with some additional code to give you a head-start.

  • complete A completed example. It is the result of working through the steps presented by the guide and applying those changes to the initial folder.

To complete the guide, go to the initial folder

  • cd into grails-guides/grails-multi-project-build/initial

and follow the instructions in the next sections.

You can go right to the completed example if you cd into grails-guides/grails-multi-project-build/complete

3 Writing the Application

Grails 3.x uses the Gradle Build System for build related tasks such as compilation, runnings tests and producing binary distrubutions of your project. It is recommended to use Gradle 2.2 or above with Grails 3.1.

Gradle powerful support for multi-project builds is one of Gradle’s unique selling points.

A multi-project build in Gradle consists of one root project, and one or more subprojects that may also have subprojects.

3.1 Overview

We are going to create an app which combines a Grails App build with the web profile, a Grails Plugin with the plugin profile and a plain Groovy library.

grails multi project build diagramm

3.2 Create Projects

Create root project

$ mkdir multiproject
$ cd multiproject

Create Grails App with web profile

multiproject$ mkdir app
multiproject$ cd app
multiproject/app$ grails
grails> create-app --profile web --inplace
| Application created at multiproject/app
| Resolving Dependencies. Please wait…
CONFIGURE SUCCESSFUL
Total time: 12.262 secs
grails> exit
multiproject/app$ cd ..

Create Grails Plugin with plugin profile

multiproject$ mkdir plugin
multiproject$ cd plugin
multiproject/plugin$ grails
grails> create-app --profile plugin --inplace
| Application created at multiproject/plugin
| Resolving Dependencies. Please wait…
CONFIGURE SUCCESSFUL
Total time: 12.262 secs
grails> exit
multiproject/plugin$ cd ..

Create Groovy Lib

multiproject$ mkdir groovylib
multiproject$ cd groovylib
multiproject/groovylib$ touch build.gradle

Create a build file for the Groovy Lib:

groovylib/build.gradle
apply plugin: 'groovy'
repositories {
    jcenter()
}
dependencies {
    compile 'org.codehaus.groovy:groovy-all:2.4.11'
}

Move Gradle files to Root Project

multiproject$ mv app/gradlew .
multiproject$ mv app/gradlew.bat .
multiproject$ mv app/gradle.properties .
multiproject$ mv app/gradle .

Add projects to settings.gradle

settings.gradle
include 'app'
include 'plugin'
include 'groovylib'

Remove unnecessary files

Clean-up plugin folder

multiproject$ rm plugin/gradlew
multiproject$ rm plugin/gradlew.bat
multiproject$ rm plugin/grailsw.bat
multiproject$ rm plugin/grailsw
multiproject$ rm plugin/grailsw.bat
multiproject$ rm plugin/grails-wrapper.jar
multiproject$ rm plugin/settings.gradle
multiproject$ rm plugin/gradle.properties
multiproject$ rm -rf plugin/gradle
multiproject$ rm plugin/.gitignore

Clean-up app folder

multiproject$ rm app/grailsw
multiproject$ rm app/grailsw.bat
multiproject$ rm app/grails-wrapper.jar
multiproject$ rm app/settings.gradle
multiproject$ rm app/.gitignore

3.3 Projects' dependencies

We want to configure the next dependencies:

dependencies

app depends on plugin

app/build.gradle
dependencies {
    ...
    compile project(':plugin')
}

plugin depends on groovylib

plugin/build.gradle
dependencies {
  ...
    compile project(':groovylib')
}

Hot Reloading

To get hot reloading for changes made in the Grails Plugin, replace the configuration

app/build.gradle
dependencies {
    ...
    compile project(':plugin')
}

with:

app/build.gradle
grails {
    plugins {
        compile project(':plugin')
    }
}

3.4 Open project in IDE

You can open you Grails multi-project build with an IDE such as IntelliJ IDEA by telling the IDE to open the multiproject folder.

Once the project is imported you will see such as window:

grails multi project build ide

3.5 Code in multiple projects

We are going to add code to different projects to verify the multi-project build is working.

codedependencies
app/grails-app/controllers/app/PersonController.groovy
package app

import demo.RandomPersonService
import groovy.transform.CompileStatic

@CompileStatic
class PersonController {
    RandomPersonService randomPersonService
    def index() {
        render randomPersonService.randomOciPersonName()
    }
}
plugin/grails-app/services/demo/RandomPersonService.groovy
package demo

import groovy.transform.CompileStatic

@CompileStatic
class RandomPersonService {

    String randomOciPersonName() {
        List<String> people = OCI.PEOPLE
        Collections.shuffle(people)
        people.first()
    }
}
groovylib/src/main/groovy/demo/OCI.groovy
package demo

import groovy.transform.CompileStatic

@CompileStatic
class OCI {
    public static final List<String> PEOPLE = [
            'Ryan',
            'Jeff',
            'Paul',
            'Søren',
            'Sergio'
    ]
}

To verify our app, we use the Rest Client Builder Grails Plugin. Add the plugin to our app dependencies:

app/build.gradle
testCompile 'org.grails:grails-datastore-rest-client'
app/src/integration-test/groovy/app/PersonControllerSpec.groovy
package app

import demo.OCI
import grails.testing.mixin.integration.Integration
import spock.lang.Specification
import grails.plugins.rest.client.RestBuilder

@Integration
class PersonControllerSpec extends Specification {

    def '/person endpoints return one of the names'() {

        given:
        RestBuilder rest = new RestBuilder()

        when:
        def resp = rest.get("http://localhost:${serverPort}/person")

        then:
        OCI.PEOPLE.contains resp.text
    }
}

3.6 Keep your build dry

If you compare app/build.gradle and plugin/build.gradle you will see a lot of duplication. To remove duplication, we are going to add a ROOT build.gradle

build.gradle
buildscript {
    repositories {
        mavenLocal()
        maven { url "https://repo.grails.org/grails/core" }
    }
    dependencies {
        classpath "org.grails:grails-gradle-plugin:$grailsVersion"
        classpath "com.bertramlabs.plugins:asset-pipeline-gradle:2.14.2"
        classpath "org.grails.plugins:hibernate5:${gormVersion-".RELEASE"}"
    }
}

ext {
    grailsApps = ['app']
    grailsPlugins = ['plugin']
}

subprojects { project ->
    boolean isGrailsApp = grailsApps.contains(project.name)
    boolean isGrailsPlugin = grailsPlugins.contains(project.name)
    boolean isGrailsProject = isGrailsApp || isGrailsPlugin

    if ( isGrailsProject ) {
        apply plugin:"eclipse"
        apply plugin:"idea"

        if ( isGrailsApp ) {
            apply plugin:"war"
            apply plugin:"org.grails.grails-web"
            apply plugin:"org.grails.grails-gsp"
            apply plugin:"asset-pipeline"
        }

        if ( isGrailsPlugin ) {
            apply plugin:"org.grails.grails-plugin"
            apply plugin:"org.grails.grails-plugin-publish"
        }

        repositories {
            mavenLocal()
            maven { url "https://repo.grails.org/grails/core" }
        }

        dependencies {
            compile "org.springframework.boot:spring-boot-starter-logging"
            compile "org.springframework.boot:spring-boot-autoconfigure"
            compile "org.grails:grails-core"
            console "org.grails:grails-console"
        }

        if ( isGrailsApp ) {
            dependencies {
                compile "org.springframework.boot:spring-boot-starter-actuator"
                compile "org.springframework.boot:spring-boot-starter-tomcat"
                compile "org.grails:grails-dependencies"
                compile "org.grails:grails-web-boot"
                compile "org.grails.plugins:cache"
                compile "org.grails.plugins:scaffolding"
                compile "org.grails.plugins:hibernate5"
                compile "org.hibernate:hibernate-core:5.1.5.Final"
                profile "org.grails.profiles:web"
                runtime "org.glassfish.web:el-impl:2.1.2-b03"
                runtime "com.h2database:h2"
                runtime "org.apache.tomcat:tomcat-jdbc"
                runtime "com.bertramlabs.plugins:asset-pipeline-grails:2.14.2"
                testCompile "org.grails:grails-gorm-testing-support"
                testCompile "org.grails.plugins:geb"
                testCompile "org.grails:grails-web-testing-support"
                testRuntime "org.seleniumhq.selenium:selenium-htmlunit-driver:2.47.1"
                testRuntime "net.sourceforge.htmlunit:htmlunit:2.18"
            }
        }

        if ( isGrailsPlugin ) {
            dependencies {
                profile "org.grails.profiles:plugin"
                provided "org.grails:grails-plugin-services"
                provided "org.grails:grails-plugin-domain-class"
                testCompile "org.grails:grails-gorm-testing-support"
            }
        }

        bootRun {
            jvmArgs('-Dspring.output.ansi.enabled=always')
            addResources = true
        }

        if ( isGrailsPlugin ) {
            // enable if you wish to package this plugin as a standalone application
            bootRepackage.enabled = false
            grailsPublish {
                // TODO: Provide values here
                user = 'user'
                key = 'key'
                githubSlug = 'foo/bar'
                license {
                    name = 'Apache-2.0'
                }
                title = "My Plugin"
                desc = "Full plugin description"
                developers = [johndoe:"John Doe"]
                portalUser = ""
                portalPassword = ""
            }
        }
    }

    afterEvaluate {
        if ( isGrailsProject ) {
            assets {
                minifyJs = true
                minifyCss = true
            }
        }
    }
}

the app and plugin gradle files are really simple:

app/build.gradle
version "0.1"
group "app"
dependencies {
    testCompile 'org.grails:grails-datastore-rest-client'
}
grails {
    plugins {
        compile project(':plugin')
    }
}
plugin/build.gradle
version "0.1"
group "plugin"
dependencies {
    compile project(':groovylib')
}

3.7 Features as Gradle files

Another possibility to keep your build dry is to create specific Gradle files for different features.

Imagine you have Geb tests in several projects of your multi-project build.

We can encapsulate a set of dependencies to run Geb tests for multiple browsers.

gradle/geb.gradle
project.ext.seleniumVersion = '2.53.1'
project.ext.htmlUnitVersion = '2.18'
project.ext.htmlUnitDriverVersion = '2.47.1'
project.ext.phantomJsDriverVersion = '1.3.0'

dependencies {
    testCompile 'org.grails.plugins:geb'
    testRuntime "net.sourceforge.htmlunit:htmlunit:$htmlUnitVersion"
    testRuntime "org.seleniumhq.selenium:selenium-support:$seleniumVersion"
    testCompile "org.seleniumhq.selenium:selenium-htmlunit-driver:$htmlUnitDriverVersion"
    testCompile "com.codeborne:phantomjsdriver:$phantomJsDriverVersion"
    testCompile "org.seleniumhq.selenium:selenium-chrome-driver:$seleniumVersion"
    testCompile "org.seleniumhq.selenium:selenium-firefox-driver:$seleniumVersion"
}

Then you can apply these dependencies to a particular project:

app/build.gradle
apply from: "${rootProject.projectDir}/gradle/geb.gradle"

4 Running the Application

To run the app:

./gradlew app:bootRun

To run tests:

./gradlew check

5 Do you need help with Grails?

OCI sponsored the creation of this Guide. OCI offers several Grails services:

Free consultation

The OCI Grails Team includes Grails co-founders, Jeff Scott Brown and Graeme Rocher. Check our Grails courses and learn from the engineers who developed, matured and maintain Grails.

Grails OCI Team