Ant and Build Version Numbers

11 PM September 26, 2005

Our customer recently found themselves confused about which version of the software they had running. Our response is to modify our ant packaging script to add the version number and time stamp to various parts of the release package. This is how I went about it.

First, requirements. To the customer, the most important piece of information is the build version number. The version number is an alphanumeric string that identifies the phase, milestone and release of the build. The next most important piece of information is when the build was done, and by whom. Finally, I will also include a unique "build number" because ant has a handy feature for that.

I changed the script's init target to gather this information:

01 <target name="init">

02     <description>Prepare for a build tasks</description>

03     <tstamp />

04     <buildnumber file="installation-package/build.number"/>

05

06     <input message="Please enter the build version number:" addproperty="build.version" />

07     <property name="app.jar.filename" value="app-${build.version}.jar" />

08 </target>

The important pieces are:

Line 03
The tstamp task puts the current date and time into script properties named DSTAMP (a date stamp), TSTAMP (a time stamp) and TODAY (human readable date).
Line 04
The buildnumber task creates a unique number for this build. It does this by storing the build number in a disk file named "build.number", which we check in along with the build script.
Line 06
The script asks the user for the version string interactively, using the input task. I did consider getting the version number from a property file. Unfortunately, I'm likely to forget to update the property file before a release. Fortunately, we don't need to run the packaging script unattended, so asking interactively is fine.
Line 07
Finally, the script includes the build version string sets in the name of the output application JAR file.

Next, we are going to create a readme file to hold the detailed version information. The task looks like this:

01 <target name="make-readme">

02     <echo file="${build.dir}/readme-${build.version}.txt">Version:    ${build.version}

03 Build time: ${TODAY} at ${TSTAMP}

04 Build #:    ${build.number}</echo>

05 </target>

The echo task conveniently expands properties in its input text. We include the version number in the file name too, so that it can be seen without having to open the file. Note that the open and close <echo> tags are hard up against the content, and lines 3 and 4 are hard against the left margin. If we don't do this, then the output file ends up filled with whitespace.

Since we are changing the application JAR file name based on the version number, we need to ensure that the startup scripts use the correct JAR file. We're deploying on Windows, so the simplest thing to do is to modify the startup scripts to use the calculated name.

This ant task copies scripts from the startup-files directory to the build directory, replacing all instances of the string "@jarfile@" with the application jar file name:

01 <target name="copy-run-scripts">

02     <filter token="jarfile" value="${app.jar.filename}" />

03     <copy todir="${build.dir}" filtering="true">

04         <fileset dir="${install.dir}/startup-files" includes="**/*" />

05     </copy>

06 </target>

The filter task defines a filter that is then used by the copy task. To use the filter, the copy task must have filtering="true" in its attribute list.

I also added some other little touches - such as stuffing the build time and number into the jar manifest - but they're just a matter of including the right "${property.name}" references in the right places.

So, with version numbers and timestamps whereever you look, nobody need be confused about what version of the software they have, ever again.

By alang | # | Comments (5)
(Posted to javablogs and Java)

Comments

At 08:22, 27 Sep 2005 Lorenzo Gatti wrote:

These techniques are good practices for using version names and build numbers, but they are obtained in ways that work for a single user only.
When multiple developers and automated build systems can invoke Ant using a "build.number" file becomes inadequate, since it must be either shared (and possibly locked or mangled) or replicated (with repeated build numbers). We could instead use a shared database, with proper concurrency and with the possibility of logging user, time, outcome etc. of each build.

Another problem of multiuser development is relating version numbers in revision control and version numbers appearing in the built software: it must be possible to obtain the exact file versions uset to build every release.
In a rather large project I worked in, the "release" shell scripts and Ant targets took the version number from a property file in the sources and operated exclusively on a fresh checkout of a CVS tag (with Ant's cvs task); the command was "build cvs tag X with whatever version number it contains". This way, unless someone hacked the CVS tags, there was a reliable and complete record of what went into each released version.
Forgetting to update the property file was possible, but you could simply correct it and repeat the build (and having build times over 10 minutes did wonders for the build manager's attention).

In other cases, when version numbers are not constrained, they could be automatically generated from counters in a database or from the revision control system.
Or the dependency could be inverted: with a local copy of the sources and an arbitrary new version number Ant could check in changes in the revision control systems and label them.

(#)
At 12:46, 27 Sep 2005 Dmitry wrote:

Why not just use MANIFEST.MF for all that info? Include the Main Class in each jar that will read the MANIFEST.MF and output the information that you need.
From working on a large systems, with ton of modules it would be a pia if I had to change the jar name each day after a build in my IDE or in start up scripts. Leaving that data in the MANIFEST.MF allowed us to have artifact names to stay the same.

(#)
At 08:31, 28 Sep 2005 Anthony B. Coates wrote:

My approach in the past has been to dynamically generate a Java "Version" class containing an API for the version information, and a "main" method that prints the information to the console. If your aren't already producing an executable JAR, so can then make the "Version" class the executable one.

Cheers, Tony.

(#)
At 12:42, 28 Sep 2005 Charles May wrote:

We use a JSP to convert our automatically built version number into a format accepted by the Java Plugin's standard "cache_version" applet parameter. That way, end users automatically receive new applet jars each time new build is installed on our server.

(#)
At 11:55, 30 Sep 2005 Richard Atkins wrote:

Good arguments for not simply relying on MANIFEST.MF version info are:
- having to browse into a war or jar is not intuitive for end users or tech support, and not a lot of fun for developers (for example, figuring out that jars are just zips is beyond many people, and associating .jar with WinZip breaks the autorun magic of java)
- only putting the version in the manifest fails to address all the other places where obvious version numbers would help (even if it fails to help your users directly, it will help your helpdesk, and that could save your developers from having to wade in on the issue).

If you have any log files, I recommend your application start by printing its version there too. If you have a UI, then you'll also need an about box/web page that displays this info as well.

I'd probably go with having a Version class read a version.properties file, rather than generating a new sourcefile, but it's up to you what you do.

Regarding the multi-user problem, you can structure the build string so that it includes: on CVS, Subversion and friends, the current username; on a branch-oriented VCS (eg ClearCase), the branch name. The integration build can skip adding this userid through the use of properties optionally loaded when ant runs. e.g.:

  <property file="local.properties"/>
  <property environment="env"/>
  <buildnumber file="build.number"/>
  <tstamp/>
  
  <!-- Updates the version.properties file -->
  <property prefix="old" file="version.properties"/>
  <property name="build.id" value="${env.USER}"/>
  <property name="version.major" value="${old.version.major}"/>
  <property name="version.minor" value="${old.version.minor}"/>
  <property name="version.build" value="${build.id}${build.number}"/>
  
  <propertyfile file="version.properties">
    <entry key="version.major" value="${version.major}"/>
    <entry key="version.minor" value="${version.minor}"/>
    <entry key="version.build" value="${version.build}"/>
    <entry key="version.date" value="${DSTAMP}${TSTAMP}"/>
  </propertyfile>
  
  <echo>Building Version ${version.major}.${version.minor}.${version.build}</echo>

Doing this will give you build.number and version.properties files that could be put under source control, since the next person to build will automatically get their own unique version.build property. The integration build would then have a local.properties file with the property

  build.id=

ensuring that no id is used for these builds.

(#)

Add Comment




(Not displayed)






(Leave blank line between paragraphs. URLs converted to links. HTML stripped. Indented source code will be formatted with <pre> tags.)




© 2003-2006 Alan Green