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]

Reply via email to