Mastering Grails 3
Plugins
Álvaro Sánchez-Mariscal
Álvaro Sánchez-Mariscal
Software Engineer
Grails Development Team
sanchezmariscala@ociweb.com
OCI is the new home of Grails
More at ociweb.com/grails
The Basics
Creating a Grails 3 plugin
$ grails create-plugin myWebPlugin
| Plugin created at /private/tmp/myWebPlugin
$ grails create-plugin myPlugin -profile plugin
| Plugin created at /private/tmp/myPlugin
Understanding profiles
• A profile defines:
• Project’s build.gradle.
• Commands: create-domain-class,
run-app, etc.
• Features: hibernate, json-views, etc.
• Skeleton: files and folders.
plugin vs. web-plugin
Trim your plugin!
Keep clean
• Start with the plugin profile whenever
possible.
• Remove empty and/or unwanted files/
folders.
• Otherwise, the burtbeckwith bot will
send you a cleanup pull request!
The burtbeckwith bot
The burtbeckwith bot
• Watches messy plugin repos and sends a PR
to clean them up.
• 14 pull requests in the last 3 months!
• Likely hundreds in the last years!
The minimal plugin
• Folder containing:
• build.gradle
• src/main/groovy with plugin descriptor.
• Empty grails-app folder.
The plugin descriptor
• A class inside src/main/groovy. Extends
grails.plugins.Plugin.
• Can override methods to define behaviour
in the plugin lifecycle.
• Syntax has changed a bit from Grails 2.
Plugins features
Plugin configuration
• A plugin can define:
• Configuration values for the host Grails app.
• One of plugin.yml or plugin.groovy.
• Configuration for running the plugin as an
application, to test it.
• application.yml / application.groovy.
Excluding content
• In the plugin descriptor:
• In build.gradle:
// resources that are excluded from plugin packaging

def pluginExcludes = [

'**/com/example/myplugin/tests/**'

]
jar {

exclude 'com/example/myplugin/tests/**/**'

}
Command Line extensions
• Use create-script for code generation
commands.
• Runnable with the Grails CLI.
• Use create-command for interacting with a
loaded Grails application.
• Runnable with the Grails CLI or as a Gradle task.
Scripts
• Base class:
org.grails.cli.profile.commands.script.GroovyScriptCommand
import org.grails.cli.interactive.completers.DomainClassCompleter



description( "Generates a controller that performs REST operations" ) {

usage "grails generate-resource-controller [DOMAIN CLASS]"

argument name:'Domain Class', description:"The name of the domain class", required:true

completer DomainClassCompleter

flag name:'force', description:"Whether to overwrite existing files"

}



if(args) {

generateController(*args)

generateViews(*args)

generateUnitTest(*args)

generateFunctionalTest(*args)

} else {

error "No domain class specified"

}
Commands
import grails.dev.commands.ApplicationCommand

import grails.dev.commands.ExecutionContext



class MyCommand implements ApplicationCommand {



@Override

boolean handle(ExecutionContext ctx) {

def dataSource = applicationContext.getBean(DataSource)

//Run some SQL...



return true

}



}
Enhancing artefacts
import grails.artefact.Enhances

import groovy.transform.CompileStatic



@Enhances(['Controller', 'Service'])

@CompileStatic

trait DateSupport {



Date now() {

return new Date()

}



}
Modularisation
Modularisation
• If your plugin becomes to grow, you might
end up creating a monolith.
• You can modularise your plugins as you
would do with your apps.
Modularisation
Monolithic plugin
Multi-module plugin
Modularisation
• Benefits:
• Optional dependencies.
• Smaller JAR files.
• Build logic reuse.
Modularisation setup
• settings.gradle:
include ‘myPlugin-core', ‘myPlugin-domain' //etc
Modularisation setup
• Root build.gradle:
allprojects {

apply plugin:"idea"

}



subprojects { Project project ->

ext {

grailsVersion = project.grailsVersion

gradleWrapperVersion = project.gradleWrapperVersion

}



repositories {

//Common repos

}



version "1.0.0.M1"

group "org.grails.plugins"



apply plugin: "org.grails.grails-plugin"



dependencies {

//Common deps

}

}
Modularisation setup
• Sub-module build.gradle:
dependencyManagement {

imports {

mavenBom "org.grails:grails-bom:$grailsVersion"

}

applyMavenExclusions false

}



dependencies {

compile project(":myPlugin-core")



compile "com.example:library:1.0.0"

}
Aggregating Docs
task aggregateGroovyDoc(type: Groovydoc) {

group = JavaBasePlugin.DOCUMENTATION_GROUP



dependsOn subprojects.groovydoc

source subprojects.groovydoc.source

destinationDir file("${buildDir}/docs/groovydoc")

classpath = files(subprojects.groovydoc.classpath)

groovyClasspath = files(subprojects.groovydoc.groovyClasspath)

}
Publishing
Artifact publication
• Snapshots:
• Using the artifactory Gradle plugin.
• Published in OJO (oss.jfrog.org).
• Releases:
• Using the grails-plugin-publish Gradle plugin.
• Published in Bintray.
Bintray setup
• For Snapshots:
Build setup
artifactory {

contextUrl = 'http://oss.jfrog.org'

publish {

repository {

repoKey = 'oss-snapshot-local'

username = bintrayUser

password = bintrayKey

}

defaults {

publications('maven')

}

}

}



artifactoryPublish {

dependsOn sourcesJar, javadocJar

}
grailsPublish {

user = bintrayUser

key = bintrayKey

portalUser = pluginPortalUser

portalPassword = pluginPortalPassword



repo = 'plugins'

githubSlug = 'alvarosanchez/my-plugin'

license = 'APACHE 2.0'

title = "My Plugin"

desc = "A very cool Grails plugin"

developers = [

alvarosanchez: "Alvaro Sanchez-Mariscal"

]

}
• For Releases:
Build setup
Build setup
• Define rootProject.name in
settings.gradle.
• Define credentials in
~/.gradle/gradle.properties.
Running it
• Snapshot publishing:
• Release publishing:
$ ./gradlew artifactoryPublish
$ ./gradlew publishPlugin notifyPluginPortal
Plugin portals
• Once your packages are published in your
Bintray repo, go to https://
bintray.com/grails/plugins and click
on “Include my package”.
• Grails 3: http://grails.org/plugins.html
• Grails 2: http://grails.org/plugins
Testing
Testing with a profile
• You can create a profile and use it as a TCK
for your plugin:
• Create test apps from that profile.
• Apps come with a set of tests.
• Use features to test different configurations.
Profile descriptor
description: Creates a test app for Spring Security REST plugin

build:

excludes:

- org.grails.grails-core

dependencies:

compile:

- "org.grails.plugins:spring-security-rest:${pluginVersion}"

- "org.grails:grails-datastore-rest-client:5.0.0.RC3"

testCompile:

- "com.codeborne:phantomjsdriver:1.2.1"

- "org.seleniumhq.selenium:selenium-api:2.47.1"

- "org.seleniumhq.selenium:selenium-firefox-driver:2.47.1"
profile.yml.tmpl
Feature descriptor
description: First configuration of GORM

dependencies:

build:

- "org.grails.plugins:hibernate4:5.0.0.RC2"

compile:

- "org.grails.plugins:hibernate4"

- "org.hibernate:hibernate-ehcache"

- "org.grails.plugins:spring-security-rest-gorm:${pluginVersion}"

runtime:

- "com.h2database:h2"
features/gorm1/feature.yml.tmpl
Build setup
task generateProfileConfig << {

copy {

from 'profile.yml.tmpl'

into '.'

rename { String fileName -> fileName.replaceAll '.tmpl', '' }

expand pluginVersion: project.version

}



file('features').eachDir { feature ->

copy {

from "features/${feature.name}/feature.yml.tmpl"

into "features/${feature.name}/"

rename { String fileName -> fileName.replaceAll '.tmpl', '' }

expand pluginVersion: project.version

}

}

}



compileProfile.dependsOn generateProfileConfig
Skeleton
• Put in the skeleton all your test files and
resources.
• You can use features to have different sets of
tests, resources and configuration.
• Define global configuration values in profile’s
root skeleton folder.
Test them all!
for feature in `ls ../spring-security-rest-testapp-profile/features/`

do

grails create-app -profile 

org.grails.plugins:spring-security-rest-testapp-profile:$pluginVersion 

-features $feature $feature && cd $feature && ./gradlew check && cd ..

done
Use case: the Spring
Security REST plugin
Mange tak!
Álvaro Sánchez-Mariscal

Mastering Grails 3 Plugins - GR8Conf EU 2016

  • 1.
  • 2.
    Álvaro Sánchez-Mariscal Software Engineer GrailsDevelopment Team sanchezmariscala@ociweb.com
  • 4.
    OCI is thenew home of Grails More at ociweb.com/grails
  • 5.
  • 6.
    Creating a Grails3 plugin $ grails create-plugin myWebPlugin | Plugin created at /private/tmp/myWebPlugin $ grails create-plugin myPlugin -profile plugin | Plugin created at /private/tmp/myPlugin
  • 7.
    Understanding profiles • Aprofile defines: • Project’s build.gradle. • Commands: create-domain-class, run-app, etc. • Features: hibernate, json-views, etc. • Skeleton: files and folders.
  • 8.
  • 9.
  • 10.
    Keep clean • Startwith the plugin profile whenever possible. • Remove empty and/or unwanted files/ folders. • Otherwise, the burtbeckwith bot will send you a cleanup pull request!
  • 11.
  • 12.
    The burtbeckwith bot •Watches messy plugin repos and sends a PR to clean them up. • 14 pull requests in the last 3 months! • Likely hundreds in the last years!
  • 13.
    The minimal plugin •Folder containing: • build.gradle • src/main/groovy with plugin descriptor. • Empty grails-app folder.
  • 14.
    The plugin descriptor •A class inside src/main/groovy. Extends grails.plugins.Plugin. • Can override methods to define behaviour in the plugin lifecycle. • Syntax has changed a bit from Grails 2.
  • 15.
  • 16.
    Plugin configuration • Aplugin can define: • Configuration values for the host Grails app. • One of plugin.yml or plugin.groovy. • Configuration for running the plugin as an application, to test it. • application.yml / application.groovy.
  • 17.
    Excluding content • Inthe plugin descriptor: • In build.gradle: // resources that are excluded from plugin packaging
 def pluginExcludes = [
 '**/com/example/myplugin/tests/**'
 ] jar {
 exclude 'com/example/myplugin/tests/**/**'
 }
  • 18.
    Command Line extensions •Use create-script for code generation commands. • Runnable with the Grails CLI. • Use create-command for interacting with a loaded Grails application. • Runnable with the Grails CLI or as a Gradle task.
  • 19.
    Scripts • Base class: org.grails.cli.profile.commands.script.GroovyScriptCommand importorg.grails.cli.interactive.completers.DomainClassCompleter
 
 description( "Generates a controller that performs REST operations" ) {
 usage "grails generate-resource-controller [DOMAIN CLASS]"
 argument name:'Domain Class', description:"The name of the domain class", required:true
 completer DomainClassCompleter
 flag name:'force', description:"Whether to overwrite existing files"
 }
 
 if(args) {
 generateController(*args)
 generateViews(*args)
 generateUnitTest(*args)
 generateFunctionalTest(*args)
 } else {
 error "No domain class specified"
 }
  • 20.
    Commands import grails.dev.commands.ApplicationCommand
 import grails.dev.commands.ExecutionContext
 
 classMyCommand implements ApplicationCommand {
 
 @Override
 boolean handle(ExecutionContext ctx) {
 def dataSource = applicationContext.getBean(DataSource)
 //Run some SQL...
 
 return true
 }
 
 }
  • 21.
    Enhancing artefacts import grails.artefact.Enhances
 importgroovy.transform.CompileStatic
 
 @Enhances(['Controller', 'Service'])
 @CompileStatic
 trait DateSupport {
 
 Date now() {
 return new Date()
 }
 
 }
  • 22.
  • 23.
    Modularisation • If yourplugin becomes to grow, you might end up creating a monolith. • You can modularise your plugins as you would do with your apps.
  • 24.
  • 25.
    Modularisation • Benefits: • Optionaldependencies. • Smaller JAR files. • Build logic reuse.
  • 26.
    Modularisation setup • settings.gradle: include‘myPlugin-core', ‘myPlugin-domain' //etc
  • 27.
    Modularisation setup • Rootbuild.gradle: allprojects {
 apply plugin:"idea"
 }
 
 subprojects { Project project ->
 ext {
 grailsVersion = project.grailsVersion
 gradleWrapperVersion = project.gradleWrapperVersion
 }
 
 repositories {
 //Common repos
 }
 
 version "1.0.0.M1"
 group "org.grails.plugins"
 
 apply plugin: "org.grails.grails-plugin"
 
 dependencies {
 //Common deps
 }
 }
  • 28.
    Modularisation setup • Sub-modulebuild.gradle: dependencyManagement {
 imports {
 mavenBom "org.grails:grails-bom:$grailsVersion"
 }
 applyMavenExclusions false
 }
 
 dependencies {
 compile project(":myPlugin-core")
 
 compile "com.example:library:1.0.0"
 }
  • 29.
    Aggregating Docs task aggregateGroovyDoc(type:Groovydoc) {
 group = JavaBasePlugin.DOCUMENTATION_GROUP
 
 dependsOn subprojects.groovydoc
 source subprojects.groovydoc.source
 destinationDir file("${buildDir}/docs/groovydoc")
 classpath = files(subprojects.groovydoc.classpath)
 groovyClasspath = files(subprojects.groovydoc.groovyClasspath)
 }
  • 30.
  • 31.
    Artifact publication • Snapshots: •Using the artifactory Gradle plugin. • Published in OJO (oss.jfrog.org). • Releases: • Using the grails-plugin-publish Gradle plugin. • Published in Bintray.
  • 32.
  • 33.
    • For Snapshots: Buildsetup artifactory {
 contextUrl = 'http://oss.jfrog.org'
 publish {
 repository {
 repoKey = 'oss-snapshot-local'
 username = bintrayUser
 password = bintrayKey
 }
 defaults {
 publications('maven')
 }
 }
 }
 
 artifactoryPublish {
 dependsOn sourcesJar, javadocJar
 }
  • 34.
    grailsPublish {
 user =bintrayUser
 key = bintrayKey
 portalUser = pluginPortalUser
 portalPassword = pluginPortalPassword
 
 repo = 'plugins'
 githubSlug = 'alvarosanchez/my-plugin'
 license = 'APACHE 2.0'
 title = "My Plugin"
 desc = "A very cool Grails plugin"
 developers = [
 alvarosanchez: "Alvaro Sanchez-Mariscal"
 ]
 } • For Releases: Build setup
  • 35.
    Build setup • DefinerootProject.name in settings.gradle. • Define credentials in ~/.gradle/gradle.properties.
  • 36.
    Running it • Snapshotpublishing: • Release publishing: $ ./gradlew artifactoryPublish $ ./gradlew publishPlugin notifyPluginPortal
  • 37.
    Plugin portals • Onceyour packages are published in your Bintray repo, go to https:// bintray.com/grails/plugins and click on “Include my package”. • Grails 3: http://grails.org/plugins.html • Grails 2: http://grails.org/plugins
  • 38.
  • 39.
    Testing with aprofile • You can create a profile and use it as a TCK for your plugin: • Create test apps from that profile. • Apps come with a set of tests. • Use features to test different configurations.
  • 40.
    Profile descriptor description: Createsa test app for Spring Security REST plugin
 build:
 excludes:
 - org.grails.grails-core
 dependencies:
 compile:
 - "org.grails.plugins:spring-security-rest:${pluginVersion}"
 - "org.grails:grails-datastore-rest-client:5.0.0.RC3"
 testCompile:
 - "com.codeborne:phantomjsdriver:1.2.1"
 - "org.seleniumhq.selenium:selenium-api:2.47.1"
 - "org.seleniumhq.selenium:selenium-firefox-driver:2.47.1" profile.yml.tmpl
  • 41.
    Feature descriptor description: Firstconfiguration of GORM
 dependencies:
 build:
 - "org.grails.plugins:hibernate4:5.0.0.RC2"
 compile:
 - "org.grails.plugins:hibernate4"
 - "org.hibernate:hibernate-ehcache"
 - "org.grails.plugins:spring-security-rest-gorm:${pluginVersion}"
 runtime:
 - "com.h2database:h2" features/gorm1/feature.yml.tmpl
  • 42.
    Build setup task generateProfileConfig<< {
 copy {
 from 'profile.yml.tmpl'
 into '.'
 rename { String fileName -> fileName.replaceAll '.tmpl', '' }
 expand pluginVersion: project.version
 }
 
 file('features').eachDir { feature ->
 copy {
 from "features/${feature.name}/feature.yml.tmpl"
 into "features/${feature.name}/"
 rename { String fileName -> fileName.replaceAll '.tmpl', '' }
 expand pluginVersion: project.version
 }
 }
 }
 
 compileProfile.dependsOn generateProfileConfig
  • 43.
    Skeleton • Put inthe skeleton all your test files and resources. • You can use features to have different sets of tests, resources and configuration. • Define global configuration values in profile’s root skeleton folder.
  • 44.
    Test them all! forfeature in `ls ../spring-security-rest-testapp-profile/features/`
 do
 grails create-app -profile 
 org.grails.plugins:spring-security-rest-testapp-profile:$pluginVersion 
 -features $feature $feature && cd $feature && ./gradlew check && cd ..
 done
  • 45.
    Use case: theSpring Security REST plugin
  • 46.