ant-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From "Guthrie, John" <>
Subject RE: article: combining make and ant
Date Thu, 21 Mar 2002 14:42:35 GMT
One reason I am still using makefiles in certain parts of the build tree is
it's ability to assign variables a la:
  ARCH_DIR=`uname -ms | sed -e "s/ /_/g"`

I wrote a couple of taskdefs to try to do this from ant but I couldn't
figure out how to do the pipe stuff easily using a java Runtime.

Is there a(n easy) way to do this in ant?

John Guthrie
American Institutes for Research

-----Original Message-----
From: stephan beal []
Sent: Thursday, March 21, 2002 7:22 AM
Subject: article: combining make and ant

Hello, fellow Anters,

This post describes one method for keeping Ant itself in your build tree and

using 'make' as a front-end for launching ant. This article will only be of 
use to Unix users (and maybe Cygwin users, but i have no experience with 
Cygwin so i cannot say for sure).

Why mix Ant and Make? There are several advantages to doing so, which i hope

will become clear as the article progresses. i'm not condoning mixing the
out of any dislike for Ant - i've come to like Ant quite a lot. i'm
mixing the two because it opens up new possibilities without taking any

The Problem: My company's source tree gets built on a variety of
machines, mostly running Solaris or Linux. Installing Ant on each of
these machines, and making sure each Ant installation always has the
latest copies of our custom tasks, is a huge PITA (pain in the ass).
One complication is the fact that any custom Ant extensions (new tasks) must

be built and in the CLASSPATH before ant is actually run on our source tree.

This means we have to maintain copies of our tasks' jar file(s) on each 
buildmachine, and that type of maintenance is No Fun. (And anyone who knows 
me knows that i am incredibly lazy, and therefor always look for the
which requires the least maintenance .)

The Solution: install Ant in your source tree, and use make as a front-end
take care of the drudgery, including environment configuration and building 
of custom Ant tasks.

Our source tree contains a binary installation of ant, and that Ant 
installation is used to build our source tree. In addition, the Ant tree 
contains the sources for our custom tasks, and make ensures that these tasks

are built before doing the "main build."

The source tree looks something like this:

./src/ = our java files
./classes/ = compiled output
./ant/bin, ant/lib = Ant installation
./ant/src = our custom classes
./build.therealbuild.xml = our "real" build.xml
./build.xml = a bogus build.xml, explained below.
./ant/build.antextensions.xml = the build.xml for our custom classes
(again, that tree has a bogus build.xml).

build.xml: This file is a bogus build file which fails with an error
message, explaining the "proper" way to run the build. This is to keep
people from running the build with their own ant version. This ensures:
  a) build compatibility across machines (no surprises caused by different 
Ant versions).
  b) the PATH & CLASSPATH are set up exactly how we need it done, without 
having to rely on the developer to set up h{is,er} environment correctly. 
This is accomplished by forcing the user to run 'make', which does the 
configuration for the user.

Here's our top-level Makefile:
SHELL = /bin/bash
# using Solaris' bourne shell will almost certainly 
# not work. Don't use Solaris' make, either: use GNU make.
.PHONY: ant clean

default: all
ant_home = ${PWD}/ant
ant = ${ant_home}/bin/ant
ant_libdir = ${ant_home}/lib

# ${ant_extensions} must be in the classpath BEFORE ant is started
# or they won't get loaded in time to be used in <taskdef>s.
ant_extensions = ${ant_libdir}/einsurance_ant.jar
ant_classpath = ${ant_extensions}
ant_args := ${ant_args} -buildfile build.therealbuild.xml
${ant_extensions}: Makefile ${ant_home}/build.antextensions.xml
        cd ${ant_home} && ${MAKE}

ant: ant-main

ant-%: ${ant_extensions}
        @target=$@; target=$${target#*-}; \
	ant_cmd="${ant} $$target ${ant_args}"; \
        echo "ant command=[$$ant_cmd]"; \
        ANT_HOME=${ant_home}; \
        PATH=${ant_home}/bin:$$PATH; \
        CLASSPATH=${ant_classpath}:$$CLASSPATH; \
        export ANT_HOME PATH CLASSPATH; \
# note: the 'export' line is not strictly necessary, but i 
# like it, and it actually is necessary for part of our process.

clean: ant-clean
all: ant-main

Now the command:
   make ant-main

will run 'ant main -buildfile build.therealbuild.xml'
You can send more arguments to ant with this:

    ant_args="-logfile foo -emacs" make

Those will be prepended to the hard-coded list of arguments.

Make is, let's face it, far better at dependency checking than Ant, and we 
take advantage of make's dependency capabilities in allowing it to build our

custom Ant tasks before it runs the main build. The tasks must be built 
before Ant is run on our source tree, and make takes care of that for us, 
running ant once to build the tasks then once more to compile our source
(which uses those just-compiled tasks).

If we want to simplify the process of running ant, we can add this to your 
        @echo "Select a build to run:"
        @select build in main clean cvs compile; \
		do ${MAKE}; ant-$$build;break; done

Now running 'make' will present you with a list of ant targets to run, and 
you just need to tap a number, then Enter. Selecting 'cvs' from the list
run 'make ant-cvs'.

Note that the Ant target names are determined dynamically - you never need
add Ant target names to the Makefile unless you want to add shortcuts, like:

clean: ant-clean
rebuild: ant-clean ant-main

Whenever we run make, it will make sure that our custom extensions
(${ant_extensions}) are rebuilt, and will then run ant with those
extensions in the classpath. We put our custom tasks in a separate
source tree, under ./ant/src, so that we can build them separately.  We
cannot build our source tree if the extensions are not built, because
our build.therealbuild.xml contains <taskdefs> which are not valid
unless our extensions are built and in our CLASSPATH. Thus we cannot
store the tasks' source code in our main source tree. Rather than deal
with all of this mess manually (on each build machine), we let make do
it for us.

The best part is now that we can check out our source tree to any
machine and immediately build it without having to install ant:

cvs co ......
cd einsurance
make ant
<builds extensions>
<builds source tree>

The Makefile for our extensions (./ant/src/Makefile) looks like this:
default: all
libdir = ${PWD}/lib
classdir = ${PWD}/classes
ant_home = ${PWD}
CLASSPATH := ${classdir}:${CLASSPATH}
ant_args := ${ant_args} -buildfile build.antextensions.xml
	ANT_HOME=${ant_home}; \
	${ant_home}/bin/ant ${ant_args}

	ANT_HOME=${ant_home}; \
	${ant_home}/bin/ant clean ${ant_args}

all: build_extensions
Note that in this case we let Ant take care of the dependencies for the jar 
file, since that type of dependency tracking is much simpler to implement in


The ./ant tree knows nothing about our main source tree, by the way, making 
it a completely standalone environment for developing our custom tasks.

Having just converted our tree from Makefile-based to Ant-based, i can
say that Ant is certainly more suited for the task - we have over 1700
classes, and make takes over an hour to build the whole thing (because
it runs javac once per source file, causing a huge overhead). Ant normally 
does the build in under 3 minutes. However, make has some features which Ant

could not hope to accomplish (like INCREDIBLE dependency checking, and 
dynamic target names (like ant-%)), and some things are just plain simpler
make (like running shell code, which we simply cannot avoid in some cases).

Another, perhaps not immediately obvious, advantage in using make is the 
ability to run commands which can accept user input. Ant 1.x cannot
do this. We use exactly this to send our compiled classes to our test

in Makefile:
	@echo "Type in the number of the machine you want to send this stuff
	@select svr in int mirror; do ${MAKE} sync-$$svr; break; done

	echo "syncing to INTEGRATION..."; \
		cd ${top_srcdir}/classes; \
		rsync -C -z -v -r -W -e ssh .
build@integration:/u/www/INT/servlets; \
		cd ${top_srcdir}/webapps; \
		rsync -C -z -v -r -W -e ssh .

	echo "syncing to MIRROR..."; \
		cd ${top_srcdir}/classes; \
		rsync -C -z -v -r -W -e ssh .
build@mirror:/u/www/WWW/servlets; \
		cd ${top_srcdir}/webapps; \
		rsync -C -z -v -r -W -e ssh .

# if i wasn't so lazy that would be one sync-% 
# target with variables in it.
("Integration" and "mirror" are internally-used terminology referring to our

test servers. Your organization probably has something similar.)
Now running 'make sync' will send our classes to the remote machine we 

We could also use make to generate properties files for our build, before
is run. There are lots of possibilities.

Granted, using make is not as cross-platform as using Only Ant, but if
running Unix machines, then a mix of make and Ant is a powerful combination.

While the purists (and Windows users ;) out there can justifiably argue, "we

must implement it all in build.xml, using Pure Ant and Java", i disagree. 
We may as well use the tools available to us, especially when they make our 
jobs easier.

----- stephan
Generic Universal Computer Guy -
Office: +49 (89)  552 92 862 Handy:  +49 (179) 211 97 67
"I ain't gen'rally given to physicality of that nature but it saves
a lot of arguing." -- Nanny Ogg

To unsubscribe, e-mail:   <>
For additional commands, e-mail: <>

To unsubscribe, e-mail:   <>
For additional commands, e-mail: <>

View raw message