Loading...

November 23, 2016

Gradle Goodness: Delegate Build And Run Actions To Gradle In IntelliJ IDEA

IntelliJ IDEA 2016.3 introduces the option to delegate the IDE build and run actions to Gradle. So if we invoke the Build Project action from the Build menu IntelliJ IDEA invokes the correct tasks using Gradle. Also the Run and Debug actions from the Run menu are executed with Gradle.

If we want this behaviour we need to changed the preferences of IntelliJ IDEA. We must open the preferences dialog window and then go to Build, Execution, Deployment | Build Tools | Gradle | Runner. Here we check the option Delegate IDE build/run actions to gradle and we close the window:


Let's open a simple Java project with a Gradle build file in IDEA. Next we invoke the Build Project action with the shortcut key Cmd+F9 (on macOS, other operating systems probably have a different shortcut key). Our code is compiled and we can open the Run view to see the output:


We have a Java class in our project with a main method we want to run. We use the Run action (for example using the shortcut key Ctrl+R on macOS) and IDEA uses Gradle's JavaExec task to run the class. Also this time we can see the output in the Run view:


Written with Gradle 3.2 and IntelliJ IDEA 2016.3.

Gails Goodness: Enabling Grails View In IntelliJ IDEA For Grails 3

IntelliJ IDEA 2016.3 re-introduced the Grails view for Grails 3 applications. Grails 2 applications already were supported with a Grails view in IDEA. Using this view we get an overview of all the Grails artifacts like controller, services, views and more. We can easily navigate to the the class files we need. Now this view is also available for Grails 3 applications.

To enable the view we must click on the view selector in the project view:


We select the Grails option and we get an nice overview of our Grails project in the Grails view:


Also the New action is context sensitive in the Grails view. If we right click on the Services node we can see the option to create a new service class:


If we right click on the root node we get the option to create Grails artifacts:


Written with IntelliJ IDEA 2016.3 and Grails 3.2.2.

November 21, 2016

Gradle Goodness: Creation Rules For Rule Based Model Configuration Using Model DSL

In a previous post we learned how to create a class that extends RuleSource with methods that define rules on how to create and change objects in the Gradle model space. With the model configuration block in a build file we can also define creation rules for Rule based model configuration.

In the following build file we define a model block and define a creation rule for creating the object versionInfo of type VersionFile. Also we add a new task to the tasks object of type ModelMap<Task>. To reference another object from the model space inside the Closure for a creation rule we use the syntax $.<objectName>:

// File: model.gradle
import mrhaki.gradle.VersionFile
import mrhaki.gradle.VersionFileTask

model {
    // Creation rule to create object
    // with name versionInfo (name of the method)
    // and type VersionFile.
    versionInfo(VersionFile) {
        // Set default value for version to project.version.
        version = project.version
        
        // Set default outputFile to 
        // file version.txt in build directory.
        outputFile = project.file("${buildDir}/version.txt")
    }
    
    // tasks is of type ModelMap<Task>.
    tasks {
        // Create task generationVersionFile 
        // with custom task type VersionFileTask.
        create('generateVersionFile', VersionFileTask) {
            // Set properties with values
            // from managed object versionInfo,
            // that we created with the creation rule
            // versionInfo(VersionFile).
            // We use the special $.<name> notation
            // to reference object from the model space.
            version = $.versionInfo.version
            outputFile = $.versionInfo.outputFile
        }
    }
}

The supporting VersionFile class is a Gradle managed object:

// File: buildSrc/src/main/groovy/mrhaki/gradle/VersionFile.groovy
package mrhaki.gradle

import org.gradle.model.Managed

@Managed
interface VersionFile {
    String getVersion() 
    void setVersion(final String version) 

    File getOutputFile() 
    void setOutputFile(final File outputFile) 
}

And the custom task is very straight forward for our example:

// File: buildSrc/src/main/groovy/mrhaki/gradle/VersionFileTask.groovy
package mrhaki.gradle

import org.gradle.api.DefaultTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction

/**
 * Simple task to save the value for the
 * {@link #version} property in a file.
 * The file is set with the {@link #outputFile}
 * property.
 */
class VersionFileTask extends DefaultTask {

    /**
     * Value for version to be saved.
     */
    @Input
    String version

    /**
     * Output file to store version value in.
     */
    @OutputFile
    File outputFile

    /**
     * Actual task actions to save the value
     * for {@link #version} in {@link #outputFile}.
     */
    @TaskAction
    void generateVersionFile() {
        outputFile.parentFile.mkdirs()
        outputFile.text = version
    }

}

We can use the model rules in our build script when we use apply from: 'model.gradle' in our build script. In our example we also add a model configuration block to configure the versionInfo object:

// File: build.gradle
apply from: 'model.gradle'

model {
    // Configuration rule for the versionInfo
    // object, that is created with the
    // creation rule from 'model.gradle'.
    versionInfo {
        version = '3.0.0.RELEASE'
    }   
}

Let's invoke the model task and check the output to see where are rules are applied:

$ gradle -q model
...
 tasks
      | Type:           org.gradle.model.ModelMap<org.gradle.api.Task>
      | Creator:        Project.<init>.tasks()
      | Rules:
         ⤷ tasks { ... } @ model.gradle line 18, column 5
...
    + generateVersionFile
          | Type:       mrhaki.gradle.VersionFileTask
          | Value:      task ':generateVersionFile'
          | Creator:    create(generateVersionFile, mrhaki.gradle.VersionFileTask) { ... } @ model.gradle line 21, column 9
          | Rules:
             ⤷ copyToTaskContainer
...
+ versionInfo
      | Type:           mrhaki.gradle.VersionFile
      | Creator:        versionInfo(mrhaki.gradle.VersionFile) { ... } @ model.gradle line 8, column 5
      | Rules:
         ⤷ versionInfo { ... } @ build.gradle line 47, column 5
    + outputFile
          | Type:       java.io.File
          | Value:      /Users/mrhaki/Projects/mrhaki.com/blog/posts/samples/gradle/versionrule/build/version.txt
          | Creator:    versionInfo(mrhaki.gradle.VersionFile) { ... } @ model.gradle line 8, column 5
    + version
          | Type:       java.lang.String
          | Value:      3.0.0.RELEASE
          | Creator:    versionInfo(mrhaki.gradle.VersionFile) { ... } @ model.gradle line 8, column 5
...
$

Written with Gradle 3.2.

Gradle Goodness: Getting Project Information Into Rule Based Model Configuration

Rule based model configuration in Gradle allows us to have a graph of objects with dependencies that are resolved by Gradle. To make this work Gradle needs to know about the object in this model space. The model space is populated with objects of our own and with objects from Gradle. At time of writing this blog post we can not interact with the Gradle Project object in our rule based model configuration. It is not officially part of the model space. Probably in the future this might change and will the Project object managed by Gradle be part of the model space. Which means we can use then a Project object as input parameter for any rule methods we have. For now the official way to pass project information to the rule based model space is via the model configuration block in our build script. The model configuration block can be used to set properties on objects with values from our Project object.

In the following example we have VersionFile object that is part of the model space.

// File: buildSrc/src/main/groovy/mrhaki/gradle/VersionFile.groovy
package mrhaki.gradle

import org.gradle.model.Managed

@Managed
interface VersionFile {
    String getVersion() 
    void setVersion(final String version) 

    File getOutputFile() 
    void setOutputFile(final File outputFile) 
}

The rules to create the VersionFile object and to use this object to add a new task are defined in the file VersionFileTaskRules:

// File: buildSrc/src/main/groovy/mrhaki/gradle/VersionFileTaskRules.groovy
package mrhaki.gradle

import org.gradle.api.Task
import org.gradle.model.Model
import org.gradle.model.ModelMap
import org.gradle.model.Mutate
import org.gradle.model.RuleSource

class VersionFileTaskRules extends RuleSource {

    @Model
    void versionFile(final VersionFile versionFile) {}

    @Mutate
    void createVersionFileTask(final ModelMap<Task> tasks, final VersionFile versionFile) {
        tasks.create('generateVersionFile', VersionFileTask) { task ->
            task.version = versionFile.version
            task.outputFile = versionFile.outputFile
        }
    }

}

The custom task is not very exiting:

// File: buildSrc/src/main/groovy/mrhaki/gradle/VersionFileTask.groovy
package mrhaki.gradle

import org.gradle.api.DefaultTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction

/**
 * Simple task to save the value for the
 * {@link #version} property in a file.
 * The file is set with the {@link #outputFile}
 * property.
 */
class VersionFileTask extends DefaultTask {

    /**
     * Value for version to be saved.
     */
    @Input
    String version

    /**
     * Output file to store version value in.
     */
    @OutputFile
    File outputFile

    /**
     * Actual task actions to save the value
     * for {@link #version} in {@link #outputFile}.
     */
    @TaskAction
    void generateVersionFile() {
        outputFile.parentFile.mkdirs()
        outputFile.text = version
    }

}

If we want to set the version property with the project.version value we use the model configuration in our build script:

// File: build.gradle
apply plugin: mrhaki.gradle.VersionFileTaskRules

// Configure model space.
model {

    // Configure VersionFile instance created 
    // by method versionFile() from VersionFileTaskRules.
    versionFile {

        // Set value for version property of VersionFile
        // using the project.version property.
        version =  project.version

        // Set value for outputFile property of VersionFile,
        // using the project.buildDir property and 
        // project.file method.
        outputFile = project.file("${buildDir}/version.txt")
    }

}

// Set project version
version = '1.0.1.RELEASE'

We run the model task to see that our VersionFile object has the right property values:

$ gradle -q model
...
+ versionFile
      | Type:           mrhaki.gradle.VersionFile
      | Creator:        VersionFileTaskRules#versionFile(VersionFile)
      | Rules:
         ⤷ versionFile { ... } @ build.gradle line 9, column 5
    + outputFile
          | Type:       java.io.File
          | Value:      /Users/mrhaki/Projects/mrhaki.com/blog/posts/samples/gradle/versionrule/build/version.txt
          | Creator:    VersionFileTaskRules#versionFile(VersionFile)
    + version
          | Type:       java.lang.String
          | Value:      1.0.1.RELEASE
          | Creator:    VersionFileTaskRules#versionFile(VersionFile)
$

Because the Project object is not part of the managed model space, we can not give the version property of VersionFile a default value that is the project.version property value. In our example we can give the version property of the VersionFileTask a default value that is project.version. So if the we don't use the model configuration block the version value of the Project object is used in the task. Unfortunately we cannot see this default from the model task.

There are some internal objects created by Gradle that represent the project build directory and the Project object. The build directory can be accessed as input argument using the syntax @Path('buildDir') File and the project as ProjectIdentifier. But these are for internal use and can be removed or changed without warning. So to have a set of rules for the model space that will be useable in future Gradle versions we must not rely on those objects.

Another workaround, which might not work in the future, would be to rely on the ExtensionContainer object that is available in the model space. Gradle adds this as a hidden object, so also here we need to keep in mind this solution might not work in future Gradle versions. We could write an extension that wraps the Project object and add it to the ExtensionContainer. Next we use the ExtensionContainer as input argument for a rule method to get the extension with the wrapper Project object. The create the extension we use a custom Gradle plugin:

// File: buildSrc/src/main/groovy/mrhaki/gradle/VersionFilePlugin.groovy
package mrhaki.gradle

import groovy.transform.TupleConstructor
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.plugins.ExtensionContainer
import org.gradle.model.Defaults
import org.gradle.model.Model
import org.gradle.model.ModelMap
import org.gradle.model.Mutate
import org.gradle.model.RuleSource

class VersionFilePlugin implements Plugin<Project> {
    
    @Override
    void apply(final Project project) {
        // Create extension 'projectWrapper' and add to ExtensionContainer.
        project.extensions.create('projectWrapper', ProjectWrapper, project)        
    }
    
    // The rules to work with the model space.
    static class VersionTaskRules extends RuleSource {
        @Model
        void versionFile(final VersionFile versionFile) {}

        /**
         * Set default value with {@link VersionFile#setVersion(java.lang.String)} 
         * and {@link VersionFile#setOutputFile(java.io.File)}.
         * 
         * @param versionFile Object to set defaults for is {@link VersionFile}.
         * @param extensionContainer {@link ExtensionContainer} is managed by Gradle. 
         */
        @Defaults
        void setVersionFileDefaults(
                final VersionFile versionFile,
                final ExtensionContainer extensionContainer) {
            
            // Get ProjectWrapper and enclosed Project object.
            final ProjectWrapper projectWrapper = extensionContainer.getByType(ProjectWrapper)
            final Project project = projectWrapper.project
            
            // Set version and outputFile properties with project information.
            versionFile.version = project.version
            versionFile.outputFile = new File(project.buildDir, 'version.txt')
        }

        @Mutate
        void createVersionFileTask(final ModelMap<Task> tasks, final VersionFile versionFile) {
            tasks.create('generateVersionFile', VersionFileTask) { task ->
                task.version = versionFile.version
                task.outputFile = versionFile.outputFile
            }
        }

    }
}

@TupleConstructor
class ProjectWrapper {
    final Project project
}

In our build file we apply the plugin and do not use the model configuration:

// File: build.gradle
apply plugin: mrhaki.gradle.VersionFilePlugin

When we run the model task we see the setVersionFileDefaults method is used to set VersionFile properties:

$ gradle -q model
...
+ versionFile
      | Type:           mrhaki.gradle.VersionFile
      | Creator:        VersionFilePlugin.VersionTaskRules#versionFile(VersionFile)
      | Rules:
         ⤷ VersionFilePlugin.VersionTaskRules#setVersionFileDefaults(VersionFile, ExtensionContainer)
    + outputFile
          | Type:       java.io.File
          | Value:      /Users/mrhaki/Projects/mrhaki.com/blog/posts/samples/gradle/versionrule/build/version.txt
          | Creator:    VersionFilePlugin.VersionTaskRules#versionFile(VersionFile)
    + version
          | Type:       java.lang.String
          | Value:      2.0.1
          | Creator:    VersionFilePlugin.VersionTaskRules#versionFile(VersionFile)
...
$

Written with Gradle 3.2.

Gradle Goodness Notebook Updated

Gradle Goodness Notebook has been updated. If you have bought the book you can download the latest version for free. The following blog posts have been added:

  • Running Groovy Scripts Like From Groovy Command Line
  • Specify Spock As Test Framework At Initialization
  • Build Script Using Java Syntax
  • Create Objects Using DSL With Domain Object Containers
  • Using Nested Domain Object Containers
  • Inter-Project Artifact Dependencies
  • Lazy Task Properties
  • Methods Generated For Setting Task Properties
  • Adding Custom Extension To Tasks
  • Add Spring Facet To IntelliJ IDEA Module
  • Configure IntelliJ IDEA To Use Gradle As Testrunner
  • Enable Compiler Annotation Processing For IntelliJ IDEA
  • Set VCS For IntelliJ IDEA In Build File
  • Source Sets As IntelliJ IDEA Modules
  • Get Property Value With findProperty
  • Running All Tests From One Package
  • Check The Gradle Daemon Status
  • Lazy Project Property Evaluation
  • Change Gradle Wrapper Script Name
  • Specify Wrapper Version and Distribution Type From Command Line
  • Use Command Line Options With Custom Tasks

November 18, 2016

Grails Goodness Notebook Is Updated

Grails Goodness Notebook has been updated with the latest blog posts. If you have purchased the book before you can download the latest version of the book for free.

  • Saving Server Port In A File
  • Creating A Runnable Distribution
  • Change Version For Dependency Defined By BOM
  • Use Random Server Port In Integration Tests
  • Running Tests Continuously
  • Add Git Commit Information To Info Endpoint
  • Adding Custom Info To Info Endpoint
  • Add Banner To Grails 3.1 Application
  • Creating A Fully Executable Jar
  • Pass JSON Configuration Via Command Line

Groovy Goodness Notebook Is Updated

Groovy Goodness Notebook which contains the Groovy blog posts in an organised form is updated with the following blog posts. If you have purchased the book before you get the update for free.

  • Using Tuples
  • Creating Files And Directories With Nice DSL Using FileTreeBuilder
  • Make Class Cloneable With @AutoClone
  • IntelliJ IDEA Formatting Of Closure Chains
  • Represent Map As String
  • Turn A Map Or List As String To Map Or List
  • Customise Log AST Annotations

November 16, 2016

Gradle Goodness: Validate Model In Rule Based Model Configuration

Rule based model configuration gives Gradle more knowledge about the objects and their dependencies. This information can be used by Gradle to optimise the build process. We define rules on how we want Gradle to create objects and how we want to mutate objects in a class that extends RuleSource. We can also add rules to validate objects available in the Gradle model space. We use the @Validate annotation on methods that have validation logic. The first argument of the method is of the type of the object we want to validate. This type must be managed by Gradle.

In the following example we use the sample from a previous post. In this sample we have a VersionFile class that is managed by Gradle. The class has a version and outputFile property. The version must be set and must start with a v. The outputFile property is also required.

// File: buildSrc/src/main/groovy/mrhaki/gradle/VersionFileTaskRules.groovy
package mrhaki.gradle

import org.gradle.api.GradleException
import org.gradle.api.Task
import org.gradle.model.Model
import org.gradle.model.ModelMap
import org.gradle.model.Mutate
import org.gradle.model.RuleSource
import org.gradle.model.Validate

class VersionFileTaskRules extends RuleSource {

    @Model
    void versionFile(final VersionFile versionFile) {}

    /**
     * Version property of {@link VersionFile} must have a value and the value
     * must start with a 'v'.
     * 
     * @param versionFile Gradle managed {@link VersionFile} object we want to validate
     */
    @Validate
    void validateVersionFileVersion(final VersionFile versionFile) {
        def message = """\
            Property VersionFile.version is not set. Set a value in the model configuration.
            
            Example:
            -------
            model {
                versionFile {
                    version = 'v1.0.0'
                }
            }
            """.stripIndent()
        checkAssert(message) {
            assert versionFile.version
        }
        
        message = """\
            Property VersionFile.version should start with 'v'. Set a value starting with 'v' in the model configuration.
            
            Example:
            -------
            model {
                versionFile {
                    version = 'v${versionFile.version}'
                }
            }
            """.stripIndent()
        checkAssert(message) {
            assert versionFile.version.startsWith('v')
        }
    }

    /**
     * Outputfile property of {@link VersionFile} must have a value.
     *
     * @param versionFile Gradle managed {@link VersionFile} object we want to validate
     */
    @Validate
    void validateVersionFileOutputFile(final VersionFile versionFile) {
        def message = """\
            Property VersionFile.outputFile is not set. Set a value in the model configuration.

            Example:
            -------
            model {
                versionFile {
                    outputFile = project.file("\${buildDir}/version.txt")
                }
            }
            """.stripIndent()
        checkAssert(message) {
            assert versionFile.outputFile
        }
    }

    /**
     * Run assert statement in assertion Closure. If the assertion fails
     * we catch the exception. We use the message with the error appended with an user message
     * and throw a {@link GradleException}. 
     * 
     * @param message User message to be appended to assertion error message
     * @param assertion Assert statement(s) to run
     */
    private void checkAssert(final String message, final Closure assertion) {
        try {
            // Run Closure with assert statement(s).
            assertion()
        } catch (AssertionError assertionError) {
            // Use Groovy power assert output from the assertionError
            // exception and append user message.
            final exceptionMessage = new StringBuilder(assertionError.message)
            exceptionMessage << System.properties['line.separator'] << System.properties['line.separator']
            exceptionMessage << message
            
            // Throw exception so Gradle knows the validation fails.
            throw new GradleException(exceptionMessage, assertionError)
        }
    }
    
    @Mutate
    void createVersionFileTask(final ModelMap<Task> tasks, final VersionFile versionFile) {
        tasks.create('generateVersionFile', VersionFileTask) { task ->
            task.version = versionFile.version
            task.outputFile = versionFile.outputFile
        }
    }

}

Let's use the following build file and apply the rules to the project:

// File: build.gradle
apply plugin: mrhaki.gradle.VersionFileTaskRules

model {
}

From the command line we run the model task to check the Gradle model space:

$ gradle -q model

FAILURE: Build failed with an exception.

* What went wrong:
A problem occurred configuring root project 'versionrule'.
> Exception thrown while executing model rule: VersionFileTaskRules#validateVersionFileOutputFile(VersionFile)
   >                 assert versionFile.outputFile
            |           |
            |           null
            VersionFile 'versionFile'

     Property VersionFile.outputFile is not set. Set a value in the model configuration.

     Example:
     -------
     model {
         versionFile {
             outputFile = project.file("${buildDir}/version.txt")
         }
     }



* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
$

Notice the validation rules are evaluated in alphabetical order of the methods names that have the @Validate annotation.

Let's fix this and set also the version property in our build file:

// File: build.gradle
apply plugin: mrhaki.gradle.VersionFileTaskRules

model {
    versionFile {
        version = '1.0.3.RELEASE'
        outputFile = project.file("${buildDir}/version.txt")
    }
}

We rerun the model task and in the output we see the version is invalid, because it doesn't start with a v:

$ gradle -q model

FAILURE: Build failed with an exception.

* What went wrong:
A problem occurred configuring root project 'versionrule'.
> Exception thrown while executing model rule: VersionFileTaskRules#validateVersionFileVersion(VersionFile)
   > assert versionFile.version.startsWith('v')
            |           |       |
            |           |       false
            |           1.0.3.RELEASE
            VersionFile 'versionFile'

     Property VersionFile.version should start with 'v'. Set a value starting with 'v' in the model configuration.

     Example:
     -------
     model {
         versionFile {
             version = 'v1.0.3.RELEASE'
         }
     }


* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
$

Let's make our validation pass with the following build script:

// File: build.gradle
apply plugin: mrhaki.gradle.VersionFileTaskRules

model {
    versionFile {
        version = 'v1.0.3.RELEASE'
        outputFile = project.file("${buildDir}/version.txt")
    }
}

And in the output of the model we see the properties are set with our values:

$ gradle -q model
...
+ versionFile
      | Type:           mrhaki.gradle.VersionFile
      | Creator:        VersionFileTaskRules#versionFile(VersionFile)
      | Rules:
         ⤷ versionFile { ... } @ build.gradle line 10, column 5
         ⤷ VersionFileTaskRules#validateVersionFileOutputFile(VersionFile)
         ⤷ VersionFileTaskRules#validateVersionFileVersion(VersionFile)
    + outputFile
          | Type:       java.io.File
          | Value:      /Users/mrhaki/Projects/mrhaki.com/blog/posts/samples/gradle/versionrule/build/version.txt
          | Creator:    VersionFileTaskRules#versionFile(VersionFile)
    + version
          | Type:       java.lang.String
          | Value:      v1.0.3.RELEASE
          | Creator:    VersionFileTaskRules#versionFile(VersionFile)
...
$

Written with Gradle 3.2.

November 15, 2016

Gradle Goodness: Replacing << Operator For Tasks

Gradle 3.2 deprecates the << operator to add actions to a task. The << operator maps to the leftShift method of a task. This operator confuses a lot people that are new to Gradle. Because without the operator we are configuring a task instead of adding actions. I can tell from experience the mistake is easily made. If we use the << in our build script with Gradle 3.2 we get a warning on the console. The warning message already mentions a solution: use the doLast method to add actions.

In the following example build script we define the task deprecatedSample using the << operator. The other task newSample uses the doLast method to add an action:

// Since Gradle 3.2 the << (leftShift) operator
// is deprecated. The operator can confuse
// people, because without the operator
// we would configure the deprecatedSample task,
// instead of adding the action statement:
// println 'Sample task'.
task deprecatedSample << {
    println 'Sample task'
}

// To have no confusion we should use
// the doLast method of a task to add
// the action statement:
// println 'Sample task'.
task newSample {
    doLast {
        println 'Sample task'
    }
}

When we run the deprecatedSample task we see in the output the warning that the leftShift method has been deprecated:

$ gradle deprecatedSample
The Task.leftShift(Closure) method has been deprecated and is scheduled to be removed in Gradle 5.0. Please use Task.doLast(Action) instead.
        at build_dq65b0mbv52w2ikhya3h9ru8d.run(/Users/mrhaki/Projects/mrhaki.com/blog/posts/samples/gradle/leftShift/build.gradle:7)
:deprecatedSample
Sample task

BUILD SUCCESSFUL

Total time: 0.793 secs
$

We still have time to fix our build scripts, because in Gradle 5 the leftShift method will be removed.

Written with Gradle 3.2.

Gradle Goodness: Set Default Values With Rule Based Model Configuration

When we use Rule based model configuration in our Gradle project we can give Gradle rules on how to manage objects from the model space. These rules are defined in a class that extends RuleSource. When we want to set some default values for properties of a model object (in Gradle terms this is a subject) we can use the @Defaults annotation. Rules annotated with @Defaults are invoked right after the object is created and before any other methods that can mutate the state of the object.

The method, to set the default values, must have the type of the object as first parameter. Other parameters are considered input parameters and can be used to set a default value based on other model objects.

In a previous post we worked with a managed object VersionFile. Let's add a default value for the version property. We want the version default value to be the unspecified:

// File: buildSrc/src/main/groovy/mrhaki/gradle/VersionFileTaskRules.groovy
package mrhaki.gradle

import org.gradle.api.Task
import org.gradle.model.Defaults
import org.gradle.model.Model
import org.gradle.model.ModelMap
import org.gradle.model.Mutate
import org.gradle.model.Path
import org.gradle.model.RuleSource

class VersionFileTaskRules extends RuleSource {

    @Model
    void versionFile(final VersionFile versionFile) { }

    /**
     * Method to set default values for {@link VersionFile} object
     * created by the {@link #versionFile} method.
     * 
     * @param versionFile First argument is the type we want to set default values for
     */
    @Defaults
    void defaultsVersionFile(
            final VersionFile versionFile) {

        // Set default value for version property to.
        versionFile.version = 'unspecified'
    }

    @Mutate
    void createVersionFileTask(final ModelMap<Task> tasks, final VersionFile versionFile) {
        tasks.create('generateVersionFile', VersionFileTask) { task ->
            task.version = versionFile.version
            task.outputFile = versionFile.outputFile
        }
    }
    
}

When we run the model task we can see Gradle knows the defaultsVersionFile methods was used to change the state of the VersionFile instance:

$ gradle -q model 
...
+ versionFile
      | Type:           mrhaki.gradle.VersionFile
      | Creator:        VersionFileTaskRules#versionFile(VersionFile)
      | Rules:
         ⤷ VersionFileTaskRules#defaultsVersionFile(VersionFile)
    + outputFile
          | Type:       java.io.File
          | Value:      /Users/mrhaki/Projects/mrhaki.com/blog/posts/samples/gradle/versionrule/build/version.txt
          | Creator:    VersionFileTaskRules#versionFile(VersionFile)
    + version
          | Type:       java.lang.String
          | Value:      null
          | Creator:    VersionFileTaskRules#versionFile(VersionFile)
...
$

Written with Gradle 3.2.