A Feel of Groovy DSL with an Example from Gradle

If you have used Gradle build system, probably you have already been used and benefited from the DSL capabilities of Groovy language. In this post, let us take a peek at such an example.

Consider the following piece of DSL from a Gradle configuration.

plugins {
    id 'java'
}

The plugins section configures the Gradle plugins used in your build, and you mention all the plugins that you want in your build. Let us understand what goes under the hood to get this working with a low sophisticated example.

def plugins (def pluginConfig) {
    println "Plugins called"
}

//--------------- DSL
plugins {
    id 'java'
}

When we run the above code, we see "Plugins called" printed on the console. We understand that plugins is a method. Take a look at the alternate way to invoke plugins method.

plugins({
    id 'java'
})

Now it is clear that the content inside the '{}' is passed as an argument to method plugins. These block of code are called closures in Groovy. Whenever your methods last argument (it could be the only one as well) is a closure, Groovy allows you to write them outside the parentheses. So the above code can be written as follows.

plugins() {
    id 'java'
}

Additionally, Groovy allows you to omit the parentheses if your method has at least one argument, which is the case here. Thus we arrive at the original code used in our DSL.

plugins {
    id 'java'
}

Since we want to add the plugins to the project, let's create a structure to hold the project information.

class Project {
    List<Plugin> plugins = []
}

import groovy.transform.ToString

@ToString(includePackage = false)
class Plugin {
    String id
}

Now we are left with the part to create an instance of Plugin class and add it to plugins of Project. As you might have guessed already id 'java' means we call the method id with 'java' as the argument. Let's add this method to the Project class, as it seems to be a good place to host this method.

class Project {
    List<Plugin> plugins = []

    def id(String pluginId) {
        println "Adding plugin $pluginId"
        plugins << new Plugin(id: pluginId)
    }
}

Let us update our plugins method to invoke the closure on the project instance.

def plugins(def pluginConfig) {
    Project project = new Project()
    println "Plugins called"
    project.with pluginConfig
}

You should see "Adding plugin java" printed on the console. Here we use with method to ensure the closure we received as the argument gets called for the 'project' object (not on the instance of this script)

Let's add another plugin to our DSL and ensure this works as expected. Let's also print all the plugins from the project.

def plugins(def pluginConfig) {
    Project project = new Project()
    println "Plugins called"
    project.with pluginConfig
    println project.plugins
}

//---------------
plugins{
    id 'java'
    id 'groovy'
}

You should see the following on the console

Plugins called
Adding plugin java
Adding plugin groovy
[Plugin(java), Plugin(groovy)]

Of course, the full-fledged DSL brings in more sophistication. My attempt here was to convey the idea with the simplest possible examples.

Show Comments