Skip to main content

More Groovy Magic with Maven pom files

Posted by johnsmart on November 1, 2009 at 6:39 PM PST

Last time, I introduced some of the new Groovy support available in Maven 3, and looked at how you will be able to write your pom files in Groovy, or in other non-XML notations. In this article, we'll take a further look at what you can do with a Maven pom file written in Groovy.

Jason Dillon, the guy who brought us GMaven, has been working hard on extending the Groovy pom scripting features, and is coming up with some great new capabilities. In the last article, we saw how the Groovy DSL makes dependencies more concise, with dependency declarations along the following lines:

[prettify]    dependencies {
        dependency { groupId 'junit'; artifactId 'junit'; version '4.7'; scope 'test' }
    }
[/prettify]

In fact, you can do even better than this. A short-hand notation for dependencies lets you define dependencies in a single line. So

[prettify]    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.5</version>
      <scope>test</scope>
    </dependency>
[/prettify]
becomes:
[prettify]    dependency('junit:junit:4.5:test')</pre>

This also works for the project parent, plugins, modules and many other elements. For example, you could write the following:

project {
    parent('tweeter:co.nz.codingdojo.tweeter:1.0.0-SNAPSHOT')
    ...
}
[/prettify]

This shorthand notation also works with dependency exclusions. Here is a real-world example from the graven project itself:

[prettify]dependencies {
  dependency('org.codehaus.groovy:groovy:1.7-beta-2') {
    exclusions('junit:junit', 'org.apache.ant:ant', 'org.apache.ant:ant-launcher')
  }
}
[/prettify]

Here is a more complete example, showing this notation in action in the parent element, as well as in dependencies and plugins:

project(modelVersion: '4.0.0') {
    parent('tweeter:co.nz.codingdojo.tweeter:1.0.0-SNAPSHOT')
    artifactId 'tweeter-web'
    packaging 'war'
    name 'Tweeter Web Application'

    dependencies {
        dependency('javax.servlet:jstl:1.2')
        dependency('junit:junit:4.7:test')
    }

    build {
        plugins {
            plugin('org.mortbay.jetty:maven-jetty-plugin:6.1.21') {
                configuration {
                    port '9090'
                    scanIntervalSeconds '5'
                    stopPort '9966'
                    stopKey 'foo'
                }
                executions {
                    execution(id: 'start-jetty', phase: 'pre-integration-test') {
                        goals('run')
                        configuration {
                            scanIntervalSeconds '0'
                            daemon 'true'
                        }
                    }
                }
            }
        }
    }
}

You can also bind Groovy scriptlets to different phases in the lifecycle with a minimum of fuss. For example, you will be able to integrate Groovy code directly at different points in the lifecycle. At this stage, the syntax uses the $execute macro, and looks something like this:

[prettify]project {
   build {
       $execute(id: 'test1', phase: 'validate') {
           println "foo"
       }

       $execute(id: 'test2', phase: 'compile') {
           println "bar"
       }
       ...
}
[/prettify]
Within the Groovy closure, you will be able to access the usual pom variable such as $project and $settings, as you can with GMaven.

You can also use Groovy in the pom file itself, to make the pom file itself more dynamic and concise. For example, in this example (taken from the graven pom file), the project modules are defined in a central list, and used both in the dependency management section (to avoid duplicating the version number for each module) and in the modules element:

[prettify]project {
    ...
    def mods = ['pmaven-common','pmaven-cli','pmaven-maven-plugin', 
                           'pmaven-groovy','pmaven-yaml','pmaven-jruby']
    ...
    dependencyManagement {
        dependencies {
        mods.each { artifact ->
            dependency {
                groupId 'org.sonatype.pmaven'
                artifactId "$artifact"
                version '1.0-SNAPSHOT'
            }
        }
    }
    ...
    modules(*mods)
}
[/prettify]

This really is the best of both worlds. If you need flexibility, you can add Groovy scripting to do most anything you want. However it remains within the structure of the Maven build lifecycle. We are not going back to Ant-like anarchy of build scripts built up of arbitrary targets and task dependencies: 'mvn verify', for example, will still do what you expect it to do.

Some readers seemed concerned that this would just add another notation to learn, and rightly so! Keeping your code maintainable is an essential part of good development practices! However, this is not a totally new formalism - this is more a transcription of the XML pom format with a few fairly intuitive short-cuts. For anyone familiar with Groovy and Maven, a pom file written in Groovy is very easy to read and understand indeed.

Indeed, under the hood, the pom files are converted to the canonical XML pom format that we all know and love. When an artifact is deployed to a Maven repository (be it a local one, or the official central repository), the Pom file will be in the standard, canonical XML format. The support for multi-language pom files is clearly intended to make your live easier within your organization writing and maintaining pom files in a format that suits you - not to turn the central repository into a Tower of Babel.

Regarding tool support, the standard m2eclipse integration will work fine with any POM file format, as m2eclipse uses the canonical XML format under the hood. However, Groovy support in Eclipse being what it is, the pom editor will probably take a while before you get all of the nice auto-completion features you currently get with the XML format. Nevertheless, for a groovy-savvy developer, this new pom format is a great step forward.

Related Topics >>

Comments

nice, but...

I don't care what IDE you're using you'll never be able to use the write-back features of Netbeans or Eclipse if you have this in your POM: mods.each { artifact -> dependency { groupId 'org.sonatype.pmaven' artifactId "$artifact" version '1.0-SNAPSHOT' } } We depend heavily on the dependency graphing features of Netbeans and make changes directly in those.

IDEs will use the canonical XML format behind the scenes

The canonical XML format will be used by the IDEs, so Eclipse, Netbeans and the like should have no problem with this, even for dependency graphs. The IDE would use the evaluated pom.xml file, not the initial Groovy scripted version.

but I'm talking specifically about the write-back features

that when you visually make a change to your dependencies it should reflect back into your pom. These poms would be read-only.

Fair point

I expect IDE integration will catch up as Groovy support improves, but it will probably take a little time before you can graphically add dependencies to a pom.groovy file. With sufficient Groovy support, it shouldn't be that hard, though.

why stop there?

I've noticed groovy has support for executing other jvm-hosted languages, especially Scala and Clojure from inside groovy scripts. This opens up some new perspectives for incomprehensible build scripts. How about mutually recursive polyglot plugin binding with software transactional memory to rescue the project into a buildable state in case of failure? XML is so dull these days.

Nice new features

John, are these nice new features available in a (timestamped) snapshot somewhere or do you have to build from the 3.x trunk to have a play with them?

I do feel duty bound to mention that the new groovier poms look like a close relative of Kundo recipes - I guess Gradle and Buildr (using Ruby) do something similar too?

Fair play to the Maven team and Jason Dillon though - that new execute macro feature looks great and should make dealing with trickier build management edge cases much easier.

Adam

Nice new features (update)

I guess I can try the latest snapshot from here

Maven 3 snapshots

Yes, that is where the latest snapshots are at. Some of this stuff is pretty experimental at the moment, but it is moving pretty fast. I'm not sure if the $execute macro has made it into the snapshots yet, as there is still a lot of work going on in this area.