Use a unique property to set Maven version

This post is also available in: fr

When working with a Maven multi-module (dozens of them) project on multiple branches, I always end up with merge conflicts on my pom.xml <version> elements.
Here is a solution I use to remove all the <version> element duplication.

Our example project is made of two modules:

.
├── pom.xml
├── submodule-1
│   └── pom.xml
└── submodule-2
    └── pom.xml

Parent project pom.xml looks like:

<project>
    <groupId>com.github.jcgay.example.version</groupId>
    <artifactId>parent-pom</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <modules>
        <module>submodule-1</module>
        <module>submodule-2</module>
    </modules>

</project>

and the ones for modules:

<project>
    <parent>
        <groupId>com.github.jcgay.example.version</groupId>
        <artifactId>parent-pom</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>submodule-1</artifactId>

</project>

Project version is duplicated in each module’s pom.xml, it’s easy to change them with the Versions plugin. But merges between branches are a pain, conflicts are happening and we just can’t keep the changes from one of the branch 😓.

Define the version with a property

First try, extract the version is a Maven property. Our parent pom becomes:

<project>
    <groupId>com.github.jcgay.example.version</groupId>
    <artifactId>parent-pom</artifactId>
    <version>${my-version}</version>
    <packaging>pom</packaging>

    <properties>
        <my-version>1.0-SNAPSHOT</my-version>
    </properties>

    <modules>
        <module>submodule-1</module>
        <module>submodule-2</module>
    </modules>

</project>

and the module use the same ${my-version} property to designate its parent:

<project>
    <parent>
        <groupId>com.github.jcgay.example.version</groupId>
        <artifactId>parent-pom</artifactId>
        <version>${my-version}</version>
    </parent>

    <artifactId>submodule-2</artifactId>

</project>

Surprisingly it works 😲
Only downside is that warning messages are displayed by Maven.

Some problems were encountered while building the effective model for com.github.jcgay.example.version:submodule-1:jar:1.0-SNAPSHOT
'version' contains an expression but should be a constant. @ com.github.jcgay.example.version:parent-pom:${my-version}, /Users/jcgay/dev/toolbox-examples/unique-maven-version/pom.xml, line 7, column 14

Some problems were encountered while building the effective model for com.github.jcgay.example.version:submodule-2:jar:1.0-SNAPSHOT
'version' contains an expression but should be a constant. @ com.github.jcgay.example.version:parent-pom:${my-version}, /Users/jcgay/dev/toolbox-examples/unique-maven-version/pom.xml, line 7, column 14

Some problems were encountered while building the effective model for com.github.jcgay.example.version:parent-pom:pom:1.0-SNAPSHOT
'version' contains an expression but should be a constant. @ com.github.jcgay.example.version:parent-pom:${my-version}, /Users/jcgay/dev/toolbox-examples/unique-maven-version/pom.xml, line 7, column 14

It is highly recommended to fix these problems because they threaten the stability of your build.

For this reason, future Maven versions might no longer support building such malformed projects.

So we can’t just really use that, and there is also a much bigger problem but we will discover it later.

Continuous delivery friendly version

Since Maven 3.2.1 we can use registered properties to hide the previous warning messages.

Replacing the property my-version by revision will do the trick 🎆.

But the real problem is showing now… When Maven install (or deploy) ours artifacts, pom.xml are copied without modification (${revision} properties are not replaced by their resolved value). If you try to depend on one of these artifacts it will fail because ${revision} will not be set in this outside project.

Example with this simple project which is not built within parent-pom reactor:

<project>
    <groupId>com.github.jcgay.example.version</groupId>
    <artifactId>independant-project</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>com.github.jcgay.example.version</groupId>
            <artifactId>submodule-1</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

</project>

When we launch a build:

$> mvn test

Scanning for projects...

Using the MultiThreadedBuilder implementation with a thread count of 4

------------------------------------------------------------------------
Building independant-project 1.0-SNAPSHOT
------------------------------------------------------------------------
Downloading: https://repo.maven.apache.org/maven2/com/github/jcgay/example/version/parent-pom/$%7Brevision%7D/parent-pom-$%7Brevision%7D.pom
------------------------------------------------------------------------
BUILD FAILURE
------------------------------------------------------------------------
Total time: 0.882 s (Wall Clock)
Finished at: 2016-08-09T15:26:09+02:00
Final Memory: 12M/193M
------------------------------------------------------------------------
Failed to execute goal on project independant-project: Could not resolve dependencies for project com.github.jcgay.example.version:independant-project:jar:1.0-SNAPSHOT: Failed to collect dependencies at com.github.jcgay.example.version:submodule-1:jar:1.0-SNAPSHOT: Failed to read artifact descriptor for com.github.jcgay.example.version:submodule-1:jar:1.0-SNAPSHOT: Could not find artifact com.github.jcgay.example.version:parent-pom:pom:${revision} in central (https://repo.maven.apache.org/maven2) -> [Help 1]

In fact the pom.xml available in the local repository still defines ${revision} as a <version> for the pom-parent.

Filtering pom at installation/deployment

These non filtered properties remind me an old blog post. The author was filtering (with maven-resources-plugin) its pom.xml to install them instead of the originals. I was wondering if this could be achieved by a Maven extension (the amount of necessary XML and the hacky feeling of the solution just discourage me…). This is doable with Aether (used by Maven 3) by implementing a org.eclipse.aether.impl.MetadataGenerator. We can code our hack 🤔.

Code is available at GitHub. This a Maven extension which will transform pom artifacts when installing and deploying. Each ${revision} occurrence is replaced by the artifact’s resolved version. Problem solved!

Activate it using the core extensions configuration mechanism by creating a ${maven.multiModuleProjectDirectory}/.mvn/extensions.xml file with:

<?xml version="1.0" encoding="UTF-8"?>
<extensions>
    <extension>
      <groupId>fr.jcgay.maven.extension</groupId>
      <artifactId>unique-revision-maven-filtering</artifactId>
      <version>[latest-version]</version>
    </extension>
</extensions>

or by copying jar to %M2_HOME%/lib/ext.

Conclusion

We can now use a unique property to set version across all modules in a multi-modules project. This does not seems to play by the rules of the continuous delivery friendly version but merges can now be done without resolving multiple conflicts on <version> XML elements!
I may have totally missed a simpler way to do this ? (in that case I’d really like to know it 😉).

comments powered by Disqus