Maven polyglot : replacing pom.xml with scala or groovy build script


Goal of this example

This example demonstrates how to create a Maven project that uses build script written in Groovy, Scala or Clojure instead of pom.xml. This is possible by using Maven Polyglot that is an official extension of Maven

Technology Used

  • Maven 3.3.9
  • Maven Polyglot 0.1.19

What is 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 new technologies were invented and all the above 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 meta data, 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 scripts

The quickest way is to convert an existing Maven project by executing the following in the project directory :

mvn io.takari.polyglot:polyglot-translate-plugin:translate -Dinput=pom.xml -Doutput=pom.groovy

In case of multi module projects it will process all sub modules too. The result will be the pom.groovyfile being generated next to every pom.xml.

2. Create extensions file

The next step is to create a the

.mvn/extensions.xml

file with the below 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. The project will behave exactly like any other Maven project, mvn clean install and all other commands work seamlessly.

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
  • Any CI tool that supports Maven

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 used 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 then 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"
            }
    
        }
    
    }

Achieving the same with Maven would require finding the right Maven plugin and configuring it.

Break points

As of IntelliJ Idea 2016.3 break points can be set inside build scripts. Here is a proof :

intellij-breakpoint

There are two limitations

  • At the time of writing this article this seemed to work only on single module projects
  • It works only with the groovy dialect

Debug messages

Anywhere in the build script language specific debug statements can be added. This includes obviously the inlined plugins, but in other places e.g.: in profile specific parts a debug statement can be useful

Accessing the execution context

Inlined plugins can access the Maven execution context that will allow programmatic access of the meta data 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 only works with polyglot-groovy 0.1.20 or newer. (At the time of writing this article 0.1.19 was the latest)

The same works with scala too

  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 lambda or a method call. It is really nice, we just need to wait for this GitHub issue implemented…

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, knows 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, but it does not support the other langues Maven Polyglot does. Gradle is newer than Maven and many of it’s mistakes are avoided. On the other hand switching from Maven to Gradle takes more effort then switching to Maven Polyglot, both in terms of learning curve and updating build infrastructure.

Further examples

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
https://github.com/takari/polyglot-maven-examples

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: