What are the usage restrictions on Ant's if/unless conditional attributes? - ant

So we're using basic Ant 1.9.4, with a little use of 1.9.1's new if/unless attributes. For example,
<project xmlns:if="ant:if" xmlns:unless="ant:unless">
.... miles and miles of XML ....
<jar ....>
<service ....>
<provider classname="a.b.c.X" if:true="${some.flag}"/>
<provider ..../>
</service>
<fileset>
....the stuff in here will matter shortly....
</fileset>
</jar>
with some.flag always set to true or false, never left unset, and never set to any other value (assigned inside a property file read in earlier, if that matters), and it works wonderfully, giving us exactly the behavior we need. Joy!
More recently, we tried to make some of the jar task's fileset entries a little smarter, such as
<fileset dir="somedir">
<include name="optional_file" if:true="${some.flag}"/> <!-- same property name as before -->
</fileset>
With this syntax, we get an error "A zip file cannot include itself", with a line number pointing to the start of the jar task. This is obviously bogus syntax. However, changing this second if:true to simply if out of desperation -- and making no other changes -- avoids the error and gives us correct flag-based optional inclusion behavior.
What's going on here? Is the new syntax simply unavailable in <fileset> and/or fileset's nested <include> blocks?
As an experiment, I tried using an if:true or if:set attribute as appropriate in a few other useful places. Some places it worked perfectly. Some places I got some bizarre nonsense error, clearly the kind of thing that a parser prints when it's gone off the rails. Each time, reworking if:set="$(foo}" into if="foo" and if:true="${foo}" into if="${foo}" got back to the desired if-then behavior. So it's not a blocking problem, but we'd rather have the self-documenting :condition if we could.
I couldn't find mention of any such restriction in the Ant manual, but the manual describes the if/unless syntax in at least two different places using different descriptions. (I'm not sure where they are due to the manual's use of HTML frames; every URL shows up as index.html. Anytime I refer to the manual it feels like I'm browsing like it's 1999, baby! *does MC Hammer slide out of the room*)

Since Ant 1.4, <include> and <exclude> elements nested under <patternset> elements have supported if and else attributes. Each <fileset> has an implicit <patternset> nested under it, so the if and else attributes are available to it...
<condition property="some.flag.istrue">
<istrue value="${some.flag}"/>
</condition>
<fileset dir="somedir">
<!-- The "if" below is different than "if:true". -->
<include name="optional_file" if="some.flag.istrue"/>
</fileset>
In the above example, the if in <include> is an ordinary Ant attribute in the "default" Ant XML namespace. On the other hand, if:true is in the ant:if XML namespace.
The namespaced if:true doesn't work well with <include> elements. If the value provided to if:true doesn't evaluate to true, then Ant behaves as if the entire <include> element never existed. This is bad because Ant takes empty patternsets to mean "match every file". This is likely why you received the "A zip file cannot include itself" error; the <fileset> was likely containing the destination JAR file.
Stick with the plain if and else attributes for <include> and <exclude> elements and things should work.

Related

Exclude fileset from another fileset using a property

I've been using Gradle almost exclusively lately, but every now and again I have to dive back into our antiquated ant build system and figure out how to do something. Then I realize how little I know about ant and/or how difficult even some of the simplest tasks appear to be.
For example, I have a target that does some operation on a fileset:
<target name="some-operation">
<fileset dir="blah" id="stuff">
<filename name="**/*.txt" />
<not>
<filename name="**/foo/*" />
</not>
</fileset>
<!-- do some operations on "stuff" -->
</target>
Imagine this is in some old build system that multiple projects use. I want to exclude additional things in the fileset (let's say, files inside directory "bar", similar to "foo"), but since this is something multiple projects use, I can't just go putting my custom exclusions into the build system. I need some way to plug that additional fileset in (it could contain multiple exclusions).
What's the best way of doing this? I'm thinking I'll set a property in my build with the files to exclude, but the some-operation target will have to handle it gracefully when that property is missing. However, if I set a fileset to a property, I'm not quite sure how to get it excluded from the original fileset in some-operation.
Any ideas of the best/cleanest way to do this?
For reuse create a macrodef with nested element holding 1-n filesets for flexibility.
See this answer providing an example of macrodef using nested element.

How to pass nested arguments from one ant target to another

How to pass nested arguments from one ant target to another?
I need to pass a variable number of nested elements from one target to another.
I have a common file with all of my standard build tasks that's included in all of my projects.
I am adding a new custom task that takes a variable number of nested arguments
As a standard, all ant calls are made through the common file to ensure consistency of build style and logging.
Thus the new custom task and its nested child will be defined in the common script.
The project build script looks like this
<target name="projectBuild">
...
<ant target="_newFooTaskWrapper" antfile="commonFile">
<property name="_arg1" value="hello"/>
<property name="_arg2" value="world"/>
<nestedArg value="qux"/>
<nestedArg value="baaz"/>
...
<nestedArg value="AAAAA"/>
</ant>
...
</target>
The common script looks like this:
<target name ="_newFooTaskWrapper">
<echo message="Target _newFooTaskWrapper in project ${ant.project.name} from base directory ${basedir}"/>
<echo message="arg1 = ${_arg1}"/>
<echo message="arg2 = ${_arg2}"/>
<taskdef name="newFooTask" classname="org.foo.NewFooTask"/>
<typedef name="nestedArg" classname="org.foo.NewFooTask$NestedArg"/>
<newFooTask arg1="${_arg1}" arg2="${_arg2}">
<nestedArg value="qux"/>
<nestedArg value="baaz"/>
...
<nestedArg value="AAAAA"/>
</newFooTask>
Obviously, this isn't right. My question is, what's the right way to do this?
I need to pass a variable number of nested elements from one target to another.
For "varible", I assume you mean you don't know the exact number of the nested elements you want to pass to the task, so what you want is something like method(Object param...) in java, is it?
It's not a good idea to try such a way. Ant is not a scripting language but a build tool. It provides limited "scripting" possibilities.
However, you can try it in the following two ways:
1, If your nested element is just in the form of <elementName value="xx" />, you don't need anything complicated. Just pass another property containing a comma seperated list of the values, and process the list in your custom ant task. It's easy for Java to split the property into a list and process it.
2, If your nested element may be more complicated... maybe you can try reference:
Make a type fooTaskParams which can be referenced via an id:
<fooTaskParams id="_foo_task_params">
<nestedArg value="qux"/>
<nestedArg value="baaz"/>
...
<nestedArg value="AAAAA"/>
</fooTaskParams>
and pass the reference to the other build file:
<ant target="_newFooTaskWrapper" antfile="commonFile">
<property name="_arg1" value="hello"/>
<property name="_arg2" value="world"/>
<reference refid="_foo_task_params"/>
</ant>
and then make your task to be able to process the reference:
<newFooTask arg1="${_arg1}" arg2="${_arg2}" paramRefId="_foo_task_params" />
You may need to take care of reference override, or make your task able to process the ref as well as taking nested elements.
Read ant's manual about <ant> and <typedef> for more about this approach, and refer to SO Q&As like this when you encount any problem.

concat as resource collection in zip not working?

I'm trying to use <concat> as a resource collection in a <zip> task and, according to the documentation, this should work. I'd like to do this because some of the files I want to include in the zip need to have some properties expanded, so I will also add a <filterchain> to the <concat> to do this. I'd prefer to do it directly rather than copying to a temp location (with property substitution) and including the copy in the zip file.
However, I can't seem to get <zip> to correctly use the <concat> element.
A simplified example of what I have so far:
<zip destfile="target/dist.zip">
<concat>
<fileset file="CHANGES.txt" />
</concat>
</zip>
This creates a zip file containing several directories all named concat (C: (obviously this is on a Windows machine).
What am I missing?
A colleague and I came up with the answer by looking through the <zip> and <concat> source. There are really two answers:
<concat>'s implementation of the ResourceCollection interface is odd, but we understand why.
There's a way around that.
For #1, while <concat> is a ResourceCollection (like FileSet), under the hood it returns as the name of single Resource it contains a hard-coded value:
"concat (" + String.valueOf(c) + ")";
Look familiar?
The name of resources is normally ignored--except by <zip> and its related tasks, which uses the resource name as the ZipEntry name. Since <concat> returns the odd-looking name, that's what we get in the zip file.
I haven't quite figured out why I get multiple entries, but it doesn't matter: the observation leads to a convoluted solution.
Since I know the name of the ZipEntry I want to create, I can use a <mapper> to give the <concat> resource a name. Here's what I came up with in all its glory:
<zip destfile="target/distribution.zip">
<fileset dir=".">
<exclude name="target/**" />
<exclude name="CHANGES.txt" />
</fileset>
<mappedresources>
<concat>
<fileset file="CHANGES.txt" />
<filterchain>
<expandproperties />
</filterchain>
</concat>
<mergemapper to="CHANGES.txt" />
</mappedresources>
</zip>
As my colleague says "In Ant every problem can be solved using a mapper."
This only works for Ant 1.8+ because <mappedresources> was added in that release.
I'm going to post some comments to the Ant mailing list and suggest a couple of enhancements:
Allow a resource name to be specified as an attribute on <concat> when it's being used as a ResourceCollection.
Throw an exception (and don't create a synthetic value) if getName() is called without having a value specified.
Finally, though not directly related, I do wish <expandproperties> could take a <propertyset> so I can control which properties get substituted.
Do you want the final zip to contain a single file or multiple files? As far as I can see, using concat (when done successfully, which isn't done above) would produce a single file, the result of concatenation of all files in the resource collection.
If you want multiple files rather than concatenation, I think intermediate copy is what you'll need.
From Ant manual for the concat task:
Since Apache Ant 1.7.1, this task can
be used as a Resource Collection that
will return exactly one resource.

Ant 1.8 include or import with nested resource collection

I'd like to have Ant automatically include or import resources matching a particular pattern, but I'm really struggling with the syntax. Here's what I've tried:
<import>
<fileset dir="${basedir}" includes="*-graph.xml" />
</import>
However, I just get the error message
import requires file attribute or at least one nested resource
The documentation for import (and include) both say that you can use a nested resource collection, and the documentation for resource collections says <fileset> is a resource collection. I've Googled and can't find any useful examples at all.
I'm using Ant 1.8.1 (verified with ant -version)
EDIT
Found the problem. Firstly, SOMETHING has to match the expression. With no files that match, it blows up - even with optional=true, which is odd! Secondly, the matching files have be valid Ant file (even if they just contain <project/> - simply creating an empty file wasn't good enough).
Better error messages, please Apache! :-)
Try:
<foreach target="-import" param="file.name">
<fileset dir="${basedir}" includes="*-graph.xml" />
</foreach>
<target name="-import">
<import file="${file.name}" />
</target>
is in the ant-contrib tasks (http://ant-contrib.sourceforge.net/tasks/tasks/index.html) I'm not sure if this will work, I just came up with it on the fly.
Here is a simple way to deal with this problem:
<project name="the-name-of-the-project">
<import>
<fileset dir="${basedir}" includes="*-graph.xml" erroronmissingdir="false"/>
<fileset file="${ant.file.the-name-of-the-project}"/>
</import>
</project>
This way, the set of imported files is never empty since it includes the file containing this declaration. This works for <import> directives only, since it omits already imported files; <include> directives would trivially lead to a recursion.
Note the erroronmissingdir="false" is optional if basedir always exists.

How can I iterate over properties from a file?

All my projects and their versions are defined in a properties file like this:
ProjectNameA=0.0.1
ProjectNameB=1.4.2
I'd like to iterate over all the projects, and use their names and versions in an Ant script.
At present I read the entire file using the property task, then iterate over a given list in a for loop like this:
<for list="ProjectNameA,ProjectNameB" param="project">
<sequential>
<echo message="#{project} has version ${#{project}}" />
</sequential>
</for>
How can I avoid the hard-coding of the project names in the for loop?
Basically iterate over each line and extract the name and the version of a project as I go.
Seeing as you're already using antcontrib for, how about making use of the propertyselector task:
<property file="properties.txt" prefix="projects."/>
<propertyselector property="projects" match="projects\.(.*)" select="\1"/>
<property file="properties.txt" />
<for list="${projects}" param="project">
...
</for>
The idea here is to read the properties once with the projects prefix, and use the resulting set of properties to build a comma-separated list of projects with the propertyselector task. Then the properties are re-read without the prefix, so that your for loop can proceed as before.
Something you want to keep in mind, if you are reading additional .property files (besides build.properties) is scoping. If you read an additional file (via the property file="foo.property") tag, ant will show that the file was read, and the properties loaded. However, when you goto reference them, they come up un-defined.

Resources