Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Maven Polyglot: replacing pom.xml with Clojure, Scala, or Groovy Script

DZone's Guide to

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.

· Java Zone
Free Resource

Learn how to troubleshoot and diagnose some of the most common performance issues in Java today. Brought to you in partnership with AppDynamics.

Maven'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:

intellij-breakpoint

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:

The official example:

Understand the needs and benefits around implementing the right monitoring solution for a growing containerized market. Brought to you in partnership with AppDynamics.

Topics:
java ,maven ,build script ,tutorial

Published at DZone with permission of Peter Szanto. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}