(Trying to) Sanely Deal with Maven Dependency Versions

Posted by Tejus Parikh on November 5, 2011

One challenge of the maven way is how to sensibly version dependencies in a large, but constantly changing, project.

In the ideal maven world, each module is a highly defined project, with a highly structured team and releases only occur once the contract has been fully defined, tested, and whatever else enterprise teams do. In many realities, projects are simply logical constructs that don't have strict contracts between the other module. The production environment is a mix of highly stable and rigorously tested products and experimental, pushed-constantly projects.

In this environment, automatic versioning of dependencies is both unnecessary and costly, and the dependencies need to be frozen at branch time of the stable, top-level projects. The other requirement is that the changes to the poms need to be minimal, so that any merge conflicts can be easily resolved.

The approach I've used, combining a few different ideas from the internet, is to remove any version numbers referencing internal projects from child modules. The versions are then included as properties in the parent project.

Freezing the set of dependencies for the module tree is then a three part process of: - Change the version number in the parent pom - Change the version properties of the parent pom - Change the parent version number in the child poms

To illustrate how this works, consider a project that has com.tejusparikh.web with a parent pom com.tejusparikh.

<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/maven-v4_0_0.xsd">
     <modelversion>4.0.0</modelversion>
     <groupid>com.tejusparikh</groupid>
     <artifactid>tejusparikh</artifactid>
     <packaging>pom</packaging>
     <version>1.0-SNAPSHOT</version>
     <modules>
         <module>web</module>
     </modules>
     <properties>
         <tejusparikh-version>1.0-SNAPSHOT</tejusparikh-version>
     </properties>
 </project>
<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/maven-v4_0_0.xsd">
    <parent>
        <groupid>com.tejusparikh</groupid>
        <artifactid>tejusparikh</artifactid>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelversion>4.0.0</modelversion>
    <groupid>com.tejusparikh.web</groupid>
    <artifactid>tejusparikh-web</artifactid>
    <packaging>war</packaging>
    <version>${tejusparikh-version}</version>
</project>

If you wanted to freeze the poms at 1.0.RELEASE then the poms would look like the following:

<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/maven-v4_0_0.xsd">
    <modelversion>4.0.0</modelversion>
    <groupid>com.tejusparikh</groupid>
    <artifactid>tejusparikh</artifactid>
    <packaging>pom</packaging>
    <version>1.0.RELEASE</version>
    <modules>
        <module>web</module>
    </modules>
    <properties>
        <tejusparikh-version>1.0.RELEASE</tejusparikh-version>
    </properties>
</project>
<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/maven-v4_0_0.xsd">
    <parent>
        <groupid>com.tejusparikh</groupid>
        <artifactid>tejusparikh</artifactid>
        <version>1.0.RELEASE</version>
    </parent>
    <modelversion>4.0.0</modelversion>
    <groupid>com.tejusparikh.web</groupid>
    <artifactid>tejusparikh-web</artifactid>
    <packaging>war</packaging>
    <version>${tejusparikh-version}</version>
</project>

The point is that every child pom would have a property as it's version number, making it easy to manage an arbitrary number of children.

Of course, you don't want to have to update everything manually. The only method I've found to effectively use maven is to wrap it all in rake. Therefore, I've created a rake task to recurse the directories and change the version numbers.

NAMESPACE_MAP = {'ns' =&gt; 'http://maven.apache.org/POM/4.0.0'}
require 'nokogiri'
require 'find'

namespace :mvn do 
    desc "[path, version] upgrade this version and all of it's children to the version specified as the second character"
    task :version, :path, :version_str do |t, args|
        base_path = args[:path]
        version = args[:version_str]
        puts "Updating all poms in #{base_path} to parent version #{version}"
        Find.find(base_path) do |path|
            if FileTest.directory?(path)
                if File.basename(path)[0] == ?.
                    Find.prune       # Don't look any further into this directory.
                else
                    next
                end
            else
                basename = File.basename(path)
                if(basename == 'pom.xml') 
                    handle_pom(version, path)
                end
            end
        end
    end
    
    def handle_pom(version, path)
        doc = Nokogiri::XML(File.open(path)) {|x| x.noblanks }
        packaging = doc.xpath('/ns:project/ns:packaging', NAMESPACE_MAP).first.text().strip()
        if(packaging == "pom")
            puts "Updating parent pom (#{path}) "
            version_node = doc.xpath('/ns:project/ns:version', NAMESPACE_MAP).first
        else 
            puts "Updating child pom (#{path}) "
            version_node = doc.xpath('/ns:project/ns:parent/ns:version', NAMESPACE_MAP).first
        end
        unless version_node.nil?
            version_node.content = version
            File.open(path,'w') do |f| 
                doc.write_xml_to f, :indent_text =&gt; ' ', :indent =&gt; 4
            end
        end
    end
end

Freezing all of your poms is as simple as calling rake mvn:version[/path/to/parent/dir,VERSION].

Related Posts:

Tejus Parikh

Tejus is an software developer, now working at large companies. Find out when I write new posts on twitter, via RSS or subscribe to the newsletter: