Dependency Management with Ant and Ivy
Thursday, January 17, 2008 |One of my long-standing complaints with Ant is that project dependency management is non-existent in the core Ant distribution. Many will quickly point to the Maven Ant tasks, but I’ve never been really fond of them for one reason or another. The other advice I often get is to use Ivy, but even after several attempts, I had never gotten Ivy to work. With the recent release of 2.0 beta 1, though, I thought I’d try again, and I’m glad I did. Not only have I gotten it to work for me, but I was also able to successfully configure custom resolvers. Below is what I had to do to migrate the Mojarra Scales dependency management to Ivy.
Using Ivy requires, at least in the way I’m using it, two new files, ivy.xml, which defines my dependencies, and ivysettings.xml, which configures my custom resolvers. The usage from my Ant build file is actually pretty simple, so we’ll start there:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<target name="-download-ivy" unless="skip.download">
<!-- download Ivy from web site so that it can be
used even without any special installation -->
<echo message="installing ivy..."/>
<get src="http://repo1.maven.org/maven2/org/apache/ivy/ivy/${ivy.install.version}/ivy-${ivy.install.version}.jar"
dest="${ivy.jar.file}" usetimestamp="true"/>
</target>
<target name="-install-ivy" depends="-download-ivy"
description="--> install ivy">
<path id="ivy.lib.path">
<fileset dir="${lib.build.dir}" includes="*.jar"/>
</path>
<taskdef resource="org/apache/ivy/ant/antlib.xml"
uri="antlib:org.apache.ivy.ant" classpathref="ivy.lib.path"/>
</target>
<target name="update" depends="prepare,-install-ivy"
description="Download project dependencies">
<!-- edited for brevity -->
<ivy:settings file="ivysettings.xml" />
<ivy:retrieve pattern="lib/[conf]/[artifact]-[revision].[ext]" />
<!-- edited for brevity -->
</target>
You will also need to define these properties somewhere:
1
2
ivy.install.version=2.0.0-beta1
ivy.jar.file=${lib.build.dir}/ivy.jar
But what’s going on here? The -download-ivy
target, simply downloads the Ivy jar and installs it in the target directory, lib/build in Scales' case. Next, -install-ivy
tells Ant how to find the Ivy tasks. Finally, in the update
target, we tell Ivy where to find the settings (which we’ll look at in a moment), and then we tell Ivy to download the dependencies.
But what’s that pattern
all about? This, I think, is a pretty interesting aspect of Ivy. Unlike Maven (and the related Ant tasks), the files aren’t just downloaded into a local repo and left, exposed to the build system through some sort of path
or fileset
reference. Ivy does download and cache the artifacts, but it also copies those to a directory in one’s project (at least, that’s how I have it set up ;). The pattern tells Ivy to copy the files a directory named after the configuration (more on that in a moment) under the lib/ directory, using an artifact-version.ext file name structure. It is then up to me to create a classpath
or fileset
reference from those files, and I think I like it that way. So, what’s this "configuration" I mentioned? For that, let’s turn our eyes to ivy.xml
.
The ivy.xml
for Scales looks like this at the moment:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<ivy-module version="1.0">
<info organisation="org" module="standalone" revision="working"/>
<configurations defaultconfmapping="runtime->*">
<conf name="build"/>
<conf name="javascript"/>
<conf name="applet" />
<conf name="compile" extends="applet" />
<conf name="test" extends="compile"/>
</configurations>
<dependencies>
<dependency org="com.sun.wts.tools.mri" name="maven-repository-importer" rev="1.2" conf="build->*>"/>
<dependency org="commons-httpclient" name="commons-httpclient" rev="3.1" conf="applet->*"/>
<dependency org="commons-fileupload" name="commons-fileupload" rev="1.1.1" conf="applet->*"/>
<dependency org="commons-codec" name="commons-codec" rev="1.3" conf="applet->*"/>
<dependency org="commons-io" name="commons-io" rev="1.2" conf="applet->*"/>
<dependency org="commons-logging" name="commons-logging" rev="1.1" conf="compile->*"/>
<dependency org="commons-lang" name="commons-lang" rev="2.3" conf="compile->*"/>
<dependency org="commons-collections" name="commons-collections" rev="3.1" conf="compile->*"/>
<dependency org="com.sun.facelets" name="jsf-facelets" rev="1.1.14" conf="compile->*"/>
<dependency org="com.sun.jsftemplating" name="jsftemplating" rev="1.2-SNAPSHOT" conf="compile->*"/>
<dependency org="com.sun.jsftemplating" name="jsftemplating-dynafaces-0.1" rev="1.2-SNAPSHOT" conf="compile->*"/>
<dependency org="javax.faces" name="jsf-api" rev="1.2_07" conf="compile->*"/>
<dependency org="javax.servlet.jsp" name="jsp-api" rev="2.1" conf="compile->*"/>
<dependency org="javax.el" name="el-api" rev="1.0" conf="compile->*"/>
<dependency org="taglibrarydoc" name="tlddoc" rev="1.3" conf="compile->*"/>
<dependency org="velocity" name="velocity" rev="1.4" conf="compile->*"/>
<dependency org="findbugs" name="findbugs" rev="1.0.0" conf="test->*"/>
<dependency org="findbugs" name="findbugs-ant" rev="1.0.0" conf="test->*"/>
<dependency org="yui" name="yui" rev="2.4.1" conf="javascript->*"/>
</dependencies>
</ivy-module>
Let’s take that one section at a time. The first element we see is info
, and I’ll be honest here: I’m really not sure what that’s for at the moment. I think it may be related to publishing (or installing, in Maven parlance) project-specific artifacts to a repository somewhere, but don’t quote me on that. The next section is where we define "configurations," which seem to be roughly analagous to Maven’s scope’s, but completely configurable and arbitrary. I have defined five configurations:
-
build: Jars need only for the build environment
-
javascript: This is an unusual one, but I use this only for the YUI files (currently) to make extracting the files easier later in the build process
-
applet: Jars which need to be bundled for use with the applet
-
compile: Jars needed only for compilation, API classes, for example
-
test: Jars needed for testing
Take note of the extends
attribute. It works just like you probably think it does: since compile
extends applet
, it will get all of the artifacts defined for it, as well as those defined for applet
. Given how Ivy (is configured to) handle dependencies, that means you’ll get two copies of some of those artifacts, but I’m OK with that.
Next in the file is the list of dependencies, which is pretty straightforward. The org
maps to Maven’s groupId
, the name
maps to artifactId
, rev
maps to version
, and conf
maps to scope
in Maven terms. Please don’t ask what that odd "→*" is there for, as I don’t understand that fully yet. All I know is that it won’t work without it. :)
Next up is ivysettings.xml
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<ivysettings>
<settings defaultResolver="chained"/>
<property name="java.net.maven.pattern" value="[organisation]/jars/[module]-[revision].[ext]"/>
<resolvers>
<chain name="chained" returnFirst="true">
<ibiblio name="ibiblio" m2compatible="true"/>
<ibiblio name="java-net-maven1" root="http://download.java.net/maven/1" pattern="${java.net.maven.pattern}" m2compatible="false"/>
<ibiblio name="java-net-maven2" root="http://download.java.net/maven/2/" m2compatible="true"/>
<url name="sourceforge">
<artifact pattern="http://easynews.dl.sourceforge.net/sourceforge/[organization]/[module]_[revision].zip" />
<artifact pattern="http://easynews.dl.sourceforge.net/sourceforge/[organization]/[module]-[revision].zip" />
</url>
</chain>
</resolvers>
</ivysettings>
One of the great things about Ivy 2 is that is configured to look at Maven repos by default. For Scales, though, I need some artifacts from the java.net Maven repository. To enable that, I have to define a custom resolver, which we see in the resolvers/chain
elements. The chain
resolver instructs Ivy to try one resolver after another to find the current artifact, with the returnFirst
attribute telling Ivy to bail out as soon as a resolver that locates the artifact. Inside the chain, I instruct Ivy to use the ibiblio repository first. I then configure both the Maven 1 and Maven 2 java.net repositories. To do that, I configure additional instances of the ibilio resolver, but I change the root URL for the repository. In the case of the Maven 1 repository, I describe the pattern
needed to find the artifact, as well as telling the resolver that the repository is not Maven 2 compatible. Finally, I create a URL repository, called "sourceforge," which I will use to resolve my YUI dependency.
With all of that in place, I can issue an ant update
from the command line, and sit back and watch Ivy checking the configured repositories for my dependencies. It may seem like a lot to configure for dependencies, but Ivy is certainly better than the homegrown dependency management schemes I’ve seen (and devised), and is certainly less intrusive than migrating wholesale to Maven. While I <i>am</i> coming around on Maven 2, this will be a great tool for those projects that I can’t (or won’t) migrate.