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