136
votes

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.]

  • If you are using SVN, versioning of a specific checkout comes along for the ride. Build numbering can exploit this to create a unique build number that identifies the specific checkout/revision. [CVS, which we are using for legacy reasons, doesn't provide quite this level of insight... manual intervention with tags gets you part way there.]

  • If you are using maven as your build system, there is support for producing a version number from the SCM, as well as a release module for automatically producing releases. [We can't use maven, for a variety of reasons, but this helps those who can. [Thanks to marcelo-morales]]

  • If you are using ant as your build system, the following task description can help produce a Java .properties file capturing build information, which can then be folded into your build in a number of ways. [We expanded on this idea to include hudson-derived information, thanks marty-lamb].

  • Ant and maven (and hudson and cruise control) provide easy means for getting build numbers into a .properties file, or into a .txt/.html file. Is this "safe" enough to keep it from being tampered with intentionally or accidentally? Is it better to compile it into a "versioning" class at build time?

  • Assertion: Build numbering should be defined/enacted in a continuous integration system like hudson. [Thanks to marcelo-morales] We have taken this suggestion, but it does crack open the release engineering question: How does a release happen? Are there multiple buildnumbers in a release? Is there a meaningful relationship between buildnumbers from differing releases?

  • Question: What is the objective behind a build number? Is it used for QA? How? Is it used primarily by developers to disambiguate between multiple builds during development, or more for QA to determine what build an end-user got? If the goal is reproducibility, in theory this is what a release version number should provide -- why doesn't it? (please answer this as a part of your answers below, it will help illuminate the choices you have made/suggested...)

  • Question: Is there a place for build numbers in manual builds? Is this so problematic that EVERYONE should be using a CI solution?

  • Question: Should build numbers be checked in to the SCM? If the goal is reliably and unambiguously identifying a particular build, how to cope with a variety of continuous or manual build systems that may crash/restart/etc...

  • Question: Should a build number be short and sweet (i.e., monotonically increasing integer) so that it's easy to stick into file names for archival, easy to refer to in communication, etc... or should it be long and full of usernames, datestamps, machine names, etc?

  • Question: Please provide details about how the assignment of build numbers fits into your larger automated release process. Yes, maven lovers, we know this is done and done, but not all of us have drunk the kool-aid quite yet...

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 marty-lamb'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.

9
For future readers, please note that the revision number in the code you suggested is of the file, not the global revision of the repository. For more info see: subversion.apache.org/faq.html#version-value-in-sourcemaayank
I'm wondering if anyone has similar simple approaches when using gradle and/or git?bnjmn

9 Answers

63
votes

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.

48
votes

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();
6
votes
  • 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.
6
votes

Software:

  • SVN
  • Ant
  • Hudson, for continuous integration
  • svntask, an Ant task to find SVN revision: http://code.google.com/p/svntask/

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.

4
votes

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.

3
votes

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.

3
votes

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>
2
votes

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.

1
votes

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.