DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
  1. DZone
  2. Coding
  3. Languages
  4. Gradle Goodness: Getting Project Info Into Rule-Based Model Configuration

Gradle Goodness: Getting Project Info Into Rule-Based Model Configuration

If you want to use Gradle's rule-based model configuration, beware that it might not be able to see important objects in the model space. Here's how to bypass that.

Hubert Klein Ikkink user avatar by
Hubert Klein Ikkink
·
Jan. 03, 17 · Tutorial
Like (1)
Save
Tweet
Share
6.81K Views

Join the DZone community and get the full member experience.

Join For Free

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 objects in this model space. The model space is populated with objects of our own and with objects from Gradle.

At the time of writing this blog post, we cannot interact with the Gradle Project object in our rule-based model configuration. It is not officially part of the model space. This might, and probably will, change in the future, and the Project objects managed by Gradle will be part of the model space.

That would mean we could then use a Project object as an 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 a 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 exciting:

// 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 cannot 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 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 arguments 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 we need to keep in mind that this solution might not work in future Gradle versions. We could write an extension that wraps the Project object and adds it to the ExtensionContainer. Next, we use the ExtensionContainer as an input argument for a rule method to get the extension with the wrapper Project object. Then, to 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 Object (computer science)

Published at DZone with permission of Hubert Klein Ikkink, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Bye-Bye, Regular Dev [Comic]
  • ChatGPT: The Unexpected API Test Automation Help
  • Top Five Tools for AI-based Test Automation
  • Better Performance and Security by Monitoring Logs, Metrics, and More

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: