How to transform property into multiple arguments to <exec> task - ant

I have a property that contains multiple values, and I want to execute a command with a separate "-j" argument for each value in the property.
E.g. <property name="arguments" value="foo bar hello world"/>
Should execute: mycommand -j foo -j bar -j hello -j world
I'm using Ant 1.7.1, so I can't use the "prefix" attribute (Ant 1.8) on the <arg> element of an <exec> task.
One workaround is to insert the "-j" directly into the property by hand and then use the "line" attribute of <arg>:
<property name="args" value="-j foo -j bar -j hello -j world"/>
<exec executable="mycommand">
<arg line="${args}"/>
</exec>
...But I prefer to have the property be a simple list without the embedded arguments.
Edit: Actually, my arguments are paths within an XML file, so a more accurate argument list would be:
<property name="arguments" value="/foo/bar /hello/world /a/very/long/path"/>
I would like the command to then execute with arguments: "-j /foo/bar -j /hello/world -j /a/very/long/path". Note that the slashes remain forward slashes even under Windows (these are arguments to a command, not filenames).

You can use Ant resource tools for this.
<property name="arg_list" value="foo bar hello world"/>
<resources id="arguments">
<mappedresources>
<string value="${arg_list}" />
<filtermapper>
<replacestring from=" " to=" -j "/>
</filtermapper>
</mappedresources>
</resources>
<property name="arguments" value="-j ${toString:arguments}" />
The above will result in property arguments having the value -j foo -j bar -j hello -j world, which can then be used in the exec arg line.
Alternatively a pathconvert task can help in this regard:
<property name="arg_list" value="foo bar hello world"/>
<pathconvert property="arguments" pathsep=" ">
<chainedmapper>
<flattenmapper />
<regexpmapper from="(.*)" to="-j \1" />
</chainedmapper>
<filelist files="${arg_list}" />
</pathconvert>
If you have absolute paths, rather than just strings in the list, then remove the flattenmapper.
If you have relative paths, replace the flattenmapper line with:
<globmapper from="${basedir}/*" to="*" />
to prevent the paths being converted to absolute.
In the event that you have UNIX-like paths in the arg_list on a Windows system the default settings for pathconvert won't work - the paths get converted to Windows style. Instead, to process the list use:
<pathconvert property="arguments" pathsep=" " targetos="unix">
<chainedmapper>
<regexpmapper from="C:(.*)" to="-j \1" />
</chainedmapper>
<filelist files="${arg_list}" />
</pathconvert>
Note the targetos setting and the revised regexmapper from argument.

Use some for loop, here's a solution based on Ant Addon Flaka :
<project name="demo" xmlns:fl="antlib:it.haefelinger.flaka">
<!-- make standard ant tasks like i.e. exec understand EL expressions -->
<fl:install-property-handler/>
<property name="arguments" value="foo bar hello world"/>
<fl:for var="arg" in="split('${arguments}',' ')">
<exec executable="mycommand">
<arg line="#{arg} -j"/>
</exec>
</fl:for>
</project>

Not sure your comfort with scripting languages, but you can also embed script to do the parsing/reassembling:
http://ant.apache.org/manual/Tasks/script.html
I will say, I think it's a bit fragile because you have an explicit reference to the ANT project's name, but up to you:
<?xml version="1.0" encoding="utf-8"?>
<project name="ScriptProj" basedir=".">
<property name="arguments" value="foo bar hello world"/>
<target name="test">
<script language="javascript">
<![CDATA[
argsParm = ScriptProj.getProperty("arguments");
argsParmAmend = "";
args = argsParm.split(" ");
for (i = 0; i < args.length; i++)
argsParmAmend = argsParmAmend + "-j " + args[i] + " ";
// remove trailing string
ScriptProj.setProperty("arguments.amended", argsParmAmend.substr(0, argsParmAmend.length-1));
]]>
</script>
<echo>${arguments}</echo>
<echo>${arguments.amended}</echo>
</target>
</project>

Related

How to loop executable argument for Ant <exec>?

I have a command that will receive option that can be used multiple times, for example
$./myprogram --param a --param b --param c --param d
the input param
a
b
c
d
I want to execute this program using Ant <exec> and ant-contrib's <for>.
Instead of looping the <exec>, like below
<for list="a,b,c,d" param="var">
<exec executable="myprogram">
<arg value="--param"/>
<arg path="#{var}"/>
</exec>
</for>
I tried this looping the param, like below
<exec executable="myprogram">
<for list="a,b,c,d" param="var">
<arg value="--param"/>
<arg path="#{var}"/>
</for>
</exec>
But it doesn't work. The terminal returns this message
exec doesn't support the nested "for" element.
Is there any way to do this?
I just tried this from this question, and it works although it's longer than what I thought
<property name="arg_list" value="a,b,c,d"/>
<resources id="arguments">
<mappedresources>
<string value="${arg_list}" />
<filtermapper>
<replacestring from="," to=" --param "/>
</filtermapper>
</mappedresources>
</resources>
<property name="arguments" value="--param ${toString:arguments}" />
<exec executable="myprogram">
<arg line="${arguments}"/>
</exec>

trouble using replaceregexp when replacing string DIR location

I am having trouble in doing this.
there is 1 batch file with this line:
set TEST_DIR=C:\temp\dir1
I just want to set some new value to TEST_DIR
But, when I use in my ant script, it escapes forward slashes and gives this result:
set TEST_DIR=C:homedir2
Instead, I want to give it:
set TEST_DIR=C:\home\dir2
I am using this command:
<replaceregexp file="${MT_BATCH_FILE_LOCATION}\myfile.bat" match="TEST_DIR=C:\\temp\\dir1" replace="TEST_DIR=C:\home\dir2" byline="true" />
You can get the result you want by using this replace pattern:
replace="TEST_DIR=C:\\\\home\\\\dir2"
The reason is that you must escape the backslash once for the regex and once for Java - backslash is an escape character in both those contexts.
In answer to your subsequent questions in comments...
I expect the answer will be the same. You will need to double-escape the backslash in the value of ${new_loc}, i.e. use C:\\\\my_projcode not C:\my_projcode.
If new_loc is coming in as an environment variable, you could use the propertyregex task from ant-contrib to escape backslashes in the value:
<project default="test">
<!-- import ant-contrib -->
<taskdef resource="net/sf/antcontrib/antlib.xml">
<classpath>
<pathelement location="C:/lib/ant-contrib/ant-contrib-1.0b3.jar"/>
</classpath>
</taskdef>
<target name="test">
<!-- load environment variables -->
<property environment="env"/>
<!-- escape backslashes in new_loc -->
<propertyregex property="loc" input="${env.new_loc}" regexp="\\" replace="\\\\\\\\\\\\\\\\" />
<echo message="env.new_loc: ${env.new_loc}"/>
<echo message="loc: ${loc}"/>
<!-- do the replace -->
<replaceregexp file="test.bat" match="TEST_DIR=C:\\temp\\dir1" replace="TEST_DIR=${loc}\\\\home\\\\dir2" byline="true" />
</target>
Output:
c:\tmp\ant>set new_loc=c:\foo\bar
c:\tmp\ant>ant
Buildfile: c:\tmp\ant\build.xml
test:
[echo] new_loc: c:\foo\bar
[echo] env.new_loc: c:\foo\bar
[echo] loc: c:\\\\foo\\\\bar
BUILD SUCCESSFUL
Total time: 0 seconds
c:\tmp\ant>type test.bat
set TEST_DIR=c:\foo\bar\home\dir2
I have found another simple solution use replace instead of replaceregexp.
<replace file="${MT_BATCH_FILE_LOCATION}\myfile.bat"
token='TEST_DIR=C:\temp\dir1'
value='TEST_DIR=${new_loc}\home\dir2' />

How to replace newline in ant in outputfilterchain?

In my build.xml, I want to do the equivalent of cmd1 | xargs cmd2 (and also store the list of files from cmd1 into the variable ${dependencies}), where cmd1 gives a newline-separated list of paths. I can't figure out how to do this in Ant.
<project default="main">
<target name="main">
<exec executable="echo"
outputproperty="dependencies">
<arg value="closure/a.js
closure/b.js
closure/c.js"/>
<redirector>
<outputfilterchain>
<replacestring from="${line.separator}" to=" "/>
<!-- None of these do anything either:
<replacestring from="\n" to=" "/>
<replacestring from="
" to=" "/>
<replaceregex pattern="
" replace=" " flags="m"/>
<replaceregex pattern="\n" replace=" " flags="m"/>
<replaceregex pattern="${line.separator}" replace=" " flags="m"/>
-->
</outputfilterchain>
</redirector>
</exec>
<!-- Later, I need to use each file from ${dependencies} as an argument
to a command. -->
<exec executable="echo">
<!--This should turn into 3 arguments, not 1 with newlines.-->
<arg line="${dependencies}"/>
</exec>
</target>
</project>
This filter might do for the first part - it assumes though that none of your files start with a space character.
<outputfilterchain>
<prefixlines prefix=" " />
<striplinebreaks />
<trim />
</outputfilterchain>
It prefixes each line with a space, then removes the line breaks - giving a single line with all the filenames separated by single spaces, but with one space at the beginning. So the trim is used to chop that off.
Thanks martin. I also found another solution upon reading the filterchain documentation more carefully.
<outputfilterchain>
<tokenfilter delimoutput=" ">
<!--The following line can be omitted since it is the default.-->
<linetokenizer/>
</tokenfilter>
</outputfilterchain>

How can I echo a filename twice using 'FileSet' and 'PathConvert'?

I have this simple Ant task that lists all '.png' files in a folder:
<target name="listimages">
<!-- Assume files a A and B -->
<fileset id="dist.contents" dir="${basedir}">
<include name="**/*.png"/>
</fileset>
<pathconvert pathsep="${line.separator}"
property="prop.dist.contents"
refid="dist.contents">
<mapper type="flatten" />
<map from="${basedir}" to=""/>
</pathconvert>
<echo>${prop.dist.contents}</echo>
</target>
This prints
[echo] A.png
[echo] B.png
But, what I want is for the filenames to appear twice on each line.
[echo] A.png,A.png
[echo] B.png,B.png
How can I do that?
(This question is a follow up to How can I print a fileset to a file, one file name per line?)
You could use a regexp mapper (instead of the flatten) that implements the flattening and duplication. This is pretty simplistic, but might do:
<mapper type="regexp" from=".*/(.*)" to="\1,\1" />
Would need adjusting for your local path separator.
Better though, use a chainedmapper in place of the flatten:
<chainedmapper>
<mapper type="flatten" />
<mapper type="regexp" from="(.*)" to="\1,\1" />
</chainedmapper>

Filtering Files In-Place with Ant?

I have a directory of files for which I'd like to do "in-place" string filtering using Apache Ant (version 1.7.1 on Linux).
For example, suppose that in directory mydir I have files foo, bar, and baz. Further suppose that all occurences of the regular expression OLD([0-9]) should be changed to NEW\1, e.g. OLD2 → NEW2. (Note that the replace Ant task won't work because it does not support regular expression filtering.)
This test situation can be created with the following Bash commands (ant will be run in the current directory, i.e. mydir's parent directory):
mkdir mydir
for FILE in foo bar baz ; do echo "A OLD1 B OLD2 C OLD3" > mydir/${FILE} ; done
Here is my first attempt to do the filtering with Ant:
<?xml version="1.0"?>
<project name="filter" default="filter">
<target name="filter">
<move todir="mydir">
<fileset dir="mydir"/>
<filterchain>
<tokenfilter>
<replaceregex pattern="OLD([0-9])" replace="NEW\1" flags="g"/>
</tokenfilter>
</filterchain>
</move>
</target>
</project>
Running this first Ant script has no effect on the files in mydir. The overwrite parameter is true by default with the move Ant task. I even fiddled with the granularity setting, but that didn't help.
Here's my second attempt, which "works," but is slightly annoying because of temporary file creation. This version filters the content properly by moving the content to files with a filtered suffix, then the filtered content is "moved back" with original filenames:
<?xml version="1.0"?>
<project name="filter" default="filter">
<target name="filter">
<move todir="mydir">
<globmapper from="*" to="*.filtered"/>
<fileset dir="mydir"/>
<filterchain>
<tokenfilter>
<replaceregex pattern="OLD([0-9])" replace="NEW\1" flags="g"/>
</tokenfilter>
</filterchain>
</move>
<move todir="mydir">
<globmapper from="*.filtered" to="*"/>
<fileset dir="mydir"/>
</move>
</target>
</project>
Can the first attempt (without temporary files) be made to work?
See the replace task:
<replace
dir="mydir"
includes="foo, bar, baz">
<replacefilter token="OLD" value="NEW" />
</replace>
or the replaceregexp task:
<replaceregexp
file="${src}/build.properties"
match="OldProperty=(.*)"
replace="NewProperty=\1"
byline="true"/>

Resources