Dimitris Mouchritsas wrote:
Are there any best practices that you know of?
I'm unsure if there is one set of practices that work well in all
circumstances, but I'd be happy to share a few ideas that I put into
place for our in-house build system which is used by 3-4 different
projects, one of which consists of over 50 sub-project modules.
Our project was on the cusp of growing to be unmanageable. Maven2 had
just been released and it looked interesting and had lots of features I
liked, but there were a few strikes against it in our circumstances that
made it unsuitable at the time. We ended up with a build system built
mostly with Ant, Ant-Contrib, Maven Ant Tasks, and a few in-house
developed tasks. The system relies heavily on features introduced in 1.6
such as <import> and <macrodef>, and shuns the use of the <antcall>
task. The setup also adopted a lot of basic Maven2 conventions, which is
one of the primary places we looked to for "best practices".
Each module in our project has a build.xml file AND a pom.xml. Basic
characteristics of projects are defined in the pom.xml. Name, type,
dependencies, parent. etc. Each project imports a skeleton project, that
provides for a basic build life cycle. Each project can also have
additional imports to "decorate" the skeleton project with process
enhancements (such as creating xmlbeans, using xdoclet, using hibernate,
etc). Functionality that was truly unique to a specific project was put
directly into its build.xml in standalone targets, or into special
targets that were hooked into the build life cycle.
There were two tasks we had to created that helped tie all of this
together. One was a task that extended the Ant-Contrib run-target, which
could call all targets defined by the same name (honoring their
dependencies). This may not seem like a big deal, but it was huge:
We could for example have the following task in the skeleton project:
<custom:call-target target="generate-code"
scope="all"
existance="optional" />
And other imported files could define a generate-code target (xmlbeans,
hibernate, etc) These would all be executed in the order in which they
were imported, without the skeleton target having to know the details of
which file they were defined. We could have added inclusions/exclusions,
but didn't need the additional complexity. The existance value told the
task not to complain if it couldn't find any matching targets, though it
was log the fact when verbose logging was configured.
Another important task was one which was tied into our Maven
integration. Early in the build cycle for each project, we checked a
project's dependencies to see if there were any dependencies within the
same project group (and internal dependency), if so, before the current
projects build would go any further, it would first use the <ant> task
to call the dependent projects default target, which in nearly all cases
was the "build" target, which ran through the same build life cycle on
the dependency and so on in a recursive fashion. The trickiest part here
was ensuring that a dependent project was not built more than once while
recursing.
Overall, it works pretty well. We have several large projects that use
the system and it appears to scale fairly well, though it does offer a
basic trade off. It is *definitely* slower that a setup where all the
source code is located in one directory and one large (and very
complicated) script would be used to do the same builds. On the other
hand maintenance is far easier, and it is a fairly easy thing to add new
modules without disrupting other existing functionality. The build for
each project is extremely small and in many cases consists of one or two
imports, along with a property override or two.
One thing we ran into which did present a challenge was that a a project
that created a WAR file might have a different set of depenedencies when
it was bundled by itself instead of deployed within an EAR. That was one
wrinkle that we never adequately addressed nor saw an easy solution for
using Maven's dependency management tasks.
We went with Maven, but after looking at Ivy, we could just as easily
have used it instead. At the time we were looking at striking a balance
between ant and maven, and creating a solution that wouldn't take us too
far from a Maven2 setup if we decided to reconsider it later. Since we
had already done quite a bit of legwork looking at Maven2, it made more
sense at the time to use the Maven2 plugin over Ivy for dependency
management.
We are able to get a lot of mileage out of a setup like this since we
have several folks who are very comfortable using and extended Ant.
There are things to watch out for when attempting something like this:
- be extremely carefully with naming conventions and properties.
- Watch out for ClassLoader issues and custom tasks.
- Be extremely careful about using <antcall> when importing files and
in large builds because of its consequences. We essentially outlawed it
within our system. If we needed to extract a commom process that never
needed to be called by itself and didn't really serve as a decent
override point, we used a macro. If that macro become overly complex
(obviously subjective) we replaced it with a pure Java task. There were
times when having one target call another are useful. It gives you the
opportunity to override the second target. In these cases we either took
advantage of target dependencies, or our <call-target> task, which was
based off of Ant-Contrib's <runtarget> task.
Anyway... just a few of my thoughts on it.
Rob
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]