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.