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:
- The
application
plugin applies thejava
plugin thus compilation can occur. - 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 thePropertyAdapter
class. The inline plugin just happens to ship with an adapter forJavaExec
'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
pluginId:task
wherepluginId
is either a core Gradle plugin or an aliased plugin.pluginId:version:task
wherepluginId
is a core Gradle plugin or an aliased plugin.groupId:artifactId:version:task
.groupId:artifactId:task
whereversion
will be set aslatest.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
Trackbacks/Pingbacks