Maven Assembly Plugin Filtering

A colleague at Caplin made some changes to a build pom to setup some filtering on some files. We followed the instructions given on the maven site here. It basically tells you to list the files you want to filter in your assembly descriptor, and to then list the filter and then configure your filter file inside your assembly plugin, like this:

      <plugin>

<artifactId>maven-assembly-plugin</artifactId>

<version>2.2.1</version>

<configuration>

<filters>

<filter>src/assemble/filter.properties</filter>

</filters>

<descriptors>

<descriptor>src/assemble/distribution.xml</descriptor>

</descriptors>

</configuration>

</plugin>

If you do this though, you’ll get an error saying something like:

[INFO] Error configuring: org.apache.maven.plugins:maven-assembly-plugin.

Reason: ERROR: Cannot override read-only parameter: filters in goal: assembly:single

This is basically because the Maven documentation is wrong. In reality you need to add the filters section as a child of the build element, not a child of the assembly plugin’s configuration element.

So, it should look more like this:

    <build>
<filters>
<filter>src/assemble/filter.properties</filter>
</filters>
<plugins>

<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.2-beta-1</version>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptors>
<descriptor>src/main/assembly/kit.xml</descriptor>
</descriptors>
<finalName>${pom.artifactId}-${pom.version}-${p4.revision}</finalName>
<outputDirectory>build/maven/${pom.artifactId}/target</outputDirectory>
<workDirectory>build/maven/${pom.artifactId}/target/assembly/work</workDirectory>
</configuration>
</execution>
</executions>
</plugin>

</plugins>

</build>

Changing a filename using the maven assembly plugin

I’m currently working on a project which requires the build to produce a zip archive of the jar and some other stuff (doc files mainly). I’ve used the maven assembly plugin, with an assembly descriptor to help me out here. However, there was one slightly unusual requirement – I needed to change the name of the jar so that inside the zip, it has a non-standard name, and no version number (the reasons behind this are fairly weak, but basically it’s because a load of other scripts, which we can’t change, expect to find the jar in this non-standard format).

So, the build produces:

myproject-1.0.0.0-SNAPSHOT.jar

but in the zip I need to have:

my-project.jar

Here’s how I did it.

  • Include the maven-assembly plugin in the build:

<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.2-beta-1</version>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptors>
<descriptor>src/main/assembly/kit.xml</descriptor>
</descriptors>
<finalName>${pom.artifactId}-${pom.version}</finalName>
<outputDirectory>build/maven/${pom.artifactId}/target</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>

  • Create the kit.xml in src/main/assembly and specify <destName> in the file inclusion. Here’s my kit.xml:

<assembly>
<id>kit</id>
<formats>
<format>zip</format>
</formats>
<fileSets>
<fileSet>
<directory>src/main/resources</directory>
<outputDirectory>doc</outputDirectory>
<includes>
<include>*.*</include>
</includes>
</fileSet>
</fileSets>
<files>
<file>
<source>build/maven/${artifactId}/target/${artifactId}-${version}.${packaging}</source>
<outputDirectory>/</outputDirectory>
<destName>my-project.jar</destName>
</file>
</files>
</assembly>

As you can see, the assembly descriptor has a “files” section, which is what does the trick for us. I’ve isolated the actual section below for clarity:

<files>
<file>
<source>build/maven/${artifactId}/target/${artifactId}-${version}.${packaging}</source>
<outputDirectory>/</outputDirectory>
<destName>my-project.jar</destName>
</file>
</files>

Maven assembly plugin inheritance headache

Today I’ve had a headache with the maven assembly plugin, and the way it inherits from a parent. The story goes as follows:

I have a uber parent pom, which defines the assembly plugin in the pluginManagement section, and it looks like this:

<artifactId>maven-assembly-plugin</artifactId>
<version>2.2-beta-1</version>
<configuration>
<descriptors>
<descriptor>src/assembly/kit.xml</descriptor>
</descriptors>
<finalName>${pom.artifactId}-${pom.version}</finalName>
<outputDirectory>build/maven/${pom.artifactId}/target</outputDirectory>
<workDirectory>build/maven/${pom.artifactId}/target/assembly/work</workDirectory>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>

Then I have a parent pom for a collection of projects, and this parent pom has the following definition in it:

<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>attached</goal>
</goals>
</execution>
</executions>
<configuration>
<descriptors>
<descriptor>src/main/assembly/kit.xml</descriptor>
</descriptors>
</configuration>

Now, if I simply put <artifactId>maven-assembly-plugin</artifactId> in one of the module’s pom files, it inherits most of everything from the project parent, which makes sense to me. The problem arises when we want to do something nifty with the assembly plugin, namely, create a jar with dependencies, and THEN include that in the package as described by an assembly descriptor which is different to the parent.

Here’s what I had in my project pom:

<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>attached</goal>
</goals>
</execution>
<execution>
<id>copy-fields-conf-file</id>
<phase>package</phase>
<goals>
<goal>attached</goal>
</goals>
</execution>
</executions>
<configuration>
<descriptors>
<descriptor>src/main/assembly/kit.xml</descriptor>
</descriptors>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifestEntries>
<build-number>${build-number}</build-number>
</manifestEntries>
</archive>
</configuration>

The problem was that the initial make-assembly execution was inheriting its configuration from the parent, which tells it there’s an assembly descriptor in src/main/assembly/kit.xml. That file DOES exist there, but inside it, it has:

<file>
<source>build/maven/permissioning-auth-module/target/permissioning-auth-module-${project.version}-jar-with-dependencies.jar</source>
<outputDirectory>/</outputDirectory>
</file>

Now, this is a problem because this jar hasn’t been created yet, that’s what we’re trying to create! So, I commented this section out from the parent, only to find that it inherits it from the uber parent, and here’s why:

In the assembly plugin definition you have execution sections and configurations. You can tie a configuration in to an execution phase, by simply adding it inside the execution scope. If you don’t, and you leave it outside, then it becomes a general definition which is inherited by any child projects whenever they call the assembly plugin and don’t override it explicitly. This is usually fine, but not when you don’t want to declare an assembly descriptor. Because the asembly descriptor is defined in the parent(s), it always goes looking for it even if you don’t want it to in your execution (which we don’t). There’s a workaround: create a blank assembly descriptor and point your execution at that, but that’s not very elegant. The trick is to always tie your configurations in to an execution phase, so the parent pom(s) end up looking something more like this:

<artifactId>maven-assembly-plugin</artifactId>
<version>2.2-beta-1</version>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptors>
<descriptor>src/assembly/kit.xml</descriptor>
</descriptors>
<finalName>${pom.artifactId}-${pom.version}</finalName>
<outputDirectory>build/maven/${pom.artifactId}/target</outputDirectory>
<workDirectory>build/maven/${pom.artifactId}/target/assembly/work</workDirectory>
</configuration>
</execution>
</executions>

And the module’s pom can now look like this:

<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<id>pack-assembly</id>
<phase>prepare-package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptors>
</descriptors>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifestEntries>
<build-number>${build-number}</build-number>
</manifestEntries>
</archive>
</configuration>
</execution>
<execution>
<id>copy-fields-conf-file</id>
<phase>package</phase>
<goals>
<goal>attached</goal>
</goals>
<configuration>
<descriptors>
<descriptor>src/main/assembly/kit.xml</descriptor>
</descriptors>
</configuration>
</execution>
</executions>

Headache over 🙂

Automate Configuration Management Using Tokens!

Devops engineers are often tasked with the job of managing deployments of code to multiple environments. Each one may have different environmental settings such as server name/ip address, URL, subnet name and different connection settings such as db connection strings and app layer connections to name but a few. In all, there’s a truck load of differences. These differences, for convenience sake, are usually stored in config and ini files…

Usually they’re a nightmare (sorry, a challenge) to manage. But here’s a solution that has worked well for me…..

  • Use “master” config files that have ALL environmental details replaced with tokens
  • Move copies of these files to folders denoting the environments they’ll be deployed to
  • Use a token replacement operation to replace the tokens
  • Deploy over the top of your code deployments, in doing so replacing the default config files

All the above can be automated very easily, and here’s how:
First off, make tokenised copies of your config files, so that environmental values are replaced with tokens, e.g.
change things like:

<add key=”DB:Connection” value=”Server=TestServer;Initial Catalog=TestDB;User id=Adminuser;password=pa55w0rd”/ >

to

<add key=”DB:Connection” value=”Server=%DB_SERVER%;Initial Catalog=%DB_NAME%;User id=%DB_UID%;password=%DB_PWD%”/ >

Then save a copy of these tokens, and their associated values in a sed file. This sed file should contain values specific to one environment, so that you’ll end up with 1 sed file per environment. These files act as lookups for the tokens and their values.

The syntax for these sed files is:

s/%TOKEN%/TokenValue/i

So here’s the contents of a test environmemt sed file (testing.sed)

s/%DB_SERVER%/TestServer/i

s/%DB_NAME%/TestDB/i

s/%DB_UID%/Adminuser/i

s/%DB_PWD%/pa55w0rd/i

And here’s live.sed:

s/%DB_SERVER%/LiveServer/i

s/%DB_NAME%/LiveDB/i

s/%DB_UID%/Adminuser/i

s/%DB_PWD%/Livepa55w0rd/i

Next up, we want to have a section in our build script which renames the web_master.config files and copies them, and then runs the token replacement task….so here it is:

<target name=”moveconfigs” description=”renames configs, copies them to respective prep locations”>

<delete file=”${channel.dir}\web.config” verbose=”true” if=”${file::exists (webconfig)}” />

<move file=”${channel.dir}\web_Master.config” tofile=”${channel.dir}\web.config” if=”${file::exists (webMasterConfig)}” />

<delete file=”${channel.dir}\web.config” verbose=”true” if=”${file::exists (webconfig)}” />

<move file=”${channel.dir}\web_Master.config” tofile=”${channel.dir}\web.config” if=”${file::exists (webMasterConfig)}” />

<mkdir dir=”${build.ID.dir}\configs\TestArea” />

<mkdir dir=”${build.ID.dir}\configs\Live” />

<copy todir=”${build.ID.dir}\configs\TestArea\${channel.output.name}” >

<fileset basedir=”${channel.dir}” >

<include name=”**\*.config” />

<exclude name=”*.bak” />

</fileset>

</copy>

<copy todir=”${build.ID.dir}\configs\Live\${channel.output.name}” >

<fileset basedir=”${channel.dir}” >

<include name=”**\*.config” />

<exclude name=”*.bak” />

</fileset>

</copy>

</target>

<target name=”EditConfigs” description=”runs the token replacement by calling the sed script and passing the location of the tokenised configs as a parameter” >

<exec program=”D:\compiled\call_testarea.cmd” commandline=”${build.ID.dir}” />

<exec program=”D:\compiled\call_Live.cmd” commandline=”${build.ID.dir}” />

</target>

As you can see, the last target calls a couple of cmd files, the first of which looks like this:

xfind “%*\TestArea” -iname *.* |xargs sed -i -f “D:\compiled\config\testing.sed”

xfind “%*\TestArea” -iname *.* |xargs sed -i s/$/\r/

This is the sed command to read the config file, pipe the contents to sed and run the script file against it, and edit it in place. the second line handles Line Feeds so that the file ends up in a readable state. Essentially we’re telling sed to recursively read through the config file, and replace the tokens with the relevant value.

The advantage that this method has over using Nant’s “replacetokens” is that we can call the script for any number of files in any number of subdirectories using just one call, and the fact that the tokens and values are extracted from the build script. Also, the syntax means that the sed files are a lot smaller than a similar functioning Nant script would be.

And that’s about it.

Building ClickOnce Applications with NAnt

Since I don’t like to actually do any work, and would much rather automate everything I’m required to do, I decided to automate a ClickOnce application build, because doing it manually was taking me literally, er, seconds, and this is waaaaaay too much like hard work. So, I naturally turned to NAnt, which is so often the answer to all my deployment questions….The answer came in the form of using NAnt to call MSBuild and pass the publish target, along with the version number. So, this is what you need to do:

Add a property to your nant script containing your build number (you can get this from CruiseControl.Net if you’re using CCNet to do your builds)
<property name=”version.num” value=”1.2.3.4″/>
Then just compile the project using NAnt’s MSBuild task, and call the publish target:

<target name=”publish” >

<msbuild project=”${base.dir}\ClickOnce.vbproj”>

<arg value=”/property:Configuration=Release”/>

<arg value=”/p:ApplicationVersion=${version.num}”/>

<arg value=”/t:publish” />

</msbuild>

</target>

The next thing you need to do is create or update the publish.htm file. What I’ve done for this is to take a copy of a previously generated publish.htm, and replace the occurrences of the application name with a token. Then in the NAnt script, I replace the token with the relevant application name with a version number. I do this because the version number will change with each build, and rather than manually update it, which is much too complicated for me, I’d rather just automate it so that I can go back to sleep while it builds.I tokenised the application name because of a much darker, more sinister reason that I’ll maybe explain at another time, but the world’s just not ready for that yet.
Anyway, here’s all that in NAntish:

<copy todir=”${config.dir}\${project.name}”>

<fileset basedir=”.”>

<include name=”publish.htm” />

</fileset>

<filterchain>

<replacetokens>

<token key=”VERSION” value=”${version.num}” />

<token key=”APPNAME” value=”${appname}” />

</replacetokens>

</filterchain>

</copy>

Installing Go (crusie) build agents on linux

This is just an easy at-a-glance reference for installing the Go cruise agent on Linux because I’ve done it a few times and just want to have the instructions in one place. I’m using centos for my OS, but these instructions are true for most rpm supporting linux varieties.

Download the rpm:

You have to download the agent from the website here. Copy this to somewhere sensible on the target box, like /tmp for example.

Create User and Extract rpm:

After following the standard instructions a couple of times I noticed that the group and user “cruise” were not being created correctly on my servers. This could be an issue with the rpm I was using or an issue with the VM servers. Either way, to get around this issue I just manually create the group and user before extracting the rpm:

useradd cruise
groupadd cruise
useradd -G cruise cruise

Next I just install the rpm as root:

sudo rpm -i cruise-agent-2.0.0-11407.noarch.rpm

N.B. The latest rpm at this point in time is actually “go-agent-2.1.0-11943.noarch.rpm”.

The files are installed here:

/etc/default/cruise-agent

/var/lib/cruise-agent

/var/log/cruise-agent

/usr/share/cruise-agent

Connecting the Agent with the Server:

The file /etc/default/cruise-agent needs to be edited so that the cruise agent knows how to connect to the cruise server.

Open this file in vim or something similar. it should look like this:

CRUISE_SERVER=192.168.xxx.xx
export CRUISE_SERVER
CRUISE_SERVER_PORT=8153
export CRUISE_SERVER_PORT
AGENT_WORK_DIR=/var/lib/cruise-agent
export AGENT_WORK_DIR
DAEMON=Y
VNC=N

Simply change the IP address of the CRUISE_SERVER to the IP address of the cruise server! You might also need to change the port number if you’ve installed your cruise server to a non default port.

Next you need to start the agent:

/etc/init.d/cruise-agent start

And that’s about it. You should now see the agent appear in your agents list on the cruise server. Put a tick next to it and click enable and this will add the new agent to your cruise-config.xml, where you can assign resources or add it as an environment.

 

 

 

 

JDepend design metrics in CI

This article is intended to give the reader enough information to understand what JDepend is, what it does, and how to use it in a maven build. It’s a kind of cheat sheet, if you like.

What is it?

JDepend is more of a design metric than a code metric, it gives you information about your classes with regards to how they’re related to each other. Using this information you should be able to identify any unwanted or dubious dependencies.

How does it do that?

It traverses Java class files and generates design quality metrics, such as:

  • Number of Classes and Interfaces
  • Afferent Couplings (Ca) – What is this?? Someone probably feels very proud of themselves for coming up with this phrase. Afferent coupling means the number of other packages which depend on the package being measured, in a nutshell. JDepend define this as a measure of a package’s “responsibility”
  • Efferent Couplings (Ce) – Sort of the opposite of Ca. It’s a measure of the number of other packages that your package depends on
  • Abstractness (A) – The ratio of abstract classes to total classes.
  • Instability (I) – The ratio of efferent coupling (Ce) to total coupling (Ce + Ca)
  • Distance from the Main Sequence (D) – this sounds fairly wishy-washy and I’ve never paid any attention to it. It’s defined as: “an indicator of the package’s balance between abstractness and stability”. Meh.

 

To use JDepend with Maven you’ll need Maven 2.0 or higher and JDK 1.4 or higher. You don’t need to install anything, as maven will sort this out for you by downloading it at build time.

Here’s a snippet from one of my project POMs, it comes from in the <reporting> section:


 

<plugin>

    <groupId>org.codehaus.mojo</groupId>

    <artifactId>jdepend-maven-plugin</artifactId>

    <configuration>

        <targetJdk>1.6</targetJdk>

        <outputDirectory>build/maven/${pom.artifactId}/target/jdepend-reports</outputDirectory>

    </configuration>

</plugin>

 

What you’ll get is a JDepend entry under the project reports section of your maven site, like this:

project-reports-page

Maven Project Reports Page

 

And this is what the actual report looks like (well, some of it):

jdepend-report

jdepend report

Summary:

JDepend isn’t something I personally use very heavily, but I can understand how it could be used to good effect as a general measure of how closely related your classes are, which, in certain circumstances could prompt you to redesign or refactor your code.

I don’t think this sort of information is required on a per commit basis, so I’d be tempted to only include it in my nightly reports. However, I also use Sonar, and that has a built-in measure of afferent coupling, so if you’re only interested in that measurement and you’re already running Sonar, then JDepend is probably a bit of an unnecessary overhead. Also, Sonar itself has some good plugins which can provide architectural and design governance features, at least one of which I know implemented JDepend.

Build Pipelines Using Hudson/Jenkins or Bamboo

I’m currently working with the Go CI system from Thoughtworks. One of the things I really like about it is the way that you can have a build “pipeline”. What this means is that you can have a build job which is broken down into, let’s say, 3 different steps:

  1. Checkout and build code, and run unit tests
  2. Package and deploy to a test server
  3. Run acceptance test suite

The way they’ve set this up is pretty decent, it means that every check-in build can be setup to do all of those steps. But you don’t want to wait an hour just to get some feedback on your commit, so Go handles this by giving feedback at the end of each step, rather than having to wait to the end of the whole job.

In the past, when I’ve used Bamboo or Hudson, I’ve setup separate jobs for the check-in builds (which mainly just run unit tests and some light static analysis) and the nightly builds (which do the same, but also include much more static analysis as well as run automated functional tests).

However, it is possible to mimic the Go behaviour using Hudson/Jenkins or Bamboo.  These all support the practice of dependent builds. Hudson jobs have a “Build after other projects are built” option for example. So, to recreate what Go does, you can create separate jobs/plans which run in series. For example:

Job 1: Checks out code, compiles and runs unit tests.

Job 2: Runs coverage report and other static analysis tools such as CPD, PMD and FindBugs

Job 3: Packages up distributable, deploys it and initiates automated tests (e.g. using Selenium)

The developers will get their compile & unit test feedback as quickly as usual, but the process of running a much more exhaustive system will have been spawned as well. No need to wait for the results of the nightly builds to get the full build reports 🙂

Sonar now supports Ant

Great news for Ant users, the new version of Sonar (2.6) now includes support for Ant. Previously it only supported Maven, which was a bit of a pity because it’s such a good tool, so it’s really good to hear that you can now get all the goodness of Sonar with Ant as well. There’s also talk of support for gradle too.

There’s a new Sonar Ant task so you can add Sonar to your ant scripts (details here).

Here’s the link to the release page for version 2.6 of Sonar.

 

Maven 2 – An Overview

Here’s a high level Maven2 cheat sheet which just explains what Maven 2 is and the steps that are executed when Maven does a build.

What is Maven?
It’s a build tool, basically! It also manages dependencies and produces build reports. It uses xml syntax to create build files (called POMs), and the build files support the concept of inheritance. Maven uses plugins to extend basic functionality and perform cool tricks. It’s mainly used in the Java world but apparently there’s also a .Net plugin (I haven’t actually tried it though).

Why Use Maven?
I would recommend using maven if you need a build tool which will:

  • Take over dependency management
  • Enforce a (fairly) rigid format and practice on developers

The dependency management system uses repositories to store binaries. You can have external (third party) binaries as well as internal dependencies. You can reference them from external repositories (like the maven central repository) or you can put them in your own repository. I actually really like the way Maven enforces it’s own best practices on things like naming conventions and project structures, it’s a real time-saver in the long run.

Installing Maven
Installing maven is a doddle. Simply download it from here. Check the installation pre-requisites (you basically need JDK installed).

For instructions on installing maven on Mac OSX look here

For instructions on installing maven on Windows look here

For instructions on installing Maven on Linux you basically do the same as you do for Mac OSX. That is:

Extract the archive you downloaded.

Add an environment variable called M2_HOME and point it to your Maven directory (I installed mine in /home/maven/maven2).

Add the bin directory ${M2-HOME}/bin to your PATH

Add these to your bash_profile by simply adding these 2 lines:

export M2_HOME=/home/maven/maven2

export PATH=${M2_HOME}/bin:${PATH}

To confirm Maven is installed on your system, simply type: mvn -version

What Happens When I Run a Build?
A lot. And it varies between different types of builds. You’ll need to have a POM file for your project in order to be able to run a build, and I’m going to assume you’ve already got one. If you haven’t, try checking out this post for a couple of examples to get you started.
When you run Maven, it steps through a series of phases in a build lifecycle. You can also call goals defined in plugins. So essentially it works like this:

A lifecycle is a sequence of phases, phases can call plugins, plugins contain goals.

Clear? Yeah, it’s not exactly crystal, but stick with it.

There are 3 basic lifecycles: clean, default and site. You call these when you do a bild, like this:

mvn clean deploy site-deploy

I’ll explain exactly what’s happening in this command a little later, but first I’ll briefly explain what each of the three lifecycles do:

Clean, as the name suggests, does the cleaning work – it deletes the build output directory.

Default does all the build work. It compiles the code and the tests, runs the unit tests, packages up the build, and deploys it to the repository.

The Site lifecycle generates all of the build reports and uploads them to a site for your project.

So, back to our mvn command:

mvn clean deploy site-deploy

What’s actually happening here is we’re calling the clean lifecycle by passing the command “clean”. So far so easy.

Next up is “deploy”. Where has this come from? Well, deploy is a phase in the Default lifecycle. In fact, it’s the last phase in the default lifecycle (for a full list of all the phases that run in all 3 lifecycles take a look here. Because it’s the last phase in the default lifecycle, it automatically includes the execution of all of the others, so simply by calling “deploy” we are ensuring that the phases for compiling our source code, compiling our tests, running our tests and packaging our code are also run. It’s a bit like using the “depends” feature in ant targets, but it’s implicit. In other words, it’s a shortcut way of making sure all the phases in the default lifecycle are executed.

Finally we have site-deploy, and again this is the final phase of the site lifecycle, so we’re implicitly calling the other preceeding site phases.

The Maven Release Plugin
The Release plugin is commonly used when making an official Release Candidate build. In a nutshell it increments the build version and tags your code in SCM. It has 2 main goals. relase:prepare and release:perform.

Release:prepare does the following:

  • It checks that your source is up to date
  • It checks the POM to make sure there’s no occurances of the word SNAPSHOT in the dependencies section (the idea here being that you should never make a release build using snapshot dependencies)
  • It removes the word SNAPSHOT from your version
  • It compiles the code to make sure it works
  • It tags the code and commits the POM to scm
  • It then increments the version and re-adds “SNAPSHOT”, and commits this back to the original SCM location (i.e. not the tags location)

Once this completes successfully, release:perform is executed. Release:perform checks out the code from the tag location, and then runs the standard maven goals “deploy site-deploy” which has the effect of recompiling the code, running the tests, packaging and uploading etc etc (see above for detail on what the deploy phase actually does).

Summary
In general, I think Maven is a good build tool, it does a lot of the hard work for you. It’s far from easy to understand and follow though, unless you’re prepared to dedicate a bit of time to reading through the documentation.

In the past, I’ve spent quite a  bit of time and effort creating build systems using Ant, which, through my own design, have enforced some standards and best practices on the build process, and at the end of the day, I ended up with something fairly identical to what you get with Maven. I would definitely use Maven again, especially if standardisation and repository management were on my to-do list.