Maven Polyglot: replacing pom.xml with Clojure, Scala, or Groovy Script
Maven is a great build tool, but your pom.xml file can get aggravating. Using Maven Polyglot, you can create build scripts using a variety of JVM-friendly languages.
Join the DZone community and get the full member experience.
Join For FreeMaven's best-kept secret is that it supports pom.groovy
, pom.scala
, and many other dialects, on top of pom.xml
. This feature was introduced in 2015, but somehow, it was left out from the documentation (or no one read it). Maven Polyglot, an official Maven extension makes this possible.
What's wrong with pom.xml?
XML was the next big thing 15 years ago. Every cool technology used XML. The list includes J2EE, Spring, Hibernate, Ant, EJB, SOAP, and, of course, Maven, too. As time passed, all those projects moved away from XML, mostly in favor of Java annotations. Maven’s case is a bit different, not just because annotations wouldn’t work, but also because XML was used not only for configuration and metadata, but also for some sort of build script, something that it was never meant to be used for.
Getting started with maven polyglot in three easy steps
1. Generate the build script from an existing pom.xml
The quickest way to get started is to convert an existing Maven project by executing the below command in the project directory:
mvn io.takari.polyglot:polyglot-translate-plugin:translate -Dinput=pom.xml -Doutput=pom.groovy
It will recursively process all submodules if they exist. The result will be a pom.groovy
file being generated next to the pom.xml. The generated build script's structure is quite self-explanatory, as it is very similar to the pom.xml, just not an XML anymore. For example, this pom.xml
...
<?xml version="1.0" encoding="UTF-8"?>
<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>org.exampledriven</groupId>
<artifactId>polyglot-example</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>polyglot-example</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
...will be translated to this pom.groovy
.
project {
modelVersion '4.0.0'
groupId 'org.exampledriven'
artifactId 'polyglot-example'
version '1.0-SNAPSHOT'
name 'polyglot-example'
properties {
'project.build.sourceEncoding' 'UTF-8'
}
dependencies {
dependency {
groupId 'junit'
artifactId 'junit'
version '3.8.1'
scope 'test'
}
}
}
Or into this pom.scala
.
import org.sonatype.maven.polyglot.scala.model._
import scala.collection.immutable.Seq
Model(
"org.exampledriven" % "polyglot-example" % "1.0-SNAPSHOT",
name = "polyglot-example",
dependencies = Seq(
"junit" % "junit" % "3.8.1" % "test"
),
properties = Map(
"project.build.sourceEncoding" -> "UTF-8"
),
modelVersion = "4.0.0"
)
2. Create an extensions file
The next step is to create a .mvn/extensions.xml
file with the following content:
<?xml version="1.0" encoding="UTF-8"?>
<extensions>
<extension>
<groupId>io.takari.polyglot</groupId>
<artifactId>polyglot-groovy</artifactId>
<version>0.1.19</version>
</extension>
</extensions>
Yes, old habits die hard. In order to get rid of XML, we have to create one more XML, but this will be the last one, I promise.
3. Clean up
To avoid confusion, delete all the pom.xml files. Execute mvn clean install
or any other command, the project will behave exactly like a regular Maven project.
Main features
Build scripts can be written in the following languages
- Ruby
- Groovy
- Scala
- Clojure
Or in the following markup languages
- Atom
- YAML
These dialects can be generated by replacing the word groovy
with the desired name in the translate command and in the extensions.xml file. The size of the build script can be significantly reduced using Atom or YAML, but other than that there is no real benefit of those. The real power comes when a programming language is used.
Inlined plugins
The single biggest selling point is the use of inlined plugins. This means that ordinary code can be executed inside the POM file. Here is a Groovy example that creates a file during the compile phase if a system property named file-test is set to true:
build {
$execute(id: 'hello', phase: 'compile') {
if ("true".equals(System.getProperty("file-test"))) {
println "File generation is enabled"
println properties.size()
println this.getProperties().containsKey('greet')
println properties
println properties.get('greet')
if (getProperties().getOrDefault('greet', false)) {
System.out.println 'Hello from groovy'
}
def directory = "target/classes/new"
def dirCreated = new File(directory).mkdir();
println "Directory was created? " + dirCreated
def file = new File(directory + "/hello.txt")
if (!file.exists()) {
println "Creating hello.txt"
file.createNewFile();
file.write("hello from groovy")
} else {
println "hello.txt is already created"
}
} else {
println "File generation is disabled"
}
}
}
The same could be achieved by configuring a maven plugin, but that often requires cumbersome configuration, that is very hard to understand. Inlined plugins allow bigger level of control over the build script and they are closer to traditional build scripts.
Break points
As of IntelliJ IDEA 2016.3, break points can be set inside build scripts. Here is a proof:
At the time of writing this article, there were some limitations:
- When running into break points in multi-module projects, IntelliJ opens the wrong build script
- It works only with the Groovy dialect.
Debug messages
Anywhere in the build script, language-specific debug statements can be added. This obviously includes the inlined plugins, but in other places, such as in profile-specific parts, a debug statement can be useful
Accessing the execution context
Inlined plugins can access the Maven execution context, which will allow programmatic access of the metadata of the Maven project.
$execute(id: 'hello', phase: 'compile', ) {ec ->
println 'Version : ' + ec.getProject().getModel().getVersion()
println 'Group ID : ' + ec.getProject().getModel().getGroupId()
println 'Artifact ID : ' + ec.getProject().getModel().getArtifactId()
println 'Basedir : ' + ec.basedir()
This works only with polyglot-groovy 0.1.20
or newer. At the time of writing this article 0.1.19, was the latest, but polyglot-scala 0.1.19
already supports it, like this :
build = Build(
tasks = Seq(Task("someTaskId", "verify") { ec =>
println(s"\nbaseDir: \n${ec.basedir}")
})
),
What about the Java DSL?
Wouldn’t it be nice to build Java with a build script written in Java? Something like this:
project(
modelVersion -> "4.0.0",
parent(
groupId -> "io.takari",
artifactId -> "takari",
version -> "14"
),
groupId -> "io.takari.polyglot",
artifactId -> "polyglot",
version -> "0.1.16-SNAPSHOT",
packaging -> "pom",
name -> "Polyglot :: Aggregator",
licenses(
license(
name -> "The Eclipse Public License, Version 1.0",
url -> "http://www.eclipse.org/legal/epl-v10.html",
distribution -> "repo"
)
)
...
plugin("maven-release-plugin", new ImmutableMap.Builder<String, String>()
.put("preparationGoals", "clean install")
.put("mavenExecutorId", "forked-path")
.build()
)
);
Everything is either a lambda or a method call. If you like it, vote up this GitHub issue, and maybe it will help prioritizing it.
Tooling support and requirements
Maven Polyglot has the following requirements:
- Maven 3.3.1 +
- Java 7 +
The following tools support Maven Polyglot:
- IntelliJ 2016.3 +
- Eclipse through experimental (at the time of writing) plugins
- All CI tools that supports Maven
Summary
The big benefit of Maven Polyglot is certainly the backward compatibility with traditional Maven. A polyglot project can be converted back to a regular Maven project any time (of course, the inline plugins will be lost). The learning curve is very small — those who know Maven know Maven Polyglot, too. The project is quite stable and safe to use on production projects.
How does it compare to gradle?
Gradle, by default, supports Groovy (starting with Gradle 3.0 it supports Kotlin too), but it does not support the other languages that Maven Polyglot does. Gradle is newer than Maven, and many of its mistakes are avoided. On the other hand, switching from Maven to Gradle takes more effort than switching to Maven Polyglot, both in terms of learning curve and updating build infrastructure.
Further reading
Examples and source code
Examples used for this project:
https://github.com/ExampleDriven/maven-polyglot-groovy-example
https://github.com/ExampleDriven/maven-polyglot-groovy-simple-example
https://github.com/ExampleDriven/maven-polyglot-scala-example
The official example:
Published at DZone with permission of Peter Szanto. See the original article here.
Opinions expressed by DZone contributors are their own.
Trending
-
Design Patterns for Microservices: Ambassador, Anti-Corruption Layer, and Backends for Frontends
-
Micro Frontends on Monorepo With Remote State Management
-
Five Java Books Beginners and Professionals Should Read
-
Auto-Scaling Kinesis Data Streams Applications on Kubernetes
Comments