Last update: $Date: 2009-02-02 03:47:30 +0000 (Mon, 02 Feb 2009) $
Project Dependencies Using Ant
Overview
This article discusses a technique for managing the build order of separate sub-projects in a large software system purely using task dependencies within Ant scripts.
Unlike other solutions, this technique for managing dependencies does not need any external tasks to those already distributed with Apache Ant, as it leverages Ant's inbuilt target dependency behaviour. See the Ant Related Projects page for many examples of other alternative solutions to the method presented in this article.
Example System
For the purposes of describing this technique, I'll use an example application that has been split into four components:
- web
- Views and user interaction code. Depends upon the model and common components.
- admin
- Functionality to administer the application data. Depends upon the model and common components.
- model
- Objects that encapsulate data and associated behaviour. Depends upon the common component.
- common
- Shared utilities and methods. Has no other dependencies.
Based upon this description, the dependencies between components can be illustrated as follows:
These projects are contained in sibling directories underneath a top
level "example" directory.
Building a Component
The standard way to build a component from Ant is to declare a
build or compile target and use the javac task.
Other targets can build a distributable or clean up generated files.
For example:
build.xml (example)
<?xml version="1.0" encoding="ISO-8859-1"?> <project name="build" default="dist"> <target name="clean" description="Delete generated files"> ... </target> <target name="compile" description="Compile code"> ... </target> <target name="dist" depends="compile" description="Build distributable"> ... </target> </project>
Traditionally, a script similar to the example above would have been
copied into each of the component directories. However, Ant 1.6 introduced
the <import> task, which greatly simplifies writing
scripts for multiple projects with a similar structure. Making use of this
feature, we will use a common build script containing all the shared
targets, and a much simplified script in each component.
The common build script looks like this:
build-common.xml
<?xml version="1.0" encoding="ISO-8859-1"?> <project name="build-common" default="default"> <target name="default" depends="dist"/> <target name="clean"> <echo message="${ant.project.name} - build-common.clean"/> </target> <target name="compile"> <echo message="${ant.project.name} - build-common.compile"/> </target> <target name="dist" depends="compile"> <echo message="${ant.project.name} - build-common.dist"/> </target> </project>
This example is only echoing messages to enable the targets to be traced.
A real script would be calling the <delete>,
<javac> and <jar> tasks
respectively.
To use these common tasks, each component creates a build.xml file
that simply imports build-common.xml. In the case of the
admin component, build.xml will look like
this:
admin/build.xml
<?xml version="1.0" encoding="ISO-8859-1"?> <project name="admin" default="default"> <import file="../build-common.xml"/> </project>
The only difference for the other projects is the value of the
name attribute of the project element. To build
the distributable for a project, enter the following:
> ant dist
This works fine if all dependencies are already present. However, in the case of the admin, model and web components, the code depends upon jars that may not yet be built. There needs to be a way to ensure that building any component will always build dependencies beforehand.
Declaring Dependencies
Dependencies between components are described within a single Ant script
called dependencies.xml, saved at the top level of the
directory structure. For each component, there is a
corresponding target called depend.{componentname} defined
within dependencies.xml. The depends
list for this target will specify the other component dependencies. The
sole task for each of these targets is to recursively call Ant within the
corresponding component directory. Putting this all together, the script
looks like this:
dependencies.xml
<?xml version="1.0" encoding="ISO-8859-1"?> <project name="dependencies" default="depend.all"> <dirname property="dependencies.basedir" file="${ant.file.dependencies}"/> <!-- ================================================================== --> <target name="depend.all" depends="depend.admin, depend.web"> </target> <!-- ================================================================== --> <target name="depend.admin" depends="depend.model, depend.utilities"> <ant dir="${dependencies.basedir}/admin" inheritAll="false"/> </target> <!-- ================================================================== --> <target name="depend.model" depends="depend.utilities"> <ant dir="${dependencies.basedir}/model" inheritAll="false"/> </target> <!-- ================================================================== --> <target name="depend.utilities"> <ant dir="${dependencies.basedir}/utilities" inheritAll="false"/> </target> <!-- ================================================================== --> <target name="depend.web" depends="depend.model, depend.utilities"> <ant dir="${dependencies.basedir}/web" inheritAll="false"/> </target> </project>
Calling Dependencies
Now that dependencies have been defined, there needs to be a way to automatically call the builds of dependenent projects. Making the following changes will achieve this:
- update
build-common.xmlto importdependencies.xml - declare a new target
dist.dependenciesthat uses<antcall>to call into the appropriate target. Since the project name is always stored in the propertyant.project.name, a single target is sufficient
The updated build-common.xml now looks like this:
build-common.xml
<?xml version="1.0" encoding="ISO-8859-1"?> <project name="build-common" default="default"> <import file="dependencies.xml"/> <target name="default" depends="dist"/> <target name="clean"> <echo message="${ant.project.name} - build-common.clean"/> </target> <target name="compile"> <echo message="${ant.project.name} - build-common.compile"/> </target> <target name="dist" depends="compile"> <echo message="${ant.project.name} - build-common.dist"/> </target> <target name="dist.dependencies"> <antcall target="depend.${ant.project.name}"/> </target> </project>
That's it! Running the following command from within any component will always build dependant components first:
> ant dist.dependencies
For example, here is the output of running the command from within the
web component:
Buildfile: build.xml
dist.dependencies:
depend.utilities:
compile:
[echo] utilities - build-common.compile
dist:
[echo] utilities - build-common.dist
default:
depend.model:
compile:
[echo] model - build-common.compile
dist:
[echo] model - build-common.dist
default:
depend.web:
compile:
[echo] web - build-common.compile
dist:
[echo] web - build-common.dist
default:
BUILD SUCCESSFUL
Total time: 0 seconds
Download the Example
To save typing, download the example scripts.
Feedback
Is there a problem or mistake on this page? Is there any way to improve this article? Send me an email at joe@exubero.com.