ant-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From stephan beal <step...@einsurance.de>
Subject article: combining make and ant
Date Thu, 21 Mar 2002 12:22:11 GMT
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 two 
out of any dislike for Ant - i've come to like Ant quite a lot. i'm proposing 
mixing the two because it opens up new possibilities without taking any away.

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 approach 
which requires the least maintenance .)

The Solution: install Ant in your source tree, and use make as a front-end to 
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; \
        $$ant_cmd
# 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 tree 
(which uses those just-compiled tasks).

If we want to simplify the process of running ant, we can add this to your 
Makefile:
===================
all:
        @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 will 
run 'make ant-cvs'.

Note that the Ant target names are determined dynamically - you never need to 
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 feasibly 
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
build_extensions:
	@CLASSPATH=${CLASSPATH}; \
	ANT_HOME=${ant_home}; \
	${ant_home}/bin/ant ${ant_args}

clean:
	@CLASSPATH=${CLASSPATH}; \
	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 
Ant.

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 in 
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 currently 
do this. We use exactly this to send our compiled classes to our test servers:

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

sync-int:
	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 . build@integration:/u/www/INT/webapps

sync-mirror:
	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 . build@mirror:/u/www/WWW/webapps

# 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 
select.

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


Granted, using make is not as cross-platform as using Only Ant, but if you're 
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
stephan@einsurance.de - http://www.einsurance.de
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:   <mailto:ant-dev-unsubscribe@jakarta.apache.org>
For additional commands, e-mail: <mailto:ant-dev-help@jakarta.apache.org>


Mime
View raw message