A major part of any non-trivial application these days is the inclusion
and re-use of 3rd party libraries which implement functionality your
applications require. When a project starts, it’s probably easy enough
to manually drop the odd jar library into a lib directory and forget
about it, but maintaining a large application which depends on many
libraries, which in turn depend on additional libraries for their own
functionality, it can quickly turn into a nightmare to manage.

To solve this problem, many dependency management tools have been
introduced, most notably, Apache Maven.
Maven however, is so much more than just a dependency management tool,
and is actually intended to manage your entire project structure. I
believe however, the combination of Ant and Ivy provides far more
flexibility, extensibility and control over your build and dependency
management processes.

As mentioned in my post on FindBugs integration with
Ant, I’m
a strong believer in the idea that a developer (or user) should be able
to check out or download a project’s code and be able to build and start
working on the code with minimal time and effort investment. This is
especially useful in a work environment where you have many developers
working on projects, and time spent flapping around with setting up
build environments has a direct impact on timelines and team output.

To this end, our Ant script will be responsible for downloading and
configuring Ivy as part of the regular build process, meaning a new
developer or user simply has to get your code and have Ant installed -
all additional features and capabilities of the build process should be
“self maintaining”. I believe this is another testament to Ant’s
flexibility.

Since we’re going to be incorporating additional libraries into our
project, this is a new configuration option which we’ll use to point to
where we want them stored. This will map to the project-root/lib
directory.

Again, since we’re incorporating additional libraries, the new lib
directory needs to be added to the class-path so dependencies can be
made use of at compile time. The new
Fileset tag is
pointing out the location of the “default” dependencies (more on what
“default” refers to later - see the explanation on the addition of the
ivy.xml file), and is including all .jar packages in that location.

Defining another new class-path. As you’ll see later on in the updated
dist build target, this is used to add libraries to our application’s
manifest file (the manifest includes a list of all packages and paths to
include in the class-path).

As the comment tag suggests, these are a couple of options used for
configuring Ivy usage.

Firstly, the version is defined as it’s own variable. Later, this will
be built into a download URL, but defining it as a separate value makes
it easier to configure.

Then, the ivy.home property sets where the base path to where we want
Ivy to live. I’ve set this to the .ant directory in the user’s home
directory.

ivy.jar.dir is where Ivy will be downloaded to, and ivy.jar.file is
the path to the actual ivy.jar file. Also note how the paths are built
up from various properties, so it can be quite easily customised.

Finally, ivy.lib.dir references the library directory, and is used as
the root target directory for dependencies Ivy downloads.

Now that everything is set up and ready, Ivy itself can be downloaded.
The download operation is defined as it’s own build
target, so it can be
performed independently of everything else in the script (this is very
useful for testing), as well as be used as a dependency by targets which
actually use Ivy.

It starts by creating the target
directory where the Ivy
library will be downloaded to, and then using Ant’s get
task to perform a
download of the desired Ivy version, to the previously configured
download location. The usetimestamp attribute of the get task will
skip re-downloading the library on every build if it hasn’t been
modified on the remote server.

Again, a small target dedicated to a single function. This time, we need
to make Ant aware that an Ivy task is available, and this is done using
the taskdef task.
Another path is defined here as well, used to inform Ant where it may
find the task that is being defined.

Note that the “classifier” place-holder does not appear to be
documented clearly anywhere, and refers to a Maven package classifier -
for example “sources” or “javadocs”, which may accompany a library.

The end result of the retrieve task should end up (assuming some
dependencies have been defined) in a structure something like
project-root/lib/default/some_lib-1.0.jar.

Here’s the whole revised dist target, which has grown substantially. A
number of new elements have been added;

Firstly, the mkdir task has been updated to simply create the /lib
directory within the already-configured ${dist.dir}. It makes parent
directories as needed, so we don’t need a separate mkdir for each.

The addition of the copy
task here copies all
default libraries (that is, not ones used for testing, coverage, or
other configurations - more on these kinds of configurations in another
episode). It’s also explicitly excluding any javadoc or source packages
from the distributable files - you typically don’t want to include these
in your distribution.

Another new addition is the manifestclasspath, which is referencing
the dist.classpath we defined earlier, to make sure that the contents
of the dist/lib directory are included in the runtime class-path. This
is referenced by a new attribute named Class-Path in the jar
task’s manifest.

<deletedir="${lib.dir}"/>

Finally, since we created the lib directory as part of the build
process, we need to be sure to clean it up as part of the clean
target.

New file: ivy.xml:

At last, we arrive at the actual Ivy
file
itself. Since we haven’t explicitly declared an Ivy resolve
task
pointing to a specific Ivy file (via the file attribute on the
resolve task), Ivy will by default use a file named ivy.xml, which
is what we’re providing in this instance:

First up,
info,
which describes some simple properties of the project. This is not
immediately useful to us, but will be for publishing artefacts, and more
intricate dependency management (where the branch and status
attributes prove particularly useful).

Configurations
are one of the primary tools Ivy gives us for organising primarily the
dependencies used in different types of builds (for example if we’re
running unit tests, we require additional dependencies which we don’t
want included as part of our standard build - configurations allow us to
separate out these sorts of things), as well as artefact publishing. You
can read a bit more about
Configurations.

In this case, the inclusion of the configurations is not strictly
required, since we’re just redefining the defalut configuration, which
exists by default anyway. In another part of this series, it will get
much more useful.

Often when reviewing the usage documentation for a 3rd party project you
want to use as a dependency, they will provide a Maven dependency,
containing the fields groupId, artifactId and version, which map
directly to Ivy’s org, name and rev.

In fact, by default Ivy will download dependencies from the Central
Maven Repository, giving you immediate
access to all the same content and libraries.

In my example, I’m downloading some simple SLF4J
resources which my Main
class
requires to build.

In Summary

When you execute an ant build or ant dist now, the following actions
are going to take place:

Ant will download the Ivy package if it doesn’t exist or is out of
date (ivy-download)

Ivy’s Ant task will be registered and made available (ivy-init)

Dependencies will be resolved and downloaded (ivy-resolve)

Source code will be compiled, with the downloaded dependencies on
the class-path (build)

Compiled source and downloaded libraries will be packaged for
distribution (dist)

This step turned out significantly chunkier than expected, but hopefully
it makes sense and is digestible. What we have here is a fairly clean
and simple Ivy implementation, and we’ll be building on top of this for
the next edition of this epic saga. In the mean time, I highly recommend
following some of the task and configuration elements I’ve linked
throughout this document, there are many fun and interesting knobs and
dials to play with.