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:
-
Download and unzip the source
or
-
Clone the Git repository:
git clone https://github.com/grails-guides/grails-multi-project-build.git
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 theinitial
folder.
To complete the guide, go to the initial
folder
-
cd
intograils-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.
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:
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
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:
App dependencies
app
depends on plugin
. You can express it in the dependencies
block such as:
dependencies {
...
compile project(':plugin')
}
or with:
grails {
plugins {
compile project(':plugin')
}
}
Plugin dependencies
plugin
depends on groovylib
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:
3.5 Code in multiple projects
We are going to add code to different projects to verify the multi-project build is working.
package app
import demo.RandomPersonService
import groovy.transform.CompileStatic
@CompileStatic
class PersonController {
RandomPersonService randomPersonService
def index() {
render randomPersonService.randomOciPersonName()
}
}
package demo
import groovy.transform.CompileStatic
@CompileStatic
class RandomPersonService {
String randomOciPersonName() {
List<String> people = OCI.PEOPLE
Collections.shuffle(people)
people.first()
}
}
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:
testCompile "io.micronaut:micronaut-http-client"
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
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:
version "0.1"
group "app"
dependencies {
testCompile "io.micronaut:micronaut-http-client"
}
grails {
plugins {
compile project(':plugin')
}
}
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.
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:
apply from: "${rootProject.projectDir}/gradle/geb.gradle"
4 Running the Application
To run the app:
./gradlew app:bootRun
To run tests:
./gradlew check