Noticing that many Ant file operations were rather slow on large directory trees, I fell back to the tried and true: <exec/>
. But I noticed that as I coded up cp, mv and rm, that the calls to <exec/>
became rather tedious and repetitious. Looking around a bit, I found just the thing: <macrodef/>
! First the starting code using rm as an example:
<echo message="rm -rf ${some.dir}"/> <exec executable="rm"> <arg value="-rf"/> <arg value="${some.dir}"/> </exec>
Pretty straight-forward. Just note that <exec/>
is silent so I add an <echo/>
. This corresponds to the shell command:
$ rm -rf $some_dir
The next step is to turn this into a macro:
<macrodef name="rm"> <attribute name="src"/> <sequential> <echo message="rm -rf @{src}"/> <exec executable="rm"> <arg value="-rf"/> <arg value="@{src}"/> </exec> </sequential> </macrodef>
Notice that the '$' became a '@' and the <exec/>
is now wrapped in a <sequential/>
tag. That is how Ant tells apart macro parameters from properties. With this change, I can now use this Ant script snippet:
<rm src="${some.dir}"/>
Much more readable!
Next I want to make this a bit more reusable. My example was super simple, but other cases might need to use some of the attributes for <exec/>
. For my purposes, I added dir, spawn and failonerror. I found real uses of dir and failonerror in our codebase, and I wish to single out spawn in just a minute. That yields:
<macrodef name="rm"> <attribute name="src"/> <attribute name="dir" default="."/> <attribute name="spawn" default="false"/> <attribute name="failonerror" default="false"/> <sequential> <echo message="rm -rf @{src}"/> <exec executable="rm" dir="@{dir}" spawn="@{spawn}" failonerror="@{failonerror}" <arg value="-rf"/> <arg value="@{src}"/> </exec> </sequential> </macrodef>
Combined here are the techniques for macro attribute defaults and for passing down attributes for tasks wrapped in a macro. These serve well to preserve expected defaults and avoid surprises for macro users.
Aside: Why does failonerror default to false? This seems a perverse choice for a build system when fail-fast strategies save so much developer time in large projects.
Lastly, I want to make the macro generic to work with cp and mv, not just rm. So I did the obvious (to me) thing: I made a macro with the macro. Thus:
<macrodef name="file-operation"> <attribute name="operation"/> <attribute name="message"/> <element name="attributes"/> <element name="args"/> <sequential> <macrodef name="@{operation}"> <attributes/> <attribute name="dir" default="."/> <attribute name="spawn" default="false"/> <attribute name="failonerror" default="false"/> <sequential> <echo message="@{message}"/> <exec executable="@{operation}" dir="@{dir}" spawn="@{spawn}" failonerror="@{failonerror}"> <args/> </exec> </sequential> </macrodef> </sequential> </macrodef>
And my definition of the <rm/>
macro becomes:
<file-operation operation="rm" message="rm -rf @{src}"> <attributes> <attribute name="src"/> </attributes> <args> <arg value="-rf"/> <arg value="@{src}"/> </args> </file-operation>
Usage stays the same:
<rm src="${some.dir}"/>
And likewise for cp and mv:
<file-operation operation="cp" message="cp -a @{src} @{dst}"> <attributes> <attribute name="src"/> <attribute name="dst"/> </attributes> <args> <arg value="-a"/> <arg value="@{src}"/> <arg value="@{dst}"/> </args> </file-operation> <file-operation operation="mv" message="mv @{src} @{dst}"> <attributes> <attribute name="src"/> <attribute name="dst"/> </attributes> <args> <arg value="@{src}"/> <arg value="@{dst}"/> </args> </file-operation>
With corresponding Ant script calls:
<cp src="${some.dir}" dst="${dupliate.dir}"/> <mv src="${some.dir}" dst="${renamed.dir}"/>
One last itch remains for me. The raison d’ĂȘtre I started down this road in the first place was to speed up <delete dir="${some.dir}"/>
. An optimization I realized early on was not just to call to rm, but to run the operation in the background:
<tempfile property="tmp.dir" prefix=".tmp."/> <mv src="${some.dir}" dst="${tmp.dir}"/> <rm src="${tmp.dir}" spawn="true"/>
This pattern renames ${some.dir}
to a random temporary directory and deletes the temporary directory in the background. The documentation of <exec/>
even claims the operation continues after the Ant script exits. Perfect! Now to simplify usage of the pattern:
<macrodef name="rm-background"> <attribute name="src"/> <attribute name="property"/> <attribute name="dir" default="."/> <attribute name="failonerror" default="false"/> <sequential> <tempfile property="@{property}" prefix=".@{property}."/> <mv src="@{src}" dst="${@{property}}" dir="@{dir}" failonerror="@{failonerror}"/> <rm src="${@{property}}" spawn="true" dir="@{dir}" failonerror="@{failonerror}"/> </sequential> </macrodef>
Gives:
<rm-background src="${some.dir}" property="tmp.some.dir"/>
Notice that the temporary directory begins with a '.'. This is to make it hidden under UNIX/Cygwin so it doesn't clutter ls.
Time for a peanut butter sandwich.
UPDATE: I've saved everything in one place for easy examination.
2 comments:
a very nice example of macro.learnt macors in one shot.
i bookmarked this.thank you so much
-Raj,India
Can you invoke a macro from a macrodef? If so, how? Do you invoke it within a sequential element or instead of a sequential element?
My attempts to do this either way have failed.
Thanks,
Chuck
Post a Comment