I don't know who William Lopez is---I've never met the man, and before yesterday at 19:40 EST, I'd never heard of him. But then a mail message landed in my inbox saying, "I think I've solved your Ant + Hibernate problem." He included his solution in a ZIP file; it took me all of five minutes to download it, edit a couple of settings for my machine (library files in different places), and convince myself that yes, it did work. It took another ten minutes to figure out which of his changes was the crucial one, and guess what: it's an Ant bug.

Or maybe not, because three hours later, Eric Burke added a comment to the original posting. His solution was to rearrange some of the Hibernate-specific stuff in the build file. So who's fault is it really, Ant's or Hibernate's? Let's take a look.

I was trying to get Ant to clean my workspace, generate a Java source file from a Hibernate mapping file, compile that file and another one I'd written by hand, and then generate a database schema, all in one invocation. The problem was that when I got to the schema generation step, the Hibernate schema generation task told me that the compiled class file for the generated class didn't exist. If I ran Ant twice, though, everything worked; it also all worked if I ran Ant as far as "compile the Java", then ran it again to do the schema generation. But how could there be a timing bug in a strictly sequential chain of operations?

William Lopez's solution was to create all of the temporary directories the build process needed once, at the start of the build process, rather than creating each directory just before it was needed. Basically, I had:

<target name="clean">
    <delete dir="${gen}"/>
    <delete dir="${class}"/>
    <delete dir="${data}"/>
</target>

<target name="codegen">
    <mkdir dir="${gen}"/>
    <hbm2java output="${gen}"> />
</target>

<target name="compile" depends="codegen">
    <mkdir dir="${class}"/>
    <javac srcdir="${gen}" destdir="${class}" ...> />
    <javac srcdir="${src}" destdir="${class}" ...> />
</target>

<target name="schema" depends="compile">
    <mkdir dir="${data}"/>
    <schemaexport ... />
</target>

and William changed it by moving the mkdir actions inside the clean target:

<target name="clean">
    <delete dir="${gen}"/>
    <delete dir="${class}"/>
    <delete dir="${data}"/>
    <mkdir dir="${gen}"/>
    <mkdir dir="${class}"/>
    <mkdir dir="${data}"/>
</target>

<target name="codegen">
    <hbm2java output="${gen}"> />
</target>

<target name="compile" depends="codegen">
    <javac srcdir="${gen}" destdir="${class}" ...> />
    <javac srcdir="${src}" destdir="${class}" ...> />
</target>

<target name="schema" depends="compile">
    <schemaexport ... />
</target>

So it has to be an Ant problem---except that Eric Burke fixed the problem by moving the schema export taskdef inside the schema export target, like this:

<target name="schema" depends="compile">

    <taskdef name="schemaexport"
             classname="net.sf.hibernate.tool.hbm2ddl.SchemaExportTask"
             classpathref="cpath.compile" />

    <mkdir dir="${data}"/>

    <schemaexport .../>

</target>

and that works too. His explanation says:

I suspect the taskdef is resolving the classpath at the instant Ant parses the taskdef tag. Since one of the required .class files has not been generated yet, the SchemaExportTask does not find the necessary files... While you could blame Ant, I think it's more likely that the SchemaExportTask should probably be modified to accept a classpath of its own. This would make the task more flexible since you wouldn't have to worry about where you define it.

He then goes on to say:

I hope someone finds this useful. [Yes, Eric, very; thank you, and thanks to William, too.] There is still a very steep barrier to entry for tools like Hibernate, various AOP frameworks, etc... The barrier is figuring out all of the initial configuration options and getting your build process to flow. These things are overwhelming when you are first getting started.

Amen, Eric. Eleven of my department's brightest undergraduates have been banging their heads against Eclipse, Ant, JUnit, Tomcat, Hibernate, Tapestry, and Checkstyle for more than four weeks now. They're smart, they work very hard, and they really want to master this stuff, but the learning "curve" they face is more like a vertical wall. If the open source community wants colleges and universities to include modern tools and frameworks in the curriculum, they're going to have to do a better job fixing the "last 10%" of problems that so often gobble up all the hours students are supposed to spend learning...