In the previous parts, I talked about the requirements for our maven process and how we met some of them setting up our maven infrastructure.

Setting up your infrastructure is just one part of the process. The other part, and in some ways a more important one, is about how you set up your projects. A Maven project is configured through its pom.xml - also known as simply a POM. Among other things in this file you specify:

  • Maven plugins you use to build your project and their versions
  • configuration of some of the plugins
  • repositories to deploy our artifacts to
  • details of the release procedures

We wanted to specify generic enough bits of the above information in one base POM that everyone would then reuse.
This is important in order to make sure that every time someone runs a Maven build on one of our projects – it runs exactly as it should. In other words – we wanted to minimise the dependency on Maven plugin updates and environment differences.

In the base POM we also enforce (to certain degree) version requirements for JDK, Maven and other tools the build might depend on.

Instead of one super POM, we came up with a little POM hierarchy. Yes, you can use inheritance with Maven POMs.

Our POM hierarchy starts with a base POM.

The base POM contains the bulk of the configuration. Among other things it configures:

  • Maven plugin versions
  • a WebDAV extension for proxy and repository access.
  • the Maven JavaDoc plugin for the release phase
  • the details of publishing a Maven site for our libraries
  • workarounds for some known issues of Maven plugins
  • some of the Maven plugins such as Clover, compiler, unit test / surefire, IDEA etc.

The base POM is usually not used directly on its own. Instead we have extensions for each type of libraries we deal with:

These extend the base POM and configure their respective repositories to deploy to by default. There is an exception however. The closed source POM has some additional configuration trickery to split the deployment of sources into our private Maven repository since Maven does not support more than one repository for deployment.

Some general guidelines

Since most of the stuff is already configured in the base POMs, the developers responsibility is simply to maintain project specific configuration. Apart from the usual name, version, description and dependencies this only means to make sure that the SCM information is correct.

Dependency management is, of course, a responsibility of the project developers. However James, our Release Engineer, has been looking at various ways of automating some of that tedious process.

We also started playing with the Maven changes plugin that will generate a changelog from the relevant Jira project. When we roll it out this will mean every project will have to have an issueManagement element correctly set up in its POM too. (issueManagement is another Maven configuration option that specifies where the issue tracking system for this project is. Therefore upon release it will be possible to retrieve the list of issues that was fixed for this version of the project)

More complex projects

We have a lot of common modules that can simply extend one of the base POMs. They are self-contained individual libraries that products depend on. However, for products themselves such an approach is suitable. Each product contains a few sub-components that have to be built together.

For products we use Maven aggregation. There is a parent POM for each product that specifies modules for each product sub-component. The product POM specifies a version for each dependency, SCM information, variables that sub-components share etc.

The modules reflect the natural components of the product. We did not have to do any extra decomposition for Maven. For example you can consider the following. Each product might have a web-interface part. This normally contains the UI code, velocity templates, JSPs, CSS etc. This is one module called confluence-webapp for example. There might be another module that contains specific APIs or implementation. This is another module that bears a relevant name.

Once organised that way all you need to build and package the product is call a Maven goal at the top level and each module is built and packaged according to its own POM. If you even need to upgrade a specific dependency – you only have to do it in the product level POM.

Archetypes

We also have set up a few Maven archetypes. This is mainly done for plugins since you are definitely going to need to start new Maven projects when writing a plugin than in product code.

We have a Confluence plugin archetype and Jira plugin archetype available on public SVN and documentation on how to use them. Archetypes for the rest of the products are coming up fairly soon as well.

Once there is an archetype, setting up a new project is a command line away:

mvn archetype:create 
-DarchetypeGroupId=com.atlassian.maven.archetypes 
-DarchetypeArtifactId=confluence-plugin-archetype 
-DarchetypeVersion=6 
-DremoteRepositories=https://maven.atlassian.com/repository/public/ 
  -DgroupId=$MY_PACKAGE$ -DartifactId=$MY_PLUGIN$

One of the main benefits of these archetypes is that they are pre-configured to run functional tests. If you have written some tests that will run against a live instance of the product – all you need to do is call:

mvn integration-test

and a new instance of the product will be fired up locally on your box, tests executed against it and the instance shut down.
Plugin archetypes are also pre-configured to deploy your plugin to either contrib or contrib-snapshot repository as appropriate.

Maven processes we use

As a direct result of the Maven project and infrastructure setup our daily Maven processes are fairly simple and straight forward:

Development

In daily development we use:

mvn idea:idea

or

mvn eclipse:eclipse

to set up the development environment. There are, unfortunately, a few limitations in how well Maven supports IDEs and visa versa.

We also use

mvn test

or

mvn integration-test

for building and testing the code. Although a lot of it is also done from within the IDE. These steps are mainly used as a verification before committing to SVN.

To deploy a current snapshot or a release – all that you needed is:

mvn deploy

Release process

The release process is as easy as

mvn release:prepare release:perform

in most of the cases…

More on the Release process

The above described release process only releases the product JAR and WAR files to our private Maven repository. This is not what we ship to the end user however.

Distribution builds

We have a separate project for each product. We call them distribution builds and they fall under release engineering.

Distribution builds produce a few final packages that you can find on the downloads page. These are source build, standalone build and WAR build. Some products have a few additional builds such as development bundles etc.

Distribution builds mainly rely on Maven assembly plugin to build a package. For the source build however, we had to build our own Maven plugin.

Source build plugin

Our source build plugin looks at the project it is meant to build and gathers its dependencies. Then it retrieves metadata for each of the dependencies and extracts from SCM locations for each project. Once that is done – making up a source build is simply a matter of checking out the source and passing the process over to the assembly plugin. That is all our plugin does at the moment.

Summary

There is a lot of design that goes into making Maven projects work on a large scale. It is not something to take lightly. The above design made our builds much more stable and robust. After all – pain amplifies tenfold if your build system does not behave in a predictable way.

Despite all of this work, there are still some issues that crop up every now and again. In the final part of this series I will describe them and our efforts in tackling them.

Fresh ideas, announcements, and inspiration for your team, delivered weekly.

Subscribe now