Show Navigation

Grails Multi-Project Build

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

Authors: Sergio del Amo

Grails Version: 4.0.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.8 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 uses the Gradle Build System for build related tasks such as compilation, runnings tests and producing binary distrubutions of your project.

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
plugins {
    id 'groovy'
}

repositories {
    jcenter()
}

dependencies {
    implementation 'org.codehaus.groovy:groovy-all:2.5.8'
}

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 dependencies

app depends on plugin. You can express it in the dependencies block such as:

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

or with:

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

Plugin dependencies

plugin depends on groovylib

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

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 "io.micronaut:micronaut-http-client"
app/src/integration-test/groovy/app/PersonControllerSpec.groovy
package app

import demo.OCI
import grails.testing.mixin.integration.Integration
import grails.testing.spock.OnceBefore
import io.micronaut.http.HttpRequest
import io.micronaut.http.client.HttpClient
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification

@Integration
class PersonControllerSpec extends Specification {

    @Shared
    @AutoCleanup
    HttpClient client

    @OnceBefore
    void init() {
        String baseUrl = "http://localhost:$serverPort"
        this.client = HttpClient.create(new URL(baseUrl))
    }

    def '/person endpoints return one of the names'() {
        when:
        String text = client.toBlocking().retrieve(HttpRequest.GET('/person'), String)

        then:
        OCI.PEOPLE.contains 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:$assetPipelineVersion"
        classpath "org.grails.plugins:hibernate5:7.0.0"
        classpath "gradle.plugin.com.github.erdi.webdriver-binaries:webdriver-binaries-gradle-plugin:2.1"

    }
}

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:"com.bertramlabs.asset-pipeline"
            apply plugin:"com.github.erdi.webdriver-binaries"
        }

        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" }
            maven {
                url "https://oss.sonatype.org/content/repositories/snapshots/"
                content {
                    includeVersionByRegex('io\\.micronaut.*', '.*', '.*BUILD-SNAPSHOT')
                }
            }

        }

        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 ) {
            configurations {
                developmentOnly
                runtimeClasspath {
                    extendsFrom developmentOnly
                }
            }

            dependencies {
                developmentOnly("org.springframework.boot:spring-boot-devtools")
                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.4.0.Final"
                compile "io.micronaut:micronaut-inject-groovy"
                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:$assetPipelineVersion"
                testCompile "org.grails:grails-gorm-testing-support"
                testCompile "org.grails:grails-web-testing-support"
            }
            apply from: "${rootProject.projectDir}/gradle/geb.gradle"
        }

        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',
                    '-noverify',
                    '-XX:TieredStopAtLevel=1',
                    '-Xmx1024m')
            sourceResources sourceSets.main
        }

        if (isGrailsApp) {
            webdriverBinaries {
                chromedriver "$chromeDriverVersion"
                geckodriver "$geckodriverVersion"
            }

            tasks.withType(Test) {
                systemProperty "geb.env", System.getProperty('geb.env')
                systemProperty "geb.build.reportsDir", reporting.file("geb/integrationTest")
                systemProperty "webdriver.chrome.driver", System.getProperty('webdriver.chrome.driver')
                systemProperty "webdriver.gecko.driver", System.getProperty('webdriver.gecko.driver')
            }
        }

        if ( isGrailsPlugin ) {
            // enable if you wish to package this plugin as a standalone application
            bootJar.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 = ""
            }
        }
    }
}

the app and plugin gradle files are really simple:

app/build.gradle
version "0.1"
group "app"
dependencies {
    testCompile "io.micronaut:micronaut-http-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
dependencies {

    testCompile "org.grails.plugins:geb"
    testRuntime "org.seleniumhq.selenium:selenium-chrome-driver:$seleniumVersion"
    testRuntime "org.seleniumhq.selenium:selenium-firefox-driver:$seleniumVersion"
    testCompile "org.seleniumhq.selenium:htmlunit-driver:2.35.1"
    testRuntime 'net.sourceforge.htmlunit:htmlunit:2.35.0'
    testCompile "org.seleniumhq.selenium:selenium-remote-driver:$seleniumVersion"
    testCompile "org.seleniumhq.selenium:selenium-api:$seleniumVersion"
    testCompile "org.seleniumhq.selenium:selenium-support:$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?

Object Computing, Inc. (OCI) sponsored the creation of this Guide. A variety of consulting and support services are available.

OCI is Home to Grails

Meet the Team