Grails Programmer : How to create a multi project build with Grails 3 (apps and plugins)?

Updated to 3.2.8

Back in February I wrote a post about multi-project build. Since then, Graeme Rocher did a great Quickcast where he explains it much better. Because of that I have reviewed this post to include all the points covered in the video. Feel free to view the video or follow the post.

Creating a multi project build in Grails 3 is really easy.

Lets create a folder where we are going to place a core projects and a plugin.

$ mkdir multiproject
$ cd multiproject

We are running in the current latest Grails version.

 
$ grails --version
grails --version
| Grails Version: 3.2.8
| Groovy Version: 2.4.10
| JVM Version: 1.8.0_121

Checkout the list of available profiles. We are going to use the REST profile

$ grails list-profiles
| Available Profiles
--------------------
* angular - A profile for creating applications using AngularJS
* rest-api - Profile for REST API applications
* base - The base profile extended by other profiles
* angular2 - A profile for creating Grails applications with Angular 2
* plugin - Profile for plugins designed to work across all profiles
* profile - A profile for creating new Grails profiles
* react - A profile for creating Grails applications with a React frontend
* rest-api-plugin - Profile for REST API plugins
* web - Profile for Web applications
* web-jboss7 - A Profile for Creating a JBoss 7.1 EAP Project
* web-plugin - Profile for Plugins designed for Web applications
* webpack - A profile for creating applications with node-based frontends using webpack

Let’s create the core app project which we are going to use the web-api profile.

| grails create-app myapi --profile=rest-api
| Application created at /Users/sdelamo/Developer/tests/multiproject/myapi

Let’s create a plugin which we are going to use from the main app.

| grails create-plugin myplugin --profile=plugin
| Plugin created at /Users/sdelamo/Developer/tests/multiproject/myplugin

Lets create a service in the plugin which returns a name:

$ cd myplugin
$ grails create-service Name

Lets return the a string in a method within the service:
grails-app/services/myplugin/NameService.groovy

package myplugin

class NameService {

    def name() {
        'Sergio'
    }
}

Now, lets go back to the root of the multiproject and create a settings.gradle which includes both projects:

$ pwd
/Users/sdelamo/Developer/tests/multiproject/
$ echo "include 'myapi', 'myplugin'" >>  settings.gradle

myapi app project already contained settings.gradle file, remove it:

$ pwd
/Users/sdelamo/Developer/tests/multiproject/
$ rm myapi/settings.gradle

Reference the plugin project – Gradle way

Although you can do this, don’t. Please, use the grails plugin block described later.

Edit myapi/build.gradle and add a reference to the plugin project.

buildscript {
    repositories {
        mavenLocal()
        maven { url "https://repo.grails.org/grails/core" }
    }
    dependencies {
        classpath "org.grails:grails-gradle-plugin:$grailsVersion"
        classpath "org.grails.plugins:hibernate5:${gormVersion-".RELEASE"}"
        classpath "org.grails.plugins:views-gradle:1.1.6"
    }
}

version "0.1"
group "myapi"

apply plugin:"eclipse"
apply plugin:"idea"
apply plugin:"war"
apply plugin:"org.grails.grails-web"
apply plugin:"org.grails.plugins.views-json"

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"
    compile "org.springframework.boot:spring-boot-starter-actuator"
    compile "org.springframework.boot:spring-boot-starter-tomcat"
    compile "org.grails:grails-plugin-url-mappings"
    compile "org.grails:grails-plugin-rest"
    compile "org.grails:grails-plugin-codecs"
    compile "org.grails:grails-plugin-interceptors"
    compile "org.grails:grails-plugin-services"
    compile "org.grails:grails-plugin-datasource"
    compile "org.grails:grails-plugin-databinding"
    compile "org.grails:grails-plugin-async"
    compile "org.grails:grails-web-boot"
    compile "org.grails:grails-logging"
    compile "org.grails.plugins:cache"
    compile "org.grails.plugins:hibernate5"
    compile "org.hibernate:hibernate-core:5.1.3.Final"
    compile "org.hibernate:hibernate-ehcache:5.1.3.Final"
    compile "org.grails.plugins:views-json"
    compile "org.grails.plugins:views-json-templates"
    console "org.grails:grails-console"
    profile "org.grails.profiles:rest-api"
    runtime "com.h2database:h2"
    testCompile "org.grails:grails-plugin-testing"
    testCompile "org.grails.plugins:geb"
    testCompile "org.grails:grails-datastore-rest-client"
    testRuntime "org.seleniumhq.selenium:selenium-htmlunit-driver:2.47.1"
    testRuntime "net.sourceforge.htmlunit:htmlunit:2.18"
    compile project(":myplugin")
}

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

in the myapi project create a controller which uses the service:

myapi$ grails create-controller Salute 
| Created grails-app/controllers/myapi/SaluteController.groovy
| Created src/test/groovy/myapi/SaluteControllerSpec.groovy
package myapi


import grails.rest.*
import grails.converters.*
import static org.springframework.http.HttpStatus.*

class SaluteController {

    def nameService

    def index() { 
        render text:"Hello ${nameService.name()}", status: OK
    }
}

Start the app:

$ grails run-app
Grails application running at http://localhost:8080 in environment: development

Hit the endpoint:

$ curl -H "Accept: application/json" http://localhost:8080/salute 
Hello Sergio

We are calling service defined on its own plugin form the main app.

Reference the plugin project – Grails Plugin block

Note the recommended way to add a project dependency between a grails project and a grails plugin is by means of the grails plugin block. It allow us to get automatically reloading of subproject dependencies which is really important from a development productivity stand point.

buildscript {
    repositories {
        mavenLocal()
        maven { url "https://repo.grails.org/grails/core" }
    }
    dependencies {
        classpath "org.grails:grails-gradle-plugin:$grailsVersion"
        classpath "org.grails.plugins:hibernate5:${gormVersion-".RELEASE"}"
        classpath "org.grails.plugins:views-gradle:1.1.6"
    }
}

version "0.1"
group "myapi"

apply plugin:"eclipse"
apply plugin:"idea"
apply plugin:"war"
apply plugin:"org.grails.grails-web"
apply plugin:"org.grails.plugins.views-json"

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"
    compile "org.springframework.boot:spring-boot-starter-actuator"
    compile "org.springframework.boot:spring-boot-starter-tomcat"
    compile "org.grails:grails-plugin-url-mappings"
    compile "org.grails:grails-plugin-rest"
    compile "org.grails:grails-plugin-codecs"
    compile "org.grails:grails-plugin-interceptors"
    compile "org.grails:grails-plugin-services"
    compile "org.grails:grails-plugin-datasource"
    compile "org.grails:grails-plugin-databinding"
    compile "org.grails:grails-plugin-async"
    compile "org.grails:grails-web-boot"
    compile "org.grails:grails-logging"
    compile "org.grails.plugins:cache"
    compile "org.grails.plugins:hibernate5"
    compile "org.hibernate:hibernate-core:5.1.3.Final"
    compile "org.hibernate:hibernate-ehcache:5.1.3.Final"
    compile "org.grails.plugins:views-json"
    compile "org.grails.plugins:views-json-templates"
    console "org.grails:grails-console"
    profile "org.grails.profiles:rest-api"
    runtime "com.h2database:h2"
    testCompile "org.grails:grails-plugin-testing"
    testCompile "org.grails.plugins:geb"
    testCompile "org.grails:grails-datastore-rest-client"
    testRuntime "org.seleniumhq.selenium:selenium-htmlunit-driver:2.47.1"
    testRuntime "net.sourceforge.htmlunit:htmlunit:2.18"
}

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

grails {
    plugins {
        compile project(':myplugin')
    }
}

If we run the app with the verbose flag. We will see how the plugin is built first and placed on the classpath before the application.

$ grails run-app -verbose
| Running application...
:myplugin:compileAstJava UP-TO-DATE
:myplugin:compileAstGroovy UP-TO-DATE
:myplugin:processAstResources UP-TO-DATE
:myplugin:astClasses UP-TO-DATE
:myplugin:compileJava UP-TO-DATE
:myplugin:configScript UP-TO-DATE
:myplugin:compileGroovy UP-TO-DATE
:myplugin:copyCommands UP-TO-DATE
:myplugin:copyTemplates UP-TO-DATE
:myplugin:processResources UP-TO-DATE
:myapi:compileJava UP-TO-DATE
:myapi:compileGroovy
:myapi:buildProperties UP-TO-DATE
:myapi:processResources UP-TO-DATE
:myapi:classes
:myapi:findMainClass
:myapi:bootRun
...
..
.
Grails application running at http://localhost:8080 in environment: development

You can drag and drop the settings.gradle to IntelliJ and it will configure correctly your multi-project build there.

Remove duplication

You can move most of the configuration to the root project to avoid duplication. For example:

$ pwd
/Users/sdelamo/Developer/tests/multiproject/
$ mv myapi/gradle .
$ mv myapi/gradle.properties .
$ mv myapi/gradlew .
$ mv myapi/gradlew.bat .
$ rm myapi/grailsw
$ rm myapi/grailsw.bat
$ rm myplugin/gradlew.bat
$ rm myplugin/gradlew
$ rm myplugin/grailsw
$ rm myplugin/grailsw.bat
$ rm myplugin/gradle.properties
$ rm -rf myplugin/gradle

You can check the code example in this Github Repository.

Do you like to read about Groovy/Grails development? Yes, then Subscribe to Groovy Calamari a weekly curated email newsletter about the Groovy ecosystem which I write

16 thoughts on “Grails Programmer : How to create a multi project build with Grails 3 (apps and plugins)?

    1. Chris Malan

      Hi Sergio, Haven’t tried yet. If I’m in the myapp directory and I go grails run-app, will there be access to the build.gradle in the root directory? The build.gradle in the myapp directory is now only a shadow of its former self. I suppose to run the app access to everything relevant in the build.gradle file is needed.

      Reply
  1. sdelamo Post author

    Think of everything as modules.

    From the root directory:

    ./gradlew myapp:bootRun

    is the same as doing cd myapp; grails run-app

    Reply
  2. Matias

    Is this still working with latest Grails 3.2.8? I tried to build the example but I’m getting:

    | Resolving Dependencies. Please wait…

    FAILURE: Build failed with an exception.

    * Where:
    Build file ‘/Users/matias/Sites/multiproject/myapp/build.gradle’ line: 30

    * What went wrong:
    A problem occurred evaluating root project ‘myapp’.
    > Project with path ‘:myplugin’ could not be found in root project ‘myapp’.

    And this is my config:

    -I have multiproject/settings.gradle with:
    include ‘myapp’, ‘myplugin’

    – I also added this lines to multiproject/myapp/build.gradle

    grails {
    plugins {
    compile project(‘:myplugin’)
    }
    }

    Do you see anything wrong in this configuration?
    Thanks,
    Matias.

    Reply
      1. Matias

        Thanks Chris, I dowloaded the project and tried to run it but I’m getting this:

        MacBook-Pro-de-Matias-2:web-admin matias$ grails run-app
        | Error Error occurred running Grails CLI: No profile found for name [web]. (Use –stacktrace to see the full trace)

        Also tried to do it step by step and I having the same issue I described before…

      2. Chris Malan

        Hi Matias, I downloaded it and ran it. You need to be inside the web-admin project when calling ‘grails run-app.’ It’s also Grails 3.2.4. Even so, I changed the version in gradle.propertites to 3.2.8 and it still runs.

        Your error message says there isn’t a ‘web’ profile. Do $ ‘grails list-profiles’ from the project base directory (domain-example) and you will see even Grails 3.2.8 has a web profile. I have no idea why your set-up tells you there’s no web profile. A quick Google shows this: http://stackoverflow.com/questions/36501249/grails-3-1-4-and-docker-grails-commands-return-no-profile-found-for-name-web

  3. sdelamo Post author

    I’ve updated the post to Grails 3.2.8. I uploaded a Github repository too. Hope it helps

    Reply
  4. Lee

    Hi Sergio,

    I was also having the same issue as Matias –

    * What went wrong:
    A problem occurred evaluating root project ‘myapp’.
    > Project with path ‘:myplugin’ could not be found in root project ‘myapp’.

    I noticed the settings.gradle file in the myapp folder had rootProject.name=’myapp’

    I removed the settings.gradle files in the myapp folder and the myplugin folder and the app loaded as expected.

    not sure if this is the correct way to do this but it was the only way it would load

    Reply
  5. sdelamo Post author

    Lee, yes you need to have only one settings.gradle file. Note: the post contains this sentence:

    “myapi app project already contained settings.gradle file, remove it:”

    Reply
  6. David

    Hi Sergio

    This post helped me to quickly setup a multi-project gradle build with grails!

    What doesn’t seem to work in this scenario is the auto completion in IntelliJ. For example or inside a gsp-file of the main application doesn’t see the views from my plugins. Also IntelliJ reports errors (red text) for these occurrences, because it doesn’t seem to find the plugin views.

    Is there an option to let IntelliJ know of views inside plugins? Or is this simply a bug in IntelliJ?

    Reply
    1. David

      Seems like the grails tags were removed from my post. What I wanted to say was the following:

      For example (g:message) or (g:render temlate=””) inside a gsp-file of the main application doesn’t see the views from my plugins.

      Reply
  7. sdelamo Post author

    David, Can you upload a sample project to Github ? If it is just an IntelliJ IDEA issue, you maybe able to get a better answer in Grails Slack. There is a IntelliJ channel dedicated to that. https://grails.signup.team

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *