Enforcing No Maven SNAPSHOTs (Or No Means No)

Continuous Mindset

When I first set out on my quest to integrate Maven into a Continuous Delivery deployment pipeline, I was pretty used to working with Maven SNAPSHOTs. Back then I wouldn’t have dreamed of getting rid of them because I couldn’t see how or why I should do so. That’s all changed for me now though because as I read and re-read the excellent Continuous Delivery book, it finally sunk in that you can live without SNAPSHOTs – if you think a bit differently.

One of the overarching goals of the Continuous Delivery deployment pipeline is to make releases into production so common and so frequent that we can live up to the first principle of the Agile Manifesto:

Our highest priority is to satisfy the customer through early and continuous delivery of valuable software.

After working with Maven for years, all the time using SNAPSHOTs, how could I possibly come to believe that they aren’t needed anymore? Isn’t that Maven heresy (or some techno-crime variant)?

There was a change in my thinking along the way. Here’s how I used to think about it:

It sure does seem like SNAPSHOTs make it easy to develop because I can pick up other developer’s changes via their SNAPSHOT versioned artifacts just by updating my dependencies. We’ll get around to releasing when we’re ready, but hey right now I just need to get some coding done! Come back later when I have that done and then we’ll talk about the release!

Did that last bit sound like procrastination to you? Upon reflection I realized that while us developers really like the SNAPSHOT, it does little to move our software into production. In fact, the SNAPSHOT is a bit of a crutch because it has temporary written all over it. While we can’t release a temporary artifact, we sure can develop around it with the latest changes magically arriving via SNAPSHOT updates. But how are we going to reconcile that with Continuous Delivery?

Here’s how I think about SNAPSHOTs now with a Continuous Delivery mindset:

I’ll work in one source codebase that has no SNAPSHOT dependencies and every time I commit to Subversion, the Jenkins continuous integration server deploys a releasable artifact into the Nexus Maven repository. If I need the latest changes from other developers, I will update my local source codebase from the Subversion repository. This means that I’ll need to build the whole project, but it also means that when I test my code I’ll be in sync with the other developers.

However I won’t need to build all of the modules every time I make a coding change, but if I really expect this business application to ship with everything working (say tomorrow, or perhaps next week) it would be wise for me to stay on top of those other developer’s changes as I work.

Maven includes a command option that will let you perform a build on a subset of the modules in a multimodule project. You just need to let it know you want to do so. For instance, the following Maven command builds just the demo-domain module in our sample multimodule project:

mvn --projects demo-domain clean install

The Enforcer

The Maven enforcer plugin comes with rules that can enforce no SNAPSHOT versions. When a rule is violated the enforcer plugin will fail the build. That way we’ll know immediately when either the project version or a dependency is using a SNAPSHOT version.

The LOCAL suffix on the version supports builds on a developer workstation. The developer can build and install artifacts into the local Maven repository (~/.m2/repository) that include the LOCAL suffix. However, when Jenkins builds the project, the version will include the Subversion revision number instead of LOCAL as was described in a previous article.

The following plugin configuration illustrates how to enforce a no SNAPSHOT versions project:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-enforcer-plugin</artifactId>
  <version>1.0.1</version>
  <executions>
    <execution>
      <id>enforce-no-snapshots-and-version-format</id>
      <goals>
        <goal>enforce</goal>
      </goals>
      <configuration>
      <rules>
        <requireProperty>
          <property>project.version</property>
          <message>"Project version must comply with standard."</message>
          <regex>^\d+\.\d+\.\d+-(LOCAL|\d+)$</regex>
          <regexMessage>"Project version must include major.minor.patch
 number parts and end in svn revision number or LOCAL (e.g.,
 1.2.0-LOCAL)."
          </regexMessage>
        </requireProperty>
        <requireReleaseVersion>
          <message>current project must not be a snapshot</message>
        </requireReleaseVersion>
        <requireReleaseDeps>
          <message>no snapshot dependencies allowed</message>
        </requireReleaseDeps>
      </rules>
      <fail>true</fail>
    </configuration>
    </execution>
  </executions>
</plugin>

Living without SNAPSHOTs

So now that we’ve rigged up a POM that enforces no SNAPSHOTs, can we live with it? Does it still let developers develop and also work with Continuous Delivery? Well my short answer is that I think it can work. The longer answer is that this needs to be battle-tested on real projects for a while before making any grand claims for success. As with all technical endeavors, this sort of thing is always a moving target. For example, the recently released Thoughtworks Technology Radar report has put Maven on hold and recommends considering a move to Gradle.

For those of us that are going to stick with Maven for some time, there’s more to this story regarding the deployment pipeline. And I’ll be writing about some of these other topics in future articles. So for now, let’s just say that while SNAPSHOTs are a thing of the past in some circles, there are plenty of other challenges when it comes to fulfilling the goals around delivering software that we can ponder.

Using Maven versions plugin in the pipeline

Maven versions plugin

The Maven versions plugin includes a goal to set the version for a Maven project. It’s a best practice to always use the versions plugin to set the version of a Maven project because this can prevent mistakes. This is especially handy when the project includes multiple modules, because the versions plugin can set the version in each of the pom.xml files in one command.

For this article, we’ll be using a simple multimodule Maven project to illustrate the use of the versions plugin. The project is named demo-parent and includes multiple Maven modules as illustrated below.

The following example illustrates use of the versions plugin to set the project version. This command is run from the root directory of the Maven project (where the parent pom.xml lives):

mvn versions:set -DnewVersion=1.2.3-LOCAL

After running the above command, the version will have been set in each module of the demo-parent Maven project. It turns out that in my projects, the only version entries that require setting are the /project/version tag in the parent module and the /project/parent/version in each of the modules that inherit from the parent (i.e., sub modules). I don’t include the /project/version tag in the sub modules since they can inherit the version from the parent module. However, Maven will throw a fit if you don’t specify the /project/parent/version in each sub module, so we must set it.

The following is an excerpt from the demo-webapp pom.xml that illustrates what this looks like after setting the version:

<parent>
 <artifactId>demo-parent</artifactId>
 <groupId>com.acme.system</groupId>
 <version>1.2.3-LOCAL</version>
 </parent>

 <artifactId>demo-webapp</artifactId>
 <packaging>war</packaging>
 <name>demo-webapp</name>
 . . .

There is one other minor detail when using the versions plugin that needs our attention. When the command is run as illustrated earlier, a backup copy of the pom.xml file will be written into the same directory (i.e., pom.xml.versionsBackup). Depending on your scenario, you may want this backup pom.xml file, or not. This will come into play later on when I demonstrate how to avoid performing a clean Subversion checkout for each Jenkins build  job execution. To control whether the backup pom.xml file is written, we need to add one more parameter to our Maven command:

mvn versions:set -DnewVersion=1.2.3-LOCAL -DgenerateBackupPoms=false

Setting the version in Jenkins

In order to eliminate Maven SNAPSHOTs in our Jenkins builds, we’ll need to set the Maven version before the Maven build job runs. A script that executes as a Jenkins Pre Step will perform the work. This script could be written in any number of scripting languages. I happen to like Ruby so that’s what we’ll use to set the Maven version.

The script shown below performs the following steps:

  1. Obtain the Subversion revision number from Jenkins environment
  2. Look up the current Maven project version from the parent pom.xml
  3. Extract the main Maven version portion (e.g., 1.2.3)
  4. Combine the main version with the Subversion revision number (e.g., 1.2.3-45678)
  5. Sets the Maven version using the versions plugin.

This all happens in a Jenkins Pre Step before the standard Maven build runs, so the result is indistinguishable from a normal build as far as Maven can tell. The Jenkins job is set up to deploy the resultant build artifacts to a Nexus artifact repository.

#!/usr/bin/env ruby
require 'rexml/document'
include REXML
# [1] obtain Subversion revision number from Jenkins environment:
svn_revision = ENV['SVN_REVISION']
# [2] obtain project version from pom.xml file:
pom_doc = Document.new(File.new("pom.xml") )
pom_project_version = XPath.first(pom_doc, "/project/version").text
# [3] extract the three-part Maven version number:
main_version = "INVALID_VERSION: #{pom_project_version}"
if pom_project_version =~ /(\d+\.\d+\.\d+)(.*)/
 if "-SNAPSHOT".eql?($2)
  puts "main_version: #{main_version}"
  exit(1)
 end
 main_version = $1
else
 puts "main_version: #{main_version}"
 exit(1)
end
# [4] compose the actual Maven version number:
maven_version = "#{main_version}-#{svn_revision}"
# [5] set the Maven version using versions plugin:
mvn_command = "mvn versions:set -DnewVersion=#{maven_version} -DgenerateBackupPoms=true"
system(mvn_command)
puts "\n\nSet version for build #{ENV['BUILD_NUMBER']} to #{maven_version} \n\n"

So is that gonna do it?

Now we have build jobs running in Jenkins that deploy “released” Maven artifacts to a Nexus release artifact repository. The version includes the main Maven three-part numerical version plus the Subversion revision number. And each artifact has the correct version baked into all of its Maven metadata. There aren’t any Maven warnings because we are no longer using version expressions. Furthermore, artifacts are only deployed once into Nexus, so we don’t need to turn off the Nexus feature that prevents overwriting of artifacts with the same Maven coordinates.

While this is much better than my first attempt, there are still more things we can do to tune this solution. Next time I’ll go over a technique to ensure that there are no SNAPSHOTs in the Maven build. After all, what would be the point of all of this if we didn’t fail the build when SNAPSHOTs sneak into the Maven project?

Maven build pipeline without SNAPSHOTs

Introduction

After reading Continuous Delivery (CD) a while back, I began to think about how Maven could be used to support a Continuous Delivery deployment pipeline. One of the first things that gets in the way of CD principles is the concept of SNAPSHOTs in Maven. In the pipeline, every commit build needs to produce final artifacts that could be deployed into production if all tests pass, QA approves and the application features are complete enough for use by the business.

So I posted a question and the solutions started to roll in. Eventually I got around to trying out a combination of the recommended approaches and ran into some issues. This article describes how I went back to the beginning and came up with what I think is a better way to eliminate SNAPSHOTs from Maven for a deployment pipeline.

Maven SNAPSHOTs are not suitable for deployment into production and the Maven release plugin really doesn’t fit into a deployment pipeline – I won’t be rehashing the reasons why this is so. This has already been very well covered by others including James Betteley, Steve Smith and Axel Fontaine.

First Attempt

My initial effort involved combining James Betteley’s version expression approach with Axel Fontaine’s pom filtering. The version expression goes into the version element of the pom.xml and then the properties involved are set within the continuous integration server job execution. The relevant section of the pom.xml file looks something like the excerpt shown below:

<modelVersion>4.0.0</modelVersion>
<groupId>com.acme</groupId>
<artifactId>foo-bar</artifactId>
<packaging>jar</packaging>
<version>${main.version}-${build.number}</version>
<description>Description about this application</description>
<properties>
<svn.revision>LOCAL</svn.revision>
<build.number>${svn.revision}</build.number>
<main.version>5.0.2</main.version>
</properties>
. . .

The default value of the version expression in the example above is 5.0.2-LOCAL. This is the version a developer would see when building on a workstation. When the developer commits to Subversion and a job is triggered on a continuous integration server (e.g., Jenkins) the svn.revision property will be assigned from the environment. For Jenkins this would utilize SVN_REVISION which is one of the environmental variables available in a job.

The pom filtering technique involves the use of resource filtering to create a copy of the pom.xml in which all of the variables have been substituted with actual values so that the pom.xml that is deployed to the Maven repository will contain the actual version instead of the version expression. This pom filtering technique is covered in Axel Fontaine’s article.

So what’s not to like?

At this point I had Maven build jobs running in Jenkins that were deploying artifacts to my Nexus Maven repository with versions made up from main.version and build.number, so why wasn’t I all done at this point?

It turns out that this set of techniques lead to some issues:

  • Using a version expression in Maven 3 results in a warning. Although it’s just a warning and lots of input has been made to liberalize/improve Maven’s ability to deal with version expressions, the issue still remains unfixed.
  • While Nexus properly tracks the release version numbers in the releases repository and the uploaded POMs have the correct version, the filtering technique involves two uploads of the pom.xml files: one which contains the version expression and a second which provides the filtered pom.xml file. This won’t work unless I let down Nexus’s guard against overwriting the pom.xml files for a deploy.
  • The filtered pom.xml file has ALL of its properties filtered which includes properties such as target directory location which looks a bit chunky in the deployed pom.xml.
  • For a multimodule Maven project transitive dependencies for modules do not get resolved so I had to redundantly specify each module’s coordinates.
  • The pom.xml that gets written into the jar or war packaged Maven artifact still contains the version expression. Yes, I know that might seem a bit picky, but sometimes QA people get worked up if they see that sort of thing – I’m just sayin’.

What to do about it?

In other encounters with Maven I generally have found that it’s better to “fight fire with fire”, so inspired by this idea, I decided that I would try to use the Maven versions plugin to set the version in the POMs as part of the Jenkins job execution in a pre-build step. Of course, this needs to happen before Maven needs to use the version in a regular build, and after the Subversion checkout of the project source. In addition, I want to preserve the version in the pom.xml as stored in Subversion in the general form (i.e., <version>5.0.2-LOCAL</version> ) and not end up with Subversion conflicts on subsequent checkouts in the Jenkins job.

With this as a starting point, I set out to come up with a complete solution. In the next post, we’ll cover how this works in detail.

Getting started

This blog will be a compilation of technical tidbits that I have collected and found to be interesting enough to share.

Stay tuned for the first article about how to make no-SNAPSHOT Maven builds play nicely in a Continuous Delivery-style build pipeline…

Design a site like this with WordPress.com
Get started