Firstly, here are some assumptions about the projects which this document covers:
Ant is also a great adjunct to an IDE; a way of doing all the housekeeping of deployment and for clean, automated builds. But a good modern IDE is a productivity tool in its own right - one you should continue to use. Ant just lets you give the teams somewhat more freedom in IDE choice - "you can use whatever you want in development, but Ant for the deployment builds" Now that many modern open source and commercial IDEs include Ant support (including jEdit, Forte, Eclipse and IDEA), developers can use a great IDE, with Ant providing a rigorous and portable build process integrated into the tool.
With standard target names, it is easy to build encompassing Ant build files which just hand off the work to the classes below using the ant task. For example. the clean target could be handed down to the intf and impl subdirectories from a parent directory
<target name="clean" depends="clean-intf, clean-impl"> </target> <target name="clean-intf" > <ant dir="intf" target="clean" /> </target> <target name="clean-impl"> <ant dir="impl" target="clean" /> </target>If you give targets a description tag, then calling ant -projecthelp will list all tasks with their description as 'main targets', and all tasks without a description as subtargets. Describing all your entry points is therefore very useful, even before a project becomes big and complicated.
Ant lets you call JUnit tasks, which unit test the code your team has written. Automated testing may seem like extra work at first, but JUnit makes writing unit tests so easy that you have almost no reason not to. Invest the time in learning how to use JUnit, write the test cases, and integrate them in a 'test' target from Ant so that your daily or hourly team build can have the tests applied automatically. One of the free to download chapters of Java Development with Ant shows you how to use JUnit from inside Ant.
Once you add a way to fetch code from the SCM system, either as an Ant task, in some shell script or batch file or via some continuous integration tool. the integration test code can be a pure Ant task run on any box dedicated to the task. This is ideal for verifying that the build and unit tests work on different targets from the usual development machines. For example, a Win95/Java1.1 combination could be used even though no developer would willingly use that configuration given the choice.
System tests are harder to automate than unit tests, but if you can write java code to stress large portions of the system - even if the code can not run as JUnit tasks - then the java task can be used to invoke them. It is best to specify that you want a new JVM for these tests, so that a significant crash does not break the full build. The Junit extensions such as HttpUnit for web pages, and Cactus for J2EE and servlet testing help to expand the testing framework. To test properly you will still need to invest a lot of effort in getting these to work with your project, and deriving great unit, system and regression tests - but your customers will love you for shipping software that works.
The common barriers to cross-platform Ant are the use of command line tools (exec tasks) which are not portable, path issues, and hard coding in the location of things.
Both the command line invocation tasks let you specify which platform you want the code to run on, so you could write different tasks for each platform you are targeting. Alternatively, the platform differences could be handled inside some external code which Ant calls. This can be some compiled down java in a new task, or an external script file.
This difference between platforms (indeed, the whole java classpath paradigm) can cause hours of fun.
Ant reduces path problems; but does not eliminate them entirely. You need to put in some effort too. The rules for handling path names are that 'DOS-like pathnames are handled', 'Unix like paths are handled'. Disk drives -'C:'- are handled on DOS-based boxes, but placing them in the build.xml file ruins all chances of portability. Relative file paths are much more portable. Semicolons work as path separators - a fact which is useful if your Ant invocation wrapper includes a list of jars as a defined property in the command line. In the build files you may find it better to build a classpath by listing individual files (using location= attributes), or by including a fileset of *.jar in the classpath definition.
There is also the PathConvert task which can put a fully resolved path into a property. Why do that? Because then you can use that path in other ways - such as pass it as a parameter to some application you are calling, or use the replace task to patch it into a localised shell script or batch file.
Note that DOS descended file systems are case insensitive (apart from the obscure aberration of the WinNT POSIX subsystem run against NTFS), and that Windows pretends that all file extensions with four or more letters are also three letter extensions (try DELETE *.jav in your java directories to see a disastrous example of this).
Ant's policy on case sensitivity is whatever the underlying file system implements, and its handling of file extensions is that *.jav does not find any .java files. The Java compiler is of course case sensitive - you can not have a class 'ExampleThree' implemented in "examplethree.java".
Some tasks only work on one platform - Chmod being a classic example. These tasks usually result in just a warning message on an unsupported platform - the rest of the target's tasks will still be called. Other tasks degrade their functionality on platforms or Java versions. In particular, any task which adjusts the timestamp of files can not do so properly on Java 1.1. Tasks which can do that - Get, Touch and Unjar/Unwar/Unzip for example, degrade their functionality on Java1.1, usually resorting to the current timestamp instead.
Finally, Perl makes a good place to wrap up Java invocations cross
platform, rather than batch files. It is included in most Unix
distributions, and is a simple download for Win32 platforms from
ActiveState. A Perl file with .pl extension, the usual Unix
path to perl on the line 1 comment and marked as executable can be run
on Windows, OS/2 and Unix and hence called from Ant without issues. The
perl code can be left to resolve its own platform issues. Don't forget to
set the line endings of the file to the appropriate platform when you
redistribute Perl code; fixCRLF
can do that for you.
Team Development Processes
Even if each team member is allowed their choice of IDE/editor, or even
OS, you need to set a baseline of functionality on each box. In
particular, the JDKs and jars need to be in perfect sync. Ideally pick
the latest stable Java/JDK version available on all developer/target
systems and stick with it for a while. Consider assigning one person to
be the contact point for all tools coming in - particularly open source
tools when a new build is available on a nightly basis. Unless needed,
these tools should only really be updated monthly, or when a formal
release is made.
Another good tactic is to use a unified directory tree, and add on extra
tools inside that tree. All references can be made relative to the tree.
If team members are expected to add a directory in the project to their
path, then command line tools can be included there - including those
invoked by Ant exec tasks. Put everything under source code control and
you have a one stop shop for getting a build/execute environment purely
from CVS or your equivalent.
Deploying with Ant
One big difference between Ant and older tools such as Make is that the
processes for deploying Java to remote sites are reasonably well
evolved in Ant. That is because we all have to do it these days, so
many people have put in the effort to make the tasks easier.
Ant can Jar, Tar or Zip files for deployment, while the War task extends the jar task for better servlet deployment. Jlink is a jar generation file which lets you merge multiple sub jars. This is ideal for a build process in which separate jars are generated by sub projects, yet the final output is a merged jar. Cab can be used on Win32 boxes to build a cab file which is useful if you still have to target IE deployment.
The ftp task lets you move stuff up to a server. Beware of putting the ftp password in the build file - a property file with tight access control is slightly better. The FixCRLF task is often a useful interim step if you need to to adjust the line endings of files. A WebDav task has long been discussed, which would provide a more secure upload to web servers, but it is still in the todo list. Rumour has it that there is such a task in the jakarta-slide libraries. With MacOS X, Linux and Windows XP all supporting WebDAV file systems, you may even be able to use copy to deploy though a firewall.
EJB deployment is aided by the ejb tasks, while the serverdeploy suite can deploy to multiple servers. The popularity of Ant has encouraged vendors to produce their own deployment tasks which they redistribute with their servers. For example, the Tomcat4.1 installation includes tasks to deploy, undeploy and reload web applications.
Finally, there are of course the fallbacks of just copying files to a
destination using Copy and Copydir , or just sending them to a person or
process using Mail or the attachment
aware MimeMail.
In one project our team even used Ant to build CD images through a build followed
by a long set of Copy tasks, which worked surprisingly well, certainly
easier than when we mailed them to the free email service on
myrealbox.com, then pulled them down from the far end's web browser, which we
were running over WinNT remote desktop connection, that being tunneled
through SSH.
Directory Structures
How you structure your directory tree is very dependent upon the
project. Here are some directory layout patterns which can be used as
starting points. All the jakarta projects follow a roughly similar
style, which makes it easy to navigate around one from one project to
another, and easy to clean up when desired.
bin | common binaries, scripts - put this on the path. |
build | This is the tree for building; Ant creates it and can empty it in the 'clean' project. |
dist | Distribution outputs go in here; the directory is created in Ant and clean empties it out |
doc | Hand crafted documentation |
lib | Imported Java libraries go in to this directory |
src | source goes in under this tree in a hierarchy which matches the package names. The dependency rules of <javac> requires this. |
Javadoc output can be directed to a doc/ folder beneath build/, or to doc/javadoc.
To still have a single build across the sub projects, use parent build.xml files which call down into the sub projects.
This style works well if different teams have different code access/commitment rights. The risk is that by giving extra leeway to the sub projects, you can end up with incompatible source, libraries, build processes and just increase your workload and integration grief all round.
The only way to retain control over a fairly loosely integrated collection of projects is to have a fully automated build and test process which verifies that everything is still compatible. Sam Ruby runs one for all the apache java libraries and emails everyone when something breaks; your own project may be able to make use of Cruise Control for an automated, continuous, background build process.
This project style works well if everyone trusts each other and the
sub projects are not too huge or complex. The risk is that a split to a
more loosely coupled design will become a requirement as the projects
progress - but by the time this is realised schedule pressure and
intertwined build files make executing the split well nigh impossible.
If that happens then just keep with it until there is the time to
refactor the project directory structures.
Ant Update Policies
Once you start using Ant, you should have a policy on when and how the
team updates their copies. A simple policy is "every official release
after whatever high stress milestone has pushed all unimportant tasks
(like sleep and seeing daylight) on the back burner". This insulates you
from the changes and occasional instabilities that Ant goes through
during development. Its main disadvantage is that it isolates you from
the new tasks and features that Ant is constantly adding.
Often an update will require changes to the build.xml files. Most changes are intended to be backwards compatible, but sometimes an incompatible change turns out to be necessary. That is why doing the update in the lull after a big milestone is important. It is also why including ant.jar and related files in the CVS tree helps ensure that old versions of your software can be still be built.
The most aggressive strategy is to get a weekly or daily snapshot of the ant source, build it up and use it. This forces you to tweak the build.xml files more regularly, as new tasks and attributes can take while to stabilise. You really have to want the new features, enjoy gratuitous extra work or take pleasure in upsetting your colleagues to take this approach.
Once you start extending Ant with new tasks, it suddenly becomes much more tempting to pull down regular builds. The most recent Ant builds are invariably the best platform for writing your extensions, as you can take advantage of the regular enhancements to the foundational classes. It also prevents you from wasting time working on something which has already been done. A newly submitted task to do something complex such as talk to EJB engines, SOAP servers or just convert a text file to uppercase may be almost exactly what you need - so take it, enhance it and offer up the enhancements to the rest of the world. This is certainly better than starting work on your 'text case converter' task on Ant 0.8 in isolation, announcing its existence six months later and discovering that instead of adulation all you get are helpful pointers to the existing implementation. The final benefit of being involved with the process is that it makes it easier for your tasks to be added with the Ant CVS tree, bringing forward the date when Ant has taken on all the changes you needed to make to get your project to work. If that happens you can revert to an official Ant release, and get on with all the other crises.
You should also get on the dev mailing list
, as it is where the other developers post their work, problems and
experience. The volume can be quite high: 40+ messages a day, so
consider routing it to an email address you don't use for much else. And
don't make everyone on the team subscribe; it can be too much of a
distraction.
Installing with Ant.
Because Ant can read environment variables, copy, unzip and delete files
and make java and OS calls, it can be used for simple installation
tasks. For example, an installer for tomcat could extract the
environment variable TOMCAT_HOME, stop tomcat running, and copy a war
file to TOMCAT_HOME/webapps. It could even start tomcat again, but the
build wouldn't complete until tomcat exited, which is probably not what
was wanted.
The advantage of using Ant is firstly that the same install targets can be used from your local build files (via an ant invocation of the install.xml file), and secondly that a basic install target is quite easy to write. The disadvantages of this approach are that the destination must have an up to date version of Ant correctly pre-installed, and Ant doesn't allow you to handle failures well - and a good installer is all about handling when things go wrong, from files being in use to jar versions being different. This means that Ant is not suited for shrink wrapped software, but it does work for deployment and installation to your local servers.
One major build project I was involved in had an Ant install build file for the bluestone application server, which would shutdown all four instances of the app server on a single machine, copy the new version of the war file (with datestamp and buildstamp) to an archive directory, clean up the current deployed version of the war and then install the new version. Because bluestone restarted JVMs on demand, this script was all you needed for web service deployment. On the systems behind the firewall, we upped the ante in the deployment process by using the ftp task to copy out the war and build files, then the telnet task to remotely invoke the build file. The result was we had automated recompile and redeploy to local servers from inside our IDE (Jedit) or the command line, which was simply invaluable. Imagine pressing a button on your IDE toolbar to build, unit test, deploy and then functional test your webapp.
One extra trick I added later was a junit test case to run through the install check list. With tests to verify access permissions on network drives, approximate clock synchronisation between servers, DNS functionality, ability to spawn executables and all the other trouble spots, the install script could automatically do a system health test during install time and report problems. [The same tests could also be invoked from a JMX MBean, but that's another story].
So, Ant is not a substitute for a real installer tool, except in the
special case of servers you control, but in that context it does let
you integrate remote installation with your build.
Tips and Tricks
There are two inclusion mechanisms, an ugly one for all parsers and a clean one. The ugly method is the only one that was available on Ant1.5 and earlier:-
<!DOCTYPE project [ <!ENTITY propertiesAndPaths SYSTEM "propertiesAndPaths.xml"> <!ENTITY taskdefs SYSTEM "taskdefs.xml"> ]> &propertiesAndPaths; &taskdefs;The cleaner method in Ant1.6 is the <import> task that imports whole build files into other projects. The entity inclusion example could almost be replaced by two import statements:-
<import file="propertiesAndPaths.xml"> <import file="taskdefs.xml">We say almost as top level declarations (properties and taskdefs) do not get inserted into the XML file exactly where the import statement goes, but added to the end of the file. This is because the import process takes place after the main build file is parsed, during execution, whereas XML entity expansion is handled during the parsing process.
The <import> task does powerful things, such as let you override targets, and use ant properties to name the location of the file to import. Consult the documentation for the specifics of these features.
Before you go overboard with using XML inclusion, note that the ant task lets you call any target in any other build file - with all your property settings propagating down to that target. So you can actually have a suite of utility targets - "deploy-to-stack-a", "email-to-team", "cleanup-installation" which can be called from any of your main build files, perhaps with subtly changed parameters. Indeed, after a couple of projects you may be able to create a re-usable core build file which contains the core targets of a basic Java development project - compile, debug, deploy - which project specific build files call with their own settings. If you can achieve this then you are definitely making your way up the software maturity ladder. With a bit of work you may progress from being a SEI CMM Level 0 organisation "Individual Heroics are not enough" to SEI CMM Level 1, "Projects only succeed due to individual heroics"
NB, ant copies all your properties unless the inheritall attribute is set to false. Before that attribute existed you had to carefully name all property definitions in all build files to prevent unintentional overwriting of the invoked property by that of the caller, now you just have to remember to set inheritall="false" on all uses of the <ant> task.
Having a custom invocation script which runs off a CVS controlled library tree under PROJECT_HOME also lets you control Ant versions across the team - developers can have other copies of Ant if they want, but the CVS tree always contains the jar set used to build your project.
You can also write wrapper scripts which invoke the existing Ant scripts. This is an easy way to extend them. The wrapper scripts can add extra definitions and name explicit targets, redefine ANT_HOME and generally make development easier. Note that "ant" in Windows is really "ant.bat", so should be invoked from another batch file with a "CALL ant" statement - otherwise it never returns to your wrapper.
Moving one step further, consider proving an Ant Task interface to the code as a secondary, primary or even sole interface to the functionality. Ant actually makes a great bootloader for Java apps as it handles classpath setup, and you can re-use all the built in tasks for preamble and postamble work. Some projects, such as XDoclet only run under Ant, because that is the right place to be.
Debug/release switching can be handled with separate initialisation targets called before the compile tasks which define the appropriate properties. Antcall is the trick here, as it allows you to have two paths of property initialisation in a build file.
Internal targets should be used to structure the process
<target name="init-release" if="release.build"> <property name="build.debuglevel" value="lines,source"/> </target>You then have dependent targets, such as "compile", depend on this conditional target; there the "default" properties are set, and then the property is actually used. Because Ant properties are immutable, if the release target was executed its settings will override the default values:
<target name="compile" depends="init,init-release"> <property name="build.debuglevel" value="lines,vars,source"/> <echo>debug level=${build.debuglevel}</echo> <javac destdir="${build.classes.dir}" debug="true" debuglevel="${build.debuglevel}" includeAntRuntime="false" srcdir="src"> <classpath refid="compile.classpath"/> </javac> </target>As a result, we now have a build where the release mode only includes the filename and line debug information (useful for bug reports), while the development system included variables too.
It is useful to define a project name property which can be echoed in the init task. This lets you work out which Ant file is breaking in a multi file build.
What goes in to the internal Ant tasks depends on your own projects. One very important tactic is "keep path redefinition down through references" - you can reuse paths by giving them an ID and then referring to them via the "refid" attribute you should only need to define a shared classpath once in the file; filesets can be reused similarly.
Once you have set up the directory structures, and defined the Ant tasks it is time to start coding. An early priority must be to set up the automated test process, as that not only helps ensures that the code works, it verifies that the build process is working.
And that's it. The build file shouldn't need changing as new source files get added, only when you want to change the deliverables or part of the build process. At some point you may want to massively restructure the entire build process, restructuring projects and the like, but even then the build file you have should act as a foundation for a split build file process -just pull out the common properties into a properties file all build files read in, keep the target names unified and keep going with the project. Restructuring the source code control system is often much harder work.
If your build needs to handle exceptions then look at the sound listener as a simple example of how to write your own listener class. Complex conditional statements can be handled by having something else do the tests and then build the appropriate Ant task. XSLT can be used for this.
You also need some kind of change control process, to resist uncontrolled feature creep. Bugzilla is a simple and low cost tool for this, using Ant and a continuous test process enables a rapid evolution of code to adapt to those changes which are inevitable.
For questions related to this document, use the Ant mailing list.