Skip to main content

Shrink your POM!

Posted by fabriziogiudici on February 21, 2010 at 2:31 PM PST

There are many things, mostly implementation-related, that can be blamed on Maven, but I think most people agree on the fact that the POM concept (a declarative model of your project) is a good thing. Among other things, it allows to run a new plugin often with a minimum of configuration, or no configuration at all. For instance, a few days ago I was pointed to the Clirr plugin, a tool that allows to verify whether the API of a module has changed (and eventually breaking back-compatibility). If you have a working mavenized project, it's just the matter of running:

mvn clirr:clirr

(more about Clirr in a future post). The plugin learns everything it needs about the project from the POM and it's able to retrieve and analyze the artifacts of the previous version, so it can compare them with the current ones. With Ant my experience was that I needed to add a bit of an Ant script for every tool I wanted to integrate (e.g. Cobertura, FindBugs, PMD, CheckStyle) - for Cobertura, it wasn't the simplest patch of an Ant script ever.

Unfortunately, POMs tend to grow. The past week some of my projects' master POMs were 1500+ lines - sure, they are verbose because of XML, but there is a bunch of real information within. In my experience, the largest sections are:

  1. The declaration of plugin versions in pluginManagement
  2. The declaration of dependency versions in dependencyManagement
  3. The configuration of all plugins
  4. The profiles

Point #2 might be a surprise for many - consider that my NetBeans Platform projects are made of dozens of small modules, so a top-level project such as blueMarine has really lots of *direct* dependencies (not counting transitive dependencies that are automatically inferred).
Point #4 is related to my Hudson configuration, for which most profiles are (e.g. for automating the release jobs); furthermore they contain some workarounds for existing bugs (such as the Maven BuildNumber plugin inability of retrieving the Mercurial changeset id).

Usually you strive for having a common setup of all the projects - this reduces entropy, and also allows to have a template-based facility for creating Hudson jobs. This means that large amount of those POMs must be kept in sync between all the project - having to manage about a dozen recently drove me to cut & paste madness. What to do?

JUGs to the rescue! I asked for help to JUG Milano and JUG Genova and was pointed out to the proper solutions - two steps, actually inheritance and composition.

First, Create a "super POM" with all the common stuff. The "super POM" is similar to java.lang.Object in a Java environment, it's an implicit POM from which all your POMs with no parent implicitly inherits from - thus is the ancestor of all. But you can create your own intermediate ancestor, and declare it as the parent of your projects' master POMs: Maven will download it automatically, as for any other artifact. In this way, you can write a single POM that can be shared with an arbitrary number of independent projects. So I created a Super POM for all my projects and made it available through the Central Maven Repository, so it can be shared everywhere.
It contains the plugin version declarations and a bunch of profiles that I've blogged about in the past (e.g. here). Furthermore, since of course I'm the author of this file, I've declared the organization, author, license etc... sections that will be just inherited as the rest - you bet, since they are shared by most of my projects, I don't need to repeat them. Basically I can say that I'm saving most of the 700+ lines that it contains in all of my projects.

In case of plugins that are frequently reconfigured, you can save some lines with a simple trick. For instance, some projects of mine have got modules that contain examples of use of a library. They are just provided as documentation for people that download the stuff, but I don't want to deploy their artifacts; in other words, I need to disable the deploy plugin for them. Usually, to achieve this single effect you need a bunch of lines such as:

<plugin>

    <groupId>org.apache.maven.plugins</groupId>

    <artifactId>maven-deploy-plugin</artifactId>

    <configuration>

        <skip>true</skip>

    </configuration>

</plugin>

In my Super POM there is this section:

<properties>

    <disableDeploy>false</disableDeploy>

    ...

</properties>



...



<plugin>

    <groupId>org.apache.maven.plugins</groupId>

    <artifactId>maven-deploy-plugin</artifactId>

    <configuration>

        <skip>${disableDeploy}</skip>

    </configuration>

</plugin>

so now to disable deployment I only need to add this in my project POMs:

<properties>

    <disableDeploy>true</disableDeploy>

</properties>

If you want to go a step beyond, have a look at Calm, a project made by SourceSense where they are pushing this approach trying to achieve some standardization level (this is beyond my reach at the moment, and I'm just happy to have my own super POM, at the moment). Actually, people at SourceSense were the ones who gave me this hint.

Now, the second step for the dependencyManagement section. Well, it can be fully imported from an external file by means of the <scope>import</scope>. For instance, look at the following section in blueMarine's POM:

<dependencyManagement>

    <dependencies>

        <dependency>

            <groupId>it.tidalwave.metadata</groupId>

            <artifactId>metadata</artifactId>

            <version>${metadata.version}</version>

            <type>pom</type>

            <scope>import</scope>

        </dependency>

        <dependency>

            <groupId>it.tidalwave.semantic</groupId>

            <artifactId>semantic</artifactId>

            <version>${semantic.version}</version>

            <type>pom</type>

            <scope>import</scope>

        </dependency>

        <dependency>

            <groupId>it.tidalwave.netbeans</groupId>

            <artifactId>openbluesky</artifactId>

            <version>${openbluesky.version}</version>

            <type>pom</type>

            <scope>import</scope>

        </dependency>

        <dependency>

            <groupId>it.tidalwave.thesefoolishthings</groupId>

            <artifactId>thesefoolishthings</artifactId>

            <version>${thesefoolishthings.version}</version>

            <type>pom</type>

            <scope>import</scope>

        </dependency>

        ...

    </dependencies>

</dependencyManagement>


This replaces several dozens of single-module declarations, as it imports the whole dependencyManagement sections from the four referenced projects. The secondary advantage, in addition to the less verbosity, is that you're importing coherent module sets - that is, they come from the same release; otherwise, you'd need to keep the coherence by hand, paying attention to what you type.

Note that this is completely different than importing a POM without an explicit scope in the dependencies section: in that case, I'd actually import all the stuff from the four projects cited in the previous example, while I prefer to pick the single things I need.

To give some numbers, two POMs from the forceTen project shrank from about 1400 lines to about 600 - and the solution I've described can be further applied to other sections, shrinking them even more.

Related Topics >>

Comments

any buildnumber plugin workaround?

Nice blog, especially regarding Maven/Mercurial issues :) Could you please elaborate more on your point 4? I'm trying to automate versioning and deployment using Hudson, Mercurial and Maven, but I faced the issue described at: http://www.mail-archive.com/dev@mojo.codehaus.org/msg17145.html Do you have any workaround for it? Regards.

Unfortunately, I'm plagued

Unfortunately, I'm plagued but that bug too. I work around that with aliasing the maven command to

mvn -DbuildNumber=`hg id -i`

For having the changeset id in Hudson, I use the profiles 'generate-revision-id' and 'show-revision-id' of my super POM:

http://repo1.maven.org/maven2/it/tidalwave/superpom/superpom/1.0.1/super...

 

I don't think it works on Windows, and I frankly hope that the buildnumber plugin bugs with hg will be solved sooner or later, so I can get rid of all the workarounds.

Thanks for the response

I have had to focus on some other tasks for the last two weeks, but once I get back to the automated versioning, I'll try to implement your workaround. Thanks again.