Search code examples
gradlepropertieslazy-evaluation

In Gradle, how do you perform validation of lazily evaluated properties (on extensions)?


Is there a way to validate a property value when the property is evaluated? I can't do it in the getter because that returns the Property object - I want the validation to run only when the actual value is calculated (i.e. I want to be lazy evaluation friendly).

They show extensions using the Property object here:

However, they don't explain how to do property validation when the value is calculated. Here is the snipet of code from the Gradle documentation provided example:

// A project extension
class MessageExtension {
    // A configurable greeting
    final Property<String> greeting

    @javax.inject.Inject
    MessageExtension(ObjectFactory objects) {
        greeting = objects.property(String)
    }
}

If I wanted to make sure the value of greeting was not equal to test, then how would I do that when it is evaluated?


Solution

  • For most use cases, it should be sufficient to just validate the property value once you resolve it in your task or in other internal parts of your plugin. Only a few extensions are actually designed to be consumed by other plugins or the build script.

    Gradle does not provide some validation that can be attached to a property, however you can build this functionality on your own like in the example below:

    class MessageExtension {
    
        private final Property<String> _greeting
    
        final Provider<String> greeting
    
        @javax.inject.Inject
        MessageExtension(ObjectFactory objects) {
            _greeting = objects.property(String)
            greeting = _greeting.map { value ->
                if (value.equals('test'))
                    throw new RuntimeException('Invalid greeting')
                return value
            }
        }
    
        def setGreeting(String value) {
            _greeting.set(value)
        }
    
        def setGreeting(Provider<String> value) {
            _greeting.set(value)
        }
    }
    
    project.extensions.create('message', MessageExtension)
    
    message {
        greeting = 'test'
    }
    
    println message.greeting.get()
    

    I turned the Property into a backing field for a Provider that runs the validation when resolved. If you do not want to throw an exception, but just return an empty Provider, you may replace the map with a flatMap.