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