How can I iterate over properties from a file? - ant

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.

Related

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.

Condition Property Override

I want to use an condition property to set the property value to X if another property is defined and Y otherwise. However, I don't want the user to be able to override the condition property from the command line.
How can this be achieved?
Starting from ant 1.8 for some use cases local task may be applicable. Since a property is made local it starts with an empty value. It's scope is limited to current target, but you may pass it to subsequent targets using param argument in antcall.
Nope, you can't override a property set on the command line. At least, it's not easy to do. The whole purpose of overriding properties on the command line is to allow users to override defaults in order to make modification in the way your project builds. For example:
<property file="${basedir}/build.properties"/>
<property name="javac.debug" value="no"/>
<target name="compile">
<javac destdir="${main.destdir}"
debug="${javac.debug}">
By default, the Java code is compiled without debugging information. Maybe this is done to make jar files smaller, or faster interpretation, or maybe to make the code harder to decompile and read. Whatever reason, this build won't put debug information into the classfiles.
However, developers do want this debugging information, so they want to be able to override this setting:
$ ant -Djavac.debug=true compile
Or, they can create a build.properties file and put the value in there.
This type of issue comes up when you're not using Ant for builds. I know several sites that use Ant scripts to do deployments. I usually discourage this because Ant isn't really made for this type of thing. For example, Ant doesn't have any built in logic or loops. Once a property is set, it can't be changed. These are good ideas for a build language, but a terrible idea for a general purpose programming language.
Also, developers shouldn't be doing builds for QA or production. Those should be done by a build server that won't override defaults.
Now how to destroy this whole well thought out system and cause absolute havoc:
You can use the ant-contrib tasks in your project. Doing this will allow you to access the Ant Contrib var task to unset properties.
Download the ant-contrib.jar file (whatever the latest version is), and put it in a lib directory under your project. Then you can do this:
<project name="danger-will-robinson" default="package" basedir="."
xmlns:ac="http://ant-contrib.sourceforge.net">
<!-- Define the Ant-Contrib tasks -->
<taskdef=resource="net/sf/antcontrib/antlib.xml"
uri="http://ant-contrib.sourceforge.net">
<classpath>
<fileset dir="${basedir}/lib">
<include name="ant-contrib*.jar"/>
</fileset>
</classpath>
</taskdef>
<!-- Unset Property "foo", so you can use it -->
<ac:var name="foo" unset="true"/>
Note that the <classpath> points to the ant-contrib jar in the ${basedir}/lib directory. If you check that into your source repository, it will allow everyone who checks out your project to be able to do the build without installing the ant-contrib jar on their system.
Note that I've defined a "ac" XML namespace, so Ant-Contrib tasks won't overlap other possible third party tasks.
Properties in ant once set are immutable by design. You may overwrite an existing property with any scripting language that provides access to ant api, i.e. javascript.
JDK >= 1.6 already ships with a javascript engine, so you may use something like :
<project>
<property name="x" value="whatever"/>
<script language="javascript">
project.getProperty('x') ?
project.setProperty('foo', 'true') :
project.setProperty('foo', 'false');
</script>
<echo>$$[foo} => ${foo}</echo>
</project>
out of the box.But that won't help if someone uses ant -f yourbuild.xml -Dfoo=bla !! as userproperties (those properties defined via -Dkey=value) have a special protection.
So your requirement "..However, I don't want the user to be able to override the condition property from the command line". is not fullfilled.
But the let task from Ant addon Flaka provides the possibillity to overwrite even userproperties :
<project xmlns:fl="antlib:it.haefelinger.flaka">
<property name="x" value="whatever"/>
<!--
:= defines a new property whereas
::= overwrites any existing property
even userproperties
-->
<fl:let> foo ::= has.property['x'] ? 'true' : 'false'</fl:let>
<echo>$$[foo} => ${foo}</echo>
</project>
Run both scripts with ant -f yourbuild.xml -Dfoo=bla to see the difference.
Ant api has also method project.setUserProperty(String,String) so you may use also:
...
<script language="javascript">
project.getProperty('x') ?
project.setProperty('foo', 'true') :
project.setProperty('foo', 'false');
project.getUserProperty('x') ?
project.setUserProperty('foo', 'true') :
project.setUserProperty('foo', 'false');
</script>
...
to prevent the foo property to be set via .. -D .. and it will work even if property x is defined on commandline -Dx=whatever You have to make your choice, script task with javascript out of the box or Flaka let task
oneline solution but Flaka jar needed.

Create a fileset from a comma-separated list in a property without losing order

I use a specified property to create fileset:
<property name="cases" value="B.java,A.java,C.java" />
<fileset id="casesToBeRunning" dir="${src}" includes="${cases}" />
When casesToBeRunning created, I list the content of it:
<echo>Cases to be running: ${toString:casesToBeRunning}</echo>
it shows A.java,B.java,C.java which I'm not expected to.
I don't want Ant autosort for me, I need the original sort order of the property I defined to execute the cases orderly.
Anyone know how to handle this?
Ant filesets don't retain order - as you've seen. The related filelist type does respect ordering, so you might use:
<filelist id="casesToBeRunning" dir="${src}" files="${cases}" />
Whether the order is respected will depend on what task you plan to use to process the files. Most core Ant tasks that accept a fileset will accept a filelist instead, so you should be ok with them. For non-core tasks it may not work.
(Note that before Ant 1.8.0 some tasks didn't respect the order when traversing a filelist - among them copy for example).

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.

How do I make a fileset from a comma-separated list of directories in Ant?

In an Ant target I get a property, containing the list of directories to be included in further action (copying, filtering, etc.). It looks like this:
directories=dir1, dir2, dir3
I need a way to convert this list to a fileset or patternset that selects all the files in these directories.
I know I can use a script to generate pattern strings and then use it in the "include" or "exclude", but is there are a way to avoid scripts?
Note that as of Ant 1.9.4, there is a new construct <multirootfileset> that provides that functionality, even if the dirs are not siblings:
<multirootfileset basedirs="${directories}" includes="**/*">
How about using the antcontrib propertyregex task to convert the comma-separated list into wildcards suitable for a fileset?
<property name="directories" value="dir1, dir2, dir3" />
<property name="wildcard" value="${file.separator}**${file.separator}*" />
<propertyregex property="my_pattern"
input="${directories}"
regexp=", "
replace="${wildcard}," />
At this point we now have:
my_pattern=dir1/**/*,dir2/**/*,dir3
That can be used with a further suffixed wildcard to get the full fileset:
<fileset dir="." id="my_fileset" includes="${my_pattern}${wildcard}" />
(The fiddly ${wildcard} is to ensure portability between unix and windows filesystems, you could use /**/* if you're pure unix.)
Something like this should work:
<dirset includes="${directories}"/>
Yes, dirset isn't fileset. However, it may be enough, or else you can probably use a for or foreach from ant-contrib to iterate over the directories in your target. You might also be able to define a ResourceCollection based around the dirset. It might help to know what the "further action" is expected to be.
However, this feels like too much work ...

Resources