Thursday, September 18, 2008

Custom Ant task to read version number from Constants.class file

Problem:
To keep track of a version number, we used to have it in two files: once in a Constants.java for the application itself, and once in the Ant build script in order to have a version number in the resulted WAR's META-INF section. This duplication was cumbersome and error-prone, we always had to update both files and make sure the version numbers matched; in a distributed develop/build environment, this was a nighmare.

Solution: We decided to stick to one version number, in the Constants.java file and to create a custom Ant taskdef to retrieve this number from the compiled class file at the end of the building process.

We have the following parts:

  • in our build.xml, the following taskdef:

    <taskdef name="versionPropertyRetriever" classname="VersionPropertyRetriever" classpath="${basedir}"/>

    and the following task call

    <versionPropertyRetriever name="app.version" className="com.datamat.care.base.Constants" classpath="${basedir}; ${build.web.home}/WEB-INF/classes"/>

    which retrieves the version number and makes it available as the app.version property.


  • a VersionPropertyRetriever java class, extending Ant's Property.java, whose main purpose is to retrieve the value of the VERSION constant in the className class through introspection. Besides adding a getter and setter for the className attribute, its main business logic is implemented within the private parseClass method:

    private void parseClass() {
    try {
    ClassLoader cL = getProject().createClassLoader(classpath);
    Class c = cL.loadClass(className);
    if (c==null)
    throw new BuildException("Couldn't find the Constants class");

    Field field = c.getField("VERSION");
    if (field==null)
    throw new BuildException("'VERSION' field not found within Constants.java file");

    Object value = field.get(null);
    if (value==null)
    throw new BuildException("Version not set within Constants.java file");

    // all fine -> add as a property
    addProperty(name,value.toString());
    }
    catch(Exception e) {
    throw new BuildException("Unexpected Exception: " + e.getMessage());
    }
    }





We compiled the VersionPropertyRetriever.java file through another xml file, to be executed with Ant, buildVersionPropertyClass.xml:

<!--
Script to compile the VersionProperty class starting from the VersionProperty.java file
-->
<project name="buildVersionPropertyClass" default="main" basedir="./">
<property file="build.properties"/>


<path id="compile.classpath">
<!-- Include all JAR files needed to compile -->
<fileset dir="${basedir}" includes="*.class"/>
<fileset dir="${libraries.home}/Ant" includes="*.jar"/>
</path>


<target name="main" depends="" description="Compile java file">
<echo message="Compiling"/>
<javac srcdir="${basedir}" destdir="${basedir}" includes="*.java">
<classpath refid="compile.classpath"/>
</javac>
</target>

</project>