Build and Version Numbering for Java Projects (ant, cvs, hudson)

JavaBuildBuild ProcessVersioning

Java Problem Overview


What are current best-practices for systematic build numbering and version number management in Java projects? Specifically:

  • How to manage build numbers systematically in a distributed development environment

  • How to maintain version numbers in source / available to the runtime application

  • How to properly integrate with source repository

  • How to more automatically manage version numbers vs. repository tags

  • How to integrate with continuous build infrastructure

There are quite a number of tools available, and ant (the build system we're using) has a task that will maintain a build number, but it's not clear how to manage this with multiple, concurrent developers using CVS, svn, or similar.

[EDIT]

Several good and helpful partial or specific answers have appeared below, so I'll summarize a few of them. It sounds to me like there is not really a strong "best practice" on this, rather a collection of overlapping ideas. Below, find my summaries and some resulting questions that folks might try to answer as follow-ups. [New to stackoverflow... please provide comments if I'm doing this wrong.]

I'd really like to flesh this out into a complete answer, at least for the concrete example of our cvs/ant/hudson setup, so someone could build a complete strategy based on this question. I'll mark as "The Answer" anyone who can give a soup-to-nuts description for this particular case (including cvs tagging scheme, relevant CI config items, and release procedure that folds the build number into the release such that it's programmatically accessible.) If you want to ask/answer for another particular configuration (say, svn/maven/cruise control) I'll link to the question from here. --JA

[EDIT 23 Oct 09] I accepted the top-voted answer because I think it's a reasonable solution, while several of the other answers also include good ideas. If someone wants to take a crack at synthesizing some of these with https://stackoverflow.com/users/79194/marty-lamb">marty-lamb</a>'s, I'll consider accepting a different one. The only concern I have with marty-lamb's is that it doesn't produce a reliably serialized build number -- it depends on a local clock at the builder's system to provide unambiguous build numbers, which isn't great.

[Edit Jul 10]

We now include a class like the below. This allows the version numbers to be compiled into the final executable. Different forms of the version info are emitted in logging data, long-term archived output products, and used to trace our (sometimes years-later) analysis of output products to a specific build.

public final class AppVersion
{
   // SVN should fill this out with the latest tag when it's checked out.

   private static final String APP_SVNURL_RAW = 
     "$HeadURL: svn+ssh://user@host/svnroot/app/trunk/src/AppVersion.java $";
   private static final String APP_SVN_REVISION_RAW = "$Revision: 325 $";  

   private static final Pattern SVNBRANCH_PAT = 
     Pattern.compile("(branches|trunk|releases)\\/([\\w\\.\\-]+)\\/.*");
   private static final String APP_SVNTAIL = 
     APP_SVNURL_RAW.replaceFirst(".*\\/svnroot\\/app\\/", "");

  private static final String APP_BRANCHTAG;
  private static final String APP_BRANCHTAG_NAME;
  private static final String APP_SVNREVISION = 
    APP_SVN_REVISION_RAW.replaceAll("\\$Revision:\\s*","").replaceAll("\\s*\\$", "");


  static {
    Matcher m = SVNBRANCH_PAT.matcher(APP_SVNTAIL);
    if (!m.matches()) {
      APP_BRANCHTAG = "[Broken SVN Info]";
      APP_BRANCHTAG_NAME = "[Broken SVN Info]";
    } else {
      APP_BRANCHTAG = m.group(1);
      if (APP_BRANCHTAG.equals("trunk")) {
        // this isn't necessary in this SO example, but it 
        // is since we don't call it trunk in the real case
        APP_BRANCHTAG_NAME = "trunk";
      } else {
        APP_BRANCHTAG_NAME = m.group(2);
      }
    }
  }

  public static String tagOrBranchName()
  { return APP_BRANCHTAG_NAME; }

  /** Answers a formatter String descriptor for the app version.
   * @return version string */
  public static String longStringVersion()
  { return "app "+tagOrBranchName()+" ("+
    tagOrBranchName()+", svn revision="+svnRevision()+")"; }

  public static String shortStringVersion()
  { return tagOrBranchName(); }

  public static String svnVersion()
  { return APP_SVNURL_RAW; }

  public static String svnRevision()
  { return APP_SVNREVISION; }

  public static String svnBranchId()
  { return APP_BRANCHTAG + "/" + APP_BRANCHTAG_NAME; } 

  public static final String banner()
  {
    StringBuilder sb = new StringBuilder();
    sb.append("\n----------------------------------------------------------------");
    sb.append("\nApplication -- ");
    sb.append(longStringVersion());
    sb.append("\n----------------------------------------------------------------\n");
    return sb.toString();
  }
}

Leave comments if this deserves to become a wiki discussion.

Java Solutions


Solution 1 - Java

For several of my projects I capture the subversion revision number, time, user who ran the build, and some system information, stuff them into a .properties file that gets included in the application jar, and read that jar at runtime.

The ant code looks like this:

<!-- software revision number -->
<property name="version" value="1.23"/>

<target name="buildinfo">
    <tstamp>
        <format property="builtat" pattern="MM/dd/yyyy hh:mm aa" timezone="America/New_York"/>
    </tstamp>        
    <exec executable="svnversion" outputproperty="svnversion"/>
    <exec executable="whoami" outputproperty="whoami"/>
    <exec executable="uname" outputproperty="buildsystem"><arg value="-a"/></exec>

    <propertyfile file="path/to/project.properties"
        comment="This file is automatically generated - DO NOT EDIT">        
        <entry key="buildtime" value="${builtat}"/>
        <entry key="build" value="${svnversion}"/>
        <entry key="builder" value="${whoami}"/>
        <entry key="version" value="${version}"/>
        <entry key="system" value="${buildsystem}"/>
    </propertyfile>
</target>

It's simple to extend this to include whatever information you might want to add.

Solution 2 - Java

Your build.xml

...
<property name="version" value="1.0"/>
...
<target name="jar" depends="compile">
    <buildnumber file="build.num"/>
    <manifest file="MANIFEST.MF">
        ...
        <attribute name="Main-Class" value="MyClass"/>
        <attribute name="Implementation-Version" value="${version}.${build.number}"/>
        ...
    </manifest>
</target>
...

Your java code

String ver = MyClass.class.getPackage().getImplementationVersion();

Solution 3 - Java

  • Build numbers should be associated with a continuous integration server like hudson. Use different jobs for different branches/teams/distributions.
  • To keep the version number in the final build, I would recommend just using maven for build system. It will create a .properties file archived into the final .jar/.war/.whatever-ar on META-INF/maven/<project group>/<project id>/pom.properties. The .properties file will contain the version property.
  • Since I am recommending maven, I would urge you to check out the release plugin to prepare the release on source repository and keep the versions on sync.

Solution 4 - Java

Software:

Hudson has three builds/jobs: Continuous, Nightly and Release.

For a Continuous/Nightly build: Build number is the SVN revision, found using svntask.

For a Release build/job: Build number is the Release number, read by Ant, from a Properties file. The properties file can also be distributed with the release for displaying the build number at runtime.

The Ant build script puts the build number in the manifest file of jar/war files that are created during the build. Applies to all builds.

Post-build action for Release builds, done easily using a Hudson plug-in: tag SVN with the build number.

Benefits:

  • For a dev version of a jar/war, the developer can find the SVN revision from the jar/war and look up the corresponding code in SVN
  • For a release, the SVN revision is the one corresponding to the SVN tag that has the release number in it.

Hope this helps.

Solution 5 - Java

I'm using Hudson also, although a far more simpler scenario:

My Ant script has a target in it that looks like:

<target name="build-number">
    <property environment="env" />
    <echo append="false" file="${build.dir}/build-number.txt">Build: ${env.BUILD_TAG}, Id: ${env.BUILD_ID}, URL: ${env.HUDSON_URL}</echo>
</target>

Hudson sets these environment variables for me whenever my job runs.

In my case, this project is a webapp and I'm including this build-number.txt file in the root folder of the webapp - I don't really care who sees it.

We don't tag source control when this is done because we already have our Hudson job set up to tag it with the build number/timestamp when the build is successful.

My solution only covers the incremental build numbers for development, we haven't gotten far enough in the project where we are covering release numbers yet.

Solution 6 - Java

You may also want to take a look at BuildNumber Maven plugin and Ant task in one jar found at http://code.google.com/p/codebistro/wiki/BuildNumber. I tried to make it simple and straightforward. It is a very small jar file that only depends on command line Subversion installed.

Solution 7 - Java

This is how i resolved this:

  • the sources are copied to the build directory
  • then the anttask "versioninfo" is applied
  • compile the modified sources

Here is the java file storing the version info:

public class Settings {

	public static final String VERSION = "$VERSION$";
	public static final String DATE = "$DATE$";

}

And here is the anttask "versioninfo":

    <!-- ================================= 
     target: versioninfo              
     ================================= -->
	<target name="versioninfo"
	        depends="init"
	        description="gets version info from svn"
	>

		<!-- 
		get svn info from the src folder 
		-->
		<typedef resource="org/tigris/subversion/svnant/svnantlib.xml"
		         classpathref="ant.classpath"
		/>
		<svnSetting id="svn.setting"
		            javahl="false"
		            svnkit="true"
		            dateformatter="dd.MM.yyyy"
		/>
		<svn refid="svn.setting">
			<info target="src" />
		</svn>

		<!-- 
		if repository is a taged version use "v <tagname>"
		else "rev <revisionnumber> (SVN)" as versionnumber
		 -->
		<taskdef resource="net/sf/antcontrib/antcontrib.properties"
		         classpathref="ant.classpath"
		/>
		<propertyregex property="version"
		               input="${svn.info.url}"
		               regexp=".*/tags/(.*)/${ant.project.name}/src"
		               select="v \1"
		               defaultvalue="rev ${svn.info.lastRev} (SVN)"
		               override="true"
		/>


		<!-- 
		replace date and version in the versionfile ()
		 -->
		<replace file="build/${versionfile}">
			<replacefilter token="$DATE$" value="${svn.info.lastDate}" />
			<replacefilter token="$VERSION$" value="${version}" />
		</replace>

	</target>

Solution 8 - Java

Here is my 2 cents:

  • My build script creates a build number (with timestamp!) each time I build the app. This creates too many numbers but never too few. If I have a change in the code, the build number will change at least once.

  • I version the build number with every release (though not inbetween). When I update the project and I get a new build number (because someone else did a release), I overwrite my local version and start over. This can lead to a lower build number which is why I've included the timestamp.

  • When a release happens, the build number is committed as the last item in a single commit with the message "build 1547". After that, when it's an official release, the whole tree is tagged. This way, the build file always has all tags and there is a simple 1:1 mapping between tags and build numbers.

[EDIT] I deploy a version.html with my projects and then, I can use a scraper to simply collect an accurate map what is installed where. If you're using Tomcat or similar, put the build number and timestamp in the description element of web.xml. Remember: Never memorize anything when you can have a computer do it for you.

Solution 9 - Java

We run our build via CruiseControl (insert your favourite build manager here), and perform the main build and tests.

We then increment the version number using Ant and BuildNumber and create a property file with this info plus the date of build and other metadata.

We have a class dedicated to reading this and providing it to GUIs/logs etc.

We then package all of this up and build a deployable tying together the build number and the corresponding build. All our servers dump this meta info on start up. We can go back through the CruiseControl logs and tie the build number to the date and checkins.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionandersojView Question on Stackoverflow
Solution 1 - JavaMarty LambView Answer on Stackoverflow
Solution 2 - Javauser146146View Answer on Stackoverflow
Solution 3 - JavaHMMView Answer on Stackoverflow
Solution 4 - JavaRaleighView Answer on Stackoverflow
Solution 5 - Javamatt bView Answer on Stackoverflow
Solution 6 - JavaSasha OView Answer on Stackoverflow
Solution 7 - JavaflederohrView Answer on Stackoverflow
Solution 8 - JavaAaron DigullaView Answer on Stackoverflow
Solution 9 - JavaBrian AgnewView Answer on Stackoverflow