2009-05-08

Version number management for multi-module Maven projects

I've been thinking about how to best organize the Maven modules in Dimdwarf. My requirements are that (1) the version number of the public API module must stay the same, unless there are changes to the public API, (2) opening and developing the project should be easy, so that I can open the whole project with all its modules by opening the one POM in IntelliJ IDEA, and (3) all code for the project should be stored in one Git repository, so that the version history for all modules is combined and checking out the whole project can be done with one command.

The project structure is currently as follows (these nice graphs were produced with yEd).

I have one POM module, "dimdwarf", at the root of the project directory. It is the parent of all other modules (that's where dependencyManagement and the common plugins are configured) and it also has as submodules all other modules. The "dimdwarf-api" module is what all users of my framework will depend on, so I want its version numbers to change very rarely - only when the API is changed, not every time that I release just a new version of the server implementation. The "dimdwarf-aop" and "dimdwarf-agent" modules handle the bytecode manipulation and they are needed as part of the bootstrap process. "dimdwarf-core" does not use the AOP classes directly, but it has a dependency to "dimdwarf-aop" for testing purposes. The module "dimdwarf-dist" assembles all other modules together and builds a redistributable ZIP file.

Yesterday I was looking for a solution for reaching my requirements. StackOverflow did not have any existing questions which would have touched exactly this problem, but in one of the answers there was a link to Oliver's blog post which matched my situation perfectly (also read the follow-up). He proposed a solution that checks for consistency in the project structure and fails the build if the modules have dependencies with a wrong version.

After thinking about that some, I came up with a possibly better way to manage the version numbers. It would be a tool (possibly implemented as a Maven plugin) that helps in updating the module version numbers. The tool would be called "module version bumper" or similar. Its commands should be run the directory that contains the project's "workspace POM" (one that has as submodules all modules of the project, but none of the modules depend on it), so that the tool can find all modules that are part of the project.

For the version bumper to work with Dimdwarf, the project structure needs to be refactored:

All the common settings (dependencyManagement, plugins etc.) are in the "parent" POM file, which the other modules then extend. I decided to make "dimdwarf-api" independent from it, because I don't want library version upgrades to be reflected in the API's version number. (I could also have created "parent-common" and "parent-deps" which extends "parent-common", but let's keep it simple for now and tolerate some duplication in the API's POM.) The workspace POM, "dimdwarf", does not anymore have the added responsibility of being also the parent POM, which helps the project get rid of cyclic dependencies between the POMs.

To explain how the version bumper would work, let's start with an example of the workflow of making changes to the project. In the beginning, version 1.0.0 of Dimdwarf has recently been released and all modules have "1.0.0" as their version number.

    parent 1.0.0
    dimdwarf-api 1.0.0
    dimdwarf-api-internal 1.0.0
    dimdwarf-core 1.0.0
    dimdwarf-aop 1.0.0
    dimdwarf-agent 1.0.0
    dimdwarf-dist 1.0.0
    dimdwarf 1.0.0

I notice a bug in the "dimdwarf-aop" module, so I need to make changes to it. Since "dimdwarf-aop" now has a release version (i.e. one that does not end with "-SNAPSHOT"), I need to bump its version to be the next development version (i.e. a "-SNAPSHOT" version higher than the previous release version).

In the project's root directory, I run the version bumper tool's command: "mvn version-bump dimdwarf-aop". This command reads the version number of all modules in the project and determines that "1.0.0" is the highest version number in use. Since it is a release version number, the tool prompts me for the next development version, offering "1.0.1-SNAPSHOT" as the default. I accept the default. Then the tool changes that to be the version number of "dimdwarf-aop" and of all modules that depend on "dimdwarf-aop" at runtime ("dimdwarf-core" has only a test-time dependency, so it is not changed). So now the version numbers are as follows, with changes highlighted in blue:

    parent 1.0.0
    dimdwarf-api 1.0.0
    dimdwarf-api-internal 1.0.0
    dimdwarf-core 1.0.0
    dimdwarf-aop 1.0.1-SNAPSHOT
    dimdwarf-agent 1.0.1-SNAPSHOT
    dimdwarf-dist 1.0.1-SNAPSHOT
    dimdwarf 1.0.1-SNAPSHOT

Then I make some changes in "dimdwarf-aop" to fix the bug and commit it to version control.

Some days after that, I begin making some bug fixes to the "dimdwarf-core" module. I change the code, but forget that I have not bumped that module's version to be next development version. I commit the changes to version control (I use Git), but thankfully I have a pre-commit hook that verifies that all modules with changes use a development version (or a release version that is strictly higher than the version in the previous commit - otherwise you couldn't commit a new release). The commit fails with a message:

The following files were changed in module "dimdwarf-core" which has the release version "1.0.0". Update the module to use a development version with the command "mvn version-bump dimdwarf-core" or recommit with the --no-verify option to bypass this version check.
    dimdwarf-core/src/main/java/x/y/z/SomeFile.java
    dimdwarf-core/src/main/java/x/y/z/AnotherFile.java

I realize my mistake, so I run the command "mvn version-bump dimdwarf-core". This command reads the version number of all modules in the project and determines that "1.0.1-SNAPSHOT" is the highest version number in use. Since it is a development version number, the tool prompts me for the development version for "dimdwarf-core" module, offering "1.0.1-SNAPSHOT" as the default. I accept the default. Then the tool changes that to be the version number of "dimdwarf-core" and of all modules that depend on "dimdwarf-core" at runtime (only "dimdwarf-dist" and "dimdwarf" depend on it, but since they already have version "1.0.1-SNAPSHOT", they don't need to be updated). So now the version numbers are as follows:

    parent 1.0.0
    dimdwarf-api 1.0.0
    dimdwarf-api-internal 1.0.0
    dimdwarf-core 1.0.1-SNAPSHOT
    dimdwarf-aop 1.0.1-SNAPSHOT
    dimdwarf-agent 1.0.1-SNAPSHOT
    dimdwarf-dist 1.0.1-SNAPSHOT
    dimdwarf 1.0.1-SNAPSHOT

Now I want to publish the new release, so I run a tool that changes all the development versions to release versions (is there already a Maven plugin that does it?). After that the versions numbers are:

    parent 1.0.0
    dimdwarf-api 1.0.0
    dimdwarf-api-internal 1.0.0
    dimdwarf-core 1.0.1
    dimdwarf-aop 1.0.1
    dimdwarf-agent 1.0.1
    dimdwarf-dist 1.0.1
    dimdwarf 1.0.1

I commit the changes to version control and tag it as "dimdwarf-1.0.1". I checkout the tag to a clean directory, build it and deploy all the 1.0.1 artifacts to the central Maven repository (the already deployed 1.0.0 version may not be redeployed). I also collect the newly built redistributable ZIP file from the /dimdwarf-dist/target directory and upload it to the web site for download.

So that is my idea for managing version numbers in multi-module Maven projects. What do you think, would a workflow such as this work in practice? Do you think that there will be problems with this version numbering scheme (mixed development and release versions) when using continuous integration or when deploying to a Maven repository (where overwriting previously deployed versions is not allowed)? Would somebody with experience in Maven plugin development be willing to help in implementing this?

4 comments:

  1. Cool post. I think the idea is good. I'm in the process of designing and developing a solution to a multi module project development in the company I work for. The solution is not far from what you've suggested. I'll comment back once I'm done.

    ReplyDelete
  2. I have exactly the same problem.
    There is a maven release plugin, but the last time I looked it was too closely tied to the svn workflow and it commited stuff whithout prompting which I didn't like. I also don't think it supports this multi-module scenario nicely.
    I wish this plugin you described already existed, I think it will work perfectly and other tools won't have any problems with it.
    I'm also tempted to quickly make one although I don't have mvn-plugin development experience, but I think its simple.

    ReplyDelete
  3. I think you could use the versions-maven-plugin for that. Although I haven't tried it yet.
    It has a goal called "use-next-snapshots/use-next-releases" where you specify which artifacts to move to the next snapshot/release version.
    @see http://mojo.codehaus.org/versions-maven-plugin/examples/advancing-dependency-versions.html

    ReplyDelete
  4. Use maven release plugin http://maven.apache.org/plugins/maven-release-plugin/

    ReplyDelete