Gant is a build system that sits on top of Ant, which uses Groovy as the platform language, rather than XML; thus, build files are much more flexible when it comes to logic (such as conditionals and more importantly, custom behavior).

Gant’s syntax is highly intuitive as it follows a natural structure for defining behavior (that is eventually embodied by a tried and true Ant task). The basic structure can be defined as

target (target-name:target-description) {
  //do stuff
}

What’s more, you can define methods within Gant scripts, which can be referenced from various tasks. For instance, given that many builds have multiple compilations (such as compiling source code and compiling tests), you can easily create a generic compilation method and then reference it from different target definitions (such as compile-src and compile-test). This generic behavior can be defined as a closure like so:

genericjavac = { source, destination ->
 Ant.javac(srcdir:source, destdir:destination){
  classpath(refid:"build.classpath")
 }
}

As you can see, this closure accepts two parameters (the source directory and the destination of the resulting class files) and accordingly invokes Ant’s javac task. You can then reference this closure from within targets like so:

task(compile-tests:"compile test classes only") {
 genericjavac(testsdir, testclassdir)
}

Hudson is CI server, written with a lot of the lessons learned from previously written CI servers, that aims to be extremely easy to set up. Hudson supports running a number of different types of builds such Ant and Maven; moreover, Hudson supports executing outside shells and batch files.

Running a non-Ant or Maven build requires that you specify the Execute shell option (that is, if you are on a Unix-ish machine) in the Build section of a project’s configuration. What’s key, though, is that the command is invoked from an outside directory as opposed to the project’s root directory (where builds are usually run from). This normally isn’t an issue, however, unless you’d like to run a Gant build.

The issue is that Gant builds don’t (at this point it seems) handle base directory behavior by default like normal Ant builds do. Therefore, if your Gant build assumes some relative paths (such as those paths used in configuring Ivy) and the Gant process is invoked outside of the assumed base directory things will fail, because these relative paths will be from outside the project’s root. Luckily, this issue can be solved easily via Gant’s expressiveness.

The fix involves putting a check in a Gant build that determines where a build is being invoked from. If the build is not within the project’s root directory, a simple check (and corresponding fix) is to see if the project’s root directory is one level down (which is exactly where Hudson executes shell scripts).

Accordingly, you can create a simple check method within a Gant build that attempts to figure out from where the process is being invoked. If it is not within the project, the method attempts to see if the project is one level down and correspondingly sets Ant’s base directory.

def manageBaseDirectory(){
 def runloc = Ant.project.properties.basedir
  if(!runloc.endsWith("/acme") && new File("./acme/").exists()){
   Ant.project.setBasedir("./acme/")
  }//else clause hasn't been thought through yet...
}

This method is invoked from within a default target (which Hudson will invoke based upon the shell command given, such as acme/gant.sh -f ./acme/build.gant) and as written is safe enough to facilitate running normal builds within a project’s root directory.

Now with this small fix, builds can be executed via Hudson (from outside the root project dir) or via developers from within the root directory. There are probably some additional checks that should be added to the method as written, but at this point, it is good enough.