Monday, February 20, 2006

A tricky ant trick

It took me some time to get this exactly right (if such a thing can be said): How to get ant to copy over a set of files when one is out of date, but do nothing otherwise. Pretty simple, I know, but still I discovered several gotchas. Here is what I wound up with:

(Yes, this does not resemble a production ant script. The real task was signing jars for JNLP deployment from a CVS repository into a staging directory if they were not already there.)

<project default="boom">
    <property name="bob" location="bob"/>
    <property name="nancy" location="nancy"/>

    <fileset id="bobs" dir="${bob}"/>

    <target name="check-up">
        <uptodate property="dental">
            <srcfiles refid="bobs"/>
            <mapper type="glob" from="*" to="${nancy}/*"/>
        </uptodate>
    </target>

    <target name="boom" depends="check-up" unless="dental">
       <echo message="Ka-BOOM!"/>
       <copy todir="${nancy}">
           <fileset refid="bobs"/>
       </copy>
    </target>
</project>

Some of the mistakes I made along the way:

  • The unless attribute of target is just a plain token; do not try to surround it with dollar-curly braces>.
  • If you do not give srcfiles an includes or refid attribute, it silently does nothing rather than complaining.
  • The from attribute to mapper takes a plain asterisk and is relative to the srcfiles: it was easy to get this wrong in several ways and only experimentation saved me.

In short this is a nice setup to avoid extra build work, but it is somewhat fragile and Ant needs more documentation and examples in this area.

2 comments:

Anonymous said...

Yes, "uptodate" is very handy.

But I don't understand why you need it here. The "copy" task has similar checking built-in, and will avoid overwriting files that are already up-to-date (unless, of course, you set overwrite="true").

I tend to use "uptodate" in conjunction with "checkpoint files", to work around the shortcomings of tasks that don't have built-in up-to-date checking. For instance, the "junit" can't tell when a test needs to be re-run. So, if all my unit-tests pass, I'll create a checkpoint file called "unit-tests-done" in the build-area. Subsequent builds will skip unit-tests unless there is code newer than the checkpoint file.

Brian Oxley said...

You make a good point. Yes, "copy" already has this built in. I picked "copy" only for illustrative purposes. My actual build uses "signjar" and had to work around that "signjar" does not check the timestamps for anything.