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 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/sample/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.