Maven features I wish Gradle had: inlined plugins

Since the early days of Maven the development team recognized that they couldn't (and shouldn't) provide a solution for every problem we as consumers may face with our builds. In order to cater for our every whim and emerging requirements the Maven build tool added a pluggable mechanism to provide behavior as needed, and thus Maven plugins were born. Maven plugins are typically configured inside a <build> block found in the pom.xml file, which lets them contribute goals that may bind to lifecycle phases by default, or just providing goals that you can invoke at any time. But what if I told you can also apply a Maven plugin to a build without touching the pom.xml file at all? Sounds crazy? Let me show you how.

The following pom.xml file defines just the minimum elements to be a valid POM file

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

Now watch what happens when the echo-maven-plugin is applied on the command prompt, by defining it's GAV (groupId, artifactId, version) coordinates plus the target goal we want to execute, plus any additional arguments the goal may need. This is another case where providing plugin parameters as System properties comes in handy.

We can appreciate that Maven downloaded the plugin during execution as I didn't have the plugin installed on my local Maven repository. Let's invoke the same command once again

Note that Maven does not download the plugin again because it now resides in the local Maven repository. As a matter of fact we can omit the version from G:A:V:goal and make it just G:A:goal in which case Maven will download the latest version that matches the G:A coordinates. If you think this is a pretty neat feature then I'm in agreement with you, it is! However it may be a bit tiresome to type in G:A coordinates for every invocation of an inline plugin. It's for this reason that Maven is aware of a "blessed" list of plugins that allow us to use their shorthand pluginId instead. These plugins must be published in either the org.apache.maven.plugins or org.codehaus.mojo groupIds. The list can be expanded as well, have a look at the docs. The next example makes use of the exec-maven-plugin which happens to be published in one of those blessed packages. Let's start with the project structure

$ tree .
.
├── pom.xml
└── src
    └── main
        └── java
            └── com
                └── acme
                    └── Main.java

5 directories, 2 files

The main class looks like this

package com.acme;

public class Main {
    public static void main(String[] args) {
        System.out.println("Hello world");
    }
}

An most importantly, the pom.xml remains unchanged, that is, it has no explicit plugin definitions, nor dependencies. Alright, now for the good stuff, we compile and invoke the Main class in the following way

It works! Hold on, hold on, you might be thinking "that command line did not define exec:java as I expected". Sure it didn't but the exec:java goal was invoked. How? It's all thanks to Gum, the Maven/Gradle/JBang wrapper. See for yourself, the output does not lie. Once again we make use of passing plugin parameters on the command prompt to make it work.

On the Gradle side of things there is no option out of the box to execute inlined plugins, but the org.kordamp.gradle.inline plugin does provide an avenue for this behavior. This plugin must be applied to your settings.gradle file otherwise it won't work. The minimum configuration may look like this

buildscript {
    repositories {
        gradlePluginPortal()
        jcenter()
    }
    dependencies {
        classpath 'org.kordamp.gradle:inline-gradle-plugin:0.40.0'
    }
}
apply plugin: 'org.kordamp.gradle.inline'

With this in place and an empty build file (just to drive the point home) we can now invoke the echo-gradle-plugin just like we did in the Maven build, by passing the GAV coordinates, the task name, and any additional arguments the task may require

Lovely! Unfortunately this feature still requires you to define the version as the inline plugin does not resolve the latest version (yet). You may be wondering, can this feature be applied to core Gradle plugins? Why, yes, of course! In this case you switch the GAV:task format for pluginId:task format where pluginId is the short plugin identifier of a core plugin, such as application, as demonstrated in the following example. With a project structure similar as before

$ tree .
.
├── build.gradle
├── settings.gradle
└── src
    └── main
        └── java
            └── com
                └── acme
                    └── Main.java

5 directories, 3 files

And the same settings.gradle file and an empty build.gradle file (well, because we can) we're able to invoke the Main class in this way

A few things of note here:

  1. The application plugin applies the java plugin thus compilation can occur.
  2. The actual task property is mainClassName however the example used the same property as the Maven exec plugin. This is not Gum's doing but the inline plugin's as it supports additional metadata via implementations of the PropertyAdapter class. The inline plugin just happens to ship with an adapter for JavaExec's properties. Any plugin may provide their own adapters as needed.

Inlined plugins let you run and test behavior on builds without having to modify build files themselves.

The features described so far apply to Kordamp 0.40.0. In the next release, 0.41.0, you'll be able to use any of these formats to inline a plugin

  1. pluginId:task where pluginId is either a core Gradle plugin or an aliased plugin.
  2. pluginId:version:task where pluginId is a core Gradle plugin or an aliased plugin.
  3. groupId:artifactId:version:task.
  4. groupId:artifactId:task where version will be set as latest.release.

This means the following invocations yield the same result

$ gm org.kordamp.gradle:echo-gradle-plugin:0.41.0:echo -Decho.message="Hello World"
$ gm org.kordamp.gradle:echo-gradle-plugin:echo -Decho.message="Hello World"
$ gm org.kordamp.gradle.echo:0.41.0:echo -Decho.message="Hello World"
$ gm org.kordamp.gradle.echo:echo -Decho.message="Hello World"

Bonus: Did you compare the commands used to run the Main class for both Maven and Gradle using Gum? Here they are again

// Maven
$ gm compile run -Dexec.mainClass=com.acme.Main

// Gradle
$ gm application:run -Dexec.mainClass=com.acme.Main

They are almost identical on purpose thanks to Gum, as it can replace and map Maven goals to Gradle tasks and viceversa. Combine it with the inline plugin and other plugins from Kordamp Gradle Plugin suite and you get power builds. Fun times!

Keep on coding!

Image by David Mark from Pixabay

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

Trackbacks/Pingbacks

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