logo

ShrimpWorks

// why am I so n00b?

Apache Ant is a general-purpose build tool, primarily used for the building of Java applications, but it is flexible enough to be used for various tasks.

In the Java world at least, Ant seems to be largely passed over for the immediate convenience and IDE support of Maven, however long term, I believe a good set of Ant scripts offer far more flexibility and room for tweaking your build processes. The downside is that there’s a lot of stuff you need to learn and figure out and build by hand.

In this series of tutorials, I’ll try to document the process of learning I’ve gone through building and maintaining Ant build files, from the most basic of “just compile my stuff” steps to automatic generation of JavaDoc output, dependency management using Ant’s companion, Ivy, unit testing using JUnit, and integrating with some additional tools I’ve been using, such as Checkstyle and FindBugs.

For part 1 of this tutorial, I’ve created a simple Hello World library. It doesn’t have a main executable itself, the goal of this is to produce a .jar file we can include in other projects, to start our Ant script off fairly simply.

The source for this project can be found in GitHub. Here’s the breakdown of everything going on in this project:

At the root of the project, we have the src directory, under which all our packages live. Normally, an IDE will take care of this sort of structure for you automatically, since it’s the most commonly accepted way of structuring a Java project. Also in the root of the project, you’ll find the build.xml file - this is our Ant build configuration.

Digging into the various parts of the Ant file, starting at the top and working our way down:

<?xml version="1.0" encoding="UTF-8"?>
<project name="hello-world" default="dist" basedir=".">

First is a standard XML version and encoding declaration - Ant won’t complain if this is missing, but it’s a good idea to include in any XML documents anyway.

The next line shows the project definition. name is the name of the project, which we’ll also use as the output file name for our .jar file and any other distributable packages and artefacts - you’ll see this referred to as the variable ${ant.project.name} elsewhere.

The default attribute indicates the default target which will be run when you execute ant with no target specified. Targets are defined in <target ...> blocks, which we’ll see later.

<!-- basic paths -->
<property name="src.dir" location="src"/>
<property name="dist.dir" location="dist"/>
<property name="build.dir" location="build"/>

Here we’re defining a couple of path properties which will be used elsewhere in the script. Often these are defined in an external .properties file and imported via a &ltproperty file="my.properties"/> line, but for a few simple properties like these, I prefer defining them in the actual XML, to save the clutter of an additional file.

In particular, we’re defining the source code path (.java files), as well as output paths for the compiled .class files and our redistributable artefacts.

<!-- properties for build output, nothing should need to change beyond this point -->
<property name="build.src.dir" location="${build.dir}/src"/>

<path id="default.classpath">
    <pathelement path="${build.src.dir}"/>
</path>

These are properties building on the more commonly defined ones in the previous block. This is defining a sub-directory for the .class file output under the configured build path, as well as defining a class path, which will be used for when we actually call javac to compile our code, so it knows where everything is.

<!-- Simple source build -->
<target name="build" description="compile source">
    <mkdir dir="${build.src.dir}"/>

    <javac srcdir="${src.dir}" destdir="${build.src.dir}" includeantruntime="false">
        <classpath refid="default.classpath"/>
    </javac>
</target>

Now we’re finally getting to a build target. A target is typically one of the names you pass via the command line (or via your IDE, if it has Ant support) to Ant to perform a certain type of build or task.

For example, we’d invoke the above build target from the command line as follows: ant build

This would ignore the default attribute set in the project definition, and go straight to the build step.

Let’s break down what’s going on in this target.

Firstly, we need to make sure the output directory using the mkdir task. The variable ${build.src.dir} being referenced here was defined in a property in the previous block, and essentially resolves to project-root/build/src. The mkdir task ensures all parent directories are created as well if they don’t yet exist.

Next, we’re invoking javac to compile our code using the built-in javac task. The srcdir directory references the source path property we defined near the top of the build script, and the destdir references the build output directory that was just created with mkdir. includeantruntime is set to false to exclude Ant including itself in the classpath – I’ve never encountered a reason to do this; but it defaults to True so we should explicitly disable it.

Finally, the classpath property within the javac instruction is making sure our build class path is made available during the build.

In summary, this task will compile all our source into .class files, and if there are any compilation errors, will report on those via standard console output and stop the build process. Feel free to investigate the additional properties and attributes the javac task makes available – there are many.

<!-- Build distribution -->
<target name="dist" depends="build" description="generate distribution">
    <mkdir dir="${dist.dir}"/>

    <jar jarfile="${dist.dir}/${ant.project.name}.jar">
        <fileset dir="${build.src.dir}"/>
        <fileset dir="${src.dir}" excludes="**/*.java"/>
    </jar>
</target>

At last, and probably the most commonly used target, dist. This is generally used for building and packaging your redistributables. Note that the target names used here, build, dist, clean and others we’ll be adding over time and not fixed, you may name them anything you like, these are simply the most commonly used and generally accepted. Let’s see what this one’s doing.

Firstly, you will notice the addition of the depends attribute to the target tag. As the name suggests, this target depends on the build target. When you invoke the dist target directly, Ant will automatically execute any dependency targets. This way we can chain complex series of build targets together to orchestrate various bits of behaviour without needing to re-define everything for every target. A target may also depend on more than one other target, and these are defined as a comma-separated list - Ant will execute those dependencies in the order they are listed.

The first instruction within the target is the familiar mkdir to prepare the output directory, again referencing a property we defined earlier.

Next, the built-in jar task is responsible for packaging the build output into a .jar file we can use in other projects.

The jarfile attribute defines the output file. In this example, it will resolve to project-root/dist/hello-world.jar.

The first fileset property is letting the jar task know where to find the compiled .class files to be included in the final .jar file

  • hence the need for this target to depend on the build one first.

The second fileset is for the inclusion of resource files stored alongside the source files, within the final package. For example, these would be files you may typically read or access at runtime via a getClass().getResource(...) call. If your applications are likely to never make use of such functionality, it’s safe to remove this, but it’s handy to have available for the time you may.

That’s all. After this target executes, your build directory will contain a bunch of compiled .class files, and you’ll have a distributable .jar file in the dist directory.

<!-- Clean compiled files -->
<target name="clean" description="clean up" >
    <delete dir="${build.dir}"/>
    <delete dir="${dist.dir}"/>
</target>

It is generally a good idea to include a clean operation in build files, which should remove all output generated by a build. Essentially this should return the project to it’s freshly checked out state, with no extraneous files and directories.

Here, we’re only making use of the delete task, which will remove the contents of the paths specified, as well we the paths themselves. When you extend your builds to perform more interesting things, and you end up creating more directories or files, make sure to clean them up.

That’s all there is to a simple build script, which at this point, is perfectly usable for creating simple stand-alone libraries, which don’t depend on any external libraries, and which don’t need to be run as stand-alone applications.

In the next part, we’ll see how our application can be made runnable.


Other parts in this series: