100

I want to execute a command from an Ant buildfile, for each file in a directory.
I am looking for a platform-independent solution.

How do I do this?

Sure, I could write a script in some scripting language, but this would add further dependencies to the project.

Tuxdude
  • 47,485
  • 15
  • 109
  • 110
ivan_ivanovich_ivanoff
  • 19,113
  • 27
  • 81
  • 100

8 Answers8

89

Use the <apply> task.

It executes a command once for each file. Specify the files by means of filesets or any other resource. <apply> is built-in; no additional dependency needed; no custom task implementation needed.

It's also possible to run the command only once, appending all files as arguments in one go. Use the parallel attribute to switch the behaviour.

Sorry for being late a year.

Alex
  • 1,125
  • 8
  • 8
  • 4
    Well I just found this useful in 2011, so thanks for that anyway! – Michael Della Bitta Jan 16 '11 at 02:33
  • 7
    The problem with is that it only executes external system commands. It won't do any anty stuff directly. Not clear if that's what the original question was about or not. Basically you have to use or for the two different situations... which is totally dumb. – Archie Nov 04 '13 at 22:59
61

Short Answer

Use <foreach> with a nested <FileSet>

Foreach requires ant-contrib.

Updated Example for recent ant-contrib:

<target name="foo">
  <foreach target="bar" param="theFile">
    <fileset dir="${server.src}" casesensitive="yes">
      <include name="**/*.java"/>
      <exclude name="**/*Test*"/>
    </fileset>
  </foreach>
</target>

<target name="bar">
  <echo message="${theFile}"/>
</target>

This will antcall the target "bar" with the ${theFile} resulting in the current file.

Sean
  • 2,632
  • 2
  • 27
  • 35
blak3r
  • 16,066
  • 16
  • 78
  • 98
  • So, I have to include something? Or do I need some external ant lib? I'm getting *"Problem: failed to create task or type foreach"*. If I understand correctly, this means, **foreach** is an unknown keyword. – ivan_ivanovich_ivanoff Sep 23 '09 at 19:48
  • 4
    Ohhh lame... it's an Ant-Contrib Task. So, you have to install something. See here: http://ant-contrib.sourceforge.net/ – blak3r Sep 23 '09 at 20:47
  • 3
    That example is from a foreach before it was included in ant-contrib. There's a good example at http://ant.1045680.n5.nabble.com/Using-foreach-td1354624.html I'm going to update the example to work. – Sean Jun 04 '13 at 12:56
  • 2
    The nested fileset element is deprectated, use a nested path instead – dude Apr 07 '15 at 08:11
  • @dude Use – Sharat Jul 27 '17 at 09:18
  • it gives me `failed to create task or type foreach....Cause: The name is undefined.` at the foreach statement – mrid Jun 14 '19 at 08:50
29

An approach without ant-contrib is suggested by Tassilo Horn (the original target is here)

Basicly, as there is no extension of <java> (yet?) in the same way that <apply> extends <exec>, he suggests to use <apply> (which can of course also run a java programm in a command line)

Here some examples:

  <apply executable="java"> 
    <arg value="-cp"/> 
    <arg pathref="classpath"/> 
    <arg value="-f"/> 
    <srcfile/> 
    <arg line="-o ${output.dir}"/> 

    <fileset dir="${input.dir}" includes="*.txt"/> 
  </apply> 
martin clayton
  • 76,436
  • 32
  • 213
  • 198
Jmini
  • 9,189
  • 2
  • 55
  • 77
  • 2
    This is not very platform independent although it was clearly required in the original question... – Chucky Mar 21 '12 at 11:20
19

Here is way to do this using javascript and the ant scriptdef task, you don't need ant-contrib for this code to work since scriptdef is a core ant task.

<scriptdef name="bzip2-files" language="javascript">
<element name="fileset" type="fileset"/>
<![CDATA[
  importClass(java.io.File);
  filesets = elements.get("fileset");

  for (i = 0; i < filesets.size(); ++i) {
    fileset = filesets.get(i);
    scanner = fileset.getDirectoryScanner(project);
    scanner.scan();
    files = scanner.getIncludedFiles();
    for( j=0; j < files.length; j++) {

        var basedir  = fileset.getDir(project);
        var filename = files[j];
        var src = new File(basedir, filename);
        var dest= new File(basedir, filename + ".bz2");

        bzip2 = self.project.createTask("bzip2");        
        bzip2.setSrc( src);
        bzip2.setDestfile(dest ); 
        bzip2.execute();
    }
  }
]]>
</scriptdef>

<bzip2-files>
    <fileset id="test" dir="upstream/classpath/jars/development">
            <include name="**/*.jar" />
    </fileset>
</bzip2-files>
ams
  • 60,316
  • 68
  • 200
  • 288
  • a variable `project` is referenced in the example above, but with no prior definition made available. Would be good to have that represented or clarified. EDIT: n/m, found that `project` is a predefined var to access the current project. I'd suspected that, but wasn't sure. – Jon L. Oct 08 '14 at 14:29
  • 3
    For those trying this in JDK8 or later, keep in mind that "importClass" works if the ScriptEngine loaded by the JRE is rhino (which was true for most JDK 6 and 7), while in Nashorn (from 8 onward) you can use the backward-compatible "File = java.io.File" or the newer but not backward-compatible Java.type. Ant seems to fail silently when having issues running scriptdef, as I got to experience today. – Matteo Steccolini Aug 21 '19 at 15:10
16

ant-contrib is evil; write a custom ant task.

ant-contrib is evil because it tries to convert ant from a declarative style to an imperative style. But xml makes a crap programming language.

By contrast a custom ant task allows you to write in a real language (Java), with a real IDE, where you can write unit tests to make sure you have the behavior you want, and then make a clean declaration in your build script about the behavior you want.

This rant only matters if you care about writing maintainable ant scripts. If you don't care about maintainability by all means do whatever works. :)

Jtf

Jeffrey Fredrick
  • 4,493
  • 1
  • 25
  • 21
  • 2
    Your're right, I should just write a custom ant task in Java ;) – ivan_ivanovich_ivanoff Sep 25 '09 at 19:46
  • 4
    ant-contrib really is evil. Right now I'm in the middle of a large ant build project that makes intense use of if/then/else and antcalls and it really reads horrible. The whole thing looks like a converted batch/shell script and all the dependency stuff that ant does is completly turned off by the heavy use of ant-contrib. If you want to keep your setup clean, build your own task. :-/ – cringe Feb 15 '10 at 08:13
  • 2
    @cringe, I disagree. Like anything, you have to know when it is in your best interests to use ant-contrib. Stay away from things like if and var and use ant-contrib to avoid having to reinvent the wheel. – Neil Sep 19 '12 at 12:45
  • 1
    And should've been a scripting language, so imperative and not declarative, to begin with. So it's And who is evil IMO – mvmn Oct 11 '12 at 16:20
  • Maybe ant-contrib is a priori evil but black3r's use of it seems reasonable and ant-ish, so to speak. – ssimm Feb 16 '17 at 10:38
7

I know this post is realy old but now that some time and ant versions passed there is a way to do this with basic ant features and i thought i should share it.

It's done via a recursive macrodef that calls nested tasks (even other macros may be called). The only convention is to use a fixed variable name (element here).

<project name="iteration-test" default="execute" xmlns="antlib:org.apache.tools.ant" xmlns:if="ant:if" xmlns:unless="ant:unless">

    <macrodef name="iterate">
        <attribute name="list" />
        <element name="call" implicit="yes" />
        <sequential>
            <local name="element" />
            <local name="tail" />
            <local name="hasMoreElements" />
            <!-- unless to not get a error on empty lists -->
            <loadresource property="element" unless:blank="@{list}" >
                <concat>@{list}</concat>
                <filterchain>
                    <replaceregex pattern="([^;]*).*" replace="\1" />
                </filterchain>
            </loadresource>
            <!-- call the tasks that handle the element -->
            <call />

            <!-- recursion -->
            <condition property="hasMoreElements">
                <contains string="@{list}" substring=";" />
            </condition>

            <loadresource property="tail" if:true="${hasMoreElements}">
                <concat>@{list}</concat>
                <filterchain>
                    <replaceregex pattern="[^;]*;(.*)" replace="\1" />
                </filterchain>
            </loadresource>

            <iterate list="${tail}" if:true="${hasMoreElements}">
                <call />
            </iterate>
        </sequential>
    </macrodef>

    <target name="execute">
        <fileset id="artifacts.fs" dir="build/lib">
            <include name="*.jar" />
            <include name="*.war" />
        </fileset>

        <pathconvert refid="artifacts.fs" property="artifacts.str" />

        <echo message="$${artifacts.str}: ${artifacts.str}" />
        <!-- unless is required for empty lists to not call the enclosed tasks -->
        <iterate list="${artifacts.str}" unless:blank="${artifacts.str}">
            <echo message="I see:" />
            <echo message="${element}" />
        </iterate>
        <!-- local variable is now empty -->
        <echo message="${element}" />
    </target>
</project>

The key features needed where:

I didnt manage to make the delimiter variabel, but this may not be a major downside.

Community
  • 1
  • 1
dag
  • 1,133
  • 12
  • 25
  • Unfortunately this runs out of memory and blows up rather quickly. – David St Denis Jun 25 '14 at 05:25
  • Yes it may blow up your stack, depending on the number of files you process. I did it pretty successful with about 66 Files (without increased Memory options). However it s of cause only an option if you dont have access to ant-contrib wich offers more functionality (e.g. running parallel). – dag Jun 26 '14 at 08:27
  • This was very helpful. – DiamondDrake Dec 10 '18 at 17:11
1

You can use the ant-contrib task "for" to iterate on the list of files separate by any delimeter, default delimeter is ",".

Following is the sample file which shows this:

<project name="modify-files" default="main" basedir=".">
    <taskdef resource="net/sf/antcontrib/antlib.xml"/>
    <target name="main">
        <for list="FileA,FileB,FileC,FileD,FileE" param="file">
          <sequential>
            <echo>Updating file: @{file}</echo>
            <!-- Do something with file here -->
          </sequential>
        </for>                         
    </target>
</project>
Hemant
  • 4,537
  • 8
  • 41
  • 43
0

Do what blak3r suggested and define your targets classpath like so

<taskdef resource="net/sf/antcontrib/antlib.xml">
    <classpath>
        <fileset dir="lib">
          <include name="**/*.jar"/>
        </fileset>
    </classpath>        
</taskdef>

where lib is where you store your jar's

bungee
  • 61
  • 5