When I first start a new Java project, one of the first things I set up is a skeleton Ant build.xml file. I try to set it up so that a new developer on the project should be able to checkout and compile with no configuration. At least that’s the goal. That means setting up some reasonable defaults for the build system.

Of course, not everyone’s computer is set up the same way. Different developers on the team may be using different IDEs, different file system layouts, or even different operating systems. So I need to make sure that all the settings can be configured for individual developers and machines.

To make the system as flexible as possible I use Ant properties wherever I can. Ant properties can be set on the command line, in external files, and/or within the Ant build file itself. Once an Ant property is set, it stays set; its value does not get overwritten or set to a different value later in the build process. That makes it very easy to set up a system of defaults and a hierarchy of property values.

Sample minimal build.xml:

<?xml version="1.0"?>
<project name="sample" default="debug">
  <property environment="env"/>
  <property name="env.HOSTNAME" value="${env.COMPUTERNAME}"/> <!-- Windows vs. Linux -->
  <!-- Load local build properties first so they override anything defined later -->
  <property file="local.build.properties"/> <!-- not in version control, so useful for passwords -->
  <property file="${user.name}-${env.HOSTNAME}.build.properties"/>
  <property file="${user.name}.build.properties"/>
  <property file="${env.HOSTNAME}.build.properties"/>
  <!-- Compiler options -->
  <property name="compiler.nowarn" value="off"/>
  <property name="compiler.debug" value="true"/>
  <property name="compiler.debuglevel" value="lines,vars,source"/>
  <property name="compiler.deprecation" value="true"/>
  <!-- directories -->
  <property name="src.dir" location="src"/>
  <property name="test.src.dir" location="test"/>
  <property name="lib.dir" location="lib"/>
  <property name="build.dir" location="build"/>
  <property name="classes.dir" location="${build.dir}/classes"/>
  <!-- Debug target to show variables -->
  <target name="debug">
    <echoproperties/>
  </target>
</project>

With this build file I can put my default property values directly in the build.xml file itself. In the example above I have default values for a handful of compiler options and some directory locations.

To override those defaults, I have a series of build properties files that get loaded in. The order of those files is significant; files loaded earlier take precedence over later files.

The first file to get loaded is local.build.properties. I make sure that my version control system is set up to ignore that file, which makes it a good place to store passwords that you don’t want other people to see. Currently am I using Subversion, so I just added local.build.properties to svn:ignore. CVS users could add the file name to the.cvsignore file. All modern version control systems should support ignoring files so if you are using something else you just have to figure out how to ignore files using your repository.

The other three build properties files use a combination of user name and computer host name (both of which are set automatically by Ant) to allow for a lot of flexibility for each developer and each build machine. Starting with the combination of both of them allows for property values that a specific developer uses on a specific machine. Next, the user-specific properties are loaded, and then the machine-specific values. These files can all be checked into source control; with the naming convention there should be no unexpected file name collisions between different developers.

So if I was logged into my machine as user “gene” and the machine’s host name was “wopr“, I could have four different build properties files that I used:

  1. local.build.properties could contain any passwords I used, and would not be checked into source control.
  2. gene-wopr.build.properties might contain a setting to enable a target, for example, that I only run when I’m on this particular machine.
  3. gene.build.properties would have the settings that I typically use.
  4. wopr.build.properties might have paths to external tools on that machine.
  5. Lastly, the default values in the build.xml file itself would take effect for any properties that have not yet been set (like on a new developer’s machine).

If I need to, I can override any or all of the properties on the command line when Ant is called.

ant -Dcompiler.nowarn=on

would turn on the compiler.nowarn setting no matter what the build properties files had, for a one-time build setting.

Of course, rather than specifying individual properties and their values on the command line, I can just specify a different user name and/or machine name and pull all of the settings from a different set of build properties files.

ant -Duser.name=joshua

would pick up the settings from joshua-wopr.build.properties and joshua.build.properties, even when I’m logged in as “gene“. I tend to use this mechanism for automated build settings on any continuous integration system we are using.

My experience has been that these options are more than sufficient to handle any combination of configurations we need, without being overly complex. If you wanted to be more flexible, you could always add other levels of properties files. For example, os.nameos.version, and os.arch might be useful options to base the build properties hierarchy on if your build process depended on platform-specific settings. Do a little experimenting with the <echoproperties/> task to see some of the options you have.

Leave a comment

Your email address will not be published. Required fields are marked *

X