Maven features I wish Gradle had: override task properties

It's no secret that one of the most common properties ever set on a Maven build is -DskipTests which as the name implies, instructs Maven to skip running tests. As a matter of fact that property is read by the maven-surefire-plugin to figure out if it should run tests or not. As it turns out there's a separate property named skip that instructs Maven to skip compiling and running tests. If you're curious and peek at the documentation you may notice that the field/property names for skipTest match but for skip you'll find a property named maven.test.skip. As it happens, both properties are exposed to the outside world using the @Parameter annotation, as shown next

@Parameter( property = "skipTests", defaultValue = "false" )
protected boolean skipTests;

@Parameter( property = "maven.test.skip", defaultValue = "false" )
protected boolean skip;

While the name of the field may be used inside the plugin's <configuration> block to configure its behavior, the value of the property attribute can be used as either a project property (defined inside a <properties> block) or a System property defined in the command prompt with -D for example. Let's follow up with the echo-maven-plugin, whose EchoMojo class defines the following field

@Parameter(property = "echo.message")
private String message;

The echo plugin can be configured in a pom.xml file like so

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.acme</groupId>
    <artifactId>sample</artifactId>
    <version>0.0.0-SNAPSHOT</version>

    <build>
        <plugins>
            <plugin>
                <groupId>com.github.ekryd.echo-maven-plugin</groupId>
                <artifactId>echo-maven-plugin</artifactId>
                <version>1.2.0</version>
                <inherited>false</inherited>
                <executions>
                    <execution>
                        <id>echo</id>
                        <goals>
                             <goal>echo</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

The custom echo goal can be invoked by passing an additional argument in the command prompt

Great, we have successfully proved that the plugin can read a configured property from the command prompt. We can also define the input for the echo goal as a project property, as shown by the next pom.xml file

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.acme</groupId>
    <artifactId>sample</artifactId>
    <version>0.0.0-SNAPSHOT</version>

    <properties>
        <echo.message>Hello from pom.xml</echo.message>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>com.github.ekryd.echo-maven-plugin</groupId>
                <artifactId>echo-maven-plugin</artifactId>
                <version>1.2.0</version>
                <inherited>false</inherited>
                <executions>
                    <execution>
                        <id>echo</id>
                        <goals>
                             <goal>echo</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

As you can appreciate the value of the echo.message property is defined inside a <properties> block. The build can now be invoked like this

We can even override the value from the command prompt, proving that System properties win over project properties

Now, let's see what happens when a explicit value is defined in the plugin's configuration, like it's shown by the following file

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.acme</groupId>
    <artifactId>sample</artifactId>
    <version>0.0.0-SNAPSHOT</version>

    <properties>
        <echo.message>Hello from pom.xml</echo.message>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>com.github.ekryd.echo-maven-plugin</groupId>
                <artifactId>echo-maven-plugin</artifactId>
                <version>1.2.0</version>
                <inherited>false</inherited>
                <executions>
                    <execution>
                        <id>echo</id>
                        <goals>
                             <goal>echo</goal>
                        </goals>
                        <configuration>
                            <message>hello</message>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

Let's attempt overriding the value using a project property

Hmm no luck. What if we switch to a System property instead? Let's see

No luck either. As a matter of fact there's an ongoing thread at the Maven mailing list regarding this specific scenario. For some the property should be overridable, for others the behavior shown here is how the feature should work.

Switching to Gradle now. Some major releases ago (might been around Gradle 3) a feature was added that lets task authors accept values that may have been defined on the command prompt, effectively letting consumers define values on the go instead of inside the build file. This feature is the @Option annotation. Say we find an equivalent echo plugin for Gradle, it could define a message property in the following manner

Property<String> message = project.objects.property(String).convention('')

@Option(option='echo-message', description = 'The message to write')
void setMessage(String message) {
    getMessage().set(message)
}

@Input
Property<String> getMessage() {
    this.message
}

Once the plugins is applied in a build file, as shown next, we can invoke the echo task using the echo-message option.

plugins {
    id 'org.kordamp.gradle.echo' version '0.40.0'
}

So far so good. Gradle let's you define values for task properties either explicitly in the build file or passing an explicit flag in the command prompt. However it does not let you supply values via System properties or project properties. Also, the naming conventions that govern option names exclude characters that are not alphanumeric nor '-' (dash/hyphen) or '_' (underscore), thus a '.' (dot/period) can't be used as a separator. For this reason the Kordamp Gradle Plugin suite provides an API to solve these issues. Following an idiomatic pattern of pairing a Property (write access) with a Provider (read access) plugin authors now have more choices at their disposal. The EchoTask can be written as

import groovy.transform.CompileStatic
import org.gradle.api.DefaultTask
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.options.Option
import org.kordamp.gradle.property.SimpleStringState
import org.kordamp.gradle.property.StringState

@CompileStatic
class EchoTask extends DefaultTask {
    private final StringState message

    EchoTask() {
        message = SimpleStringState.of(this, 'echo.message', '')
    }

    @Option(option='echo-message', description = 'The message to write')
    void setMessage(String message) {
        getMessage().set(message)
    }

    @Internal
    Property<String> getMessage() {
        message.property
    }

    @Input
    @Optional
    Provider<String> getResolvedMessage() {
        message.provider
    }

    @TaskAction
    void echo() {
        println getResolvedMessage().get()
    }
}

With this code in place we can apply the echo plugin and pass parameters on the command line using either System or project properties

plugins {
    id 'org.kordamp.gradle.echo' version '0.40.0'
}

project.ext.set('echo.message', 'Hello from build.gradle')

Note that the last build file had to use a different syntax to set the project property because of the '.' (dot/period) in the property's name. Overriding a project property with a System property, like it's done in Maven, it's also possible as shown next

However overriding an explicit value defined directly in the task's configuration proves to be elusive, like it is in Maven

plugins {
    id 'org.kordamp.gradle.echo' version '0.41.0-SNAPSHOT'
}

echo {
    message = 'Hello from task'
}

Bummer. Or is it? The APIs exposed by Kordamp assume that external configuration can occur if no explicit value is set, but this behavior can be changed with yet another System property that may be set on the command prompt, the local gradle.properties file, the user's global gradle.properties file, even an init script.

A few more additional things not mentioned before

  1. Environment variables may also be used as value sources, not just System and project properties.
  2. The resolution order between Environment, System, and Project can be changed.
  3. Property names can be set with their owning Task or Project path as prefix, such that an Echo task set on the root project with name shout will check for shout.echo.message before echo.message.

There you have it. Even though Gradle offers some support for overriding task properties, the Kordamp APIs give you more options to customize task behavior.

Keep on Coding!

Image by strikers from Pixabay

Liked it? Take a second to support aalmiray on Patreon!
Become a patron at Patreon!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

ˆ Back To Top