Ant: how to write optional nested elements - ant

Say that I need to do something like:
<copy todir="${DEPLOYMENT_DIR}" overwrite="true">
<fileset dir="dir1" />
<fileset dir="dir2" />
<fileset dir="dir3" />
...
<if>
<equals arg1="${SPECIAL_BUILD}" arg2="true"/>
<then>
<fileset dir="dir7" />
<fileset dir="dir8" />
...
</then>
</if>
</copy>
(The real task is not copy, I'm just using it to illustrate the point.)
Ant will complain that my task doesn't support nested <if> which is fair enough. I've been thinking along these lines:
I could add a macrodef with an "element" attribute like this:
<macrodef name="myCopy">
<element name="additional-path" />
<sequential>
<copy todir="${DEPLOYMENT_DIR}" overwrite="true">
<fileset dir="dir1" />
<fileset dir="dir2" />
<fileset dir="dir3" />
...
<additional-path/>
</copy>
</sequential>
</macrodef>
But that would mean that the caller (target) must specify the additional path which I want to avoid (if many targets call this task, they would have to repeat the fileset definitions in the additional-path element).
How to code the additional filesets inside the macrodef so that Ant doesn't complain?

AntContrib has an Ant FileSet object augmented with if and unless conditions.
http://ant-contrib.sourceforge.net/fileset.html
if Sets the property name for the 'if' condition. The fileset will be
ignored unless the property is
defined. The value of the property is
insignificant, but values that would
imply misinterpretation ("false",
"no") will throw an exception when
evaluated.
unless Set the property name for the 'unless' condition. If named
property is set, the fileset will be
ignored. The value of the property is
insignificant, but values that would
imply misinterpretation ("false",
"no") of the behavior will throw an
exception when evaluated.
You could use it like this:
<copy todir="${DEPLOYMENT_DIR}" overwrite="true">
<fileset dir="dir1" />
<fileset dir="dir2" />
<fileset dir="dir3" />
...
<fileset dir="dir7" if="SPECIAL_BUILD" />
<fileset dir="dir8" if="SPECIAL_BUILD" />
</copy>

One way (not sure if a good one) to achieve that is to create two macrodefs - one "public" for general use and one "internal" that does the real work and is intended to be called only from the "public" macro. Like this:
<macrodef name="task-for-public-use">
<sequential>
<if>
<equal arg1="${SPECIAL_BUILD}" arg2="true" />
<then>
<internal-task>
<additional-path>
...
</additional-path>
</internal-task>
</then>
<else>
<internal-task ... />
</else>
</if>
</sequential>
</macrodef>
<macrodef name="internal-task">
<element name="additional-path" />
<sequential>
<copy ...>
...
<additional-path/>
</copy>
</sequential>
</macrodef>
I don't like it much though and hope there's a better way.

Related

Set ant property when comparing directories

I have two directories that I need to compare for having same files. I succesfully do this as follows:
<fileset dir="d:\test" id="onlyinbar">
<not>
<present targetdir="A_DIR"/>
</not>
</fileset>
<echo>these files are only in bar : ${toString:onlyinbar}</echo>
<fileset dir="A_DIR" id="differentbarfoo">
<different targetdir="d:\test" ignoreFileTimes="true"/>
</fileset>
<echo>these files are different in bar compared to foo : ${toString:differentbarfoo}</echo>
However, I need to issue an other task if either of these are true. So long the <condition> tag does seem to support only file to file comparison and I cannot see how to assign a property within the <fileset> tag. Help will be appreciated.
We needed to avoid any third party contribs for this solution. My major problem was the combination of the tags. We knew that our "tests" i.e fileset tags had to be in the condition but didn't know how. The resourcecount though short out the issue:
<target name="export-report-icons" description="A description">
<condition property="test2" else="false">
<or>
<resourcecount when="gt" count="0" property="flength">
<fileset dir="d:\test" id="onlyinbar">
<present targetdir="DIRs_A" present="srconly"/>
</fileset>
</resourcecount>
<resourcecount when="gt" count="0" property="flength">
<fileset dir="DIR_A" id="differentbarfoo">
<different targetdir="d:\test" ignoreFileTimes="true"/>
</fileset>
</resourcecount>
</or>
</condition>
</target>
<target name="copyThis" depends="export-report-icons" if="${test2}">
....
</target>
So what this does, sets an OR for the case that one of the two filesets succeeds, the counter wraps the fileset resource so that it can be hosted under the condition and the condition has a property true or false depending on ORed counts. The "copy-This" target executes if the ${test2} is true. Please note that if you set id=test2 it will always qualify as true as in this case it checks for value presence.
The <union> set operator groups resources from multiple collections into one collection.
Similarly, take a look at <intersect> and <difference>.
You mention "if", which I assume refers to the <if> task from the third-party Ant-Contrib library. Here's an example that echoes if the filesets combined by <union> match any files:
<if>
<resourcecount when="gt" count="0">
<union id="Check">
<resources refid="onlyinbar"/>
<resources refid="differentbarfoo"/>
</union>
</resourcecount>
<then>
<echo>There are differences: ${toString:Check}</echo>
</then>
</if>

How do I use a patternset to filter a list of files?

This seems like something that should be obvious, but I don't think it is. Given:
a space-delimited list of files (or comma-delimited, etc.)
a <patternset> of whitelisted patterns
How do I come up with a <fileset> that contains all of the files in the list that match the whitelisted pattern?
Getting a list of files from the list is easy enough:
<patternset id="the-patternset" includes="${list.of.files}" />
<fileset id="the-fileset" dir="${basedir}">
<patternset refid="the-patternset" />
</fileset>
<pathconvert pathsep="${line.separator}" property="the-filelist" refid="the-fileset"/>
<echo>fileset: ${the-filelist}</echo>
…will happily produce a fileset with all of the files in ${list.of.files}. But adding a filter of sorts:
<patternset id="the-filter">
<include name="includeme/**/*.java" />
<exclude name="excludeme/**/*.java" />
</patternset>
<patternset id="the-patternset" includes="${list.of.files}" />
<fileset id="the-fileset" dir="${basedir}">
<patternset refid="the-patternset" />
<patternset refid="the-filter" />
</fileset>
<pathconvert pathsep="${line.separator}" property="the-filelist" refid="the-fileset"/>
<echo>fileset: ${the-filelist}</echo>
…will list a union of the patternsets—i.e., all files that match either the-filter or the-patternset.
How do I produce a fileset containing files that are in ${list.of.files} and match the-patternset?
Here's a potted example. Create two filesets (or perhaps filelists) one from each of your patternsets. I'll just use fixed lists here:
<property name="list.1" value="a,b,c" />
<property name="list.2" value="b,c,d" />
<fileset dir="." id="set.1" includes="${list.1}" />
<fileset dir="." id="set.2" includes="${list.2}" />
Then use the <intersect> resource collection to get the required 'overlap' set:
<intersect id="intersect">
<resources refid="set.1"/>
<resources refid="set.2"/>
</intersect>
Most Ant tasks will allow you to use a resource collection in place of a simple fileset.

Where "if" task can be inserted?

I can see, that "if" tasks can be used in "target" root or anywhere, where some action is expected. But can I use it when I'm specifying some parameter?
For example there's ant-javafx task fx:deploy, in which we can declare classpath:
<fx:deploy ...>
<fx:resources>
<fx:fileset dir="..." ...>
<fx:fileset dir="..." ...>
</fx:resources>
</fx:deploy>
The question is can I use "if" in that usecase? Example:
<fx:deploy ...>
<fx:resources>
<if>
<available file="${lib.dir}" type="dir" />
<then><fx:fileset dir="${lib.dir}" ...></then>
</if>
<fx:fileset dir="..." ...>
</fx:resources>
</fx:deploy>
Okay, where are you getting your <if> tasks?
Are these from Ant-Contrib? If so, these are tasks and not sub-entities that can be used with in a task.
However, it MIGHT be possible to define a resource with in an <if> statement:
<if>
<avaliable file="${lib.dir}" type="dir"/>
<then>
<fileset dir="${lib.dir}" id="lib.fileset">
<includes name="..."/>
</fileset>
</then>
<else>
<fileset dir="${foo.dir}" id="lib.fileset">
<include name="..."/>
</fileset>
</else>
</fi>
Now you would have a fileset with an id of lib.fileset that could be one of two different definitions. You can then use that as part of a sub-entity:
<jar destfile="${jar.name}">
<fileset refid="lib.dir"/>
</jar>
I said MIGHT because I've never tried this, but I really can't see why it wouldn't. I have never used the JavaFX tasks, so I didn't want to give an example with that, but the documentation does say that <fx:resources> can use a reference id.

ant macrodefs and element naming

I have a macrodef with a an element called "libs"
<macrodef name="deploy-libs">
<element name="libs"/>
<sequential>
<copy todir="${glassfish.home}/glassfish/domains/domain1/lib">
<fileset dir="${lib}">
<libs/>
</fileset>
</copy>
</sequential>
</macrodef>
which is then invoked as
<deploy-libs>
<libs>
<include name="mysql-connector-*.jar"/>
<include name="junit-*.jar" />
<!-- .... -->
</libs>
</deploy-libs>
I then have another macrodef which calls several macrodefs including "deploy-libs". It would be nice if this macrodef had an element "libs" too but:
<macrodef name="init-glassfish">
<element name="libs"/>
<sequential>
<!-- other stuff -->
<deploy-libs>
<libs>
<libs/>
</libs>
</deploy-libs>
<!-- other stuff -->
</sequential>
</macrodef>
is obviously not working (because of <libs><libs/></libs>):
Commons/ant-glassfish-server.xml:116: unsupported element include
A solution could be to name the element in "init-glassfish" in a different way:
<macrodef name="init-glassfish">
<element name="libraries"/>
<sequential>
<!-- other stuff -->
<deploy-libs>
<libs>
<libraries/>
</libs>
</deploy-libs>
<!-- other stuff -->
</sequential>
</macrodef>
Is there a way to have the element to be named in the same way for both macrodefs?
I found a solution to my question which solves the original problem using a path id
<macrodef name="deploy-libs">
<attribute name="libraries-path-refid"/>
<sequential>
<copy todir="${glassfish.home}/glassfish/domains/domain1/lib">
<path refid="#{libraries-path-refid}"/>
</copy>
</sequential>
</macrodef>
Now this does not solve the issue with the nested elements tags but (XY problem) solves the practical issue. It would be still be nice to know if something similar to the original question is possible.
Apparently, the solution is wrapping additional <libs> elements around in the original call depending on how deep the macros nest. Of course this is a horrible solution because it requires that the macro nesting depth is known on invocation, e.g.:
<deploy-libs>
<libs>
<libs>
<include name="mysql-connector-*.jar"/>
<include name="junit-*.jar" />
<!-- .... -->
</libs>
</libs>
</deploy-libs>
Ant bug 29153 addressing this problem was unfortunately resolved as invalid :(.

Ant: Conditional Copy

I want to overwrite the hosts file on the Windows machine if the user allows it:
<input message="Do you want to overwrite the HOSTS file?"
addproperty="overwrite.hosts" validargs="yes,no" />
<copy tofile="${env.WINDIR}/system32/drivers/etc/hosts.backup">
<fileset file="${env.WINDIR}/system32/drivers/etc/hosts" />
</copy>
<copy todir="${env.WINDIR}/system32/drivers/etc">
<fileset file="${trainer.dir}/hosts" />
</copy>
How do I do the copies only if the user says yes?
EDIT:
I tried this:
<input message="Do you want to overwrite the HOSTS file?" addproperty="overwrite.hosts" validargs="yes,no" />
<if>
<equals arg1="${overwrite.hosts}" arg2="yes" />
<then>
<copy tofile="${env.windir}/system32/drivers/etc/hosts.backup">
<fileset file="${env.windir}/system32/drivers/etc/hosts">
</fileset>
</copy>
<copy todir="${env.windir}/system32/drivers/etc">
<fileset file="${trainer.dir}/hosts">
</fileset>
</copy>
</then>
</if>
and I get this output:
C:\trainer\build.xml:16: Problem: failed to create task or type if
Cause: The name is undefined.
Action: Check the spelling.
Action: Check that any custom tasks/types have been declared.
Action: Check that any <presetdef>/<macrodef> declarations have taken place.
I'm an ant rookie... What do I need to do?
You could use a condition or a if task for that. (The latter is part of the ant-contrib project.)
You can use an "if" parameter on a target to make it conditional on the property being set.
I've never used the "input" task -- I didn't know it existed until just now (thanks for the heads up!) -- but a quick look at the documentation indicates that it sets the named property to the value entered, i.e. after an "input" the property is always set. So I guess you would need a "condition" to test the value and set or not set some other property.
Something like this. I just ran a quick test and this does work. Namely, if you answer the question "y" it prints the message, and if you answer "n" it does not.
<project name="test" default="do.whatever">
<target name="decide.do.whatever">
<input message="So you wanna do this or not?" validargs="y,n" addproperty="wanna"/>
<condition property="wanna.yes">
<equals arg1="${wanna}" arg2="y"/>
</condition>
</target>
<target name="do.whatever" depends="decide.do.whatever" if="wanna.yes">
<echo message="Yeah he wannas."/>
</target>
</project>

Resources